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

栏目: IOS · Android · 发布时间: 6年前

内容简介:相信Handler对于作为Android开发者的小伙伴们来说并不陌生,某些阶段的面试中Handler甚至是必考题之一。那么Handler到底有什么用呢?为什么要用Handler呢?我们都知道在Android中分有UI线程和非UI线程,其中有一条规定就是只能在UI线程操作UI组件,这是为了防止多个线程并发的操作一个UI组件带来的问题。同时我们也知道不能在UI线程中做耗时操作,那么这个时候就带来了问题,如果我们要在一个耗时操作结束后再去操作某个UI组件,那要怎么做呢?比如我们需要下载一份文件,并且在文件下载完成
  • Handler的作用和简单使用
  • 从源码看Handler的原理
  • 一些知识点的整理
  • 总结

Handler的作用和简单使用

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

我们都知道在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的工作流程有一个整体的了解

假设有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中,这样子一个流程就完成了。

再看源码之前我们先说下Handler的整体结构,话不多说看图。

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

可以看到主要涉及到三个类

  • 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对象。我们可以看下这个方法做了什么事情:

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层面,而对这一层面博主并不了解,所以没有能提到这部分的东西,希望以后能有时间补充这部分的内容。以上内容若有错误之处欢迎大家指出,大家一起进步。


以上所述就是小编给大家介绍的《再来亿遍 一遍带你搞懂Android Handler机制》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Pro HTML5 and CSS3 Design Patterns

Pro HTML5 and CSS3 Design Patterns

Michael Bowers / Apress / 2011-11-15 / GBP 35.50

Pro HTML5 and CSS3 Design Patterns is a reference book and a cookbook on how to style web pages using CSS3 and HTML5. It contains 350 ready--to--use patterns (CSS3 and HTML5 code snippets) that you ca......一起来看看 《Pro HTML5 and CSS3 Design Patterns》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

随机密码生成器
随机密码生成器

多种字符组合密码

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具