欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android从程序员到架构师之路3

程序员文章站 2024-03-15 14:06:41
...

本文学习自高焕堂老师的Android从程序员到架构师之路系列教学视频

40 - 认识线程(Thread)模式a

1. 线程(Thread)概念

所谓线程(Thread) 是指一串连续的执行动作,以达成一项目的。現代的电脑内部都有数串连续性的动作同时在进行。也就是有多条线程并行地(Concurrently)执行。

在电脑中,若电脑拥有多颗CPU,则每颗CPU 可各照顾一个线程,于是可多个线程同时间进行。若只有单一CPU,则此CPU可同时(Concurrently)照顾数个线程。无论是多CPU或单一CPU的电脑,多条线程并行地执行,都可增加执行效率。

像Java、C++等现代的电脑语言都能让程序师们能够易于创建多条线程减化GUI 动画的设计工作,也可增加其执行效率。例如,当您想一边看动画,一边听音乐时,计算机能同时产生两个线程──“秀动画”及“播音乐”。甚至可产生另一条线程来为您做特殊服务,如让您可选择动画及音乐。

多条线程能并行地执行同一个类别,或者是不同的类别。在Android平台里也不例外,无论是在Java层或是C++层,都常常见到多条线程并行的情形。

Android采取Java的Thread框架,来协助建立多條线程並行的环境。在Java里,大家已经习惯撰写一个类别来支持Runnable接口,再搭配Thread基类就能顺利诞生一个新线程来执行该类别里的run()函数了。

2. Java的线程框架

Java提供一个Thread基类(Super Class)来支持多线程功能。这个基类协助诞生(小)线程,以及管理(小)线程的进行,让电脑系统更容易取得程序师的指示,然后安排CPU 来运作线程里的指令。
例如,线程所欲达成的任务(Task)是程序师的事,所以程序师应填写线程里的指令,来表达其指示。为配合此部份的运作,Java提供了Runnable接口,其定义了一个run()函数。

Android从程序员到架构师之路3

于是,程序师可撰写一个应用类别(Application Class)来实作(Implement)此界面,并且把线程的任务写在应用类别的run()函数里,如此即可让(小)线程来执行run()函数里的任务了。

这是几乎每一位Java开发者都常用的多线程(Multi-thread)机制,只是许多人都会用它,却不曾认识它的真实身影:就是一个幕后的框架。由于Android应用程序开发也采用Java语言,所这个Thread框架也成为Android大框架里的一个必备元素。

基于这个框架里的Thread基类和Runnable接口,你就可以撰写应用类别,来实作run()函数了,如下图:

Android从程序员到架构师之路3

于此图里,框架的Thread基类会先诞生一个小线程,然后该小线程透过Runnable接口,调用(或执行)了Task类别的run()函数。
例如,请看一个Java程序:

// Ex01-01.java
class Task implements Runnable {
	public void run() {
		int sum = 0;
		for (int i = 0; i <= 100; i++)
			sum += i;
		System.out.println("Result: " + sum);
	} 
}

public class JMain {
	public static void main(String[] args) {
		Thread t = new Thread(new Task());
		t.start();
		System.out.println("Waiting...");
	} 
}

此时,main()先诞生一个Task类的对象,并且诞生一个Thread基础的对象。接着,执行到下一个指令:t.start();
此时,main()就调用Thread的start()函数;这start()就产生一个小线程去执行run()函数。如下图:

Android从程序员到架构师之路3
Android从程序员到架构师之路3

框架的结构而言,上图里的Runnable接口与Thread基类是可以合并起来的。也就是把run()函数写在Thread的子类别里。如下图:

Android从程序员到架构师之路3

兹撰写一个Java程序(即改写上述的Ex01-01.java)来实现上图:

class myThread extends Thread {
	public void run() {
		int sum = 0;
		for (int i = 0; i <= 100; i++)
			sum += i;
		System.out.println("Result: " + sum);
	} 
}

public class JMain {
	public static void main(String[] args) {
		Thread t = new myThread();
		t.start();
		System.out.println("Waiting...");
	} 		
}

其诞生一个myThread对象,并且由JMain调用Thread的start()函数。这start()就产生一个小线程去执行 myThread子类别里的run()函数。上图是类关系图,其对象关系图,可表示如下:

Android从程序员到架构师之路3

41 - 认识线程(Thread)模式b

3. 认识Android的主线程(又称UI线程)

Android从程序员到架构师之路3

UI线程的责任:迅速处理UI事件
在Android里,关照UI画面的事件(Event)是UI线程的重要职责,而且是它的专属职责,其它子线程并不可以插手存取UI画面上的对象(如TextView)呢!

