Android面试之Handler相关学习

###Android面试之Handler相关学习

1.Android消息机制之Looper.java源代码学习

1). 准备阶段,在子线程中调用Looper.prepare()方法或者在主线程调用Looper.prepareMainLooper()方法来创建当前的Looper对象

2).Looper对象内部会创建消息队列,代码如下

    //Looper.java源代码
    // sThreadLocal.get() will return null unless you've called prepare().
        //sThreadLocal是当前子线程与绑定的Looper
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class

    final MessageQueue mQueue;

我们在调用Looper.prepare()的时候会调用如下代码

    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实例,Looper里面包含有mQueue
    }

主线程MainLooper产生的代码,由Android环境调用产生,不需要我们手工调用

    /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

    /**
     * Returns the application's main looper, which lives in the main thread of the application.
     */
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

Looper.loop()代码都做了什么,我们来仔细阅读一下,我会加入响应的一些注释.

 /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     * 返回当前线程的消息队列,确定在结束Loop的时候调用quit(),后面我们再来分析quit()做了什么
     */
    public static void loop() {
        final Looper me = myLooper();//获取当前线程的looper
        if (me == null) {  //looper为空就抛出异常
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //获取到looper对应的消息队列
        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;
            }

            //为了方便查看,我删除了一些不相关代码
                    ......

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                msg.target.dispatchMessage(msg);//分配消息,其实这个才是最重要的. msg.target是一个handler,handler的分配消息被调用,而且因为这个是在一个for的死循环里面,所以这个就是不停的再获取队列消息并分配出去
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
              //为了方便查看,我删除了一些不相关代码
                            ......
            }
            //为了方便查看,我删除了一些不相关代码
                        ......

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            //为了方便查看,我删除了一些不相关代码
                        ......
            msg.recycleUnchecked();
        }
    }

3).使用线程对应的Handler发送消息

我们可以调用handler的sendMessage()发送到Message到MessageQueue并唤醒阻塞的MessageQueue

2.一个线程能否创建多个Handler?Handler与Looper的对应关系?

我们可以从1中的代码发现,线程与Looper之间的关系,在prepare()方法内,代码的关联是针对线程和Looper的,Handler发送消息的时候是使用enqueueMessage方法,将消息传递到Looper的队列中的,消息到达队列以后,会在loop方法中执行获取消息 Message msg = queue.next(); 方法,并使用 msg.target.dispatchMessage(msg); 分配消息出去,其中,target是handler.

    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实例,Looper里面包含有mQueue
    }

总结:

一个线程可以创建多个Handler,但是只会有一个Looper并对应一个内含的MessageQueue.Handler与Looper之间没有直接对应关系

Handler通过sendMessage发送消息时,就会将handler对象存储到message中,然后Looper在loop中通过MessageQueue的next方法取出消息后,会通过之前消息封装的handler将消息发送到指定的handler,即通过调用msg.target.dispatchMessage方法

3.Handler引起内存泄露的一些情况以及应对策略

1).在handler的handleMessage内进行了对activity的引用,同时又使用了handler发送延时消息的情况.会在当前引用的Activity销毁的时候引起内存泄露问题,具体情况如下

/**
* 下面的情况会导致handler对Activity的一个引用
*/
private Handler handler = new Handler() {
  @Override
  public void handleMessage(Message msg) {
    super.handleMessage(msg);
    Toast.makeText(DemoActivity.this, "内存泄露测试", Toast.LENGTH_LONG).show();
  }
};

//当我们用Handler.sendMessageDelay等方法延迟发送消息的时候,Activity关闭了,这就会导致DemoActivity.this的引用无法被释放.

正确做法如下

    //正确情况,先要保证销毁Activity的时候,去除所有的callback和mesage
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (handler!=null){
            handler.removeCallbacksAndMessages(null);
        }
    }


        //在handler内是使用引用的时候,也要注意使用弱引用而不是直接使用对应的Activity.这样保证在Activity销毁的时候可以正确被释放,如果是使用runnable,而且是非静态内部类,也会造成类似的内存泄露,所以解决方案类似.
    private static class MyHandler extends Handler{
        private WeakReference<MainActivity>reference;
        public MyHandler(MainActivity activity){
            reference = new WeakReference<>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MainActivity activity = reference.get();
            if (activity!=null){
                Toast.makeText(activity, "测试", Toast.LENGTH_SHORT).show();
                activity.tv.setText("测试");
            }

        }
    }

文章作者: 孙老师
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 孙老师 !
 上一篇
ARM汇编学习(一) ARM汇编学习(一)
title: ARM汇编学习(一)date: 2020-03-01 17:22:07tags: ARM汇编 寄存器 指令 ARM汇编学习(一)ARM处理器基础之寻址方式1.寄存器寻址寄存器寻址,简单来说就是把ARM寄存器内的值直接赋给
2020-02-28 孙老师
下一篇 
Java泛型相关学习 Java泛型相关学习
Java泛型相关学习1.什么是泛型泛型,即”参数化类型”,在不创建新的类型的情况下,通过泛型指定不同类型来控制形参具体参数类型.这种参数类型可以用在类,接口,方法中,分别被称作泛型类,泛型接口,泛型方法. 泛型只在编译阶段有效,参数的类型是
2020-02-12 孙老师
  目录