Android子线程主线程之间的通信

摘  要:Android是一种基于Linux的自由及开放源代码的操作系统,主要使用于便携设备,如智能手机和平板电脑。Android系统属于单线程模型,子线程不能控制UI主线程。

关键词:Android,handler,aysncTask

1.   前言

1.1 Android主线程

在一个Android 程序开始运行的时候,会单独启动一个Process。默认的情况下,所有这个程序中的Activity或者Service(Service和 Activity只是Android提供的Components中的两种,除此之外还有Content Provider和Broadcast Receiver)都会跑在这个Process。

一个Android 程序默认情况下也只有一个Process,但一个Process下却可以有许多个Thread。

在这么多Thread当中,有一个Thread,我们称之为UI Thread。UI Thread在Android程序运行的时候就被创建,是一个Process当中的主线程Main Thread,主要是负责控制UI界面的显示、更新和控件交互。在Android程序创建之初,一个Process呈现的是单线程模型,所有的任务都在一个线程中运行(Android的UI是单线程(Single-threaded)的)。因此,我们认为,UI Thread所执行的每一个函数,所花费的时间都应该是越短越好。而其他比较费时的工作(访问网络,下载数据,查询数据库等),都应该交由子线程去执行,以免阻塞主线程。

1.2 Android子线程

Android禁止其他子线程来更新由UI thread创建的view(即不可以在子线程中更新 UI),否则会报错或者有异常信息CalledFromWrongThreadException:android.view.ViewRoot$CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views.

2.   Android子线程主线程的通信方法

在单线程模型下,为了解决主线程/子线程通信的问题,Android中有以下两种解决方法。

2.1 Handler

Android设计了一个Message Queue(消息队列),线程间可以通过该Message Queue并结合Handler和Looper组件进行信息交换:诞生一个主线程的Handler,当做Listener去让子线程能将信息Push到主线程的MessageQuene里,以便触发主线程的handlerMessage()函数,让主线程知道子线程的状态,并在主线程更新UI。

使用的优点:

  • 结构清晰,功能定义明确
  • 对于多个后台任务时,简单,清晰

使用的缺点:

  • 在单个后台异步处理时,显得代码过多,结构过于复杂(相对性)

在Handler 异步实现时,涉及到 Handler, Looper, Message,Thread四个对象,实现异步的流程是主线程启动Thread(子线程)àthread(子线程)运行并生成Message-àLooper获取Message并传递给HandleràHandler逐个获取Looper中的Message,并进行UI变更。

2.1.1详情

  • Message Queue

Message Queue是一个消息队列,用来存放通过Handler发布的消息。消息队列通常附属于某一个创建它的线程,可以通过Looper.myQueue()得到当前线程的消息队列。Android在第一启动程序时会默认会为UI thread创建一个关联的消息队列,用来管理程序的一些上层组件,activities,broadcast receivers 等等。也可以在自己的子线程中创建Handler与UI thread通讯。

  • Handler

通过Handler,可以发布或者处理一个消息或者是一个Runnable的实例。每个Handler都会与唯一的一个线程以及该线程的消息队列管理。当创建一个新的Handler时候,默认情况下,它将关联到创建它的这个线程和该线程的消息队列。也就是说,假如通过Handler发布消息的话,消息将只会发送到与它关联的这个消息队列,当然也只能处理该消息队列中的消息。

主要的方法有:

1)       public final booleansendMessage(Message msg)

把消息放入该Handler所关联的消息队列,放置在所有当前时间前未被处理的消息后。

2)       public voidhandleMessage(Message msg)

关联该消息队列的线程将通过调用Handler的handleMessage方法来接收和处理消息,通常需要子类化Handler来实现handleMessage。

n  Looper

Looper扮演着一个Handler和消息队列之间通讯桥梁的角色。程序组件首先通过Handler把消息传送给Looper,Looper把消息放入队列。Looper也把消息队列里的消息广播给所有的Handler,Handler接受到消息后调用handleMessage进行处理。

1)       可以通过Looper类的静态方法Looper.myLooper得到当前线程的Looper实例,假如当前线程未关联一个Looper实例,该方法将返回空。