由于Android希望UI线程能够给予用户的要求做快速的反应。如果UI 线程花费太多时间做幕后的事情,而在UI事件发生之后,让用户等待超过5秒钟而未处理的话,Android就会向用户道歉。
// ac01.java
    // ……..
    public class ac01 extends Activity implements OnClickListener {
    	public TextView tv;
    	private Button btn, btn2, btn3;
    	public void onCreate(Bundle icicle) {
    		super.onCreate(icicle);
    		LinearLayout layout = new LinearLayout(this);
    		layout.setOrientation(LinearLayout.VERTICAL);
    		btn = new Button(this); btn.setId(101);
    		btn.setBackgroundResource(R.drawable.heart);
    		btn.setText("Block UI thread"); btn.setOnClickListener(this);
    		LinearLayout.LayoutParams param = 
    			new LinearLayout.LayoutParams(150,50); 
    		param.topMargin = 10;
    		layout.addView(btn, param);
			btn2 = new Button(this); btn2.setId(102);
			btn2.setBackgroundResource(R.drawable.heart);
			btn2.setText("Show"); btn2.setOnClickListener(this);
			layout.addView(btn2, param); 
			btn3 = new Button(this); btn3.setId(103);
			btn3.setBackgroundResource(R.drawable.heart);
			btn3.setText("Exit"); btn3.setOnClickListener(this);
			layout.addView(btn3, param); 
			tv = new TextView(this);
			tv.setTextColor(Color.WHITE); tv.setText("");
			LinearLayout.LayoutParams param2 = 
			new LinearLayout.LayoutParams(150, 60); 
			param2.topMargin = 10;
			layout.addView(tv, param2);
			setContentView(layout);
			setTitle("please press <Block...> & <Show> ");
			tv.setText("then wait for 5 min...");
		}

		public void onClick(View v) {
			switch(v.getId()){
				case 101:
					try { 
						Thread.sleep(10000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					break;
				case 102: break;
				case 103: finish(); break; 
			}	
		}
	}
連續按下<Block UI thread>和<Show>按鈕,然後等待5秒鐘,就會出現剛才所說的道歉提示

主线程可以诞生多个子线程来分担其工作,尤其是比较冗长费时的幕后服务工作,例如播放动画的背景音乐、或从网络下载影片等。于是,主线程就能专心于处理UI画面的事件了。

再如,当你开发一个游戏程序时,如果你希望游戏循环不受UI事件的干扰,或希望游戏循环(GameLoop)不要阻塞住UI线程的话,就不适合拿UI线程去跑游戏循环了。

42 - 认识线程(Thread)模式c

UI线程的诞生

当我们启动某一支AP时,Android就会诞生新进程(Process),并且将该AP程序加载这新诞生的进程里。每个进程在其诞生时刻,都会诞生一个主线程,又称为UI线程

Android从程序员到架构师之路3

在进程诞生时刻,除了诞生主线程之外,还会替主线程诞生它专用的Message、Queue和Looper。如下图所示:

Android从程序员到架构师之路3

这个Main Looper就是让主线程没事时就来执行Looper,确保主线程永远活着而不会死掉;在执行Looper时,会持续观察它的Message Queue是否有新的信息进来;如果有新信息进来的话,主线程就会尽快去处理(响应)它。

在Android环境里,一个应用程序常包含有许多个类别,这些类别可以分布在不同进程里执行,或挤在一个进程里执行。例如有一个应用程序的AndroidManifest.xml文件内容如下:
// AndroidManifest.xml
    	// ………
    	<activity android:name=".FirstActivity" android:label="@string/app_name">
    	<intent-filter>
    	<action android:name="android.intent.action.MAIN" />
    	<category android:name="android.intent.category.LAUNCHER" />
    	</intent-filter> </activity>
    	<activity android:name=".LoadActivity">
    	<intent-filter>
    	<category android:name="android.intent.category.DEFAULT" />
    	</intent-filter> </activity>
    	<service android:name=".LoadService" android:process=":remote">
    	<intent-filter>
    	<action android:name="com.misoo.pkm.REMOTE_SERVICE" />
    	</intent-filter> </service>
    	</application>
    	</manifest>

	Android依据这个文件而将各类别布署于两个进程里执行,如图:

Android从程序员到架构师之路3

其中,FirstActivity和LoadActivity两个类别会加载预设的进程里。而LoadService则会加载于名为“remote”的独立进程里。
于是,由进程#1的主线程去执行FirstActivity和LoadActivity的onCreate()等函数。而由进程#2的主线程去执行LoadService的onCreate()等函数。

Android从程序员到架构师之路3
Android从程序员到架构师之路3

LoadService在独立的进程(名称叫“remote”)里执行。于是,FirstActivity与LoadService之间就属于跨进程的沟通了。这种跨进程的沟通,就是大家熟知的IPC(Inter-Process Communication)机制了。这种IPC机制是透过底层驱动(Driver)来实现的。如下图:

Android从程序员到架构师之路3

在此图的不同进程里 , 各 有 其 主 线 程(Thread)。由于线程是不能越过进程边界的。所以,当执行LoadActivity的线程必须跨越进 程 去 执 行 LoadService( 的函数 ) 时 ,Android 的内层 Binder System 即 刻 从LoadService所在进程的线程池启动线程(BinderThread) 来 配 合 接 力 , 由 此BinderThread去执行LoadService。

練習:绑定(Bind)远程的Service

Android从程序员到架构师之路3
Android从程序员到架构师之路3

Android从程序员到架构师之路3

Binder System會從進程的線程池(Thread pool)裡啟動一個線程來執行Binder::onTransact()函數。

Android从程序员到架构师之路3

当Thread_a必须跨越进程去执行JavaBBinder对象时,Android的内层Binder System即刻从myService所在进程的线程池启动线程Thread_x来配合衔接Thread_a线程,由Thread_x去执行JavaBBinder对象。

Android的每一个进程里,通常含有一个线程池,让跨进程的线程得以进行。虽然是由Thread_a与Thread_x相互合作与衔接而完成远距通讯的,但让人们能单纯地认为是单一进程(即Thread_a)跨越到另一个进程去执行JavaBBinder对象。虽然JavaBBinder是C/C++层级的;而myService是Java层级的,两者不同语言,但处于同一个进程,所以Thread_x可以执行到myService对象。

43 - 认识线程(Thread)模式d

4. 细说主线程(UI线程)的角色

近程通信

在Android里,无论组件在那一个进程里执行,于预设情形下,他们都是由该进程里的主线程来负责执行之。
例如下述的范例,由一个Activity启动一个Service,两者都在同一个进程里执行。此时,两者都是由主线程负责执行的。如下图所示:

Android从程序员到架构师之路3

// ac01.java 
//……
public class ac01 extends Activity 
implements OnClickListener {
	private Button btn, btn2;
	public void onCreate(Bundle icicle) {
		super.onCreate(icicle);
		LinearLayout layout = new LinearLayout(this);
		layout.setOrientation(LinearLayout.VERTICAL);
		btn = new Button(this); btn.setId(101);
		btn.setText("run service");
		btn.setBackgroundResource(R.drawable.heart);
		btn.setOnClickListener(this);
		LinearLayout.LayoutParams param
		= new LinearLayout.LayoutParams(135, 50);
		param.topMargin = 10; layout.addView(btn, param);
		btn2 = new Button(this); btn2.setId(102);
		btn2.setText("Exit");
		btn2.setBackgroundResource(R.drawable.heart);
		btn2.setOnClickListener(this);
		layout.addView(btn2, param);
		setContentView(layout);
		//---------------------------------------
		Thread.currentThread().setName(
		Thread.currentThread().getName()+"-ac01");
	}

	public void onClick(View v) {
		switch (v.getId()) {
			case 101:
				this.startService(new Intent(this, myService.class));
				break;
			case 102:
				finish(); break;
	}}
}

// myService.java
//……..
public class myService extends Service {
	@Override 
	public void onCreate(){
		Thread.currentThread().setName(
		Thread.currentThread().getName() + "-myService");
		Toast.makeText(this, Thread.currentThread().getName(),
		Toast.LENGTH_SHORT).show();
	}
	@Override 
	public IBinder onBind(Intent intent){ 
		return null; 
	}
}

主线程先执行ac01的onCreate()函数,然后,继续执行myService的onCreate()函数。于是,输出了主线程的执行轨迹纪录

除了上述的Activity和Service之外,还有BroadcastReceiver也是一样,是由主线程来执行的。例如,由一个Activity启动一个BroadcastReceiver,两者都在同一个进程里执行。此时,两者都是由主线程负责执行的。如下图所示

Android从程序员到架构师之路3

// ac01.java
// …….
public class ac01 extends Activity implements OnClickListener {
	//…….
	public void onCreate(Bundle icicle) {
		//………
		Thread.currentThread().setName(
		Thread.currentThread().getName()+"-ac01");
		}
		public void onClick(View v) {
			switch (v.getId()) {
				case 101:
				Intent in = new Intent(MY_EVENT);
				this.sendBroadcast(in); break;
				case 102: finish(); break;
			}
		}
	}
}

// myReceiver.java
//……..
public class myReceiver extends BroadcastReceiver {
	@Override public void onReceive(Context context, Intent intent) {
		Thread.currentThread().setName(
		Thread.currentThread().getName() + "-myReceiver");
		Toast.makeText(context, 
		Thread.currentThread().getName(),
		Toast.LENGTH_SHORT).show();
	} 
}

主线程先执行myActivity的onCreate()函数,之后继续执行myReceiver的onReceive()函数。于是输出了主线程执行的轨迹纪录:

远程通信

如果Activity、Service和BroadcastReceiver三者并不是在同一个进程里执行时,它们之间的通讯就是跨进程通讯(IPC)了。
请先看个范例,它由一个Activity启动一个远距的Service,两者分别在不同的进程里执行,如下图所示:

Android从程序员到架构师之路3

当Activity与Service(或BroadcastReceiver)之间采用IPC通讯时,意味着两者分别在不同的进程里执行。此时,于预设情形下,Activity、BroadcastReceiver或Service都是由其所属进程里的主线程负责执行之

Android从程序员到架构师之路3

Android核心的Binder System从”remote”进程的线程池里,启动一个线程(名为”Binder Thread #1”)来执行myBinder的onTransact()函数。 • 依据Binder System的同步(Synchronization)的机制,主线程会等待Binder Thread #1线程执行完毕,才会继续执行下去。

44 - 认识线程(Thread)模式e

5.线程之间的通信架构

认识Looper与Handler对象

当主线程诞生时,就会去执行一个代码循环(Looper),以便持续监视它的信息队列(Message Queue简称MQ)。当UI事件发生了,通常会立即丢一个信息(Message)到MQ,此时主线程就立即从MQ里面取出该信息,并且处理之。

例如,用户在UI画面上按下一个Button按钮时,UI事件发生了,就会丢一些信息到MQ里,其中包括onClick信息,于是,主线程会及时从MQ里取出onClick信息,然后调用Activity的onClick()函数去处理之。
处理完毕之后,主线程又返回去继续执行信息循环,继续监视它的MQ,一直循环下去,直到主线程的生命周期的终了。
通常是进程被删除时,主线程才会被删除

Android里有一个Looper类别,其对象里含有一个信息循环(Message Loop)。也就是说,一个主线程有它自己专属的Looper对象,此线程诞生时,就会执行此对象里的信息循环。此外,一个主线程还会有其专属的MQ信息结构。如下图所示:

Android从程序员到架构师之路3

由于主线程会持续监视MQ的动态,所以在程序的任何函数,只要将信息(以Message类别的对象表示之)丢入主线程的MQ里,就能与主线程沟通了。
在Android里,也定义了一个Handler类别,在程序的任何函数里,可以诞生Handler对象来将Message对象丢入MQ里,而与主线程进行沟通。

在Android的预设情况下,主线程诞生时,就会拥有自己的Looper对象和MQ(即Message Queue)数据结构。
然而,主线程诞生子线程时,于预设情形下,子线程并不具有自己的Looper对象和MQ。由于没有Looper对象,就没有信息回圈(Message Loop),一旦工作完毕了,此子线程就结束了。

既然没有Looper对象也没有MQ,也就不能接受外来的Message对象了。则别的线程就无法透过MQ来传递信息给它了。
那么,如果别的线程(如主线程)需要与子线程通讯时,该如何呢? 答案是:替它诞生一个Looper对象和一个MQ就行了。

主线程丢信息给自己

Handler是Android框架所提供的基类,用来协助将信息丢到线程的MQ里。
兹撰写个范例程序Rx01,来将信息丢到主线程的MQ里,如下:

// ac01.java
//……..
public class ac01 extends Activity implements OnClickListener {
	private Handler h;
	public void onCreate(Bundle icicle) {
		//……..
		h = new Handler(){
			public void handleMessage(Message msg) {
				setTitle((String)msg.obj);}
		}; 
	}
	public void onClick(View v) {
		switch (v.getId()) {
			case 101:
				h.removeMessages(0);
				Message m = h.obtainMessage(1, 1, 1, "this is my message.");
				h.sendMessage(m); // 将Message送入MQ里
				break;
			case 102: 
				finish(); break;
		}
	}
}

当主线程执行到onCreate()函数里的指令:
h = new Handler(){
	// ………
} 
就诞生一个Handler对象,可透过它来把信息丢到MQ里。
当执行到onClick()函数里的指令:
//………………
h.removeMessages(0);
Message m = h.obtainMessage(1, 1, 1, 
"this is my message.");
h.sendMessage(m);
就将Message对象送入MQ里。

当主线程返回到信息回圈时,看到MQ里有个Message对象,就取出来,并执行handleMessage()函数,将Message对象里所含的字符串显示于画面上。

子线程丢信息给主线程

子线程也可以诞生Handler对象来将Message对象丢到主线程的MQ里,又能与主线程通讯了。兹撰写个范例程序Rx02
如下:

// ac01.java
// ……….
public class ac01 extends Activity implements OnClickListener {
	private Handler h;
	private Timer timer = new Timer();
	private int k=0;
	public void onCreate(Bundle icicle) {
		super.onCreate(icicle);
		//………
		h = new Handler(){
			public void handleMessage(Message msg) {
				setTitle((String)msg.obj);}
		}; 
}

public void onClick(View v) {
	switch (v.getId()) {
		case 101:
			TimerTask task = new TimerTask(){
			@Override 
			public void run() {
				h.removeMessages(0);
				Message m = h.obtainMessage(1, 1, 1,
				Thread.currentThread().getName() + " : "+String.valueOf(k++));
				h.sendMessage(m);
			}
		};
		timer.schedule(task, 500, 1500); break;
		case 102:
			finish(); break;
		 }
	}
}

就启动一个Timer的线程,名字叫:”Timer-0”;然后,由它来定时重复执行TimerTask::run()函数,就不断将Message对象丢到主线程的MQ里。此时主线程会持续处理MQ里的Message对象,将其内的字符串显示于画面上。

于是,子执行透过Handler对象而将信息丢到主线程的MQ,进而成功地将信息显示于画面上。

替子线程诞生Looper与MQ

如果别的线程(如主线程)需要与子线程通讯时,该如何呢? 答案是:替它诞生一个Looper对象和一个MQ就行了。兹撰写个范例程序Rx03如下:

// ac01.java 
//……
public class ac01 extends Activity implements OnClickListener {
	private Thread t;
	private Handler h;
	private String str;
	public void onCreate(Bundle icicle) {
		//……..
		t = new Thread(new Task());
		t.start(); }
		public void onClick(View v) {
			switch(v.getId()){
				case 101:
					Message m = h.obtainMessage(1, 33, 1, null);
					h.sendMessage(m); break;
				case 102: setTitle(str); break;
				case 103: 
					h.getLooper().quit(); finish(); break;
		}
	}

class Task implements Runnable {
	public void run() {
		Looper.prepare();
		h = new Handler(){
		public void handleMessage(Message msg) {
			str = Thread.currentThread().getName() +", value=" + String.valueOf(msg.arg1);
		}
	};
	Looper.loop();}
	}
}

Step-1: 一开始,由主线程执行onCreate()函数。主线程继续执行到指令:
t = new Thread(new Task());
t.start();
就诞生一个子线程,并启动子线程去执行Task的run()函数,而主线程则返回到信息回圈,并持续监视MQ的动态了。

Step-2: 此时,子线程执行到run()函数里的指令:Looper.prepare();
就诞生一个Looper对象,准备好一个信息回圈(Message Loop) 和MQ数据结构。

继续执行到指令:
h = new Handler(){
//…..
}
就诞生一个Handler对象,可协助将信息丢到子线程的MQ上。

接着继续执行到指令:
Looper.loop();
也就开始执行信息回圈,并持续监视子线程的MQ动态了。

Step-3: 当用户按下UI按钮时,UI事件发生了,Android将此UI事件的信息丢到主线程的MQ,主线程就执行onClick()函数里的指令:
Message m = h.obtainMessage(1, 33, 1, null);
h.sendMessage(m);
主线程藉由h将该Message对象(内含整数值33)丢入子线程的MQ里面,然后主线程返回到它的信息循环(Looper),等待UI画面的事件或信息。

Step-4: 子线程看到MQ有了信息,就会取出来,调用handleMessage()函数:
public void handleMessage(Message msg) {
	str = Thread.currentThread().getName() +", value=" + String.valueOf(msg.arg1);} 
来处理之,就设定的str的值。请留意,此刻子线程因为不能碰触UI控件,所以无法直接将str值显示于画面上。

Step-5: 当用户按下<show value>按钮时,主线程就执行onClick()函数,将str值显示于画面上。于是,实现主线程与子线程之间的双向沟通了。

45 - 认识线程(Thread)模式f

6. Android UI的单线程环境

單線程程序概念

单线程程序意谓着两个(或多个)线程不能共享对象或变量值。

Android的UI是单线程程序的环境。
UI控件(如Button等)都是由UI线程所创建,内部攸关于UI显示的属性或变量都只有UI线程才能存取(Access)之,别的线程并不能去存取之。

例如下图里的View类别体系,都只限于UI线程才能去执行它们的onDraw()函数,因为它会实际更动到UI的属性。

Android从程序员到架构师之路3

public class myActivity extends Activity
implements OnClickListener {
	private Button ibtn;
	@Override 
	protected void onCreate(Bundle icicle) {
		super.onCreate(icicle);
		ibtn = new Button(this);
		//…………….
	}
	// 其它函数
}

由于UI线程来执行onCreate()函数,诞生了Button对象,因而只限UI线程能去存取该对象里攸关UI的属性,其它线程不能去碰它们。

线程安全问题就是如何避免不同线程之间,可能会相互干扰的问题。
虽然两个线程几乎同时先后执行一个类别里的(可能不同)函数,只要不共享对象、或共享变量(例如Android的UI单线程环境),就不会产生干扰现象,也就没有线程安全问题。

换句话说,如果各自使用自己的对象或变量(即不共享对象或变量),就不会干扰到别线程执行的正确性了。
例如下述范例:

// Ex01.java
class Task2{
	private int count;
	public void init(){ count = 0; }
	public void f1() {
		for(int i=0; i<3; i++) {
			count++;
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) { 
				e.printStackTrace();
		    }
			System.out.println(Thread.currentThread().getName() +"'s count: " + count);
		}
	}
}
class Task implements Runnable {
	public void run() {
		Task2 ta2 = new Task2();
		ta2.init(); ta2.f1();
	}
}

public class JMain {
	public static void main(String[] args) {
		Task ta = new Task();
		Thread t1 = new Thread( ta, "A");
		Thread t2 = new Thread( ta, "B");
		t1.start();
		t2.start();
		System.out.println("Waiting...");
	}	 
}

这里,t1和t2线程共享主线程所诞生的ta对象,但是各自诞生了Task2类别之对象。两者各自使用自己的对象(即不共享对象或变量),就不会干扰到别线程的数据。所以输出正确的结果:

SurfaceView与非UI线程

View控件是由UI 线程(主线程)所执行。如果需要去迅速更新UI画面或者UI画图需要较长时间(避免阻塞主线程),就使用SurfaceView。 它可以由背景线程(background thead)来执行,而View只能由UI(主)线程执行画面显示或更新。

Android从程序员到架构师之路3

在SurfaceView里,非UI线程可以去碰触UI显示,例如将图形绘制于Surface画布上。这SurfaceView内含高效率的rendering机制,能让背景线程快速更新surface的内容,适合演示动画(animation)。

46 - 认识线程(Thread)模式g

7. 线程安全的化解之例

View是一个单线程的类;其意味着:此类的撰写着心中意图只让有一个线程来执行这个类的代码(如函数调用)。

Android从程序员到架构师之路3

// ac01.java
// ……..
public class ac01 extends Activity implements OnClickListener {
	private Button btn;
	public void onCreate(Bundle icicle) {
		// ……..
		btn = new Button(this);
		btn.setText(“Exit");
		// ……..
	}
	public void f1() {
	// ……..
	btn.setText(“OK");
	// ……..
	}
}

同样地,View的子类开发者也不宜让多线程去执行View(基类)的代码。// ……
public class ac01 extends Activity {
	@Override 
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		okButton ok_btn = new okButton(this);
		LinearLayout.LayoutParams param =
			new LinearLayout.LayoutParams(ok_btn.get_width(),ok_btn.get_height());
		// ……..
		}
    }

/* ---- okButton ---- */
// ……….
public class okButton extends Button{
	public okButton(Context ctx){
		super(ctx);
		super.setText("OK");
		super.setBackgroundResource(R.drawable.ok_blue);
	}
	public void f1() {
		super.setText("Quit");
	}
	public int get_width(){ return 90; }
	public int get_height(){ return 50; }
}

如果共享对象或变量是不可避免的话,就得试图错开线程的执行时刻了。
由于共享对象或变量,若两个线程会争相更改对象的属性值或变量值时,则可能会互相干扰对方的计算过程和结果。 例如:

class Task implements Runnable {
	private int count;
	public void init(){ count = 0; }
	public void f1() {
		for(int i=0; i<3; i++) {
		count++;
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) { 
			e.printStackTrace(); }
			System.out.println(Thread.currentThread().getName() +"'s count: " + count);
		}
	}
	public void run() {
		this.init(); 
		this.f1();
	}
}

public class JMain {
	public static void main(String[] args) {
		Task ta = new Task();
		Thread t1 = new Thread( ta, "A");
		Thread t2 = new Thread( ta, "B");
		t1.start();
		t2.start();
		System.out.println("Waiting...");
		
	 } 
}

Android从程序员到架构师之路3

由于在这个程序只会诞生myActivity对象,却可能诞生多个Thread对象,可能出现多条线程同时并行(Concurrently)执行run()函数的情形。此时必须特别留意线程冲突问题。也就是多条线程共享变量或对象,导致互相干扰计算中的变量值,因而产生错误的计算结果。

例如,依据上图的设计结构,撰写程序码,可能无意中这会产生冲突了。 • 如下范例

// myActivity.java
//……….
public class myActivity extends Activity
implements OnClickListener, Runnable {
	private Button ibtn;
	private int sum;
	@Override
	protected void onCreate(Bundle icicle) {
		//………
		Thread th1 = new Thread(this); th1.start();
		Thread th2 = new Thread(this); th2.start();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			 e.printStackTrace();
		}
		setTitle(String.valueOf(sum));
}

public void onClick(View v) {
	finish();
}
//------------------------------------------
@Override 
public void run() {
	sum = 0;
	for(int i=0; i<10000; i++ )
		sum += 1;
} }

