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

本文深入探讨Android的线程模式,从线程概念、主线程(UI线程)的作用,到线程间通信、Looper与Handler机制。同时,讲解了SurfaceView在多线程环境的应用,以及AIDL、Messenger和JNI在跨进程通信中的重要角色,揭示了Android UI框架和本地函数调用的底层原理。
摘要由CSDN通过智能技术生成

本文学习自高焕堂老师的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()函数。

B02-00

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

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

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

在这里插入图片描述

于此图里,框架的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()函数。如下图:

B02-02
B02-03

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

B02-04

兹撰写一个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()函数。上图是类关系图,其对象关系图,可表示如下:

B02-05

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

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

B02-06

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线程

B02-07

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

B02-08

这个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依据这个文件而将各类别布署于两个进程里执行,如图:

B02-09

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

B02-10
B02-11

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

B02-12

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

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

B02-13
B02-14

B02-15

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

B02-16

当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,两者都在同一个进程里执行。此时,两者都是由主线程负责执行的。如下图所示:

B02-17

// 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,两者都在同一个进程里执行。此时,两者都是由主线程负责执行的。如下图所示

B02-18

// 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,两者分别在不同的进程里执行,如下图所示:

B02-19

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

B02-20

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信息结构。如下图所示:

B02-21

由于主线程会持续监视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的属性。

b02-22

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(主)线程执行画面显示或更新。

B02-23

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

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

7. 线程安全的化解之例

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

B02-24

// 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...");
		
	 } 
}

B02-25

由于在这个程序只会诞生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()函数,来执行实际绘图的动作。

B03-00

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

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

B03-01

2. 基本游戏循环(GameLoop)

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

B03-02

// 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)

B03-03

// 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();
	}

/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值