【Android 开发】: Android 消息处理机制之四: Android 消息循环 Looper 及其源码解析

来源:互联网 发布:windows iso 映像 编辑:程序博客网 时间:2024/05/06 03:50




上一讲我们学习Handler和Message的一些使用方式,我们知道Handler它会发送消息和处理消息,并且关联一个子线程,如何发送消息入队和出队处理消息等这些都是交给Looper去管理分发的,也就是它是负责整个消息队列运转的一个类,这一讲我们就来学习一下Android中的Looper的操作。

一、Looper类介绍

  这个类是用来在一个线程中运行一个消息循环(Message),默认情况下线程是没有一个消息循环来关联它们的,在这个线程中调用prepare()方法来启动一个循环,然后调用loop()就可以处理消息至到循环停止。

  下面就是一个典型的例子实现一个Looper线程,使用 prepare()方法 和 loop()来创建一个初始的Handler并且能够与消息循环(Looper)进行沟通关联

  【注意】:默认情况下的android新诞生的一个线程是没有开启一个消息循环(Looper)的,但是主线程除外,主线程系统会自动为其创建Looper对象,开启消息循环。

二、程序Demo

1. 布局文件定义一个Button和TextView,这里不贴出来,读者可以阅读附件源码

2. MainActivity.java

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. ...  
  2. public class MainActivity extends Activity {  
  3.   
  4.     private Button btn;  
  5.     private TextView txt;  
  6.     private MyHandler mHandler;  
  7.   
  8.     @Override  
  9.     protected void onCreate(Bundle savedInstanceState) {  
  10.         super.onCreate(savedInstanceState);  
  11.         setContentView(R.layout.activity_main);  
  12. <span style="font-family:Courier New;">       ...  
  13. </span>        mHandler = new MyHandler();  
  14.         btn.setOnClickListener(new View.OnClickListener() {  
  15.   
  16.             @Override  
  17.             public void onClick(View v) {  
  18.                 // TODO Auto-generated method stub  
  19.                 // 启动线程  
  20.                 new Thread(new MyThread()).start();  
  21.             }  
  22.         });  
  23.     }  
  24.   
  25.     public class MyThread implements Runnable {  
  26.   
  27.         @Override  
  28.         public void run() {  
  29.             // TODO Auto-generated method stub  
  30.             Message msg = Message.obtain();  
  31.             msg.obj = "AHuier";  
  32.             mHandler.sendMessage(msg);  
  33.         }  
  34.   
  35.     }  
  36.   
  37.     public class MyHandler extends Handler {  
  38.   
  39.         public MyHandler() {  
  40.             super();  
  41.             // TODO Auto-generated constructor stub  
  42.         }  
  43.   
  44.         // Handler中有个传递Looper对象的构造方法,这个构造方法比较少用  
  45.         public MyHandler(Looper looper) {  
  46.             super(looper);  
  47.             // TODO Auto-generated constructor stub  
  48.         }  
  49.   
  50.         @Override  
  51.         public void handleMessage(Message msg) {  
  52.             // TODO Auto-generated method stub  
  53.             super.handleMessage(msg);  
  54.             txt.setText("接受子线程发送的消息 --->" + msg.obj);  
  55.         }  
  56.     }  
  57. ...  
  58.  }  

3. 程序执行结果

4. 【说明】: 在上面的代码中我们并没有去手动生成Looper对象,主线程依然可以完成接受子线程消息并显示的操作,在这里我们需要明白为什么我们之前的例子中虽然没有创建一个Looper去管理消息,但是子线程中发送消息依然能够被主线程接受到,原因是因为我们主线程中已经存在了默认的一个Looper对象。

   这里我们在做一个小测试,我们给其生成一个Looper对象,在onCreate()方法中添加代码如下:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. protected void onCreate(Bundle savedInstanceState) {  
  3.     super.onCreate(savedInstanceState);  
  4.     setContentView(R.layout.activity_main);  
  5.     initComponent();  
  6.     // 在Activity中有一个默认的Looper对象,来处理子线程发送的消息  
  7.     // 这里我们尝试的给其生成一个Looper对象,也是可以的  
  8.     Looper looper = Looper.myLooper(); //获得与子线程关联的Looper对象  
  9.     mHandler = new MyHandler(looper);  
  10.     btn.setOnClickListener(new View.OnClickListener() {  
  11.   
  12.         @Override  
  13.         public void onClick(View v) {  
  14.             // TODO Auto-generated method stub  
  15.             // 启动线程  
  16.             new Thread(new MyThread()).start();  
  17.         }  
  18.     });  
  19. }  
程序执行依然会接受到子线程发送的消息。为什么会是这样的呢?我们来查看一下它们的源码