第一个线程还没做完run()函数的计算,其后的第二个线程就进来run()函数,并行共享了sum变量值,因而输出错误的结果:11373。

此时,可以使用synchronized机制来错开两个线程,就正确了。例如将上数程序码
修改如下:

// ………… 
int sum; 
Thread th1 = new Thread(this);
th1.start();
Thread th2 = new Thread(this);
th2.start();
Thread.sleep(1000);
setTitle(String.valueOf(sum));
// ………….
@Override 
public void run() {
	this.exec();
}
public synchronized void exec(){
	sum = 0;
	for(int i=0; i<10000; i++ ) sum += 1;
}
// end

第二个线程会等待第一个线程离开exec()函数之后才能进入exec(),就不会产生共享sum变量值的现象了。由于变量就存于对象内部,如果不共享对象,就可避免共享内部变量的问题。

47 - 应用Android的UI框架a

以设计游戏循环(GameLoop)为例

1. UI线程、View与onDraw()函数

1.游戏的UI画面通常是由大量美工贴图所构成的,并不会使用一般的Layout来布局,而是使用画布(Canvas)来把图片显示于View的窗口里。
2.在View类里有个onDraw()函数,View类体系里的每一个类都必须覆写(Override) 这 个onDraw()函数,来执行实际绘图的动作。

Android从程序员到架构师之路3

游戏的基本动作就是不断的进行:绘图和刷新(Refresh)画面。其中,onDraw()函数实践画图,将图形绘制于View的画布(Canvas)上,并显示出来;而invalidate()函数则启动画面的刷新,重新調用一次onDraw()函数。

当我们设计myView子类别时,也必须覆写onDraw()函数。在程序执行时,Android框架会进行反向調用到myView的onDraw()函数来进行画图动作。如下图:

Android从程序员到架构师之路3

2. 基本游戏循环(GameLoop)

游戏的基本动作就是不断的绕回圈(Loop),重复绘图和刷新画面的动作。最简单的循环实现方式是:在onDraw()函数里調用invalidate()函数,就能刷新画面(重新調用一次onDraw()函数)了。

Android从程序员到架构师之路3

// myView.java
// ………
public class myView extends View {
	private Paint paint= new Paint();
	private int line_x = 100, line_y = 100;
	private float count = 0;
	myView(Context ctx) { super(ctx); }
	@Override 
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		//-----------------------------------------------------
		if( count > 12) count = 0;
		int x = (int) (75.0 * Math.cos(2*Math.PI * count/12.0));
		int y = (int) (75.0 * Math.sin(2*Math.PI * count/12.0));
		count++;
		//---------------------------------------------
		canvas.drawColor(Color.WHITE);
		paint.setColor(Color.BLACK);
		paint.setStrokeWidth(3);
		canvas.drawLine(line_x, line_y, line_x+x, line_y+y, paint);
		paint.setStrokeWidth(2);
		paint.setColor(Color.RED);
		canvas.drawRect(line_x-5, line_y - 5, line_x+5, line_y + 5, paint);
		paint.setColor(Color.YELLOW);
		canvas.drawRect(line_x-3, line_y - 3, line_x+3, line_y + 3, paint);
		try {
			Thread.sleep(1000);
		} catch (InterruptedException ie) {}
		invalidate();
	} 
}

Android中提供了invalidate()来实现画面的刷新:即触发框架重新执行onDraw()函数来绘图及显示。

3. 使用UI线程的MQ(Message Queue)

Android从程序员到架构师之路3

// myView.java
// ………
public class myView extends View {
// ……… 
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// ……… 
// canvas.drawRect(….);
invalidate();
} }

我们可以透过Message方式来触发UI线程去調用invalidate()函数,而达到重新执行onDraw()来进行重复绘图和刷新画面的动作。

// myView.java
//……..
public class myView extends View {
	private Paint paint= new Paint();
	private int line_x = 100, int line_y = 100;
	private float count = 0;
	private myHandler h;
	myView(Context ctx)
	{ super(ctx); 
	h = new myHandler(); 
	}
	@Override 
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		if( count > 12) count = 0;
		int x = (int) (75.0 * Math.cos(2*Math.PI * count/12.0));
		int y = (int) (75.0 * Math.sin(2*Math.PI * count/12.0));
		count++;
		canvas.drawColor(Color.WHITE);
		paint.setColor(Color.RED);
		paint.setStrokeWidth(3);
		canvas.drawLine(line_x, line_y, line_x+x, line_y+y, paint);
		paint.setStrokeWidth(2);
		paint.setColor(Color.BLUE);
		canvas.drawRect(line_x-5, line_y - 5, line_x+5, line_y + 5, paint);
		paint.setColor(Color.YELLOW);
		canvas.drawRect(line_x-3, line_y - 3, line_x+3, line_y + 3, paint);
		h.removeMessages(0);
		Message msg = h.obtainMessage(0);
		h.sendMessageDelayed(msg, 1000);
	}
	class myHandler extends Handler {
		@Override 
		public void handleMessage(Message msg) {
			invalidate();
		}
	};
}

使用sendMessageDelayed()函数来暂停一下,延迟数秒钟才传递 Message给UI线程

4. 诞生一个小线程,担任游戏线程

刚才是由UI线程来丢Message到自己的MQ里;也就是UI线程丢Message给自己。同一样地,也可以由其它线程来丢Message到UI线程的MQ里,来触发UI线程去調用invalidate()函数。

// myView.java
// ……….
public class myView extends View {
	private Paint paint= new Paint();
	private int line_x = 100, line_y = 100;
	private float count = 0;
	private myHandler h;
	myView(Context ctx) { super(ctx); h = new myHandler(); }
	@Override 
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		if( count > 12) count = 0;
		int x = (int) (75.0 * Math.cos(2*Math.PI * count/12.0));
		int y = (int) (75.0 * Math.sin(2*Math.PI * count/12.0));
		count++;
		canvas.drawColor(Color.WHITE);
		paint.setColor(Color.RED);
		paint.setStrokeWidth(3);
		canvas.drawLine(line_x, line_y, line_x+x, line_y+y, paint);
		paint.setStrokeWidth(2);
		paint.setColor(Color.BLUE);
		canvas.drawRect(line_x-5, line_y - 5, line_x+5, line_y + 5, paint);
		paint.setColor(Color.MAGENTA);
		canvas.drawRect(line_x-3, line_y - 3, line_x+3, line_y + 3, paint);
		//--------------------------------
		myThread t = new myThread();
		t.start();
	}

