Android中Handler消息处理机制原理
2016-05-27 11:55
585 查看
大家在编写android项目的时候应该用到Handler了吧.
一般做法应该都是先在主线程中创建一个Handler实例
Handler handler = new Handler{
public void handleMessage(Message msg) {
//做相应处理
}
}
然后通过handler.sendMessage(Message msg)发送消息.
最后在handleMessage()回调中做相应的处理.
那么这里有几个疑问:什么是主线程?发送消息到哪里去了?handleMessage()为什么会被回调?
1.主线程也就是UI线程,所有的处理用户消息,以及绘制界面的工作都在该线程中完成的,UI线程是从ActivityThread运行的,
这里调用了两个方法Looper.prepareMainLooper()和Looper.loop();
在frameworks/base/core/java/android/os/Looper.java中
这里有一个sThreadLocal对象,这是什么呢
它是Looper类的一个静态成员.ThreadLocal类作用是提供"线程局部存储",简单的理解是:ThreadLocal对象内部会根据调用prepare()函数的线程的id保存一个数据对象,这个数据对象就是所谓的"线程局部存储对象",像这里就是主线程调用的,那么就为主线程保存一个数据对象,这个数据对象就是Looper(上面的泛型就是Looper),第一次调用prepare()函数,sThreadLocal肯定还没有存储的,所以通过sThreadLocal.set()函数设置进去,对应get()函数就是取出来.所以从上面prepare()函数可以看出来,prepare()函数在一个线程中只能调用一次,因为第二次调用该函数,get()获取的就不是null,就会报异常.
一般一个应用程序都在一个进程中,所以ActivityThread类的main()函数只会执行一次,所以prepare()函数只会执行一次.
通过上面分析我们知道,每个线程通过prepareMainLooper()函数调用prepare()函数,设置一个Looper对象,那么它们保存的Looper对象是不相同的.
好了,继续看到主线程的Looper对象是如何构造的.sThreadLocal.set(new Looper(quitAllowed));
这里MessageQueue是一个消息队列,上面handler发送的消息就是发送到这个队列里了.消息队列采用排队的方式对消息进行处理,即先到的消息会先得到处理,但如果消息本身指定了被处理的时刻,则必须等到该时刻才能处理该消息.对于消息队列我就没有深究了.
这里就是给Looper对象创建了一个消息队列.
然后回到prepareMainLooper()函数,接着看sMainLooper = myLooper();
这一步其实就是把prepare()函数设置的Looper对象赋值给sMainLooper变量.
ActivityThread类的main()函数里面的prepareMainLooper()函数做了两件事,第一是创建了一个MessageQueue(消息队列),第二是创建了一个Looper对象,一个线程只有一个Looper对象,因此只有一个MessageQueue(消息对象).
ActivityThread类的main()函数里面的另一个函数Looper.loop();
读取消息然后回调对应handler的handleMessage(Message msg) 方法.
上面的三个问题我们了解差不多,但是又有新的疑问,new Handler()发送消息就怎么就会发送到主线程的Looper对象里面的消息队列里面去呢??
好了,我们来看一下Handler的构造方法:
通过构造函数知道,在构造handler对象时(无参数),首先会取得当前线程对应的looper对象.如果该线程没有设置过looper对象,那么就会报异常.一般在我们在程序中new Handler()都是在主线程中,所以这次获取的是主线程的looper对象,而主线程的looper在程序启动的时候就创建好了(activitythread的main()方法里面),所以平时我们这样创建都没有报错.
这样一来我们也知道了,如果在一个线程中使用handler,那么该线程中必须创建了Looper对象.有了Looper对象也就有了消息队列(它在looper的构造方法里面创建).
这样线程称呼为异步消息线程.
总结一下:异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数,执行完成一个消息后则继续循环。若消息队列为空,线程则会阻塞等待.
android应用程序的主线程就这样一个异步消息线程.
那么除主线程外,我们自定义线程可不可定义成异步消息线程呢?当然可以,android提供了一个HandlerThread这个类
frameworks/base/core/java/android/os/HandlerThread.java
写一个线程MyThread 继承HandlerThread, 看它的run()方法
是不是有点熟悉,就是先创建MessageQueue消息队列和Looper对象,然后进入死循环了.那么现在就可以使用handler向该线程的消息队列发送消息了,这里得注意了handler怎么与这个线程的消息队列关联起来.handler另外的构造函数:
也就是MyThread对象调用getLooper()就可以了.
这样想要往这个线程发送消息,构建一个handler传入looper对象.
我们在项目用得最多的应该是往主线程发送更新UI的消息,因为更新UI必须要在主线程中,不然会报异常.
一般做法应该都是先在主线程中创建一个Handler实例
Handler handler = new Handler{
public void handleMessage(Message msg) {
//做相应处理
}
}
然后通过handler.sendMessage(Message msg)发送消息.
最后在handleMessage()回调中做相应的处理.
那么这里有几个疑问:什么是主线程?发送消息到哪里去了?handleMessage()为什么会被回调?
1.主线程也就是UI线程,所有的处理用户消息,以及绘制界面的工作都在该线程中完成的,UI线程是从ActivityThread运行的,
分析点击android桌面app图标启动应用程序的过程 这篇文章第三十二步和三十三步中可以看到,第一次启动应用程序时,系统会为应用程序创建一个进程,从ActivityThread的main()方法开始执行,这个main()也就是UI线程的开始.
2.主线程中用handler发送消息消息发送到哪去了?这里我们来看一下main()方法,
在frameworks/base/core/java/android/app/ActivityThread.java中
public static void main(String[] args) { ...... Looper.prepareMainLooper(); ...... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
这里调用了两个方法Looper.prepareMainLooper()和Looper.loop();
首先看第一个:
在frameworks/base/core/java/android/os/Looper.java中public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }接着看prepare()函数:
private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }
这里有一个sThreadLocal对象,这是什么呢
public class Looper { private static final String TAG = "Looper"; // sThreadLocal.get() will return null unless you've called prepare(). static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();........
它是Looper类的一个静态成员.ThreadLocal类作用是提供"线程局部存储",简单的理解是:ThreadLocal对象内部会根据调用prepare()函数的线程的id保存一个数据对象,这个数据对象就是所谓的"线程局部存储对象",像这里就是主线程调用的,那么就为主线程保存一个数据对象,这个数据对象就是Looper(上面的泛型就是Looper),第一次调用prepare()函数,sThreadLocal肯定还没有存储的,所以通过sThreadLocal.set()函数设置进去,对应get()函数就是取出来.所以从上面prepare()函数可以看出来,prepare()函数在一个线程中只能调用一次,因为第二次调用该函数,get()获取的就不是null,就会报异常.
一般一个应用程序都在一个进程中,所以ActivityThread类的main()函数只会执行一次,所以prepare()函数只会执行一次.
通过上面分析我们知道,每个线程通过prepareMainLooper()函数调用prepare()函数,设置一个Looper对象,那么它们保存的Looper对象是不相同的.
好了,继续看到主线程的Looper对象是如何构造的.sThreadLocal.set(new Looper(quitAllowed));
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mRun = true; mThread = Thread.currentThread(); }
这里MessageQueue是一个消息队列,上面handler发送的消息就是发送到这个队列里了.消息队列采用排队的方式对消息进行处理,即先到的消息会先得到处理,但如果消息本身指定了被处理的时刻,则必须等到该时刻才能处理该消息.对于消息队列我就没有深究了.
这里就是给Looper对象创建了一个消息队列.
然后回到prepareMainLooper()函数,接着看sMainLooper = myLooper();
public static Looper myLooper() { return sThreadLocal.get(); }
这一步其实就是把prepare()函数设置的Looper对象赋值给sMainLooper变量.
ActivityThread类的main()函数里面的prepareMainLooper()函数做了两件事,第一是创建了一个MessageQueue(消息队列),第二是创建了一个Looper对象,一个线程只有一个Looper对象,因此只有一个MessageQueue(消息对象).
ActivityThread类的main()函数里面的另一个函数Looper.loop();
public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) {...这个函数的就是从主线程的Looper对象的消息队列中不断的读取和发送消息.一个死循环吧.
读取消息然后回调对应handler的handleMessage(Message msg) 方法.
上面的三个问题我们了解差不多,但是又有新的疑问,new Handler()发送消息就怎么就会发送到主线程的Looper对象里面的消息队列里面去呢??
好了,我们来看一下Handler的构造方法:
public Handler() { this(null, false); } public Handler(Callback callback, boolean async) { ... mLooper = Looper.myLooper();// if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
通过构造函数知道,在构造handler对象时(无参数),首先会取得当前线程对应的looper对象.如果该线程没有设置过looper对象,那么就会报异常.一般在我们在程序中new Handler()都是在主线程中,所以这次获取的是主线程的looper对象,而主线程的looper在程序启动的时候就创建好了(activitythread的main()方法里面),所以平时我们这样创建都没有报错.
这样一来我们也知道了,如果在一个线程中使用handler,那么该线程中必须创建了Looper对象.有了Looper对象也就有了消息队列(它在looper的构造方法里面创建).
这样线程称呼为异步消息线程.
总结一下:异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数,执行完成一个消息后则继续循环。若消息队列为空,线程则会阻塞等待.
android应用程序的主线程就这样一个异步消息线程.
那么除主线程外,我们自定义线程可不可定义成异步消息线程呢?当然可以,android提供了一个HandlerThread这个类
frameworks/base/core/java/android/os/HandlerThread.java
写一个线程MyThread 继承HandlerThread, 看它的run()方法
public void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1; }
是不是有点熟悉,就是先创建MessageQueue消息队列和Looper对象,然后进入死循环了.那么现在就可以使用handler向该线程的消息队列发送消息了,这里得注意了handler怎么与这个线程的消息队列关联起来.handler另外的构造函数:
public Handler(Looper looper) { this(looper, null, false); }没错,直接传入该线程的looper对象就可以该线程的looper对象怎么获取呢:
public Looper getLooper() { .... return mLooper; }
也就是MyThread对象调用getLooper()就可以了.
这样想要往这个线程发送消息,构建一个handler传入looper对象.
我们在项目用得最多的应该是往主线程发送更新UI的消息,因为更新UI必须要在主线程中,不然会报异常.
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories