Android线程间通信机制

当android应用程序运行时,一个主线程被创建(也称作UI线程),此线程主要负责处理UI相关的事件,由于Android采用UI单线程模型,所以只能在主线程中对UI元素进行操作,如果在非UI线程直接对UI进行了操作,则会报错,另外,对于运算量较大的操作和IO操作,我们需要新开线程来处理这些工作,以免阻塞UI线程,子线程与主线程之间是怎样进行通信的呢?此时就要采用消息循环机制(Looper)与Handler进行处理。

一、基本概念

Looper:每一个线程都可以产生一个Looper,用来管理线程的Message,Looper对象会建立一个MessgaeQueue数据结构来存放message。

Handler:与Looper沟通的对象,可以push消息或者runnable对象到MessgaeQueue,也可以从MessageQueue得到消息。

查看其构造函数:

Handler()

Default constructor associates this handler with the queue for the current thread.//如不指定Looper参数则默认利用当前线程的Looper创建

Handler(Looperlooper)

Use the provided queue instead of the default one.//使用指定的Looper对象创建Handler

线程A的Handler对象引用可以传递给别的线程,让别的线程B或C等能送消息来给线程A。

线程A的Message Queue里的消息,只有线程A所属的对象可以处理。

注意:Android里没有global的MessageQueue,不同进程(或APK之间)不能通过MessageQueue交换消息。

二、Handler通过Message通信的基本方式

使用Looper.myLooper可以取得当前线程的Looper对象。

使用mHandler =newHandler(Looper.myLooper());可产生用来处理当前线程的Handler对象。

使用mHandler =newHandler(Looper.getMainLooper()); 可诞生用来处理main线程的Handler对象。

使用Handler传递消息对象时将消息封装到一个Message对象中,Message对象中主要字段如下:

public int

arg1

当需要传递的消息是整形时arg1 和 arg2 是一种低成本的可选方案,他使用setData()/getData()访问或修改字段。

public int

arg2

同上

publicObject

obj

可传送的任意object类型.

public int

what

Int类型用户自定义的消息类型码

Message对象可以通过Message类的构造函数获得,但Google推荐使用Message.obtain()方法获得,该方法会从全局的对象池里返回一个可复用的Messgae实例,API中解释如下:

Message()

Constructor (but the preferred way to get a Message is to callMessage.obtain()).

Handler发出消息时,既可以指定消息被接受后马上处理,也可以指定经过一定时间间隔之后被处理,如sendMessageDelayed(Messagemsg, long delayMillis),具体请参考API。

Handler消息被发送出去之后,将由handleMessage(Messagemsg)方法处理。

注意:在Android里,新诞生一个线程,并不会自动建立其Message Loop

可以通过调用Looper.prepare()为该线程建立一个MessageQueue,再调用Looper.loop()进行消息循环

下面举例说明:

在main.xml中定义两个button及一个textview

复制代码
<Button

android:id="@+id/a"

android:layout_width
="80dp"

android:layout_height
="60dp"

android:padding
="6dp"

android:layout_marginTop
="10dp"

android:text
="Test looper"

android:hint
="Test looper" />

<Button

android:id="@+id/b"

android:layout_width
="80dp"

android:layout_height
="60dp"

android:padding
="6dp"

android:layout_marginTop
="10dp"

android:text
="Exit" />

<TextView

android:id="@+id/tv"

android:layout_width
="fill_parent"

android:layout_height
="wrap_content"

android:layout_marginTop
="10dp"/>
复制代码



Activity源代码如下:

复制代码
  1 public class HandlerTestActivity extends Activity implements Button.OnClickListener{
2
3 public TextView tv;
4
5 private myThread myT;
6
7 Button bt1, bt2;
8
9 /** Called when the activity is first created. */
10
11 @Override
12
13 public void onCreate(Bundle savedInstanceState) {
14
15 super.onCreate(savedInstanceState);
16
17 setContentView(R.layout.main);
18
19 bt1 = (Button)findViewById(R.id.a);
20
21 bt2 = (Button)findViewById(R.id.b);
22
23 tv = (TextView)findViewById(R.id.tv);
24
25 bt1.setId(1);//为两个button设置ID,此ID用于后面判断是哪个button被按下
26
27 bt2.setId(2);
28
29 bt1.setOnClickListener(this);//增加监听器
30
31 bt2.setOnClickListener(this);
32
33 }
34
35
36
37 @Override
38
39 public void onClick(View v) {
40
41 // TODO Auto-generated method stub
42
43 switch(v.getId()){//按键事件响应,如果是第一个按键将启动一个新线程
44
45 case 1:
46
47 myT = new myThread();
48
49 myT.start();
50
51 break;
52
53 case 2:
54
55 finish();
56
57 break;
58
59 default:
60
61 break;
62
63 }
64
65 }
66
67
68
69 class myThread extends Thread
70
71 {
72
73 private EHandler mHandler;
74
75 public void run()
76
77 {
78
79 Looper myLooper, mainLooper;
80
81 myLooper = Looper.myLooper();//得到当前线程的Looper
82
83 mainLooper = Looper.getMainLooper();//得到UI线程的Looper
84
85 String obj;
86
87 if(myLooper == null)//判断当前线程是否有消息循环Looper
88
89 {
90
91 mHandler = new EHandler(mainLooper);
92
93 obj = "current thread has no looper!";//当前Looper为空,EHandler用mainLooper对象构造
94
95 }
96
97 else
98
99 {
100
101 mHandler = new EHandler(myLooper);//当前Looper不为空,EHandler用当前线程的Looper对象构造
102
103 obj = "This is from current thread.";
104
105 }
106
107 mHandler.removeMessages(0);//清空消息队列里的内容
108
109 Message m = mHandler.obtainMessage(1, 1, 1, obj);
110
111 mHandler.sendMessage(m);//发送消息
112
113 }
114
115 }
116
117
118
119 class EHandler extends Handler
120
121 {
122
123 public EHandler(Looper looper)
124
125 {
126
127 super(looper);
128
129 }
130
131 @Override
132
133 public void handleMessage(Message msg) //消息处理函数
134
135 {
136
137 tv.setText((String)msg.obj);//设置TextView内容
138
139 }
140
141 }
142
143
144
145 }
复制代码