// 诞生一个小线程,担任游戏线程,负责回圈控制
class myThread extends Thread{
	public void run() {
		h.removeMessages(0);
		Message msg = h.obtainMessage(0);
		h.sendMessageDelayed(msg, 1000);
	}
};
class myHandler extends Handler {
	@Override 
	public void handleMessage(Message msg) {
		invalidate(); // call onDraw()
	}};
}


UI线程诞生一个小线程,并且由该小线程去执行myThread类别里的run()函数。接着,这新线程执行到指令:

h.removeMessages(0);
Message msg = h.obtainMessage(0);
h.sendMessageDelayed(msg, 1000);

延迟数秒钟才传递 Message给UI线程(丢 入UI线程的MQ里)。 • 当UI线程发现MQ有个Message,就去执行myHandler类别里的handleMessage()函数。就触发UI线程去調用invalidate()函数了。

48 - 应用Android的UI框架b

5. 小线程調用postInvalidate()

刚才的小线程传递Message给UI线程(丢入UI线程的MQ里),触发UI线程去調用invalidate()函数。Android提供一个postInvalidate()函数来替代上述的动作。由小线程直接去調用postInvalidate()函数,就能间接触发UI线程去調用invalidate()函数了。

// myView.java
//……
public class myView extends View {
	//……….
	@Override 
	protected void onDraw(Canvas canvas) {
		//…………..
		myThread t = new myThread();
		t.start();
	}
	class myThread extends Thread{
		public void run() {
			postInvalidateDelayed(1000);
		}
	};
}

由小线程直接去調用postInvalidate()函数;就相当于,由小线程传递Message给UI线程,触发UI线程去調用invalidate()函数。

49 - 应用Android的UI框架c

6. 设计一个GameLoop类别

刚才的小线程,其实就扮演了游戏线程(Game thread)的角色,它负责控制游戏的循环。

Android从程序员到架构师之路3

// myView.java
//……
public class myView extends View {
	//……….
	@Override 
	protected void onDraw(Canvas canvas) {
		//…………..
		myThread t = new myThread();
		t.start();
		}
		class myThread extends Thread{
		public void run() {
		postInvalidateDelayed(1000);
	}};
}

于是,我们将刚才的小线程部分独立出来,成为一个独立的类别,通称为游戏线程(Game Thread) 或游戏循环(Game Loop)。

Android从程序员到架构师之路3

// GameLoop.java
// ………
public class GameLoop extends Thread {
	myView mView;
	GameLoop(myView v){
		mView = v;
	}
	public void run() {
		mView.onUpdate();
		mView.postInvalidateDelayed(1000);
	} 
}

// myView.java
// ………..
public class myView extends View {
	private Paint paint= new Paint();
	private int x, y;
	private int line_x = 100;
	private int line_y = 100;
	private float count = 0;
	myView(Context ctx) {
	super(ctx);
	}
	public void onUpdate(){
		if( count > 12) count = 0;
		x = (int) (75.0 * Math.cos(2*Math.PI * count/12.0));
		y = (int) (75.0 * Math.sin(2*Math.PI * count/12.0));
		count++;
	}
	@Override 
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		canvas.drawColor(Color.WHITE);
		paint.setColor(Color.BLUE);
		paint.setStrokeWidth(3);
		canvas.drawLine(line_x, line_y, line_x+x, line_y+y, paint);
		paint.setStrokeWidth(2);
		paint.setColor(Color.RED);
		canvas.drawRect(line_x-5, line_y - 5, line_x+5, line_y + 5, paint);
		paint.setColor(Color.CYAN);
		canvas.drawRect(line_x-3, line_y - 3, line_x+3, line_y + 3, paint);
		//--------------------------------
		GameLoop loop = new GameLoop(this);;
		loop.start();
	} 
}

首先由myActivity来诞生myView对象,然后由Android框架調用myView的onDraw()函数来绘图和显示。绘图完毕,立即诞生一个GameLoop对象,并調用start()函数去启动一个小线程去調用postInvalidate()函数。就触发UI线程重新調用myView的onDraw()函数。

50 - 应用Android的UI框架d

7. 只诞生一次GameLoop对象

Android从程序员到架构师之路3

每次执行onDraw()时,都会重新诞生一次GameThread对象,也诞生一次游戏线程去調用postInvalidate()函数。似乎是UI线程控制着游戏线程,这样游戏线程就不能扮演主控者的角色了。
于是,可换一个方式:一开始先诞生一个游戏线程,并且使用while(true)来创造一个无限循环(Endless Loop),让游戏线程持续绕回圈,而不会停止。

Android从程序员到架构师之路3

在诞生myView时,就诞生GameLoop对象,且調用其start()函数来启动游戏线程。此时游戏线程处于<暂停>状态,虽然继续绕回圈,但是并不会調用postInvalidate()函数。接着,由Android框架調用myView的onDraw()函数来绘图和显示。

绘图完毕,立即調用GameLoop的loopResume()函数,让GameLoop从<暂 停>状态转移到<执行>状态。此时,这游戏线程就去調用postInvalidate()函数,触发UI线程重新調用myView的onDraw()函数。如下图:

Android从程序员到架构师之路3

// GameLoop.java
// ……..
public class GameLoop extends Thread {
private myView mView;
private boolean bRunning;
GameLoop(myView v){
	mView = v; bRunning = false; }
public void run() {
	while(true){
		if(bRunning){
		mView.onUpdate();
		mView.postInvalidateDelayed(1000);
		loopPause();
		} 
	}
}
public void loopPause(){ bRunning = false; }
public void loopResme(){ bRunning = true; } }

其中,loopPause()函数将bRunning设定为false,游戏线程就处于<暂停>状态。loopResume()函数将bRunning设定为true,游戏线程就处于<执行状态,就会調用myView的onUpdate()函数,去更新绘图的设定。然后調用postInvalidate()函数,触发UI线程去重新調用onDraw()函数。

// myView.java
// ………
public class myView extends View {
	private Paint paint= new Paint();
	private int x, y;
	private int line_x = 100, line_y = 100;
	private float count;
	private GameLoop loop;
	myView(Context ctx) {
		super(ctx);
		init();
		loop = new GameLoop(this);
		loop.start();
	}
	public void init(){
		count = 0;
		x = (int) (75.0 * Math.cos(2*Math.PI * count/12.0));
		y = (int) (75.0 * Math.sin(2*Math.PI * count/12.0));
	}
	public void onUpdate(){ // 游戏线程执行的
		if( count > 12) count = 0;
		x = (int) (75.0 * Math.cos(2*Math.PI * count/12.0));
		y = (int) (75.0 * Math.sin(2*Math.PI * count/12.0));
		count++;
	}
	@Override 
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		canvas.drawColor(Color.WHITE);
		paint.setColor(Color.BLUE);
		paint.setStrokeWidth(3);
		canvas.drawLine(line_x, line_y, line_x+x, line_y+y, paint);
		paint.setStrokeWidth(2);
		paint.setColor(Color.RED);
		canvas.drawRect(line_x-5, line_y - 5, line_x+5,
		line_y + 5, paint);
		paint.setColor(Color.CYAN);
		canvas.drawRect(line_x-3, line_y - 3, line_x+3, 
		line_y + 3, paint);
		//--------------------------------
		loop.loopResme();
	} 
}

请留意:onUpdate()函数是由游戏线程所执行的;而onDraw()则是由UI线程所执行的。

51 - SurfaceView的UI多线程a

1. View与SurfaceView之区别

SurfaceView是View的子类,其内嵌了一个用来绘制的Surface。 • 当SurfaceView成为可见时,就会诞生Surface;反之当SurfaceView被隐藏时,就会删除Surface,以便节省资源。
程序里可以控制Surface的大小,SurfaceView可控制Surface的绘图位置。

Android从程序员到架构师之路3

View组件是由UI 线程(主线程所执行)。如果需要去迅速更新UI画面或者UI画图需要较长时间(避免阻塞主线程),就使用SurfaceView。 
它可以由背景线程(background thead)来执行,而View只能由UI(主)线程执行。这SurfaceView内含高效率的rendering机制,能让背景线程快速更新surface的内容,适合演示动画(animation)。

Android从程序员到架构师之路3

在程序里,可以通过SurfaceHolder接口来处理Surface,只要调用getHolder()函数就可以取得此接口。
当SurfaceView成为可见时,就会诞生Surface;反之当SurfaceView被隐藏时,就会删除Surface,以便节省资源。当Surface诞生和删除时,框架互调用SurfaceCreated()和 SurfaceDestroyed()函数。

Android从程序员到架构师之路3
Android从程序员到架构师之路3

52 - SurfaceView的UI多线程b

2. 使用SurfaceView画2D图

以SurfaceView绘出Bitmap图像

设计SpriteView类别来实作SurfaceHolder.Callback接口

首先来看个简单的程序,显示出一个Bitmap图像。这个图像就构成Sprite动画的基本图形。这个图像如下:

Android从程序员到架构师之路3
Android从程序员到架构师之路3

// SpriteView.java
// ………
public class SpriteView implements SurfaceHolder.Callback{
	private SpriteThread sThread;
	private Paint paint;
	private Bitmap bm;
	public SpriteView(Bitmap bmp) { bm = bmp; } 
	@Override 
	public void surfaceCreated(SurfaceHolder holder) {
		sThread = new SpriteThread(holder, this);
		sThread.start();
	} 
	@Override 
	public void surfaceDestroyed(SurfaceHolder holder) {}
	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, 
	int width, int height) {}
	protected void onDraw(Canvas canvas) {
		paint= new Paint();
		canvas.drawColor(Color.WHITE);
		canvas.drawBitmap(bm, 10, 10, paint);
	}
	public class SpriteThread extends Thread{
		private SpriteView mView;
		private SurfaceHolder mHolder;
		private Canvas c;
		SpriteThread(SurfaceHolder h, SpriteView v){
		mHolder = h mView = v;
		}
		public void run(){
			try{
				c = mHolder.lockCanvas(null);
				synchronized (mHolder){
					mView.onDraw(c);
				} 
			}finally{
				if(c!=null)
					mHolder.unlockCanvasAndPost(c);
			}
		}
	}
}

设计GameLoop类别把小线程移出来

Android从程序员到架构师之路3

// SpriteView.java
// ……..
public class SpriteView implements SurfaceHolder.Callback{
	private SpriteThread sThread;
	private Paint paint;
	private Bitmap bm;
	public SpriteView(Bitmap bmp) { bm = bmp; }
	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		sThread = new SpriteThread(holder, this);
		sThread.start();
	}
	@Override
	public void surfaceDestroyed(SurfaceHolder holder) 
	{ }
	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
	int height) { }
	protected void onDraw(Canvas canvas) {
		paint= new Paint();
		canvas.drawColor(Color.WHITE);
		canvas.drawBitmap(bm, 10, 10, paint);
	} 
}
	// SpriteThread.java
	//……….
