Android(安卓)Handler(说说我的理解)
前言:
一个Android开发者应该都用过Handler,而且应该说对它的用法都很熟悉。
最简单的使用案例:
// 1. new 一个Handler 实例,并重写handlerMessage方法private Handler mHandler = new Handler(){ public void handleMessage(Message msg){ mTextView.setText(""+msg.arg1+"-"+msg.arg2); };};// 在一个子线程里面通过handler.sendMessage(),发送一个消息到主线程,这样上面的handleMeassage方法就会被执行。new Thread(){ @Override public void run() { try { Thread.sleep(2000); Message message = new Message(); message.arg1 = 88; mHandler.sendMessage(message); } catch (InterruptedException e) { e.printStackTrace(); } }}.start();
就是这么简单,在Android中为了保证线程安全,只能在主线程中更新UI, 所以如果在子线程中进行一些耗时处理之后如果把数据返回到主线程进行主线程的相关处理,这个是Handler最主要的使用场景。
还记得第一次找工作的时候面试官在问我相关的Handler的问题时,我把上面的一切自信的告诉了他,他紧接着问我还有其他要说的没,我当时确实就知道这些了。但是关于Handler还有很多可以深挖的东西,后来我知道这是个面试必考题的时候我也反复去学习过几次,但是看了半天,过段时间就又似懂非懂了,所以把学到的记录下来并结合自己的思考才能真正变成自己的知识。
下面我就一步一步根据自己的学习路径和思考深入学习Handler。
1. 图解Handler
Handler确切的说是Android中的一个消息传递方案。在Android中还有以下几种消息传递方案:
a. callback interface 也就是实现接口回调的方法。
b. Broadcast Receiver 通过广播的方式
c. Observer-Subject 观察者和被观察者模式
这篇博文主要分析Handler的消息传递机制。
将工作线程中需更新UI的操作信息 传递到 UI主线程,从而实现 工作线程对UI的更新处理,最终实现异步消息的处理
Handler进行消息的传递还需要其他几个类协同完成:Message、Message Queue、Looper。下面的表格对这几个类进行解读。
Message、Message Queue、Looper,Handler之间的关系主要如下说明:
- 一个线程拥有一个Looper
- 一个Looper对应一个消息队列(MessageQueue)
- 一个线程可以有多个Handler,所以一个Looper可以对应同一个线程的多个Handler给它发送消息
- 一个Message只能属于唯一一个Handler
- 同一个Handler只能处理自己发送给Looper的那些Message
- 默认创建的是和主线程绑定的handler, 如果要创建对应子线程的Handler,需要传入子线程的Looper作为参数。一个Handler绑定到一个线程
2.源码解析
下面的表格把 Message、Looper,Handler这几个类的核心方法进行了一下总结,基本简单的说明了他们之间的联系:
清楚每个类的核心方法以及说明,我们再次回到Handler的最基本用法。我们分解出一下步骤:
1.在主线程中 通过匿名内部类 创建Handler类对象
private Handler mhandler = new Handler(){ // 通过复写handlerMessage()从而确定更新UI的操作 @Override public void handleMessage(Message msg) { ...// 需执行的UI操作 } };
2:创建消息对象
Message msg = Message.obtain(); // 实例化消息对象 msg.what = 1; // 消息标识 msg.obj = "AA"; // 消息内容存放
3:在工作线程中 通过Handler发送消息到消息队列中
Thread t = new Thread(new Runnabel(){public void run(){ mHandler.sendMessage(msg); // 发送消息到消息队列 }});
4:开启工作线程
t.start();
按照使用的步骤,对其中的Android 源码进行分析:
1.Handler的创建:
这是Handler其中的一个的构造方法,也是上面使用的一个:
public Handler() { this(null, false); }
可以看到它调用了this(null, false),找到这个构造函数:
分析以上代码片,重点分析Looper和MessageQueue相关的部分:
// 1. 获取Looper对象 mLooper = Looper.myLooper(); if (mLooper == null) { //2. Looper.myLooper()作用:获取当前线程的Looper对象;若线程无Looper对象则抛出异常 throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } // 若线程中无创建Looper对象,则也无法创建Handler对象 // 3. 获取该Looper对象中保存的消息队列对象(MessageQueue) mQueue = mLooper.mQueue;
但分析上面的代码可知,并没有看到该线程的looper和MessageQueue在什么时候创建的,所以找到他们是在哪里进行创建的。
在Looper代码中有如下代码片段:
先来看下prepareMainLooper() 这个方法,这个方法是创建主线程Looper对象的方法 , 在Looper中用sMainLooper 保存主线程的looper对象:
public static void prepareMainLooper() { // 1. 调用了带参数的prepare , 在上面的代码片中有这个方法。这个方法中主要有两部分。 prepare(false); // 2. 判断sMainlooper 是否被赋值了,如果已经赋值的话会抛出异常。 synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } //3. sMainLooper 通过myLooper 方法获取到主线程的Looper赋值。 sMainLooper = myLooper(); } }
再进一步分析上面的主要代码
1.prepare(false):
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)); }
2.这个sThreadLocal.get(), 又是什么呢:
先说明下sThreadLocal这个对象,类型是ThreadLocal
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
实现一个线程本地存储,一个让每个线程都有拥有value的变量。所有的线程都共享同一个ThreadLocal对象,存储每个线程各自的Looper对象,从而使每个线程在这拿到的value都不相同(每个线程自己的Looper对象),并且一个线程在这里做的改变并不影响其他线程,支持值为null。
sThreadLocal 的类型是ThreadLocal , 我们就看看ThreadLocal的get和set方法:
public void set(T value) { // set方法 Thread t = Thread.currentThread(); // 每个Thread的对象都有一个ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) // 如果该线程的ThreadLocalMap 不为空,就直接把数据保存到Map里面 , // 键就是ThreadLocal(这里保存的是Looper对象) map.set(this, value); else createMap(t, value); }public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // 获取到该线程的 ThreadLocalMap if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); // 获取到保存的值,(此处保存的值是Looper) if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } ThreadLocalMap getMap(Thread t) { // 获取该线程的ThreadLocalMap return t.threadLocals; }
通过上面代码的分析就看到了Looper是如何和对应的线程绑定的,就知道了一个线程只能由一个自己的Looper.
最后我们看看Looper的构造方法, 创建了一个队列,并获取到了当前线程的引用。分析到这里Handler的创建我们基本清楚了。
其中:
1.Looper和MessageQueue的创建
2.Looper和MessageQueue和线程的绑定
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
备注:
我们知道在主线程中使用Handler的时候我们并没有去调用 prepareMainLooper, 而是直接new出一个Handler。经过分析我们发现在 ActivityThread 类的main方法中系统已经调用了 prepareMainLooper (ActivityThread的main方法是整个APP的入口) ,已经实例化了主线程的 Looper,并且调用了Looper.loop();开启了主线程消息的循环处理。所以我们通过Handler发送的信息,会自动的得到处理。
2.消息处理
发送消息到消息队列 mHandler.sendMessage(msg); 然后消息队列是怎么处理的呢,
先看下简单的流程图:
我们需要调用Looper.loop, 启动Looper, 进行消息队列的循环处理。
下面就是Looper累中的源码loop方法的分析(只分析相关重点的代码)
public static void loop() { ... // 1. 获取当前Looper的消息队列 final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } // 获取Looper实例中的消息队列对象(MessageQueue) final MessageQueue queue = me.mQueue; // 2. 消息循环(通过for循环) for (;;) { // 3.从消息队列中取出消息 Message msg = queue.next(); if (msg == null) { return; } // 4.next():取出消息队列里的消息 ,**这个方法在下面贴出** // 若取出的消息为空,则线程阻塞 // 5.派发消息到对应的Handler // 把消息Message派发给消息对象msg的target属性 ,target属性实际是1个handler对象 msg.target.dispatchMessage(msg); // 6. 释放消息占据的资源 msg.recycle(); }} //作用:出队消息,即从 消息队列中 移出该消息 Message next() { ... // 该参数用于确定消息队列中是否还有消息 // 从而决定消息队列应处于出队消息状态 or 等待状态 int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } // nativePollOnce方法在native层,若是nextPollTimeoutMillis为-1,此时消息队列处于等待状态 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; // 出队消息,即 从消息队列中取出消息:按创建Message对象的时间顺序 if (msg != null) { if (now < msg.when) { nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // 取出了消息 mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { // 若 消息队列中已无消息,则将nextPollTimeoutMillis参数设为-1 // 下次循环时,消息队列则处于等待状态 nextPollTimeoutMillis = -1; } ...... } ..... }}
到现在位置基本就把Handler类基本使用的框架层源码进行了比较详细的分析,其中涉及的内容关联性比较多,写的可能遗漏或者错误,欢迎指正。
3.子线程Handler的使用
上面的分析说明了,在主线程中Looper和MessageQueue都不需要开发人员自己创建,在程序入口系统已经完成了实现。那么子线程如果要创建自己的Handler来传递消息该怎么使用呢?下面给一个案例并简要说明:
public class ChildThreadHandlerActivity extends Activity { private MyThread childThread; // 子线程 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler); childThread = new MyThread(); childThread.start(); Handler childHandler = new Handler(childThread.childLooper){ // 传入和线程绑定的Looper //这样之后,childHandler和childLooper就关联起来了。 public void handleMessage(Message msg) { }; }; } private class MyThread extends Thread{ public Looper childLooper; @Override public void run() { Looper.prepare();//创建与当前线程相关的Looper childLooper = Looper.myLooper();//获取当前线程的Looper对象 Looper.loop();//调用此方法,消息才会循环处理 } }}
所以把对应线程的Looper参数传入就可以把Handler和该线程绑定,然后处理该线程的消息。
4.Handler存在的内存泄露
private Handler mhandler = new Handler(){ // 通过复写handlerMessage()从而确定更新UI的操作 @Override public void handleMessage(Message msg) { ...// 需执行的UI操作 } };
很多时候使用handler的时候我们都是这样通过new出内部类的方法来使用的。当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有Activity的引用。这是内存泄露的主要原因。找到了这个主要原因之后 ,可以总结以下避免内存泄漏的方法:
1.使用static 修饰的handler,但是一般会弱引用activity对象,因为要使用activity对象中的成员
2.单独定义handler,同样可以弱引用activity
3.使用内部类的handler,在onDestroy方法中removeCallbacksAndMessages
更多相关文章
- Android(安卓)Runnable 运行在那个线程
- android中Http访问时 connection.getResponseCode()不被执行,且报
- android handler的警告Handler Class Should be Static or Leaks
- Android(安卓)-- SurfaceFlinger 合成主线程 系列 (三)
- Android推送之APNS 网站调用提供推送技术
- Android使用NotificationManager进行消息推送
- Android消息机制2-Handler(Native层)
- Android(安卓)WebView工作中遇到的问题记录
- DownloadManager 文件下载