程序运行后点击TestLooper按键,TextView输出如下,说明新创建的线程里Looper为空,也就说明了新创建的线程并不会自己建立Message Looper。

修改myThread类:

复制代码
 1 class myThread extends Thread
2
3 {
4
5 private EHandler mHandler;
6
7 public void run()
8
9 {
10
11 Looper myLooper, mainLooper;
12
13 Looper.prepare();
14
15 myLooper = Looper.myLooper();
16
17 mainLooper = Looper.getMainLooper();
18
19 ......
20
21 ......
22
23 mHandler.sendMessage(m);
24
25 Looper.loop();
26
27 }
28
29 }
复制代码

Looper.prepare为当前线程创建一个Message Looper,Looper.loop()开启消息循环。这样修改是OK呢?

答案是否定的!运行时Logcat将抛出CalledFromWrongThreadException异常错误,提示如下:

Android线程间通信机制_第1张图片

意思就是说“只有原始创建这个视图层次的线程才能修改它的视图”,本例中的TextView是在UI线程(主线程)中创建,因此,myThread线程不能修改其显示内容!

一般的做法是在子线程里获取主线程里的Handler对象,然后通过该对象向主线程的消息队列里发送消息,进行通信。

如果子线程想修改主线程的UI,可以通过发送Message给主线程的消息队列,主线程经行判断处理再对UI经行操作,具体可以参考之前的代码。

三、Handler通过runnable通信的基本方式

我们可以通过Handler的post方法实现线程间的通信,API中关于post方法说明如下

public final booleanpost(Runnabler)

Causes the Runnable r to be added to the message queue. The runnable will be run on the thread to which this handler is attached.

Post方法将安排runnable对象在主线程的某个位置运行,但是并不会开启一个新的线程,验证代码如下:

复制代码
 1 public class HandlerTestActivity extends Activity {
2
3
4
5 private Handler handlerTest;
6
7 Runnable runnableTest = new Runnable()
8
9 {
10
11 public void run()
12
13 {
14
15 String runID = String.valueOf(Thread.currentThread().getId());//输出Runnable 线程的ID号
16
17 Log.v("Debug",runID);
18
19 }
20
21 };
22
23 /** Called when the activity is first created. */
24
25 @Override
26
27 public void onCreate(Bundle savedInstanceState) {
28
29 super.onCreate(savedInstanceState);
30
31 setContentView(R.layout.main);
32
33 handlerTest = new Handler();
34
35 String mainID = String.valueOf(Thread.currentThread().getId());
36
37 Log.v("Debug",mainID);//输出主线程的ID号
38
39 handlerTest.post(runnableTest);
40
41 }
42
43 }
复制代码



Logcat里输出如下:

说明只是把runnable里的run方法放到UI线程里运行,并不会创建新线程

因此我们可以在子线程中将runnable加入到主线程的MessageQueue,然后主线程将调用runnable的方法,可以在此方法中更新主线程UI。

将之前的代码修改如下:

复制代码
 1 public class HandlerTestActivity extends Activity {
2
3 private Handler handlerTest;
4
5 private TextView tv;
6
7 /** Called when the activity is first created. */
8
9 @Override
10
11 public void onCreate(Bundle savedInstanceState) {
12
13 super.onCreate(savedInstanceState);
14
15 setContentView(R.layout.main);
16
17 tv = (TextView)findViewById(R.id.tv);//TextView初始化为空
18
19 handlerTest = new Handler();
20
21 myThread myT = new myThread();
22
23 myT.start();//开启子线程
24
25 }
26
27
28
29 class myThread extends Thread{
30
31 public void run(){
32
33 handlerTest.post(runnableTest);//子线程将runnable加入消息队列
34
35 }
36
37 }
38
39
40
41 Runnable runnableTest = new Runnable()
42
43 {
44
45 public void run()
46
47 {
48
49 tv.setText("此信息由子线程输出!");
50
51 }
52
53 };
54
55 }
复制代码

更多相关文章

  1. Android Studio--Android中的消息机制
  2. 从源码一次彻底理解Android的消息机制
  3. Android的消息通知--Notification
  4. android 主线程与分线程 做同步
  5. mono android 非UI线程操作UI线程
  6. 【Android】监听自定义通知栏消息事件
  7. 使用Android消息机制实现点击开始计数和暂停计数

随机推荐

  1. Android(安卓)屏幕元素层次结构
  2. MDCC印象之三:Android热的背后
  3. Frida官方手册 - 在Android上使用Frida
  4. Android设备的唯一ID
  5. 简单聊一下Android音频通路的切换
  6. (1) Android中Binder调用流程 --- 匿名和实
  7. Unity调用Android原生Java代码以及Unity
  8. 【读书笔记】【Android(安卓)开发艺术探
  9. 基于移动平台的多媒体框架——移植Live55
  10. Dagger2 在 Android(安卓)项目的正确使用