Android 中的Handler机制的深入探究
2015-12-30 18:36
676 查看
一、前言:本人之前也有大概了解过handler的基本用法,但仅是停留在会用知其然而不知其所以然的层次上,对实现的原理不清楚,也不太了解handler和handlerThread之间的关系,前几天有空就去看了下handler类的源代码搞清了handler机制的原理。现在就总结巩固一下,同时也分享下我的经验给那些像我这样初学的同学们,希望对你们学习有帮助。本人才疏学浅,难免会有描述不当的地方,如下面的文章的描述有理解错误之处或者不当之处,恳请各位大牛见谅和斧正。
二、为何使用handler的机制:相信大家都有了解,在android系统中有两大类线程分别是:**UI线程即主线程**和**worker线程**即非主线程的其它线程。主线程负责向UI组件分发事件(包括绘制事件),所以如果UI线程阻塞了就会影响到组件的事件分发和绘制事件,造成用户界面出现卡顿无反应现象。如果阻塞主线程时间过长就会出现ARN(应用程序无响应)造成非常差的用户体验,所以进行一些耗时的操作时一定不能放在主线程中操作,必须放到非主线程中操作。在android中非UI线程操作UI界面是不安全的,所以android规定在worker线程中不能操作UI界面,否则会报子线程操作UI界面的异常。那么问题来了,既不能在UI线程中进行一些耗时的操作,又不能在非UI线程中操作UI界面,那我们想要在一些耗时的操作中修改UI界面该怎么办呢?比如在UI界面显示下载文件的进度,众所周知下载是个很耗时的操作必须要放到非主线程里,但又要显示下载进度就涉及到修改UI界面了,既要在非主线程中操作又要修改UI界面这不就矛盾了吗?!是不是就不能实现这种需求呢?答案是否定的,这时候Handler就可以派上用场了!当然handler还可以在主线程发给主线程发消息实现异步但在这里不讨论这个。
三、实例:handler是Android为了使非UI线程中能够发Message和Runable对象到UI线程即主线程中进行处理的一个机制。我们先来了解下handler的用法先,随后再去深入探讨它的工作原理,拿上面说的下载的例子来说吧。在子线程即非UI线程中进行下载这样的耗时操作,为了可以在界面中显示下载进度,在主线程中实例化一个handler对象并复写这个handler对象的handleMessage()方法,因为这方法是在主线程中调用的,所以可以在这个方法中修改UI界面的下载进度
四、理解handler机制的原理:
前面了解了handler的基本用法了,现在来深入理解下handler机制的原理。我们先来了解一个类:Looper:
Looper部分源码:
每个线程都可以拥有一个Looper的对象且仅能有一个,该对象是和线程关联在一起,在Looper类中有一个MessageQueque的成员变量,在Looper的构造函数里会实例化这个MessageQueque的对象,现在我们来梳理一下思路:一个线程会有一个Looper类的成员变量而Looper类也有一个MessageQueque类的成员变量,所以我们可以理解为一个线程关联有一个Looper和一个MessQueque。现在我们来看看这个Message类:Message类中声明有一个类型为Handler的名为target的成员变量,该变量就是用来标记该message对象是由哪个handler调用sendMessage()方法发出的。在上面的例子里,Handler的sendMessage()方法就是把message对象放入到主线程的Looper的消息队列中(handler是在主线程实例化的情况下)。Looper 类对象的作用就是循环地把该looper对象的消息队列对象中的消息取出,然后调用handler对象的dispatchMessage()方法,最终调用handler对象的handleMessage()方法处理message,注意此时的handleMessage方法是在主线程中执行的并非在子线程,因为looper对象的loop()方法即循环从消息队列中取出消息的方法是在一直在主线程中运行的。我们来看看Looper循环取出消息的函数的源码:
Handler中的dispatchMessage函数源码:
每个线程都可以通过Looper.prepare()方法去获取本线程的looper对象,然后调用本线程中的looper对象的loop()方法进行循环取消息队列中的消息。UI线程不需要我们调用上面两个函数,UI线程一开始就已经调用了Looper.prepare()和loop(),所以我们可以在主线程直接实例化handler。
二、为何使用handler的机制:相信大家都有了解,在android系统中有两大类线程分别是:**UI线程即主线程**和**worker线程**即非主线程的其它线程。主线程负责向UI组件分发事件(包括绘制事件),所以如果UI线程阻塞了就会影响到组件的事件分发和绘制事件,造成用户界面出现卡顿无反应现象。如果阻塞主线程时间过长就会出现ARN(应用程序无响应)造成非常差的用户体验,所以进行一些耗时的操作时一定不能放在主线程中操作,必须放到非主线程中操作。在android中非UI线程操作UI界面是不安全的,所以android规定在worker线程中不能操作UI界面,否则会报子线程操作UI界面的异常。那么问题来了,既不能在UI线程中进行一些耗时的操作,又不能在非UI线程中操作UI界面,那我们想要在一些耗时的操作中修改UI界面该怎么办呢?比如在UI界面显示下载文件的进度,众所周知下载是个很耗时的操作必须要放到非主线程里,但又要显示下载进度就涉及到修改UI界面了,既要在非主线程中操作又要修改UI界面这不就矛盾了吗?!是不是就不能实现这种需求呢?答案是否定的,这时候Handler就可以派上用场了!当然handler还可以在主线程发给主线程发消息实现异步但在这里不讨论这个。
三、实例:handler是Android为了使非UI线程中能够发Message和Runable对象到UI线程即主线程中进行处理的一个机制。我们先来了解下handler的用法先,随后再去深入探讨它的工作原理,拿上面说的下载的例子来说吧。在子线程即非UI线程中进行下载这样的耗时操作,为了可以在界面中显示下载进度,在主线程中实例化一个handler对象并复写这个handler对象的handleMessage()方法,因为这方法是在主线程中调用的,所以可以在这个方法中修改UI界面的下载进度
public class MainActivity extends Activity { // 在主线程声明一个handler private Handler handler; // 下载进度条 ProgressDialog progressDialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 实例化进度条和设置进度条的属性 progressDialog = new ProgressDialog(this); progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progressDialog.setTitle("提示"); progressDialog.setMessage("正在下载。。。。。"); // 设置进度条最大值 progressDialog.setMax(100); progressDialog.show(); // 在主线程中实例化一个handler对象 // 复写handleMessage方法开进行UI界面更新 handler = new Handler() { @Override public void handleMessage(Message msg) { // 即在个方法里修改UI界面的下载进度,参数msg中带有从子线程过来的下载进度 //获取message中从子线程带过来的下载值 int download=msg.arg1; //更新UI界面的下载值 progressDialog.setProgress(download); super.handleMessage(msg); } }; // 开启一个子线程模拟下载操作 new Thread(new Runnable() { @Override public void run() { // 下载进度值 int downlown = 0; while (true) { try { // 让线程睡眠一秒钟,模拟下载 Thread.sleep(1000); downlown = downlown + 10; //获取message对象保存下载进度值发给主线程 Message message = handler.obtainMessage(); message.arg1 = downlown; //发到消息队列,等待主线程的Looper取出给主线程 //现在暂且可以理解为发给主线程 handler.sendMessage(message); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (downlown >= 100) { // 下载完成退出 break; } } } }).start(); }
四、理解handler机制的原理:
前面了解了handler的基本用法了,现在来深入理解下handler机制的原理。我们先来了解一个类:Looper:
Looper部分源码:
public final class Looper { private static final String TAG = "Looper"; static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static Looper sMainLooper; // guarded by Looper.class //声明消息队列对象引用 final MessageQueue mQueue; //当前线程应用 final Thread mThread; private Printer mLogging; //Looper的构造函数,这里实例化了MessageQueque的对象 //所以Looper就拥有了一个消息队列的对象 private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } private static void prepare(boolean quitAllowed) { //这里保证一个线程只能有一个Looper对象 if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }
每个线程都可以拥有一个Looper的对象且仅能有一个,该对象是和线程关联在一起,在Looper类中有一个MessageQueque的成员变量,在Looper的构造函数里会实例化这个MessageQueque的对象,现在我们来梳理一下思路:一个线程会有一个Looper类的成员变量而Looper类也有一个MessageQueque类的成员变量,所以我们可以理解为一个线程关联有一个Looper和一个MessQueque。现在我们来看看这个Message类:Message类中声明有一个类型为Handler的名为target的成员变量,该变量就是用来标记该message对象是由哪个handler调用sendMessage()方法发出的。在上面的例子里,Handler的sendMessage()方法就是把message对象放入到主线程的Looper的消息队列中(handler是在主线程实例化的情况下)。Looper 类对象的作用就是循环地把该looper对象的消息队列对象中的消息取出,然后调用handler对象的dispatchMessage()方法,最终调用handler对象的handleMessage()方法处理message,注意此时的handleMessage方法是在主线程中执行的并非在子线程,因为looper对象的loop()方法即循环从消息队列中取出消息的方法是在一直在主线程中运行的。我们来看看Looper循环取出消息的函数的源码:
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 (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } /*用发消息的handler对象调用dispatchMessage(msg) 其实在handler的dispatchMessage(msg)中就会调用回在主线程中复写的handleMessage()见下面的handler中的dispatchMessage()源码*/ msg.target.dispatchMessage(msg); if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } msg.recycleUnchecked(); } }
Handler中的dispatchMessage函数源码:
/** * Handle system messages here. */ public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } //在这调用在主线程中复写的handleMessage方法 handleMessage(msg); } }
每个线程都可以通过Looper.prepare()方法去获取本线程的looper对象,然后调用本线程中的looper对象的loop()方法进行循环取消息队列中的消息。UI线程不需要我们调用上面两个函数,UI线程一开始就已经调用了Looper.prepare()和loop(),所以我们可以在主线程直接实例化handler。
相关文章推荐
- ANDROID内存优化(大汇总——上)
- android应用开发--------------看RadioGroup源代码,写相似单选选项卡的集成控件(如底部导航,tab等等)
- ANDROID内存优化(大汇总——中)
- ANDROID内存优化(大汇总——全)
- Android常用工具类
- 如何扫描出Android系统媒体库中视频文件
- Android动画(一)
- Android中Menu的基本用法
- Android 下雪动画
- 说说arcGIS for Android
- android 消息推送机制之GCM(二)
- android 时间戳转换成字符串时24小时制与12小时制的区别
- android 消息推送机制之GCM(一)
- android开发技巧——仿新版QQ锁屏下弹窗
- Android 6.0 Changes
- Android之JAVASe基础篇-面向对象-多线程(七)
- Android Studio你不知道的调试技巧
- android自定义style
- Android混淆
- layer-list(图层叠加)