2)       可以通过静态方法Looper. getMainLooper方法得到主线程的Looper实例。

 

线程,消息队列,Handler,Looper之间的关系可以通过一个图来展现:

例子如下所示:

public class HandlerThreadActivity extendsActivity { 
     private static final int MSG_WHAT = 1; 
     @Override 
   public void onCreate(Bundle savedInstanceState) { 
       super.onCreate(savedInstanceState); 
       setContentView(R.layout.main); 
         
       // 创建对象,并启动该线程 
       HandlerThread mHandlerThread = newHandlerThread("sub_thread"); 
       mHandlerThread.start(); 
       // 获取 Looper 对象 
       MyHandler mHandler = new MyHandler(mHandlerThread.getLooper()); 
       // 创建 Bundle 对象,传递数据 
       Bundle bundle = new Bundle(); 
       bundle.putString("main", "我这边事情太多,麻烦你帮忙处理一下!"); 
       // 发送消息 
       Message msg = new Message(); 
       msg.what = MSG_WHAT; 
       msg.setData(bundle); 
       msg.setTarget(mHandler); 
       msg.sendToTarget(); 
         
       Log.d("mark", "UI----" + "threadName: " +Thread.currentThread().getName() + ",threadId: " + Thread.currentThread().getId()); 
   } 
 
   /**
    * 该Handler调用 handleMessage方法运行在子线程
    * 
    * @author mark
    */ 
   class MyHandler extends Handler { 
 
       public MyHandler() { 
           super(); 
       } 
         
       /* 这个构造方法必须有 */ 
       public MyHandler(Looper looper) { 
           super(looper); 
       } 
 
       @Override 
       public void handleMessage(Message msg) { 
           if (msg.what == MSG_WHAT) { 
                Bundle bundle =msg.getData(); 
                // 接收消息 
                String info =bundle.getString("main"); 
                Log.d("mark", "handleMessage---"+ "threadName: " + Thread.currentThread().getName() + ",threadId: " +Thread.currentThread().getId()); 
                Log.d("mark", "我接受任务:" + info); 
           } 
       } 
   } 
}

2.2 AsyncTask

Android的AsyncTask比Handler更轻量级一些,适用于简单的异步处理。

AsyncTask,是android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程

使用的优点:

  • 简单,快捷
  • 过程可控

使用的缺点:

  • 在使用多个异步操作和需要进行UI变更时,就变得复杂起来。

2.2.1详情

Android为了降低这个开发难度(为了不阻塞主线程(UI线程),且UI的更新只能在主线程中完成,因此异步处理是不可避免的。),提供了AsyncTask。

AsyncTask就是一个封装过的后台任务类,顾名思义就是异步任务。

AsyncTask直接继承于Object类,位置为android.os.AsyncTask。要使用AsyncTask工作我们要提供三个泛型参数,并重载几个方法(至少重载一个)。

AsyncTask定义了三种泛型类型 Params,Progress和Result。

  • Params 启动任务执行的输入参数,比如HTTP请求的URL。
  • Progress 后台任务执行的百分比。
  • Result 后台执行任务最终返回的结果,比如String。

 

使用过AsyncTask异步加载数据,最少要重写以下这两个方法:

1)       doInBackground(Params…) 后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用publicProgress(Progress…)来更新任务的进度。

2)       onPostExecute(Result) 相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。 此方法在主线程执行,任务执行的结果作为此方法的参数返回

 

有必要的话,还得重写以下这三个方法,但不是必须的:

1)       onProgressUpdate(Progress…) 可以使用进度条增加用户体验度。此方法在主线程执行,用于显示任务执行的进度。

2)       onPreExecute() 这里是最终用户调用Excute时的接口,当任务执行之前开始调用此方法,可以在这里显示进度对话框。

3)       onCancelled() 用户调用取消时,要做的操作。

2.2.2准则

使用AsyncTask类,以下是几条必须遵守的准则:

1)       Task的实例必须在UI thread中创建;

2)       execute方法必须在UI thread中调用;

3)       不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法;

4)       该task只能被执行一次,否则多次调用时将会出现异常。

2.2.3例子

main.xml文件:设定主程序的页面布局