1) 查看Handler源码中的构造方法

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Default constructor associates this handler with the queue for the 
  3.  * current thread. 
  4.  * 
  5.  * If there isn't one, this handler won't be able to receive messages. 
  6.  */  
  7. public Handler() {  
  8.     if (FIND_POTENTIAL_LEAKS) {  
  9.         final Class<? extends Handler> klass = getClass();  
  10.         if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&  
  11.                 (klass.getModifiers() & Modifier.STATIC) == 0) {  
  12.             Log.w(TAG, "The following Handler class should be static or leaks might occur: " +  
  13.                 klass.getCanonicalName());  
  14.         }  
  15.     }  
  16.   
  17.     mLooper = Looper.myLooper();  
  18.     if (mLooper == null) {  
  19.         throw new RuntimeException(  
  20.             "Can't create handler inside thread that has not called Looper.prepare()");  
  21.     }  
  22.     mQueue = mLooper.mQueue;  
  23.     mCallback = null;  
  24. }  
可以发现在其构造方法中就已经默认为帮其生成一个Looper对象了: mLooper = Looper.myLooper();
同时从Looper中获取到一个消息队列,并且赋值给Handler的本地的mQueque,我们在看一下Handler(Looper looper)这个构造方法如下:
[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Use the provided queue instead of the default one. 
  3.  */  
  4. public Handler(Looper looper) {  
  5.     mLooper = looper;  
  6.     mQueue = looper.mQueue;  
  7.     mCallback = null;  
  8. }  
同样也是接受用户生成的一个Looper对象。所以是底层实现方式都是一模一样了,从这里我们也知道了为什么默认情况下主线程都会默认的Looper对象去维护了。

2) 这里我们需要在看一下为什么会调用 Looper.myLooper();会获取到一个Looper对象,跟踪其源码如下:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Return the Looper object associated with the current thread.  Returns 
  3.  * null if the calling thread is not associated with a Looper. 
  4.  */  
  5. public static Looper myLooper() {  
  6.     return sThreadLocal.get();  
  7. }  
继续跟踪是谁给其sThreadLocal实例化
[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. // sThreadLocal.get() will return null unless you've called prepare().  
  2. static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();  
sThreadLocal 是从一个本地线程中获取Looper类型的本地线程ThreadLocal对象,这里只需要明白ThreadLocal是一个Android提供管理线程的一个东西。
[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1.  /** Initialize the current thread as a looper. 
  2.   * This gives you a chance to create handlers that then reference 
  3.   * this looper, before actually starting the loop. Be sure to call 
  4.   * {@link #loop()} after calling this method, and end it by calling 
  5.   * {@link #quit()}. 
  6.   */  
  7. public static void prepare() {  
  8.     if (sThreadLocal.get() != null) {  
  9.         throw new RuntimeException("Only one Looper may be created per thread");  
  10.     }  
  11.     sThreadLocal.set(new Looper());  
  12. }  
在prepare()方法中,会从sThreadLocal通过get获取一个本地线程的对象,如果是空的话,这个东西中将new出来的Looper对象set到本地线程中。查看ThreadLocal的get和set方法
[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public T get() {  
  2.     // Optimized for the fast path.  
  3.     Thread currentThread = Thread.currentThread();  
  4.     Values values = values(currentThread);  
  5.     if (values != null) {  
  6.         Object[] table = values.table;  
  7.         int index = hash & values.mask;  
  8.         if (this.reference == table[index]) {  
  9.             return (T) table[index + 1];  
  10.         }  
  11.     } else {  
  12.         values = initializeValues(currentThread);  
  13.     }  
  14.   
  15.     return (T) values.getAfterMiss(this);  
  16. }  
[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public void set(T value) {  
  2.     Thread currentThread = Thread.currentThread();  
  3.     Values values = values(currentThread);  
  4.     if (values == null) {  
  5.         values = initializeValues(currentThread);  
  6.     }  
  7.     values.put(this, value);  
  8. }  

也就是说它终究是通过set的方式讲new出来的Looper对象扔到ThreadLocal中,由它来完成初始化和关联一个线程,如果要得到一个Looper对象就从ThreadLocal中get出来。通过这种方式来关联和初始化指定线程的Looper对象。

5. 在上面的一个Demo中,我们是实现了子线程发送消息给主线程来更新UI的操作和Looper的关系,子线程默认情况下是没有Looper的对象的,下面我就来测试一下主线程向子线程发送消息,由于子线程默认没有Looper,我们就来测试一下这样实现会发生什么情况?[注意,这种方式我们一般在实际开发中是很少见的],Demo2如下所示:

1) MainActivity.java 中贴出onCreate()和 MyThread 类里面的代码段,读者可以阅读附件中的源代码

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. protected void onCreate(Bundle savedInstanceState) {  
  3.     super.onCreate(savedInstanceState);  
  4.     setContentView(R.layout.activity_main);  
  5.     initComponent();  
  6.     new Thread(new MyThread()).start();  
  7.     btn.setOnClickListener(new View.OnClickListener() {  
  8.   
  9.         @Override  
  10.         public void onClick(View v) {  
  11.             // TODO Auto-generated method stub  
  12.             // 点击按钮的时候在UI主线程中向子线程发送消息  
  13.             Message message = Message.obtain();  
  14.             message.obj = "AHuier";  
  15.             mHandler.sendMessage(message);  
  16.         }  
  17.     });  
  18. }  
  19. public class MyThread implements Runnable {  
  20.     @Override  
  21.     public void run() {  
  22.         mHandler = new Handler(){  
  23.   
  24.             @Override  
  25.             public void handleMessage(Message msg) {  
  26.                 // TODO Auto-generated method stub  
  27.                 super.handleMessage(msg);  
  28.                 // 由于不能在子线程中更新UI,所以我们输出到控制台.  
  29.                 System.out.println("接受主线程中发出来的消息" + msg.obj);  
  30.             }  
  31.               
  32.         };  
  33.     }  
  34. }  
编译执行发出异常:

从这里我们可以得出结论在子线程中,默认情况下是没有Looper对象的,所以我们需要根据博文上面的Looper类的说明添加prepare()方法 和 loop()方法来启动Looper消息循环。修改程序如下2)

2) 在MyThread子线程中添加prepare()方法 和 loop()方法完成Looper消息循环的启动

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public class MyThread implements Runnable {  
  2.     @Override  
  3.     public void run() {  
  4.         Looper.prepare();  
  5.         mHandler = new Handler(){  
  6.   
  7.             @Override  
  8.             public void handleMessage(Message msg) {  
  9.                 // TODO Auto-generated method stub  
  10.                 super.handleMessage(msg);  
  11.                 // 由于不能在子线程中更新UI,所以我们输出到控制台.  
  12.                 System.out.println("接受主线程中发出来的消息" + msg.obj);  
  13.             }  
  14.               
  15.         };  
  16.         Looper.loop();  
  17.     }  
  18. }  
程序执行结果:

6. 在这里为什么添加完这两个方法之后就会有Looper消息循环了?我们来查看一下Looper的相关源代码

1) prepare() 方法我们在上面已经知道,它会初始化当前的线程关联一个Looper.

