您的位置:首页 > 移动开发 > Android开发

Android线程间通信机制

2013-02-21 10:18 337 查看


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(Looper looper)

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 = new Handler(Looper.myLooper()); 可产生用来处理当前线程的Handler对象。

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

使用Handler传递消息对象时将消息封装到一个Message对象中,Message对象中主要字段如下:
public int
arg1
当需要传递的消息是整形时arg1 和 arg2 是一种低成本的可选方案,他使用 setData()/getData()访问或修改字段。
public int
arg2
同上
public Object
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 call Message.obtain()).

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

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



注意:在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异常错误,提示如下:





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

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

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



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

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

public
final boolean post (Runnable r)

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 }




内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