在Android中,异步消息处理机制解决线程之间的通信问题有两种方式:一种是Handler机制,一种是AsyncTask机制。
Android UI 是线程不安全的,如果在子线程中进行UI操作 ,程序就会抛出异常: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy . 并且,Android 也不建议在UI线程中做一些耗时操作,否则会导致程序 ANR 。如果我们需要做一些耗时的操作并且操作结束后要修改 UI ,那么就需要用到Handler切换到主线程来访问 UI 。因此,系统之所以提供 Handler,主要原因就是为了解决在子线程中无法访问 UI 的问题。
1.handler机制 一个Handler对应一个Looper对象,一个Looper对应一个MessageQueue对象,使用Handler生成Message,所生成的Message对象的Target属性,就是该对象。而一个Handler可以生成多个Message,所以说,Handler和Message是一对多的关系。
Handler是Android类库提供的用于接收、传递和处理消息或Runnable对象的处理类,它结合Message、MessageQueue和Looper类以及当前线程实现了一个消息循环机制,用于实现任务的异步加载和处理。 整个异步消息处理流程的示意图如下图所示: 根据上面的图片,我们来解析一下异步消息处理机制: ①Message:消息体,用于装载需要发送的对象。 对于Message对象,一般并不推荐直接使用它的构造方法得到,而是建议通过使用Message.obtain()这个静态的方法或者Handler.obtainMessage()获取。Message.obtain()会从消息池中获取一个Message对象,如果消息池中是空的,才会使用构造方法实例化一个新Message,这样有利于消息资源的利用。并不需要担心消息池中的消息过多,它是有上限的,上限为10个。 ②handler:它直接继承自Object,作用是:在子线程中发送Message或者Runnable对象到MessageQueue中;在UI线程中接收、处理从MessageQueue分发出来的Message或者Runnable对象。 发送消息一般使用Handler的sendMessage()方法,而发出去的消息经过处理后最终会传递到Handler的handlerMessage()方法中。 ③MessageQueue:用于存放Message或Runnable对象的消息队列,按照先进先出执行,内部使用的是单链表结构。它由对应的Looper对象创建,并由Looper对象管理。 每个线程中都只会有一个MessageQueue对象。 ④Looper:是每个线程中的MessageQueue的管家,循环不断地管理MessageQueue接收和分发Message或Runnable的工作。调用Looper的loop()方法后,就会进入到一个无限循环中,然后每当发现MessageQueue中存在一条消息,就将它取出,并调用Handler的handlerMessage()方法。 每个线程中也只会有一个Looper对象。
下面分别介绍这四大概念。
2.Message(消息) Message.class位于android.os.包中。 Message的构造函数为无参构造方法,且只有一个构造方法:public Message() { } 除了构造方法可以创建实例对象外,还可以通过内部的静态方法来创建: static Message obtain() static Message obtain(Message orig) static Message obtain(Handler h) static Message obtain(Handler h, Runnable callback) static Message obtain(Handler h, int what) static Message obtain(Handler h, int what, Object obj) static Message obtain(Handler h, int what, int arg1, int arg2) static Message obtain(Handler h, int what, int arg1, int arg2, Object obj) 以上几个静态方法里面都会首先调用第一个方法来创建一个Message对象。 我们来看看源码(API 28): public static final Object sPoolSync = new Object(); //同步锁对象 private static Message sPool; //全局池消息实例
//从全局池返回一个新的消息实例,允许我们在许多情况下避免分配新对象。 public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; //… return m; } } return new Message(); } 如果当前全局池的Message实例不为空,则返回第一个消息实例。所以,大多数情况下,使用obtain()来获得一个Message对象,可以避免消耗更多的内存资源。
对于其他static obtain( )的重载方法,通过源码,可以发现,都是进行赋值操作,没有太多的可讨性。唯一得注意一下的是 obtain(Handler h, Runnable callback)这个静态方法: Handler target; Runnable callback; public static Message obtain(Handler h, Runnable callback) { Message m = obtain(); m.target = h; m.callback = callback; return m; } 可以看到,也是赋值操作,target是保护级别的成员变量,即只有同包名空间可以访问,此变量意义重大。除了target,还有一个callback,这个callback也是配合着Handler来发挥作用的。 此时,大家先要记住的就几点: ①Message有8个静态方法可以创建Message实例; ②Message有两个重要的成员变量,分别为target 和callback,一个是Handler,一个是Runnable; ③Message有4个公开变量what、arg1、arg2、obj,可以存储消息进行传递; ④Message还有一个包间成员变量next,它是Message类型。
3.Handler(处理机) Handler.class也位于android.os包中。 Handler英文意思为:处理者,管理者,处理机。它在消息传递过程中扮演着重要的角色,是消息的主要处理者。Handler的主要作用,就是接收消息,最终处理消息。 ①Handler的构造方法(API 28): Handler() Handler(Callback callback) Handler(boolean async) Handler(Callback callback, boolean async) Handler(Looper looper) Handler(Looper looper, Callback callback) Handler(Looper looper, Callback callback, boolean async) 通过源码可以发现,上面的构造方法都是上面一个个往下调用的,第一个调用第二个,第二个调用第三个…所以,我们首先把目光放在最后一个方法上: public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; } 这是一个赋值的构造方法。 再看另外一个构造方法: public Handler(Callback callback, boolean async) { //… mLooper = Looper.myLooper(); //返回与当前线程关联的Looper对象 if (mLooper == null) { throw new RuntimeException( “Can’t create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()”); } mQueue = mLooper.mQueue; //返回Looper对象的消息队列 mCallback = callback; //接口回调 mAsynchronous = async; //是否异步 }
public interface Callback { public boolean handleMessage(Message msg); //这个函数大家都很熟悉了,是用来回调消息的 } 整个构造方法的过程中会确立以下几件事: (1)获取当前Handler实例所在线程的Looper对象:mLooper = Looper.myLooper() (2)如果Looper不为空,则获取Looper的消息队列,赋值给Handler的成员变量mQueue:mQueue = mLooper.mQueue (3)可以设置Callback 来处理消息回调:mCallback = callback
Handler是消息的处理者,但是它并不是最终处理消息的那个大佬,它有且只能有一个上级领导,就是Looper,Handler是将消息上报给Looper,然后排队等待,等Looper处理完消息了,就会通知Handler去领取消息,给Handler分配任务,Handler拿到消息后在自行往下分发,Handler只能听命与Looper。 举个实际运用中的情景: 当需要在子线程中更新主线程的UI时,你就会在当前的Activity下创建一个Handler对象,然后在它的handleMessage() 中更新UI,如下: private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); //… 更新UI } }; 在你创建这个mHandler 实例的时候,底层做了以下几件事情: (1)拿到mHandler所在线程的Looper,当前mHandler是在Activity中创建的,很明显,当前的线程就是主线程,所以mHandler的成员变量mLooper = Looper.myLooper(),此处就已经将当前的主线程Looper赋值过去了。 (2)紧接着,判断mLooper 是否为空,明显不为空,所以又会将主线程的消息队列赋值给mHandler的成员变量mQueue。告诉Handler,你要是有消息,就送到这个消息队列中来,我(Looper)会一个个按顺序处理,处理完后我就会告诉你,你再处理。
总结一下: (1)Handler有且只能绑定一个线程的Looper; (2)Handler的消息是发送给Looper的消息队列MessageQueue,需要等待处理; 所以,如果你在子线程中声明了一个Handler,是不能直接更新UI的,需要调用Handler相关的构造方法,传入主线程的Looper,这样创建的Handler实例,你才能进行UI的更新操作。 另外的,需要注意的是,子线程默认是没有开启专属的Looper,所以,在子线程中创建Handler之前,你必须先开启子线程的Looper,否则就会爆出异常,然后GG。从上面贴出的构造方法中的部分就可以知道: if (mLooper == null) { throw new RuntimeException( “Can’t create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()”); } 以上就是创建Handler的过程,有了Handler实例了,怎样传递消息呢?就需要用到handler的sendMessage()方法了。 ②Handler sendMessage()相关方法 首先上一幅图来表明sendXXXMessageXXX()的相互调用关系: 可以看出,当我们调用Handler进行发送消息时,最终都会调用sendMessageAtTime()方法,最后调用enqueueMessage( )发送到消息队列。 public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; //获得当前的消息队列 if (queue == null) { //若是在创建Handler时没有指定Looper,就不会有对应的消息队列queue ,自然就会为null RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w(“Looper”, e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
private boolean enqueueMessage( MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; //这个target就是当前的handler if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); } msg.target = this;在发送消息到消息队列之前,明确的指定了消息的target为当前的Handler,以便于在后面Looper分发消息时用到。 queue.enqueueMessage(msg, uptimeMillis);调用了消息队列的enqueueMessage()方法,并传递了两个参数,一个Message,一个是long型的时间。 以上就是Handler的创建和发送消息的过程。 ③Handler dispatchMessage()方法 前面说了消息的发送,交给Looper等待处理,处理完后会重新通知Handler处理,那么,是怎样通知Handler处理消息的呢?秘密就在dispatchMessage()这个方法中: //在这里处理系统消息 public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
//子类必须实现这个来接收消息 public void handleMessage(Message msg) { } 当Looper处理完Message后,会使用到Message的target,即发送消息的那个Handler,Looper会调用Handler的dispatchMessage()方法分发消息,所以前面在enqueueMessage()发送消息的时候,为什么非得指明Message的target就是这个道理。 回到dispatchMessage()这个方法: ①首先会判断Message的callback是否为空,此处的callback就是前面在Message中说到的,在静态方法创建Message时,可以指定callback (runnable对象),若不为空,则将结果回调到callback中; ②若Handler的mCallback(Callback对象)不为空,也一样的道理。 ③平时我们都没有传入这个callback,而是直接实现handleMessage()这个方法,在这个方法中处理更新UI任务。
以上就是Handler发送和接收消息的基本过程:把消息发送到队列—>然后喝茶等待—>接收消息—>分发消息—>在回调中处理。
4.MessageQueue 前面我们知道,Handler发送消息会调用MessageQueue的enqueueMessage()方法,直接上源码(API 28): boolean enqueueMessage(Message msg, long when) { if(msg.targetnull){ //判断msg的所属handler throw new IllegalArgumentException(“Message must have a target.”); } //… synchronized (this) { //因为是队列,有先后之分,所以用了同步机制 //… msg.when = when; Message p = mMessages; //对列中排在最后的那个Message //… if (pnull || when==0 || when < p.when) { //若队列为空,或者等待时间为0,或者比前面那位的等待时间要短,就插队 msg.next = p; //此处的next就是前面我们在Message提到的,指向队列的下一个结点 mMessages = msg; //… } else { //… Message prev; for (;😉 { //此处for循环是为了取出一个空的或者when比当前Message长的一个消息,然后进行插入 prev = p; p = p.next; if (p == null || when < p.when) { break; } //… } msg.next = p; // 置换插入 prev.next = msg; // 置换插入 } //… } return true; } 以上就是消息队列插入消息的过程原理,通过单向链表的数据结构来存储消息。既然有了插入消息的方法供Handler插入消息,那么应该有对应的取出消息的方法,供Looper调用取出消息处理,它就是Message next()这个方法,代码就不贴了,自行前往查看,过程还是挺简单的。
5.Looper Looper在Handler机制中扮演着关键的一环,他是循环处理消息的发动机,永不停息,它不断的从消息队列中取出的消息、处理,然后分发处理事件。每个线程都可以且只能绑定一个Looper。主线程之所以能处理消息,也是因为在APP启动时,在ActivityThread中的main()方法中就已经启动了Looper循环。 下面直接上Looper关键方法loop( )的源码(API 28): public static void loop() { final Looper me = myLooper(); //获得当前的Looper if (me==null) { throw new RuntimeException(“No Looper; Looper.prepare() wasn’t called on this thread.”); } final MessageQueue queue = me.mQueue; //获取当前Looper的消息队列 //… for (;😉 { Message msg = queue.next(); //取出队头的消息 if (msg == null) { // 如果消息为空,则跳过,继续执行下一个message return; } //… try { msg.target.dispatchMessage(msg); //… } finally { //… } //… msg.recycleUnchecked(); //回收可能正在使用的消息 } } Looper处理消息的循环还是挺简单的,就是拿出消息,然后分发,然后回收 … …
6.四大概念的联系 Handler机制正是通过这四大概念账户关联形成的,简单概括如下: Handler将Message发送到Looper的消息队列中,即MessageQueue,等待Looper的循环读取Message、处理Message,然后调用Message的target,即附属的Handler的dispatchMessage()方法,将该消息回调到handleMessage()方法中,然后完成更新UI操作。
首先要明白的是,Handler和Looper对象是属于线程内部的数据,不过也提供与外部线程的访问接口,Handler就是公开给外部线程的接口,用于线程间的通信。Looper是由系统支持的用于创建和管理MessageQueue的依附于一个线程的循环处理对象,而Handler是用于操作线程内部的消息队列的,所以Handler也必须依附一个线程,而且只能是一个线程。
我们对异步消息处理的整个流程梳理一遍: 当应用程序开启时,系统会自动为UI线程创建一个MessageQueue(消息队列)和Looper循环处理对象。首先需要在主线程中创建一个Handler对象,并重写handlerMessage()方法。然后当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handler将这条消息发送出去。之后这条消息就会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息,并找到与消息对象对应的Handler对象,然后调用Handler的handleMessage()方法。由于Handler是在主线程中创建的,所以此时handleMessage()方法中的代码也会在主线程中运行,于是我们在这里就可以安心地进行UI操作了。
一般在实际的开发过程中用的比较多一种情况的是主线程的Handler将子线程中处理过的耗时操作的结果封装成Message(消息),并将该Message(利用主线程里的MessageQueue和Looper)传递到主线程中,最后主线程再根据传递过来的结果进行相关的UI元素的更新,从而实现任务的异步加载和处理,并达到线程间的通信。
7.Handler类的两个主要用途 ①执行定时任务(post) 指定任务时间,在某个具体时间或某个时间段后执行特定的任务操作,例如使用Handler提供的postDelayed(Runnable r,long delayMillis)方法指定在多久后执行某项操作,比如当当、淘宝、京东和微信等手机客户端的开启界面功能,都是通过Handler定时任务来完成的。 ②线程间的通信(sendMessage) 在执行较为耗时的操作时,Handler负责将子线程中执行的操作的结果传递到UI线程,然后UI线程再根据传递过来的结果进行相关UI元素的更新。
8.执行定时任务(post) 对于Handler的post方式来说,它会传递一个Runnable对象到消息队列中,在这个Runnable对象中,重写run()方法。一般在这个run()方法中写入需要在UI线程上的操作。 post允许把一个Runnable对象入队到消息队列中。它的方法有: post(Runnable); postAtTime(Runnable,long); postDelayed(Runnable,long); 详细解释如下: boolean post(Runnable r):把一个Runnable入队到消息队列中,UI线程从消息队列中取出这个对象后,立即执行。 boolean postAtTime(Runnable r,long uptimeMillis):把一个Runnable入队到消息队列中,UI线程从消息队列中取出这个对象后,在特定的时间执行。 boolean postDelayed(Runnable r,long delayMillis):把一个Runnable入队到消息队列中,UI线程从消息队列中取出这个对象后,延迟delayMills秒执行 void removeCallbacks(Runnable r):从消息队列中移除一个Runnable对象。
下面通过一个Demo,讲解如何通过Handler的post方式在新启动的线程中修改UI组件的属性: public class MainActivity extends Activity { // 声明一个Handler对象 private static Handler handler=new Handler();
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
btnMes1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 新启动一个子线程 new Thread(new Runnable() { @Override public void run() { // 使用post方式修改UI组件的属性 handler.post(new Runnable() { @Override public void run() { tvMessage.setText(“使用Handler.post在工作线程中发送一段执行到消息队列中,在主线程中执行。”); } }); } }).start(); } });
btnMes2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread(new Runnable() { @Override public void run() { // 使用postDelayed方式修改UI组件属性值,并且延迟3S执行 handler.postDelayed(new Runnable() { @Override public void run() { tvMessage.setText(“使用Handler.postDelayed在工作线程中发送一段执行到消息队列中,在主线程中延迟3S执行。”); } }, 3000); } }).start(); } }); } } 注意:对于post方式,它其中Runnable对象的run()方法是运行在主线程上的(虽然看上去是写在子线程当中的)。这个Runnable对象被放到了消息队列当中,然后主线程中的Looper(因为Handler是在主线程中生成的,所以Looper也在主线程中)将这个Runnable对象从消息队列中取出来,取出来之后,做了些什么呢?为什么在执行post的Runnable对象的run()方法时,不是重新开启一个线程呢?要了解这个过程,只能求助Android的源代码: 找到Handler.java这个文件,其中的post方法: public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage?, 0); } 可以看到,post方法其实就一行代码,里面调用了sendMessageDelayed()这个方法,里面有两个参数。先看一下第一个参数getPostMessage?: private static Message getPostMessage (Runnable r) { Message m = Message.obtain(); m.callback = r; return m; } 这里将Runnable对象赋值给Message的callback属性。
现在我们再来分析一下getPostMessage()这个方法,该方法完成了两个操作: 一是生成了一个Message对象,二是将r对象复制给Message对象的callback属性。返回的是一个Message对象。 现在应该好理解了: 第一个问题,如何把一个Runnable对象放到消息队列中:实际上是生成了一个Message对象,并将r赋值给Message对象的callback属性,然后再将Message对象放置到消息队列当中。
我们再看看一下Looper做了什么。打开Looper.java的dispatchMessage的方法: //一个Handler对应一个Looper对象,一个Looper对应一个MessageQueue对象,使用Handler生成Message,所生成的Message对象的Target属性,就是该handler //Message msg = handler.obtainMessage(); //发送一个message对象 //handler.sendMessage(msg); msg.target.dispatchMessage(msg); 这里面调用了dispatchMessage()方法,打开Handler.java的dispatchMessage()方法: public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } 因为已经给Message的callback属性赋值了,所以就不为空,直接执行这行代码。即执行handleCallBack()这个方法。打开handleCallBack()方法的源码: private static void handleCallback(Message message) { message.callback.run(); } 看到这个方法,就真相大白了:message的callback属性直接调用了run()方法,而不是开启一个新的子线程。 现在可以明白了。
第二个问题: Looper取出了携带有r对象的Message对象以后,做的事情是:取出Message对象之后,调用了dispatchMessage()方法,然后判断Message的callback属性是否为空,此时的callback属性是有值的,所以执行了handleCallback(Message message),在该方法中执行了 message.callback.run()。根据Java的线程知识,我们可以知道,如果直接调用Thread对象或者Runnable对象的run()方法,是不会开辟新线程的,而是在原有的线程中执行。 因为Looper是在主线程当中的,所以dispatchMessage()方法和handleMessage()方法也都是在主线程当中运行。所以post()里面的run方法也自然是在主线程当中运行的。 使用post()方法的好处在于:避免了在主线程和子线程中将数据传来传去的麻烦。
9.Handler实现线程间通信 ①在子线程发送消息,在主线程中接收消息 public class MainActivity extends Activity { private Handler handler ;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
handler = new MyHandler() ; button.setOnClickListener(new ButtonListener()) ; }
//在主线程中接收数据,修改TextView的值 class MyHandler extends Handler { @Override public void handleMessage(Message msg) { String s = (String)msg.obj ; textView.setText(s) ; } }
//生成线程对象,让子线程启动 class ButtonListener implements OnClickListener { @Override public void onClick(View arg0) { Thread t = new NetworkThread() ; t.start(); } }
//在子线程中发送数据 class NetworkThread extends Thread { @Override public void run(){ //模拟访问网络 try { Thread.sleep(2*1000) ; } catch (InterruptedException e) { e.printStackTrace(); } String s = “从网络中获取的数据” ; //开始发送消息 Message msg = handler.obtainMessage() ; msg.obj = s ; handler.sendMessage(msg) ; //sendMessage()方法,在主线程或者Worker Thread线程中发送,都是可以的,都可以被取到 } } } ②在主线程中发送消息,在子线程中接收消息 public class MainActivity extends Activity { private Handler handler ;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
//当用户点击按钮时,发送Message对象msg button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Message msg = handler.obtainMessage() ; handler.sendMessage(msg) ; } }) ;
WorkerThread wt = new WorkerThread() ; wt.start() ; }
//在子线程中生成handler class WorkerThread extends Thread { @Override public void run() { //准备Looper对象 Looper.prepare() ; //在子线程中生成一个Handler对象 handler = new Handler() { @Override public void handleMessage(Message msg) { Log.i(“后台输出”, “收到了消息对象”); } }; Looper.loop() ; //通过Looper对象将消息取出来 } } } 这是主线程发送消息,子线程接收消息的固定写法。上面的三个步骤再重复一下: (1)准备Looper对象 : Looper.prepare(); (2)在子线程中生成一个Handler对象 (3)调用Looper.loop()方法之后,Looper对象将不断地从消息队列当中取出对象,然后调用handler的handleMessage()方法,处理该消息对象;如果消息队列中没有对象,则该线程阻塞 注意,此时handleMessage()方法是在子线程中运行的。 总结一下,首先执行Looper的prepare()方法,这个方法有两个作用:一是生成Looper对象,二是把Looper对象和当前线程对象形成键值对(线程为键),存放在ThreadLocal当中,然后生成handler对象,调用Looper的myLooper()方法,得到与Handler所对应的Looper对象,这样的话,handler、looper 、消息队列就形成了一一对应的关系,然后执行上面的第三个步骤,即Looper在消息队列当中循环的取数据。
|