2) loop()源码如下

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Run the message queue in this thread. Be sure to call 
  3.  * {@link #quit()} to end the loop. 
  4.  */  
  5. public static void loop() {  
  6.     Looper me = myLooper();  
  7.     if (me == null) {  
  8.         throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");  
  9.     }  
  10.     MessageQueue queue = me.mQueue;  
  11.       
  12.     // Make sure the identity of this thread is that of the local process,  
  13.     // and keep track of what that identity token actually is.  
  14.     Binder.clearCallingIdentity();  
  15.     final long ident = Binder.clearCallingIdentity();  
  16.       
  17.     while (true) {  
  18.         Message msg = queue.next(); // might block  
  19.         if (msg != null) {  
  20.             if (msg.target == null) {  
  21.                 // No target is a magic identifier for the quit message.  
  22.                 return;  
  23.             }  
  24.   
  25.             long wallStart = 0;  
  26.             long threadStart = 0;  
  27.   
  28.             // This must be in a local variable, in case a UI event sets the logger  
  29.             Printer logging = me.mLogging;  
  30.             if (logging != null) {  
  31.                 logging.println(">>>>> Dispatching to " + msg.target + " " +  
  32.                         msg.callback + ": " + msg.what);  
  33.                 wallStart = SystemClock.currentTimeMicro();  
  34.                 threadStart = SystemClock.currentThreadTimeMicro();  
  35.             }  
  36.   
  37.             msg.target.dispatchMessage(msg);  
  38.   
  39.             if (logging != null) {  
  40.                 long wallTime = SystemClock.currentTimeMicro() - wallStart;  
  41.                 long threadTime = SystemClock.currentThreadTimeMicro() - threadStart;  
  42.   
  43.                 logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);  
  44.                 if (logging instanceof Profiler) {  
  45.                     ((Profiler) logging).profile(msg, wallStart, wallTime,  
  46.                             threadStart, threadTime);  
  47.                 }  
  48.             }  
  49.   
  50.             // Make sure that during the course of dispatching the  
  51.             // identity of the thread wasn't corrupted.  
  52.             final long newIdent = Binder.clearCallingIdentity();  
  53.             if (ident != newIdent) {  
  54.                 Log.wtf(TAG, "Thread identity changed from 0x"  
  55.                         + Long.toHexString(ident) + " to 0x"  
  56.                         + Long.toHexString(newIdent) + " while dispatching to "  
  57.                         + msg.target.getClass().getName() + " "  
  58.                         + msg.callback + " what=" + msg.what);  
  59.             }  
  60.               
  61.             msg.recycle();  
  62.         }  
  63.     }  
  64. }  
它首先获取Looper对象,然后将消息从Looper中取出,然后赋值给MessageQueue,让MessageQueue去管理,接着在While(true)这个死循环里面一直在轮转的取消息和分发消息(从Message msg = queue.next();和msg.target.dispatchMessage(msg);)这两句代码读出。

三、总结与相关源码

    通过上述两个Demo和Looper相关源码的分析,我们可以知道Looper作为一个循环机制它的作用就是初始化线程和将Handler与该线程关联的工作,以及管理,维护整个消息循环的机制。但是具体的发送消息还有处理消息都是靠Handler和Message来完成的。所以在一个新诞生的线程中,Looper都会关联到这个Thread,以及它的MessageQueue和Handler.

    源码下载:

   http://download.csdn.net/detail/xukunhui2/6656293

原文连接: http://blog.csdn.net/ahuier/article/details/17103517

0 0
原创粉丝点击