zoukankan      html  css  js  c++  java
  • Android异步处理三:Handler+Looper+MessageQueue深入详解

    在《Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面》中,我们讲到使用Thread+Handler的方式来实现界面的更新,其实是在非UI线程发送消息到UI线程,通知UI线程进行界面更新,这一篇我们将深入学习Android线程间通讯的实现原理。

    概述:Android使用消息机制实现线程间的通信,线程通过Looper建立自己的消息循环,MessageQueue是FIFO的消息队列,Looper负责从MessageQueue中取出消息,并且分发到消息指定目标Handler对象。Handler对象绑定到线程的局部变量Looper,封装了发送消息和处理消息的接口。

    例子:在介绍原理之前,我们先介绍Android线程通讯的一个例子,这个例子实现点击按钮之后从主线程发送消息"hello"到另外一个名为” CustomThread”的线程。

    代码下载

    LooperThreadActivity.java

    [java] view plaincopy
    1. package com.zhuozhuo;  
    2.   
    3. import android.app.Activity;  
    4. import android.os.Bundle;  
    5. import android.os.Handler;  
    6. import android.os.Looper;  
    7. import android.os.Message;  
    8. import android.util.Log;  
    9. import android.view.View;  
    10. import android.view.View.OnClickListener;  
    11.   
    12. public class LooperThreadActivity extends Activity{  
    13.     /** Called when the activity is first created. */  
    14.       
    15.     private final int MSG_HELLO = 0;  
    16.     private Handler mHandler;  
    17.       
    18.     @Override  
    19.     public void onCreate(Bundle savedInstanceState) {  
    20.         super.onCreate(savedInstanceState);  
    21.         setContentView(R.layout.main);  
    22.         new CustomThread().start();//新建并启动CustomThread实例  
    23.           
    24.         findViewById(R.id.send_btn).setOnClickListener(new OnClickListener() {  
    25.               
    26.             @Override  
    27.             public void onClick(View v) {//点击界面时发送消息  
    28.                 String str = "hello";  
    29.                 Log.d("Test""MainThread is ready to send msg:" + str);  
    30.                 mHandler.obtainMessage(MSG_HELLO, str).sendToTarget();//发送消息到CustomThread实例  
    31.                   
    32.             }  
    33.         });  
    34.           
    35.     }  
    36.       
    37.       
    38.       
    39.       
    40.       
    41.     class CustomThread extends Thread {  
    42.         @Override  
    43.         public void run() {  
    44.             //建立消息循环的步骤  
    45.             Looper.prepare();//1、初始化Looper  
    46.             mHandler = new Handler(){//2、绑定handler到CustomThread实例的Looper对象  
    47.                 public void handleMessage (Message msg) {//3、定义处理消息的方法  
    48.                     switch(msg.what) {  
    49.                     case MSG_HELLO:  
    50.                         Log.d("Test""CustomThread receive msg:" + (String) msg.obj);  
    51.                     }  
    52.                 }  
    53.             };  
    54.             Looper.loop();//4、启动消息循环  
    55.         }  
    56.     }  
    57. }  
    main.xml

    [html] view plaincopy
    1. <?xml version="1.0" encoding="utf-8"?>  
    2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    3.     android:orientation="vertical"  
    4.     android:layout_width="fill_parent"  
    5.     android:layout_height="fill_parent"  
    6.     >  
    7. <TextView    
    8.     android:layout_width="fill_parent"   
    9.     android:layout_height="wrap_content"   
    10.     android:text="@string/hello"  
    11.     />  
    12. <Button android:text="发送消息" android:id="@+id/send_btn" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>  
    13. </LinearLayout>  

    Log打印结果:

    原理:

    我们看到,为一个线程建立消息循环有四个步骤:

    1、  初始化Looper

    2、  绑定handler到CustomThread实例的Looper对象

    3、  定义处理消息的方法

    4、  启动消息循环

    下面我们以这个例子为线索,深入Android源代码,说明Android Framework是如何建立消息循环,并对消息进行分发的。

    1、  初始化Looper : Looper.prepare()

    Looper.java

    [java] view plaincopy
    1. private static final ThreadLocal sThreadLocal = new ThreadLocal();  
    2. public static final void prepare() {  
    3.         if (sThreadLocal.get() != null) {  
    4.             throw new RuntimeException("Only one Looper may be created per thread");  
    5.         }  
    6.         sThreadLocal.set(new Looper());  
    7. }  

    一个线程在调用Looper的静态方法prepare()时,这个线程会新建一个Looper对象,并放入到线程的局部变量中,而这个变量是不和其他线程共享的(关于ThreadLocal的介绍)。下面我们看看Looper()这个构造函数:

    Looper.java

    [java] view plaincopy
    1. final MessageQueue mQueue;  
    2. private Looper() {  
    3.         mQueue = new MessageQueue();  
    4.         mRun = true;  
    5.         mThread = Thread.currentThread();  
    6.     }  

    可以看到在Looper的构造函数中,创建了一个消息队列对象mQueue,此时,调用Looper. prepare()的线程就建立起一个消息循环的对象(此时还没开始进行消息循环)。

    2、  绑定handler到CustomThread实例的Looper对象 : mHandler= new Handler()

    Handler.java

    [java] view plaincopy
    1. final MessageQueue mQueue;  
    2.  final Looper mLooper;  
    3. public Handler() {  
    4.         if (FIND_POTENTIAL_LEAKS) {  
    5.             final Class<? extends Handler> klass = getClass();  
    6.             if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&  
    7.                     (klass.getModifiers() & Modifier.STATIC) == 0) {  
    8.                 Log.w(TAG, "The following Handler class should be static or leaks might occur: " +  
    9.                     klass.getCanonicalName());  
    10.             }  
    11.         }  
    12.   
    13.         mLooper = Looper.myLooper();  
    14.         if (mLooper == null) {  
    15.             throw new RuntimeException(  
    16.                 "Can't create handler inside thread that has not called Looper.prepare()");  
    17.         }  
    18.         mQueue = mLooper.mQueue;  
    19.         mCallback = null;  
    20. }  

    Handler通过mLooper = Looper.myLooper();绑定到线程的局部变量Looper上去,同时Handler通过mQueue =mLooper.mQueue;获得线程的消息队列。此时,Handler就绑定到创建此Handler对象的线程的消息队列上了。

    3、定义处理消息的方法:Override public void handleMessage (Message msg){}

         子类需要覆盖这个方法,实现接受到消息后的处理方法。

    4、启动消息循环 : Looper.loop()

          所有准备工作都准备好了,是时候启动消息循环了!Looper的静态方法loop()实现了消息循环。

    Looper.java

    [java] view plaincopy
    1. public static final void loop() {  
    2.        Looper me = myLooper();  
    3.        MessageQueue queue = me.mQueue;  
    4.          
    5.        // Make sure the identity of this thread is that of the local process,  
    6.        // and keep track of what that identity token actually is.  
    7.        Binder.clearCallingIdentity();  
    8.        final long ident = Binder.clearCallingIdentity();  
    9.          
    10.        while (true) {  
    11.            Message msg = queue.next(); // might block  
    12.            //if (!me.mRun) {  
    13.            //    break;  
    14.            //}  
    15.            if (msg != null) {  
    16.                if (msg.target == null) {  
    17.                    // No target is a magic identifier for the quit message.  
    18.                    return;  
    19.                }  
    20.                if (me.mLogging!= null) me.mLogging.println(  
    21.                        ">>>>> Dispatching to " + msg.target + " "  
    22.                        + msg.callback + ": " + msg.what  
    23.                        );  
    24.                msg.target.dispatchMessage(msg);  
    25.                if (me.mLogging!= null) me.mLogging.println(  
    26.                        "<<<<< Finished to    " + msg.target + " "  
    27.                        + msg.callback);  
    28.                  
    29.                // Make sure that during the course of dispatching the  
    30.                // identity of the thread wasn't corrupted.  
    31.                final long newIdent = Binder.clearCallingIdentity();  
    32.                if (ident != newIdent) {  
    33.                    Log.wtf("Looper""Thread identity changed from 0x"  
    34.                            + Long.toHexString(ident) + " to 0x"  
    35.                            + Long.toHexString(newIdent) + " while dispatching to "  
    36.                            + msg.target.getClass().getName() + " "  
    37.                            + msg.callback + " what=" + msg.what);  
    38.                }  
    39.                  
    40.                msg.recycle();  
    41.            }  
    42.        }  
    43.    }  

    while(true)体现了消息循环中的“循环“,Looper会在循环体中调用queue.next()获取消息队列中需要处理的下一条消息。当msg != null且msg.target != null时,调用msg.target.dispatchMessage(msg);分发消息,当分发完成后,调用msg.recycle();回收消息。

    msg.target是一个handler对象,表示需要处理这个消息的handler对象。Handler的void dispatchMessage(Message msg)方法如下:

    Handler.java

    [java] view plaincopy
    1. public void dispatchMessage(Message msg) {  
    2.         if (msg.callback != null) {  
    3.             handleCallback(msg);  
    4.         } else {  
    5.             if (mCallback != null) {  
    6.                 if (mCallback.handleMessage(msg)) {  
    7.                     return;  
    8.                 }  
    9.             }  
    10.             handleMessage(msg);  
    11.         }  
    12. }  

    可见,当msg.callback== null 并且mCallback == null时,这个例子是由handleMessage(msg);处理消息,上面我们说到子类覆盖这个方法可以实现消息的具体处理过程。


    总结:从上面的分析过程可知,消息循环的核心是Looper,Looper持有消息队列MessageQueue对象,一个线程可以把Looper设为该线程的局部变量,这就相当于这个线程建立了一个对应的消息队列。Handler的作用就是封装发送消息和处理消息的过程,让其他线程只需要操作Handler就可以发消息给创建Handler的线程。由此可以知道,在上一篇《Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面》中,UI线程在创建的时候就建立了消息循环(在ActivityThread的public static final void main(String[] args)方法中实现),因此我们可以在其他线程给UI线程的handler发送消息,达到更新UI的目的。

    本博文地址:http://blog.csdn.net/mylzc/article/details/6771331 转载请注明出处

  • 相关阅读:
    C#
    C#
    ssh学习笔记
    (已解决)Could not open '/var/lib/nova/mnt/*/volume-*': Permission denied
    RPCVersionCapError: Requested message version, 4.17 is incompatible. It needs to be equal in major version and less than or equal in minor version as the specified version cap 4.11.
    如何在linux下安装idea
    The system has no LUN copy license
    调整mysql数据库最大连接数
    mysql数据库编码问题
    cinder支持nfs快照
  • 原文地址:https://www.cnblogs.com/walccott/p/4957612.html
Copyright © 2011-2022 走看看