public class SpriteThread extends Thread {
	private SpriteView mView;
	private SurfaceHolder mHolder;
	private Canvas c;
	SpriteThread(SurfaceHolder h, SpriteView v){
		mHolder = h; mView = v;
	}
	public void run(){
		try{
		c = mHolder.lockCanvas(null);
		synchronized (mHolder){ mView.onDraw(c); } }finally{
		if(c!=null){ mHolder.unlockCanvasAndPost(c); }
		}
	}
}

Android从程序员到架构师之路3

// myActivity.java
// ……..
public class myActivity extends Activity 
implements OnClickListener {
	private SurfaceView sv = null;
	private Button ibtn;
	private Bitmap bm;
	private SpriteView spView;
	@Override 
	protected void onCreate(Bundle icicle) {
		super.onCreate(icicle);
		LinearLayout layout = new LinearLayout(this);
		layout.setOrientation(LinearLayout.VERTICAL);
		sv = new SurfaceView(this);
		bm = BitmapFactory.decodeResource(
		getResources(), R.drawable.walk_elaine);
		spView = new SpriteView(bm);
		sv.getHolder().addCallback(spView);
		LinearLayout.LayoutParams param =
		new LinearLayout.LayoutParams(200, 200);
		param.topMargin = 10; param.leftMargin = 10;
		layout.addView(sv, param);
		//----------------------------------------------
		ibtn = new Button(this); ibtn.setOnClickListener(this);
		ibtn.setText("Exit");
		ibtn.setBackgroundResource(R.drawable.gray);
		LinearLayout.LayoutParams param1 =
		new LinearLayout.LayoutParams(200, 65);
		param1.topMargin = 10; param1.leftMargin = 10;
		layout.addView(ibtn, param1);
		setContentView(layout);
	}
	public void onClick(View v) { finish(); } 
}

让图像在SurfaceView里旋转

Android从程序员到架构师之路3
Android从程序员到架构师之路3

在MySurfaceView里定义一个DrawThread类,它诞生一个单独的线程来执行画图的任务。
当主线程侦测到绘图画面(Surface)被开启时,就会诞生DrawThread对象,启动新线程去画图。
一直到主要线程侦测到绘图画面被关闭时,就停此正在绘图的线程。

class MySurfaceView extends SurfaceView
	implements SurfaceHolder.Callback {
	private SurfaceHolder mHolder;
	private DrawThread mThread;
	MySurfaceView(Context context) {
		super(context);
		getHolder().addCallback(this);
	}
	public void surfaceCreated(SurfaceHolder holder) {
		mHolder = holder; 
		mThread = new DrawThread(); 
		mThread.start(); }
	public void surfaceDestroyed(SurfaceHolder holder) {
		mThread.finish();
		mThread = null; 
	}
	public void surfaceChanged(SurfaceHolder holder, int format, 
	int w, int h) { }
	class DrawThread extends Thread {
		int degree = 36;
		boolean mFinished = false;
		DrawThread() { super(); }
		@Override 
		public void run() {
			Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.x_xxx);
			Matrix matrix;
			degree = 0; 
			while(!mFinished){
			Paint paint = new Paint(); paint.setColor(Color.CYAN);
			Canvas cavans = mHolder.lockCanvas();
			cavans.drawCircle(80, 80, 45, paint);
			//------ rotate -----------------------------
			matrix = new Matrix(); matrix.postScale(1.5f, 1.5f);
			matrix.postRotate(degree);
			Bitmap newBmp 
			= Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(),
			bmp.getHeight(), matrix, true);
			cavans.drawBitmap(newBmp, 50, 50, paint);
			mHolder.unlockCanvasAndPost(cavans);
			degree += 15;
			try { Thread.sleep(100);
			} catch (Exception e) {}
			}
		}
			void finish(){ mFinished = true;}
	}
}

Android从程序员到架构师之路3

// ac01.java-
//……..
public class ac01 extends Activity {
	@Override 
	protected void onCreate(Bundle icicle) {
		super.onCreate(icicle);
		MySurfaceView mv = new MySurfaceView(this);
		LinearLayout layout = new LinearLayout(this);
		layout.setOrientation(LinearLayout.VERTICAL);
		LinearLayout.LayoutParams param =
		new LinearLayout.LayoutParams(200, 150);
		param.topMargin = 5;
		layout.addView(mv, param);
		setContentView(layout);
	}
}

53 - AIDL与Proxy-Stub设计模式a

1. 复习:IBinder接口

Android从程序员到架构师之路3
Android从程序员到架构师之路3
Android从程序员到架构师之路3
Android从程序员到架构师之路3
Android从程序员到架构师之路3
Android从程序员到架构师之路3
Android从程序员到架构师之路3

onTransact()就是EIT造形里的<I>这是标准的EIT造形,其<I>是支持<基类/子类>之间IoC调用的接口。
运用曹操(Stub)类,形成两层EIT(两层框架)。	

Android从程序员到架构师之路3
Android从程序员到架构师之路3
Android从程序员到架构师之路3

54 - AIDL与Proxy-Stub设计模式b

2. IBinder接口的一般用途

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gQ7jSnGI-1581763683510)(H:\架构师\架构师_视频\unzip\B05-10.PNG)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pn4EYFXj-1581763683512)(H:\架构师\架构师_视频\unzip\B05-11.PNG)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GiMjDv0O-1581763683513)(H:\架构师\架构师_视频\unzip\B05-12.PNG)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PdjpLFIJ-1581763683514)(H:\架构师\架构师_视频\unzip\B05-13.PNG)]

Android的IPC框架仰赖单一的IBinder接口。此时Client端调用IBinder接口的transact()函数,透过IPC机制而调用到远方(Remote)的onTransact()函数。
在Java层框架里,IBinder接口实现于Binder基类,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5VD1nkSb-1581763683515)(H:\架构师\架构师_视频\unzip\B05-14.PNG)]

myActivity调用IBinder接口,执行myBinder的onTransact()函数,可送信息给myService去播放mp3音乐,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BkD22JLv-1581763683517)(H:\架构师\架构师_视频\unzip\B05-15.PNG)]

myService也能送Broadcast信息给myActivity,将字符串显示于画面上:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MVdwlHJY-1581763683518)(H:\架构师\架构师_视频\unzip\B05-16.PNG)]

// myActivity.java
// ………
public class myActivity extends Activity implements OnClickListener {
	 private final int WC = LinearLayout.LayoutParams.WRAP_CONTENT;
	 private final int FP = LinearLayout.LayoutParams.FILL_PARENT;
	 private Button btn, btn2, btn3;
	 public TextView tv;
	 private IBinder ib = null;
	 private final String MY_S_EVENT =
	 new String("com.misoo.pk01.myService.MY_S_EVENT");
	 protected final IntentFilter filter=new IntentFilter(MY_S_EVENT);
	 private BroadcastReceiver receiver=new myIntentReceiver();
	public void onCreate(Bundle icicle) {
	super.onCreate(icicle);
	LinearLayout layout = new LinearLayout(this);
	 layout.setOrientation(LinearLayout.VERTICAL);
	btn = new Button(this); btn.setId(101); btn.setText("play");
	btn.setBackgroundResource(R.drawable.heart);
	btn.setOnClickListener(this);
	LinearLayout.LayoutParams param =
	 new LinearLayout.LayoutParams(80, 50);
	param.topMargin = 10; layout.addView(btn, param);
	btn2 = new Button(this);
	 btn2.setId(102);btn2.setText("stop");
	btn2.setBackgroundResource(R.drawable.heart);
	btn2.setOnClickListener(this);
	layout.addView(btn2, param);
	btn3 = new Button(this);
	btn3.setId(103); btn3.setText("exit");
	btn3.setBackgroundResource(R.drawable.cloud);
	btn3.setOnClickListener(this);
	layout.addView(btn3, param);
	tv = new TextView(this); tv.setText("Ready");
	LinearLayout.LayoutParams param2 = new
	LinearLayout.LayoutParams(FP, WC);
	param2.topMargin = 10;
	layout.addView(tv, param2);
	setContentView(layout);
	//---------------------------------
	registerReceiver(receiver, filter);
	//------------------------------------------------------
	bindService( new
	 Intent("com.misoo.pk01.REMOTE_SERVICE"),
	 mConnection, Context.BIND_AUTO_CREATE);
	}
	 btn3 = new Button(this);
	 btn3.setId(103); btn3.setText("exit");
	 btn3.setBackgroundResource(R.drawable.cloud);
	 btn3.setOnClickListener(this);
	 layout.addView(btn3, param);
	 tv = new TextView(this); tv.setText("Ready");
	 LinearLayout.LayoutParams param2 = new
	 LinearLayout.LayoutParams(FP, WC);
	 param2.topMargin = 10;
	 layout.addView(tv, param2);
	 setContentView(layout);
	 //---------------------------------
	 registerReceiver(receiver, filter);
	 //------------------------------------------------------
	 bindService( new Intent("com.misoo.pk01.REMOTE_SERVICE"),
	 mConnection, Context.BIND_AUTO_CREATE) );
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override 
public void onServiceConnected(ComponentName className,IBinder ibinder) {
	ib = ibinder;
}
@Override 
public void onServiceDisconnected(ComponentName name) {}};

public void onClick(View v) {
 	switch (v.getId()) {
	case 101: // Play Button
		Parcel data = Parcel.obtain();
		Parcel reply = Parcel.obtain();
		try { 
			ib.transact(1, data, reply, 0);
		} catch (Exception e) {
			 e.printStackTrace();
		}
		break;
 	 case 102: // Stop Button
		data = Parcel.obtain(); reply = Parcel.obtain();
		try { 
			ib.transact(2, data, reply, 0);
		} catch (Exception e) { 
			e.printStackTrace();
		}
		break;
 	case 103: 
		finish();
		break;
	}
}

其中的代码:
 case 101: // Play Button
 //…..
ib.transact(1, data, reply, 0);
 case 102: // Stop Button
 // …..
ib.transact(2, data, reply, 0);

• 就是对<Play>和<Stop>两个功能进行”编码” 的动作。
• 编好码之后,就将这编码值当做第1个参数传给IBinder接口的transact()函数。
• 于是编码值就跨进程地传递到myBinder类里的onTransact()函数了。

class myIntentReceiver extends BroadcastReceiver {
	@Override
	public void onReceive(Context context, Intent intent) {
		int bn = intent.getIntExtra("key",-1);
		if(bn == 0)
		 tv.setText("Playing");
		else
		 tv.setText("Stop.");
		}
	}
}


