再来亿遍 一遍带你搞懂Android Handler机制

目录

  • Handler的做用和简单使用
  • 从源码看Handler的原理
  • 一些知识点的整理
  • 总结

Handler的做用和简单使用

相信Handler对于做为Android开发者的小伙伴们来讲并不陌生,某些阶段的面试中Handler甚至是必考题之一。那么Handler到底有什么用呢?为何要用Handler呢?java

咱们都知道在Android中分有UI线程和非UI线程,其中有一条规定就是只能在UI线程操做UI组件,这是为了防止多个线程并发的操做一个UI组件带来的问题。同时咱们也知道不能在UI线程中作耗时操做,那么这个时候就带来了问题,若是咱们要在一个耗时操做结束后再去操做某个UI组件,那要怎么作呢?好比咱们须要下载一份文件,而且在文件下载完成以后须要让TextView显示下载完成来提醒用户。面试

这个时候就轮到咱们的Handler出场了:缓存

public class MainActivity extends AppCompatActivity {

    TextView tv;

    private static final int MSG_DOWNLOAD_FINISH=10;


    //建立Handler,同时会发现as会提示不建议咱们这么写,这个稍后再说
    private Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case MSG_DOWNLOAD_FINISH:
                    tv.setText("下载成功!");
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tv=findViewById(R.id.tv);
        tv.setText("downloading ...");
        
