美文网首页
Handler的几个常见问题

Handler的几个常见问题

作者: Chenyangqi | 来源:发表于2019-10-20 15:46 被阅读0次

    记录一下对Handler中常见的几个问题的理解

    • 1、Handler内存泄漏原因以及解决方案
    • 2、为什么不能在子线程创建Handler
    • 3、new Handler()两种写法的区别
    • 4、ThreadLocal用法和原理
    问题1:Handler内存泄漏原因以及解决方案

    这个问题是面试的时候高频考点,什么场景下会内存泄漏?造成的原因是什么?如何解决?
    通过下面这个例子来模拟一下Handler引起内存泄漏场景

    public class HandlerTestActivity extends AppCompatActivity {
    
        private Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                startActivity(new Intent(HandlerTestActivity.this, LoginActivity.class));
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_handler_test);
            test();
        }
    
        private void test() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Message msg = new Message();
                    //模拟网络请求等耗时操作,休眠3秒的时候双击返回键销毁当前activity
                    SystemClock.sleep(3000);
                    msg.what = 1;
                    mHandler.sendMessage(msg);
                }
            }).start();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            if (mHandler != null) {
                mHandler.removeCallbacksAndMessages(null);
            }
            Log.d("test", "activity 执行 onDestroy");
        }
    }
    

    上面这段代码在子线程调用了SystemClock.sleep(3000)模拟了一些网络请求等耗时操作场景,在这3秒期间按下系统返回键销毁当前activity并成功执行了onDestroy方法清理handler的message和callback,应用退出回到桌面
    按理说activity被销毁,三秒之后并不会执行handler.sendMessage,也不会再执行handleMessage里面的跳转业务。但是现实是3秒后又从桌面执行了handleMessage回调里面的跳转业务,也是就activity虽然执行了onDestory方法,但是并没有被回收

    原因分析

    分析Handler.class源码,当执行hanlder.sendMessage()等发送message方法,它最终都是通过enqueueMessage()这个方法将message发送到messageQueue中

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    

    而enqueueMessage()这个方法第一句是msg.target = this,target是Message的Handler类型的成员变量,从这一步就把handler传递到了message中,Message中对target定义如下

    public final class Message implements Parcelable {
       ...
        /*package*/ Handler target;
      ...
      }
    
    结论:非静态内部类持有了外部类引用

    实际上这一个流程下来Message持有了Handler的引用,而Handler又持有了Activity的引用,虽然在Activity的onDestroy方法中执行了handler.removeCallbacksAndMessages置空方法,通过该方法移除messageQueue中的message,,但是因为此时还并未执行handler.sendMessage(),message还没有入队,在队列中并不能找到message并把它移除

    /**
         * Remove any pending posts of messages with code 'what' and whose obj is
         * 'object' that are in the message queue.  If <var>object</var> is null,
         * all messages will be removed.
         */
        public final void removeMessages(int what, Object object) {
            mQueue.removeMessages(this, what, object);
        }
    
        /**
         * Remove any pending posts of callbacks and sent messages whose
         * <var>obj</var> is <var>token</var>.  If <var>token</var> is null,
         * all callbacks and messages will be removed.
         */
        public final void removeCallbacksAndMessages(Object token) {
            mQueue.removeCallbacksAndMessages(this, token);
        }
    
    Handler内存泄漏的解决方案

    通过上面的分析,总结出内存泄漏的原因:message持有了handler,而handler持有了activity从而造成activity内存无法释放

    • 解决方案1
      Handler使用静态内部类,对Activity采用弱引用的持有方式
    private MyHandler mHandler = new MyHandler(this);
    
        private static class MyHandler extends Handler {
            private final WeakReference<HandlerTestActivity> mActivity;
    
            public MyHandler(HandlerTestActivity mActivity) {
                this.mActivity = new WeakReference<>(mActivity);
            }
    
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                HandlerTestActivity activity = mActivity.get();
                if (activity != null) {
                    activity.startActivity(new Intent(activity, LoginActivity.class));
                }
            }
        }
    
    • 解决方案2
      个人比较推荐这种方法,代码书写简单快速,在Activity执行onDestroy方法的时候置空handler,在调用handler的地方进行非空判断,如果空就不去执行后续操作,那么message也就没有机会持有handler的引用了
    private void test() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Message msg = new Message();
                    //模拟网络请求等耗时操作,休眠3秒的时候双击返回键销毁当前activity
                    SystemClock.sleep(3000);
                    msg.what = 1;
                    if (mHandler != null) {
                        mHandler.sendMessage(msg);
                    }
                }
            }).start();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            if (mHandler != null) {
                mHandler.removeCallbacksAndMessages(null);
                mHandler = null;
            }
        }
    
    问题2:为什么不能在子线程创建Handler

    先分析一下创建Handler的源码过程

    public Handler(Callback callback, boolean async) {
            if (FIND_POTENTIAL_LEAKS) {
                final Class<? extends Handler> klass = getClass();
                if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                        (klass.getModifiers() & Modifier.STATIC) == 0) {
                    Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                        klass.getCanonicalName());
                }
            }
    
            mLooper = Looper.myLooper();
            if (mLooper == null) {
                throw new RuntimeException(
                    "Can't create handler inside thread " + Thread.currentThread()
                            + " that has not called Looper.prepare()");
            }
            mQueue = mLooper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
    

    Handelr的构造方法中Looper.myLooper()初始化了Looper对象

    /**
         * Return the Looper object associated with the current thread.  Returns
         * null if the calling thread is not associated with a Looper.
         */
        public static @Nullable Looper myLooper() {
            return sThreadLocal.get();
        }
    

    最终是在ThreadLocal.get()中获取的Looper,而ThreadLocal是把Looper以键值对的性质存储到它的内部类ThreadLocalMap中的,ThreadLocalMap是以线程表示为键Looper为值存的形式

    /**
         * Returns the value in the current thread's copy of this
         * thread-local variable.  If the variable has no value for the
         * current thread, it is first initialized to the value returned
         * by an invocation of the {@link #initialValue} method.
         *
         * @return the current thread's value of this thread-local
         */
        public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }
    
    结论:

    所以在子线程中无法直接创建Handler是因为,ThreadLocalMap<Thread,Looper>无法找到当前线程对应的Looper值,需要调用Looper.prepare,以当前线程为key在LocalThreadMap存储Map值

    问题3:new Handler()两种写法的区别

    先来看看new Handler()的两种写法

        //写法1
        private Handler mHandler1 = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                
            }
        };
    
        //写法2
        private Handler mHandler2 = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                
                return false;
            }
        });
    

    这两种写法的区别在Handler中的源码可以找到原因,当Looper从队列中获取到message转交给Handler处理时,Handler.dispatchMessage()负责处理消息的转发

     /**
         * Handle system messages here.
         */
        public void dispatchMessage(Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
    

    从dispatchMessage()方法可以看出,如果有回调成员变量,则优先通过回调返回消息,其次在考虑通过方法传递
    结论:优先考虑使用接口回调的方式(优先选择),其次再考虑通过方法(备胎)
    问题4:ThreadLocal用法和原理
    在第三个问题中得出的接了,子线程无法new Handler()是因为在LocalThreadMap中无法找到以当前Thread为Key的Looper为Value值,那马LocalThread是如何保证Looper是唯一的呢?
    应用启动会在ActivityThread.main()方法中创建一个全局静态变量Looper,并已当前线程(也就是主线程)为Key,Looper为Value存储在ThreadLocalMap中,流程如下

    public static void main(String[] args) {
            ...
            Looper.prepareMainLooper();
            ...
            Looper.loop();
            ...
        }
    
     public static void prepareMainLooper() {
            prepare(false);
            synchronized (Looper.class) {
                if (sMainLooper != null) {
                    throw new IllegalStateException("The main Looper has already been prepared.");
                }
                sMainLooper = myLooper();
            }
        }
    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));
        }
    
      public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    

    从源码中ThreadLocal. prepare()方法可知,第一次(ActivityThread.mian())调用prepare方法创建Looper在设置到ThreadLocalMap,当其他子线程创建Handler时只是获取当前线程作为key更新ThreadLocalMap的key值,并没有改变Looper的值,这也解释了为什么在一个进程中只有一个Looper和在子线程创建Handle时需要先调用Looper. prepare

    相关文章

      网友评论

          本文标题:Handler的几个常见问题

          本文链接:https://www.haomeiwen.com/subject/hdvnmctx.html