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

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

内容简介:相信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机制》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

社会化营销

社会化营销

陈亮途 / 万卷出版公司 / 2011-10-1 / 45.00元

这是一本讲述社会化媒体营销的书。今天,社会化媒体营销和移动互联网的势头已经是锐不可当的了,而这两者正正是最需要创意才能跟顾客,跟大众建立关系,创造利润的。假如国内的企业还是以不规范的手段来做营销行为,那么我们的营销水平一定会更加低落。本书作者一直以提升国内营销素质和营销人员的水平作为使命,经常穿梭于世界各地,本书正是作者工作经验的结晶,在书中,作者列举了大量国内外的成功(失败)案例,以帮助读者理解......一起来看看 《社会化营销》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换