// myService.java
// ……..
public class myService extends Service implements Runnable {
	private IBinder mBinder = null;
	private Thread th1;
	public static Handler h;
	private MediaPlayer mPlayer = null;
	public static Context ctx;
	private final String MY_S_EVENT = new String("com.misoo.pk01.myService.MY_S_EVENT");
	@Override 
	public void onCreate() {
		super.onCreate(); 
		ctx = this;
		mBinder = new myBinder();
		// 诞生一个子线程及其MQ;等待Message
		th1 = new Thread(this);
		th1.start();
	}
	@Override
	public IBinder onBind(Intent intent) { return mBinder; ]
		public void run() {
		Looper.prepare();
		h = new EventHandler(Looper.myLooper());
		Looper.loop();
	}
//---------------------------------------
class EventHandler extends Handler {
	public EventHandler(Looper looper) { super(looper); }
	public void handleMessage(Message msg) {
		String obj = (String)msg.obj;
		if(obj.contains("play")) {
			if(mPlayer != null) return;
		 	//----------------------------------
			Intent in = new Intent(MY_S_EVENT);
			in.putExtra("key", 0);
			ctx.sendBroadcast(in);
			//----------------------------------
			mPlayer = MediaPlayer.create(ctx, R.raw.dreamed);
		 	try {
				mPlayer.start();
		 	} catch (Exception e) {
		 		Log.e("Play", "error: " + e.getMessage(), e);
		 	}
 		} else if(obj.contains("stop")) {
 			if (mPlayer != null) {
				Intent in = new Intent(MY_S_EVENT);
				in.putExtra("key", 1);
			    ctx.sendBroadcast(in);
				//----------------------------------
				mPlayer.stop(); mPlayer.release();
				mPlayer = null;
			}
		}
	 }}
}
// myBinder.java
// …….
public class myBinder extends Binder{
	@Override 
	public boolean onTransact( int code, Parcel data,
	Parcel reply, int flags) throws android.os.RemoteException {
		switch( code ){
			case 1:
			// 将Message丢到子线程的MQ to play MP3
			String obj = "play";
			Message msg = myService.h.obtainMessage(1,1,1,obj);
			myService.h.sendMessage(msg);
			break;
			case 2:
			// 将Message丢到子线程的MQ to stop playing
			obj = "stop";
			msg = myService.h.obtainMessage(1,1,1,obj);
			myService.h.sendMessage(msg);
			break;
		}
		return true;
	}
}

其代码就是对code进行“译码”动作。如果code值為1就執行<Play>動作;如果code值為2就執行<Stop>動作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xZ6XzHqO-1581763683521)(H:\架构师\架构师_视频\unzip\B05-17.PNG)]

55 - AIDL与Proxy-Stub设计模式c

  1. 包裝IBinder接口-- 使用Proxy-Stub设计模式

    采用Proxy-Stub设计模式将IBinder接口包装起来,让App与IBinder接口不再产生高度相依性。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QnQGFIZa-1581763683522)(H:\架构师\架构师_视频\unzip\B05-18.PNG)]

其将IBinder接口包装起来,转换出更好用的新接口:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MgubrgrF-1581763683524)(H:\架构师\架构师_视频\unzip\B05-19.PNG)]

Proxy类提供较好用的IA接口给Client使用。
Stub类别则是屏蔽了Binder基类的onTransact()函数,然后将IA接口里的f1()和f2()函数定义为抽象函数。于是简化了
App开发的负担:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-juE2gWzi-1581763683525)(H:\架构师\架构师_视频\unzip\B05-20.PNG)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qijlebk5-1581763683526)(H:\架构师\架构师_视频\unzip\B05-21.PNG)]

  1. 谁来写Proxy及Stub类呢?-- 地头蛇(App开发者)自己写

    在这个范例里,定义了一个IPlayer接口,然后规划了PlayerProxy和PlayerStub两的类,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vy0v5GbW-1581763683527)(H:\架构师\架构师_视频\unzip\B05-22.PNG)]

定义一个新接口:IPlayer

// IPlayer.java
package com.misoo.pkgx;
public interface IPlayer {
	void play();
	void stop();
	String getStatus();
}

撰写一个Stub类:PlayerStub

// PlayerStub.java
package com.misoo.pkgx;
import android.os.Binder;
import android.os.Parcel;
public abstract class PlayerStub extends Binder implements IPlayer{
	@Override 
	public boolean onTransact(int code, Parcel data,
 		Parcel reply, int flags) throws android.os.RemoteException {
		reply.writeString(data.readString()+ " mp3");
		if(code == 1) this.play();
		else if(code == 2) this.stop();
		return true;
		}
		public abstract void play();
		public abstract void stop();
		public abstract String getStatus();
}

撰写一个Proxy类:PlayerProxy

// PlayProxy.java
private class PlayerProxy implements IPlayer{
	private IBinder ib;
	private String mStatus;
	
	PlayerProxy(IBinder ibinder) { 
		ib = ibinder; 
	}
	public void play(){
		Parcel data = Parcel.obtain();
		Parcel reply = Parcel.obtain();
		data.writeString("playing");
		try {
			ib.transact(1, data, reply, 0);
	 		mStatus = reply.readString();
	 	} catch (Exception e) {
	 		e.printStackTrace();
		}
	 }
	
	 public void stop(){
	 	Parcel data = Parcel.obtain();
	 	Parcel reply = Parcel.obtain();
	 	data.writeString("stop");
	 	try { 
			ib.transact(2, data, reply, 0);
	 		mStatus = reply.readString();
	 	} catch (Exception e) {
			e.printStackTrace();
		}
	 }
	 public String getStatus() { return mStatus; }
	 }
}

撰写mp3Binder类

// mp3Binder.java
// ……..
public class mp3Binder extends PlayerStub{
	private MediaPlayer mPlayer = null;
	private Context ctx;
	public mp3Binder(Context cx){ ctx= cx; }
	public void play(){
		if(mPlayer != null) return;
		mPlayer = MediaPlayer.create(ctx, R.raw.test_cbr);
		try {
			mPlayer.start();
		} catch (Exception e) {
 			Log.e("StartPlay", "error: " + e.getMessage(), e);
		}
	}
 	public void stop(){
		if (mPlayer != null) { 
 			mPlayer.stop(); 
			mPlayer.release();
			mPlayer = null; 
		}
	}
 	public String getStatus() { return null; }
}

撰写mp3RemoteService类

// mp3RemoteService.java
package com.misoo.pkgx;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class mp3RemoteService extends Service {
	private IBinder mBinder = null;
	@Override
	public void onCreate() {
		mBinder = new mp3Binder(getApplicationContext());
	}
	@Override
	public IBinder onBind(Intent intent) {
		return mBinder; 
	}
}

// ac01.java
// ………
public class ac01 extends Activity implements OnClickListener {
	 //……….
	 private PlayerProxy pProxy = null;
	 public void onCreate(Bundle icicle) {
		 // ………
		 startService(new Intent("com.misoo.pkgx.REMOTE_SERVICE"));
		 bindService(new Intent("com.misoo.pkgx.REMOTE_SERVICE"),
		 mConnection, Context.BIND_AUTO_CREATE); }
		 private ServiceConnection mConnection =
		 new ServiceConnection() {
		 public void onServiceConnected(ComponentName className,
		 IBinder ibinder)
		 { pProxy = new PlayerProxy(ibinder); }
		 public void onServiceDisconnected(ComponentName classNa){}
	 };

	 public void onClick(View v) {
		 switch (v.getId()) {
			 case 101: pProxy.play(); tv.setText(pProxy.getStatus());
			 break;
			 case 102: pProxy.stop(); tv.setText(pProxy.getStatus());
			 break;
			 case 103:
			 unbindService(mConnection);
			 stopService(
			 new Intent("com.misoo.pkgx.REMOTE_SERVICE"));
			 finish(); break;
		 }
	 }
 } 

PlayerStub类将onTransact()函数隐藏起来,提供一个更具有美感、更亲切的新接口给mp3Binder类使用。
隐藏了onTransact()函数之后,mp3Binder类的开发者就不必费心去了解onTransact()函数了。于是,PlayerProxy与PlayerStub两个类遥遥相对,并且将IPC细节知识(例如transact()和onTransact()函数之参数等)包夹起来。

56 - AIDL与Proxy-Stub设计模式d

  1. 谁来写Proxy及Stub类呢? --强龙提供AIDL工具,给地头蛇产出Proxy和Stub类

    由框架开发者来撰写Proxy-Stub类,才能减轻开发者的负担。
    框架分为:<天子框架>和<曹操框架>。
    因此,应该由两者(天子或曹操)之一来撰写Proxy-Stub类。

    但是,有个难题:IA接口(如下图所示)的内容必须等到<买主>来了才会知道。
    在框架开发阶段,买主还没来,IA接口的知识无法取得,又如何定义IA接口呢? 没有IA接口定义,又如何撰写Stub和Proxy类呢?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xhT04QwC-1581763683529)(H:\架构师\架构师_视频\unzip\B05-23.PNG)]

好办法是:“强龙(天子或曹操)撰写代码(在先) ;然后,地头蛇(App开发者)定义接口(在后)。”

技术之一是:類別模板(class template) 例如,强龙撰写模板:
template< class T >
class SomeClass
{
 private:
 T data;
 public:
 SomeClass() { }
 void set(T da)
 { data = da; }
};

地头蛇利用模板来生成一个类:

SomeClass<Integer> x;

由于接口(interface)是一种特殊的类(class),所以也可以定义模板如下:

template<interface I>
class BinderProxy
 {
 // ………
 };

地头蛇利用模板来生成一个类:

BinderProxy<IPlayer> proxy;

除了模板之外,还有其它编程技术可以实现<强龙写代码,地头蛇定义接口>的方案吗?

AIDL的目的是定义Proxy/Stub来封装IBinder接口,以便产生更亲切贴心的新接口。
所以,在应用程序里,可以选择使用IBinder接口,也可以使用AIDL来定义出新接口。

由于IBinder接口只提供单一函数(即transact()函数)来进行远距通信,呼叫起来比较不方便。
所以Android提供aidl.exe工具来协助产出Proxy和Stub类别,以化解这个困难。

只要你善于使用开发环境的工具(如Android的aidl.exe软件工具)自动产生Proxy和Stub类别的程序代码;那就很方便了。

此范例使用Android-SDK的/tools/里的aidl.exe工具程序,根据接口定义档(如下述的mp3PlayerInterface.aidl)而自动产出Proxy及Stub类别,其结构如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AYblpGbG-1581763683531)(H:\架构师\架构师_视频\unzip\B05-24.PNG)]

藉由开发工具自动产出Proxy及Stub类的代码,再分别转交给ac01和mp3Binder开发者。此范例程序执行时,出现画面如下:

依据UI画面的两项功能:<Play>和< Stop>,以Java定义接口,如下的代码:

// mp3PlayerInterface.aidl
interface mp3PlayerInterface mp3PlayerInterface{
	void play();
	void stop();
}

使用Android-SDK所含的aidl.exe工具,将上述的mp3PlayerInterface.aidl档翻译成为下述的mp3PlayerInterface.java档案。

// mp3PlayerInterface.java
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: mp3PlayerInterface.aidl
*/
// ………
public interface mp3PlayerInterface extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder
implements com.misoo.pkgx.mp3PlayerInterface
{
// ……….
public boolean onTransact(int code, android.os.Parcel data,
android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code){
case INTERFACE_TRANSACTION:{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_play:{
data.enforceInterface(DESCRIPTOR);
this.play();
reply.writeNoException();
return true;
}
case TRANSACTION_stop:{
data.enforceInterface(DESCRIPTOR);
this.stop();
reply.writeNoException();
return true;
}}
return super.onTransact(code, data, reply, flags);
}

private static class Proxy implements
com.misoo.pkgx.mp3PlayerInterface
{
private android.os.IBinder mRemote;
//………….
public void play() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_play, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}

public void stop() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_stop, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}}}
static final int TRANSACTION_play =
(IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_stop =
(IBinder.FIRST_CALL_TRANSACTION + 1);
}
public void play() throws android.os.RemoteException;
public void stop() throws android.os.RemoteException;
}

表面上,此mp3PlayerInterface.java是蛮复杂的,其实它的结构是清晰又简单的,只要对于类继承、反向調用和接口等面向对象观念有足够的认识,就很容易理解了。