        new Thread(new Runnable() {
            @Override
            public void run() {

                try {
                    //这里模拟一个耗时操做,能够看到这里是在非UI线程运行的
                    Thread.sleep(3000);
                    
                    Message message = handler.obtainMessage(MSG_DOWNLOAD_FINISH);
                    message.sendToTarget();
                    
                    //上面部分也能够这么写,效果是同样的
// Message message=new Message();
// message.what=MSG_DOWNLOAD_FINISH;
// handler.sendMessage(message);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
复制代码

运行上面的代码咱们能够发如今子线程sleep 3s后,成功的修改了tv的文字为 下载成功。咱们成功的知足了Android对UI线程的两条规定,咱们凭借Handler成功的在子线程作耗时操做而且在完成以后更新UI界面。数据结构

到此咱们否则发现Handler能够作线程直接的通讯使用,经过Handler发送的一个Message,UI线程就能收到子线程要传达的消息,那么Handler为何能作到线程间的通讯呢?并发

从源码看Handler的原理

咱们先来对Handler的工做流程有一个总体的了解less

假设有AB两个线程。在A线程建立一个Handler对象h,而且把h发送给B线程。此时A线程中经过Looper.loop()达到死循环不停的从A线程的MessageQueue中尝试获取一个Message,若是获取到Message就会经过message.target获取到A线程中建立的h,并调用h.dispatchMessage()方法(注意这一部分都是在A线程中进行的)。这时B线程获取到h对象和经过h.sendMessage(msg)将一个msg插入到了A线程的MessageQueue中,这样子一个流程就完成了。async

再看源码以前咱们先说下Handler的总体结构,话很少说看图。 ide

能够看到主要涉及到三个类函数

  • Handler类,Handler类内部会持有一个MessageQueue对象和一个Looper的实例,这两个实例都是和线程相关的,在调用Handler构造函数而且没传入Looper参数的状况下,默认就是当前线程的Looper和MessageQueue
  • Looper类,经过Looper.prepare()和Looper.prepareMainLooper()这两个静态方法构建实例,后者是用来初始化Main线程(也就是UI线程)的Looper的,咱们在应用开发中不要使用这个方法,会报错哦。
  • MessageQueue类。一个链表实现的消息队列,由Looper负责初始化并被当前Looper对象持有。由于Looper对象是和某个线程绑定的,因此MessageQueue也是和线程绑定的。即一个线程只能有一个Looper和MessageQueue实例,而且不一样线程的Looper,MessageQueue不一样。

值得注意的是线程不是一开始就拥有和本身绑定的Looper,MessageQueue的,在使用Handler以前须要咱们去调用Looper.prepare()方法来初始化当前线程的Looper,MessageQueue对象。咱们能够看下这个方法作了什么事情:oop

public static void prepare() {
        prepare(true);
    }

    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));
    }
复制代码

能够看到Looper.prepare方法最终会构建一个Looper对象并放入ThreadLocal中。关于ThreadLocal小伙伴们能够简单理解为不一样线程从同一个ThreadLocal中得到的对象是不一样的,是独属于本身线程的。

经过上述的代码咱们也可以发现同一个线程只能拥有一个Looper对象。 看到这里的小伙伴可能会奇怪了,在咱们上面的简单使用中咱们并无调用Looper.prepare(),为何还能使用Handler呢?

这是应为Android系统在启动当前APP建立出UI线程后就会去执行Looper.prepareMainLooper()方法,系统已经帮咱们初始化过了UI线程的Looper,因此咱们能够直接使用Handler对象了。

而说了Looper.prepare()就不得不提下Looper.loop()方法了。事实上咱们要想在子线程成功的new 出Handler而且顺利使用的话,必需要再调用下Looper.loop()方法。 loop方法是一个是创建一个死循环,不停的尝试从当前的MessageQueue中获取一个Message。

Looper.loop()

public static void loop() {

        //myLooper()就是经过ThreadLocal获取当前线程的Looper实例
        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;

        //...省略部分代码
        for (;;) {

            /** * 经过MessageQueue获取一个msg。这里小伙伴会问了不是说好了是循环的吗? * 可是这里若是queue.next()返回为空不就return掉了吗? * 这里大可放心,queue.next()内部又是一个死循环,只有当如下状况是才会返回空 * 1.调用MessageQueue的quite方法 * 2.Application某些状况下尝试去重启looper(这部分存疑) * 博主只发现这两个状况,有其余状况欢迎指出 */

            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            //....
            try {
                //msg.target就是发送该msg的Handler对象
                /** * 调用Handler的dispatchMessage分为三种状况 * 1.当msg含有Callback时候会调用msg的callback * 2.当msg不含有Callback可是Handler有设置Callback时候会调用Handler的callback。若是Handler的callback返回为true的话就不会在执行handleMessage方法 * 3.不知足以上二者时会调用Handler的handleMessage方法,也就是咱们例子中实现的方法 */
                msg.target.dispatchMessage(msg);
            } finally {
                //...
            }
            //...
            
            //这里可见Message在使用以后会被清空数据并缓存
            msg.recycleUnchecked();
        }
    }
复制代码

调用Handler的dispatchMessage的三种状况

  • 1.当msg含有Callback时候会调用msg的callback
  • 2.当msg不含有Callback可是Handler有设置Callback时候会调用Handler的callback。若是Handler的callback返回为true的话就不会在执行handleMessage方法
  • 3.不知足以上二者时会调用Handler的handleMessage方法,也就是咱们例子中实现的方法
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

    private static void handleCallback(Message message) {
        //这里的callback是一个Runnable。
        //调用handler.post(new Runnable())方法就是生成一个带callback的msg并投入到MessageQueue中
        message.callback.run();
    }
复制代码

handler.sendMessage(msg)

看完了取出Message并处理的操做,咱们看看发送Message部分的逻辑。 sendMessage(msg) ->sendMessageDelayed(msg, 0) ->sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis) ->enqueueMessage(queue, msg, uptimeMillis) ->queue.enqueueMessage(msg, uptimeMillis) 能够看到上面这一串调用链以后最终会调用MessageQueue的enqueueMessage方法。而这上面这一串调主要作了一些常规的检测操做,同时把当前的Handler赋值给msg.target。这部分很少说,咱们重点看入队操做

boolean enqueueMessage(Message msg, long when) {
        //常规性检测,注意下msg.isInUse判断,表明一个msg只能入队一次
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            //若是调用过stop,此时判断就会为true
            if (mQuitting) {
                msg.recycle();
                return false;
            }

            
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            
            //接下来这部分就是关于单链表的操做,相信没什么好说的(看不懂的小伙伴要补习下数据结构知识哦)
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue. Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
复制代码

看到这里,小伙伴们应该能了解Handler的整套工做流程了。关于Handler中的线程切换,若是有点迷糊的话能够这么想 无论Handler被传递了什么线程,不论是在什么线程发送的消息。最终对该消息的处理都是在最初建立Handler的线程上。

一些知识点的整理

为何主线程中使用Handler不须要初始化Looper

由于Android系统在启动APP的时候已经调用过Looper.prepareMainLooper();和Looper.loop()了 ActivityThread.java的main方法

public static void main(String[] args){
    ...
    Looper.prepareMainLooper(); 
    //初始化Looper
    ...
    ActivityThread thread = new ActivityThread();
    //实例化一个ActivityThread
    thread.attach(false);
    //创建Binder通道
    ... 
    Looper.loop();
    //主线程进入无限循环状态,等待接收消息
}
复制代码

为何主线程中Looper.loop()开启死循环不会形成APP无响应

这部分参考知乎上的一个答案 Android中为何主线程不会由于Looper.loop()里的死循环卡死?

Handler的内存泄漏

这个问题我在最初的例子中写到了,as不建议咱们这么写Handler,这是应为非静态内部类会持有外部内的引用。那么Handler将会持有Activity的引用,咱们知道handler是会被msg.target持有的,而msg又在MessageQueue队列中,那么当消息队列中拥有未消费的Message时,会致使Activity即便finish了也没法被GC回收,最终致使内存泄漏。为了不这个问题咱们能够将Handler写成外部内或者静态的内部类,而且传递的Activity引用能够用WeakReference弱引用来持有,同时能够在Activity的onDestory中使用Handler.removeCallbacksAndMessages(null);来清空消息队列

总结

因为Handler还有一部分涉及到native层面,而对这一层面博主并不了解,因此没有能提到这部分的东西,但愿之后能有时间补充这部分的内容。以上内容如有错误之处欢迎你们指出,你们一块儿进步。

相关文章
相关标签/搜索