<?xml version="1.0"encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:orientation="vertical"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent"
   >
   <TextView 
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:text="Hello , Welcome to Andy's Blog!"/>
   <Button
      android:id="@+id/download"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:text="Download"/>
   <TextView 
      android:id="@+id/tv"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:text="当前进度显示"/>
   <ProgressBar
      android:id="@+id/pb"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      style="?android:attr/progressBarStyleHorizontal"/>
</LinearLayout>

 

MainActivity.java

public class MainActivity extends Activity{
         Buttondownload;
         ProgressBarpb;
         TextViewtv;
        
   /** Called when the activity is first created. */
   @Override
   public void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.main);
       pb=(ProgressBar)findViewById(R.id.pb);
       tv=(TextView)findViewById(R.id.tv);
       
       download = (Button)findViewById(R.id.download);
       download.setOnClickListener(new View.OnClickListener() {
                 @Override
                 publicvoid onClick(View v) {
                          DownloadTaskdTask = new DownloadTask();
                          dTask.execute(100);
                 }
         });
    }
   
   class DownloadTask extends AsyncTask<Integer, Integer, String>{
         //后面尖括号内分别是参数(例子里是线程休息时间),进度(publishProgress用到),返回值类型
         @Override
         protectedvoid onPreExecute() {
                  //第一个执行方法
                 super.onPreExecute();
         }
         @Override
         protectedString doInBackground(Integer... params) {
                 //第二个执行方法,onPreExecute()执行完后执行
                 for(inti=0;i<=100;i++){
                          pb.setProgress(i);
                          publishProgress(i);
                          try{
                                  Thread.sleep(params[0]);
                          }catch (InterruptedException e) {
                                   e.printStackTrace();
                          }
                 }
                 return"执行完毕";
         }
         @Override
         protectedvoid onProgressUpdate(Integer... progress) {
                 //这个函数在doInBackground调用publishProgress时触发,虽然调用时只有一个参数
                 //但是这里取到的是一个数组,所以要用progesss[0]来取值
                 //第n个参数就用progress[n]来取值
                 tv.setText(progress[0]+"%");
                 super.onProgressUpdate(progress);
         }
 
         @Override
         protectedvoid onPostExecute(String result) {
                 //doInBackground返回时触发,换句话说,就是doInBackground执行完后触发
                 //这里的result就是上面doInBackground执行后的返回值,所以这里是"执行完毕"
                 setTitle(result);
                 super.onPostExecute(result);
         }
    }
}


执行结果:


3.   经验总结

通过上面的分析,我们可以得出如下结论:

1.        如果通过子线程刷新主界面,推荐使用AsyncTask替代handler对象来实现主线程与子线程的通信。推荐大家使用AsyncTask代替Thread+Handler的方式,不仅调用上更为简单,经过实测更可靠一些,Google在Browser中大量使用了异步任务作为处理耗时的I/O操作,比如下载文件、读写数据库等等,它们在本质上都离不开消息,但是AsyncTask相比Thread加Handler更为可靠,更易于维护,但AsyncTask缺点也是有的比如一旦线程开启即dobackground方法执行后无法给线程发送消息,仅能通过预先设置好的标记来控制逻辑,当然可以通过线程的挂起等待标志位的改变来通讯,对于某些应用Thread和Handler以及Looper可能更灵活。

2.        如果坚持使用Handler,则必须注意:

1)       注意工作线程和主线程之间的竞争关系。推荐handler对象在主线程中构造完成(并且启动工作线程之后不要再修改之,否则会出现数据不一致),然后在工作线程中可以放心的调用发送消息SendMessage等接口。

2)       除了2所述的hanlder对象之外的任何主线程的成员变量如果在工作线程中调用,仔细考虑线程同步问题。如果有必要需要加入同步对象保护该变量。

3)       handler对象的handleMessage接口将会在主线程中调用。在这个函数可以放心的调用主线程中任何变量和函数,进而完成更新UI的任务。

Android很多API也利用Handler这种线程特性,作为一种回调函数的变种,来通知调用者。这样Android框架就可以在其线程中将消息发送到调用者的线程消息队列之中,不用担心线程同步的问题。

你可能感兴趣的:(Android子线程主线程之间的通信)