// mp3Binder.java
package com.misoo.pkgx;
import android.content.Context;
import android.media.MediaPlayer;
import android.util.Log;
public class mp3Binder extends mp3PlayerInterface.Stub{
private MediaPlayer mPlayer = null;
private Context ctx;
public mp3Binder(Context cx){ ctx= cx; }
public void play(){
 if(mPlayer != null) return;
 mPlayer = MediaPlayer.create(ctx, R.raw.test_cbr);
 try { mPlayer.start();
 } catch (Exception e)
 { Log.e("StartPlay", "error: " + e.getMessage(), e); }
 }
public void stop(){
 if (mPlayer != null)
 { mPlayer.stop(); mPlayer.release(); mPlayer = null; }
}
}

撰写mp3RemoteService类

// mp3Service.java
package com.misoo.pkgx;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class mp3Service extends Service {
 IBinder ib = null;
 @Override public void onCreate() {
 super.onCreate();
 ib = new mp3Binder(this.getApplicationContext());
}
 @Override public void onDestroy() { }
 @Override public IBinder onBind(Intent intent) {return ib;}
}

// ac01.java
// ………
public class ac01 extends Activity implements OnClickListener {
 //……….
 private PlayerProxy pProxy = null;
 public void onCreate(Bundle icicle) {
 // ………
 startService(new Intent("com.misoo.pkgx.REMOTE_SERVICE"));
 bindService(new Intent("com.misoo.pkgx.REMOTE_SERVICE"),
mConnection, Context.BIND_AUTO_CREATE);
}
 private ServiceConnection mConnection = new ServiceConnection() {
 public void onServiceConnected(ComponentName className,
 IBinder ibinder) {
 pProxy = mp3PlayerInterface.Stub.asInterface(ibinder);
 }
 public void onServiceDisconnected(ComponentName className) {}
 };

public void onClick(View v) {
	 switch (v.getId()) {
		 case 101: pProxy.play(); tv.setText(pProxy.getStatus()); break;
		 case 102: pProxy.stop(); tv.setText(pProxy.getStatus()); break;
		 case 103:
		 unbindService(mConnection);
		 stopService(new Intent(
		 "com.misoo.pkgx.REMOTE_SERVICE"));
		 finish(); break;
		 }
	 }
}

对于Anrdoid的初学者而言, Android的AIDL机制可说是最难弄懂的。

57 - 活用IBinder接口于近程通信a

  1. 在同一进程里,活用IBinder接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C1p94TrW-1581763683532)(H:\架构师\架构师_视频\unzip\B06-00.PNG)]

  1. myActivity对象是谁创建的呢?

  2. myService对象是谁创建的呢?

  3. 当myService类里有个f1()函数,如何去调用它呢?

  4. 必须先取得myService对象的指针,才能调用f1()函数去存取对象的属性(Attribute)值。

  5. 那么,该如何才能取得myService对象的指针呢?

  6. 想一想,可以透过myService类的静态(static)属性或函数来取得myService对象的指针吗?

  7. 可以透过IBinder接口来取得myService对象的指针吗?

    IBinder接口的重要目的是支持跨进程的远程调用。然而,它也应用于同一进程里的近程调用。
    例如,当Activity远程调用Service时,我们常用bindService()函数去绑定Service,取得对方的IBinder接口。
    在近程(同一进程内)调用时也可以使用bindService()函数去绑定Service,并取得对方的IBinder接口。

    IBinder接口的典型实现类是Binder基类,其定义于Binder.java档案里。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-woKvFiQn-1581763683534)(H:\架构师\架构师_视频\unzip\B06-01.PNG)]

近程通信(同一进程里)如何使用IBinder接口呢?
举例说明之。
例如,myActivity和myService两者都执行于同一个进程(process)里,而且myActivity提供一个IS接口,其定义如下:

interface IS {
	void f1();
	void f2();
} 

现在,myActivity想要透过此IS接口来调用myService的函数;如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NlU26gN1-1581763683535)(H:\架构师\架构师_视频\unzip\B06-02.PNG)]

  1. 目的、议题与方法

    目的:myActivity想去直接(近程)调用myService类的函数, 例如IS接口里的f1()函数
    议题:如何取的myService对象的IS接口呢?
    方法:先取得myService对象的IBinder接口

步骤是:

Step-1. myActivity透过bindService()函数来绑定(Bind)此myService。
Step-2. myService回传myBinder类的IBinder接口给myActivity。
Step-3. myActivity将IBinder接口转换为myBinder类的接口
Step-4. myActivity调用myBinder类的getService()函数,取得myService的IS接口。
Step-5. 于是,myActivity就能调用IS接口(由myService类实现)的函数了。

在Android 说明文件里,说明道:“If your service is private to your own application and runs in the same process as the client (which is common), you should create your interface by extending the Binder class and returning an instance of it from onBind().
The client receives the Binder and can use it to directly access public methods available in either the Binder implementation or even the Service.

依据上述文件的说明:“… you should create your interface by extending the Binder class and returning an instance of it from onBind().”

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yez5HxQs-1581763683536)(H:\架构师\架构师_视频\unzip\B06-03.PNG)]

依据这个设计图,就来撰写myService类别如下:

// myService.java
// ………..
public class myService extends Service implements IS {
 private final IBinder mBinder = new myBinder();
 //…………
 @Override
 public IBinder onBind(Intent intent) {
 return mBinder;
 }
 //…………
 public class myBinder extends Binder {
 IS getService() {
 return myService.this;
}
 public void f1(){ //……. }
 Public void f2() { //…… }
}

// myActivity.java
//……….
public class myActivity extends Activity {
	IS isv;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		//………..
		Intent intent = new Intent(this, myService.class);
		bindService(intent, mConnection,Context.BIND_AUTO_CREATE);
	}
 
	private ServiceConnection mConnection = new ServiceConnection() {
		@Override
		public void onServiceConnected(ComponentName className,IBinder ibinder) {
			myBinder ib = (myBinder)ibinder;
 			isv = ib.getService();
		}
	}

	public void onClick(View v) {
		// …… 例如:isv.f1()
	}
}

第1步 
当Android框架启动myService时,就立即执行:private final IBinder mBinder = new myBinder();這诞生了myBinder对象。

第2步 
随后,当myActivity调用bindService()时,框架会反向调用到myService的onBind()函数:
public IBinder onBind(Intent intent){
	return mBinder;
}
其将 myBinder的IBinder接口回传给框架,并由框架调用onServiceConnected()函数,将此接口回传给myActivity。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kjAtf1UO-1581763683538)(H:\架构师\架构师_视频\unzip\B06-04.PNG)]

第3步 
由于myActivity与myService在同一个进程里执行,myActivity所获得的就是myBinder的真正接口(不是它的Proxy的);
于是,执行:myBinder ib = (myBinder) ibinder;
就从接获的IBinder接口转型(casting)为myBinder本身接口了。

第4步 
接着,执行:isv = ib.getService();
这透过myBinder本身接口来调用getService()函数,取得了myService的IS接口。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q3IrrX9M-1581763683539)(H:\架构师\架构师_视频\unzip\B06-05.PNG)]

第5步
最后,myActivity就能透过IS接口来调用myService的f1()或f2()函数了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a7BBcKnS-1581763683541)(H:\架构师\架构师_视频\unzip\B06-06.PNG)]

58 - 活用IBinder接口于近程通信b

  1. 留意线程的角色(用小线程执行IS或其他service的接口)

    在上述的范例程序,都是由主线程所执行的。由主线程执行所有的调用。如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pd4FaT6y-1581763683542)(H:\架构师\架构师_视频\unzip\B06-07.PNG)]

例如将上述onClick()函数内容改写为:
public void onClick(View v) {
	th1 = new Thread(this);
	th1.start();
}

public void run() {
	//……. isv.f1()
}

就诞生小线程去调用IS接口了,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LP80hZcO-1581763683543)(H:\架构师\架构师_视频\unzip\B06-08.PNG)]

// ILoad.java
// ………
interface ILoad {
	boolean loadImage();
	boolean cancel();
}

// myService.java
// ………
public class myService extends Service implements ILoad{
	private final IBinder mBinder;
	@Override public IBinder onBind(Intent intent) {
		return mBinder;
	 }
	@Override public void onCreate(){
		super.onCreate();
	 	mBinder = new myBinder();
	 }
	public class myBinder extends Binder{
		ILoad getService(){
		return myService.this;
	 }
	}
	@Override public boolean loadImage() {
		// loading image from cloud
	 }
	@Override public boolean cancel() {
		// cancel loading
	 }
}

// myActivity.java
// ……….
public class myActivity extends Activity implements OnClickListener {
 	ILoad isv;
 	Thread th1;
	 // ……..
	 @Override public void onCreate(Bundle savedInstanceState) {
	 // ………
	 Intent intent = new Intent(this,myService.class);
	 bindService(intent, mConnection,
	 Context.BIND_AUTO_CREATE);
	 }
	private ServiceConnection mConnection = new ServiceConnection(){
	 @Override public void onServiceConnected(ComponentName
	 className, IBinder ibinder) {
	 myBinder ib = (myBinder)ibinder;
	 isv = ib.getService();
	 }
	 @Override public void onServiceDisconnected(
	 ComponentName arg0) { }
	 };
	 
	@Override public void onClick(View v) {
	 switch( v.getId() ){
		 case 101:
		 th1 = new Thread(this);
		 th1.start();
		 break;
		 case 102:
		 isv.cancel();
		 break;
		 default:
		 break;
	}
 }
	public void run() {
 		isv.loadImage();
 	}
}

在这个范例里,活用Android框架提供的Binder基类和IBinder接口。
然后配合myService的onBind()函数,将myBinder的IBinder接口回传给myActivity。
接着,myActivity并不透过 IBinder接口来调用myService的服务。而是直接调用了myService的IS接口。
此外,可擅用小线程来执行比较耗时的服务。

59 - Messager框架与IMessager接口a

  1. Messenger的概念和角色

    同一进程:

    myActivity和myService两者执行于同一的进程里(IPC)
    myActivity的线程想丢信息(Message)给myService的主线程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V3SZqSoH-1581763683545)(H:\架构师\架构师_视频\unzip\B07-00.PNG)]

多条并行(Concurrently)的小线程丢信息到myService主线程的MQ,
变成同步(Synchronized)的调用myService的handleMessage()函数。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CRwi1cbj-1581763683546)(H:\架构师\架构师_视频\unzip\B07-01.PNG)]

不同进程:

myActivity和myService两者执行于不同的进程里(IPC)
myActivity的线程想丢信息(Message)给myService的主线程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hHTqUv2Q-1581763683548)(H:\架构师\架构师_视频\unzip\B07-02.PNG)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fTAJkEI8-1581763683549)(H:\架构师\架构师_视频\unzip\B07-03.PNG)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rc4j9dnN-1581763683552)(H:\架构师\架构师_视频\unzip\B07-04.PNG)]

Messenger类来扩充IBinder接口机制,让其能跨进程地将Message对象传递到另一个进程里,给其主线程(又称UI线程)。
由于Message类实作(Implement)了Parcelable接口,所以Messenger类可以透过IBinder接口而将Message对象传送到另一个进程里的MessengerImpl类。
然后,透过Handler而将Message对象丢入UI线程的MQ里,让UI线程来处理之。
由于是同步(依序)处理信息,所以myService 类的开发者,不必顾虑多线程冲突的安全议题,减轻开发者的负担。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PZS2MYAy-1581763683553)(H:\架构师\架构师_视频\unzip\B07-05.PNG)]

目的:myActivity方的多个线程想丢信息给远程的myService的线程
方法:使用Messager类包装IBinder接口,将信息丢入myService主线程的MQ里。然后,由myService主线程同步(依序)处理这些信息

在学习Android的AIDL时,通常会从Android 说明文件里看到如下的说明:
“Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service.”

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XegBp2Yo-1581763683555)(H:\架构师\架构师_视频\unzip\B07-06.PNG)]

“if you want to perform IPC, but do not need to handle multithreading, implement your interface using aMessenger.”
这短短的几句话,让一些初学者满头雾水,因为其中牵涉到多线程(multithreading)和IPC跨进程的环境。其中,Android文件又说明道:
“If you need your service to communicate with remote processes, then you can use a Messenger to provide the interface for your service. This technique allows you to perform inter-process communication (IPC) without the need to use AIDL.”

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rhv4AGxL-1581763683556)(H:\架构师\架构师_视频\unzip\B07-07.PNG)]

这适用于跨进程的IPC沟通,可让双方透过Messenger来传递Message对象。
同一进程由于是同步(依序)处理信息,所以myService 类的开发者,不必顾虑多线程冲突的安全议题,减轻开发者的负担。

60 - Messager框架与IMessager接口b

  1. Android的Messenger框架

复习:线程、信息和IBinder接口

在Android框架里,有个IBinder接口来担任跨进程的通讯。
在Android框架里,也有一个Message类,两个线程之间能互传Message对象。
于是,就能设计一个Messenger类来包装IBinder接口机制,让其能跨进程地将Message对象传递到另一个进程里,给其主线程(又称UI线程)。
其中,由于Message类实作(Implement)了Parcelable接口,所以Messenger类可以透过IBinder接口而将Message对象传送到另一个进程里的MessengerImpl类。
然后,Messenger透过Handler而将Message对象丢入UI线程的MQ里,让UI线程来处理之。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IuAh9Hhd-1581763683557)(H:\架构师\架构师_视频\unzip\B07-08.PNG)]

在传送Message对象之前,必须先建立MessengerImpl、Handler和myService三者之间的关系。如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l3fUn8vw-1581763683559)(H:\架构师\架构师_视频\unzip\B07-09.PNG)]

首先myService诞生一个Handler对象,并诞生一个Messenger对象,并让Messenger指向该Handler对象。
于是,Messenger对象调用Handler的getIMessenger()函数去诞生一个MessengerImpl对象,并让Messenger对象指向MessengerImpl对象。
此时,MessengerImpl对象也指向Handler对象。
建构完毕后,在另一个进程里的myActivity就能透过Messenger类而将Message对象传递给MessengerImpl对象。
然后,MessengerImpl继续将Message对象放入主线程(main thread)的MQ里,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X84ujSW4-1581763683560)(H:\架构师\架构师_视频\unzip\B07-10.PNG)]

步骤是:

myActivity调用bindService()去绑定myService,取得IBinder接口。
以Messenger类包装IBinder接口。
myActivity透过Messenger类接口将Message信息传给远方的MessengerImpl类。
MessengerImpl类将信息丢入对方主线程的MQ里。
主线程从MQ里取得信息,并调用myService的函数来处理信息

// myService.java
// ……….
public class myService extends Service {
class myHandler extends Handler {
@Override public void handleMessage(Message msg) {
	 //……..
	 Toast.makeText(getApplicationContext(),msg.obj.toString(),
	 Toast.LENGTH_SHORT).show();
	 //……..
}
}
}
 final Messenger mMessenger = new Messenger(new myHandler());
	 @Override
	 public IBinder onBind(Intent intent) {
	 return mMessenger.getBinder();
	}
}
// myActivity.java
// ………
public class myActivity extends Activity {
	Messenger mMessenger = null;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.main); bindService(new Intent(this,
	 MessengerService.class), mConnection,
	 Context.BIND_AUTO_CREATE); }
	 private ServiceConnection mConnection =
	 new ServiceConnection() {
	 public void onServiceConnected(ComponentName
	 className, IBinder ibinder)
	 {
	 mMessenger = new Messenger(ibinder);
	 }
	 }
	 public void onClick() {
	 Message msg = Message.obtain(null, 0, “Hello”);
	 mMessenger.send(msg);
	 }
}

一开始,框架会诞生myService对象,此时也执行指令:final Messenger mMessenger = new Messenger(new myHandler());
就诞生一个myHandler对象,并且诞生一个Messenger对象,并把myHandler对象指针存入Messenger对象里。
一旦myActivity执行到指令:bindService(new Intent(this, MessengerService.class),mConnection, Context.BIND_AUTO_CREATE);
框架会调用myService的onBind()函数,其内容为:
public IBinder onBind(Intent intent) {
	return mMessenger.getBinder();
}
此时,调用Messenger的getBinder()函数来取的MessengerImpl的IBinder接口,并回传给Android框架。如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-utcmfJZq-1581763683561)(H:\架构师\架构师_视频\unzip\B07-11.PNG)]

接着,框架就调用myActivity的onServiceConnected()函数:

public void onServiceConnected(ComponentName className, IBinder ibinder) {
	mMessenger = new Messenger(ibinder);
}
此时,就让Messenger对象指向IBinder接口了。
一旦myActivity执行到指令:
public void onClick() {
	Message msg = Message.obtain(null, “hello”, 0, 0);
	mMessenger.send(msg);
}
就诞生一个Message对象,然后调用Messenger的send()函数,此send()函数则调用IBinder接口的transact()函数,将Message对象传递给MessengerImpl,再透过myHandler将Message对象放入主线程的MQ里。

再谈线程的角色

在Android文件里,写道:“… if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger.”
但是,有许多人看不懂其涵意。其实,它的涵意很简单。如果你并不考虑让多个线程(thread)同时来执行你的Service,你就可以透过这个机制,将多个Client端(如myActivity1, myActivity2等)送来的Message对象存入单一线程的MessageQueue里,由该线程依序逐一地处理各Client传来的Message对象。 
• 虽然多个并行的Client端线程在调用IBinder接口时,会触发多个Binder驱动线程(Binder Thread)而进入MessengerImpl,然而它们则依序将Message丢入同一个(即主线程的)MessageQueue里。因此,对于Service而言,还是单线程的情境,你在撰写myService程序代码时,不必担心多线程
之间的数据冲突问题。
  1. 双向沟通的Messenger框架

    • 这个Messenger框架是对Binder框架加以扩充而来的。在双向沟通上,也继承了Binder框架机制。
    Binder框架双向沟通的应用情境是:当myActivity透过IBinder接口调用myService的函数去执行任务时(例如使用子线程去播放mp3音乐),万一发现底层播放系统故障了,则myService必须透过IBinder接口来通知myActivity。
    • 基于上述的IBinder双向通信机制,就能用Messenger来加以包装,而为跨进程双向的Messenger框架,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vl28nQrK-1581763683563)(H:\架构师\架构师_视频\unzip\B07-12.PNG)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2KBlRAet-1581763683565)(H:\架构师\架构师_视频\unzip\B07-13.PNG)]

基本設計原則

• 已知:myActivity透过Android框架去配对才取得myService对象,然后才取得myService所在进程里的IBinder接口。
• 议题:那么,myService又如何取得myActivity进程里的IBinder接口呢?
• 答案:myActivity先将IBinder接口打包到信件(Message对象)里,随着信送到对方,对方(myActivity)就接到IBinder接口了。

// myActivity.java
public class myActivity extends Activity {
	 @Override
	 protected void onCreate(Bundle savedInstanceState) {
	 super.onCreate(savedInstanceState);
	 //………
	 bindService(intent, connection, BIND_AUTO_CREATE);
	 }
	 class myHandler extends Handler {
	 @Override public void handleMessage(Message msg) {
	 // ........
	 }
	 };
	 final Messenger aMessenger
	 = new Messenger(new myHandler());
	 private Messenger ibMessenger; 
	 private ServiceConnection connection
	 = new ServiceConnection() {
	 public void onServiceConnected(ComponentName name,
	 IBinder ibinder) {
	 ibMessenger = new Messenger(ibinder);
	 }};
	 public void onClick(View v) {
	 Message message = Message.obtain(null,
	 MessengerService.MSG_SET_VALUE);
	 message.replyTo = aMessenger;
	 ibMessenger.send(message);
	 }}
} 

// myService.java
// ………
public class myService extends Service {
	 private Messenger cbMessenger;
	 class myHandler extends Handler {
	 @Override public void handleMessage(Message msg) {
	 Message message = Message.obtain(null, 0, “How are you”);
	 cbMessenger = msg.replyTo;
	 cbMessenger.send(message);
	 }};
	 final Messenger mMessenger = new Messenger(new myHandler());
	 @Override public IBinder onBind(Intent intent) {
	 return mMessenger.getBinder();
	 }
} 

myActivity的代码:
final Messenger aMessenger = new Messenger(new myHandler());

myService的代码:
final Messenger mMessenger = new Messenger(new myHandler());

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1ryYh140-1581763683566)(H:\架构师\架构师_视频\unzip\B07-14.PNG)]

myActivity的代码:
	bindService(intent, connection, BIND_AUTO_CREATE); 

myService的代码:
	return mMessenger.getBinder(); 

myActivity的代码:
	public void onServiceConnected(ComponentName name, IBinder ibinder) {
		ibMessenger = new Messenger(ibinder);
	}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Wsqvb9H-1581763683568)(H:\架构师\架构师_视频\unzip\B07-15.PNG)]

myActivity的代码:
	message.replyTo = aMessenger;
	ibMessenger.send(message); 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WF7Q4DR8-1581763683569)(H:\架构师\架构师_视频\unzip\B07-16.PNG)]

myService的代码:
	@Override 
	public void handleMessage(Message msg) {
		Message message = Message.obtain(null, 0, “How are you”);
		cbMessenger = msg.replyTo;
		cbMessenger.send(message); 
	}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fFWR1C5t-1581763683571)(H:\架构师\架构师_视频\unzip\B07-17.PNG)]

• 在myActivity调用Messenger的send()函数时,就顺便将己方的IBinder接口当作参数传递过去给myService。
• myService接到传递过来的IBinder接口时,就诞生一个新Messenger对象,并将该IBinder接口存进去。myService就能调用该新Messenger对象的send()函数,把Message对象传递到myActivity端了。

61 - Messager框架与IMessager接口c

  1. IMessenger接口

    使用AIDL
    在Messenger框架里还定义了IMessenger接口,让应用程序(App)可直接调用IMessenger接口的send()函数。如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bd7JBNJW-1581763683573)(H:\架构师\架构师_视频\unzip\B07-18.PNG)]

这是典型的Proxy-Stub模式来包装IBinder接口。
• 在myActivity进程里:Messenger类可以将IBinder接口转换成为IMessenger接口。
• 在myService进程里:也可以透过Messenger取得MessengerImpl类的IMessenger接口。

62 - JNI架构原理_Java与C的对接a

  1. 为什么 , Android应用需要Java和C对接呢?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fOoLJEtf-1581763683574)(H:\架构师\架构师_视频\unzip\C01-00.PNG)]

  1. EIT造形的Java实现

  2. EIT造形的C语言实现

  3. EIT造形的C和Java组合实现

63 - JNI架构原理_Java与C的对接b

64 - JNI架构原理_Java与C的对接c

65 - JNI架构原理_Java与C的对接d

66 - JNI架构原理_Java与C的对接e

相关标签: framework