Handler Message源码分析

Handler用于异步消息处理:
当发出一个消息之后,首先进入一个消息队列,发送消息的函数同步返回,而另一个部分在消息队列中逐一将消息取出,然后对消息进行处理。

1、Handler内存泄漏问题
2、在子线程创建Handler报错Looper没有prepare?
3、textview.setText()只能在主线程执行?有点问题
4、new Handler()的两种写法
5、ThreadLocal用法和原理

1、Handler引起的内存泄漏问题

如下代码,当在子线程中休眠或做了耗时操作后,再用Handler发送消息。此时如果Activity已经destroy了,但是Handler仍然会发送消息到消息队列里,产生了严重的内存泄漏。

        new Thread(new Runnable() {
            @Override
            public void run() {
                //内存泄漏问题
                Message message = new Message();
                message.obj = "胡军";
                message.what = 2;
                SystemClock.sleep(3000);
                handler1.sendMessage(message);
            }
        }).start();

如何解决:
(1)用handler1.sendMessageDelayed(message,3000);发送延时消息。
在Activity被消耗后,将消息移除handler1.removeMessages(2);

(2)在Activity销毁后,将Handler置空,这样就不会在延时结束后调用sendMessage了。

2、为什么不能在子线程创建Handler

会报错:Can't create handler inside thread that has not called Looper.prepare();

这里看看Handler的构造方法源码:
public Handler(){}
public Handler(Callback callback){}

mLooper = Looper.myLooper();
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
...
}

也就是默认Looper是从当前Thread里获取Looper。
但是在新生成的子线程里,并没有生成Looper。这样就会报错。

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }

在子线程里,需要先prepare,在当前thread写入相应Looper到ThreadLocal里。

                Looper.prepare();
                Handler handler = new Handler();

而在主线程里,已经默认生成了一个Looper.
ActivityThread.java里的main()方法中:

        Looper.prepareMainLooper();

而Looper里面有个static的变量sMainLooper用来存储主线程的Looper,任何时候都能获取到该主线程Looper。
看Looper.prepareMainLooper();都做了什么:
首先调用了prapare方法,生成了一个Looper放入ThreadLocal里,这是用来存储和取出和线程相关的变量的,之后会进行详细说明。这里将生成的主线程Looper放入ThreadLocal后,然后利用myLooper()方法,即从当前线程取出Looper,主线程里就是主线程Looper,然后赋值给sMainLooper。

        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 static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

3、textview.setText()只能在主线程执行?有点问题

如下,在onCreate()里调用,生成的子线程里setText()是没有报错,且执行成功的了。为什么呢?

        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "run: "+Thread.currentThread().getName());
                textView.setText("胡军");
            }
        }).start();

分析下setText()的源码:
setText() --> checkForRelayout() -->requestLayout() --> mParent.requestLayout();
而这个mParent.requestLayout();调用的是父类ViewParent的requestLayout()方法。

接口ViewParent的实现类ViewRootImpl,里面有requestLayout()的实现:
requestLayout() --> checkThread()

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

这里,mThread是在ViewParent初始化时设置的

        mThread = Thread.currentThread();

所以,"Only the original thread that created a view hierarchy can touch its views."错误并不是指必须在UI主线程调用setText(),而是需要在创建ViewParent的线程里调用setText()。
我们使用的ViewParent都是在主线程调用的,所以setText()就需要在主线程中调用。

当setText()足够快,在检查线程前就更新完成,则不会报错。

4、new Handler()的两种写法

    //Handler有下面两种构造方式
    private Handler handler1 = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            return false;
        }
    });

    private Handler handler2 = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    };

看源码:
在启动一个Thread后,会生成一个Looper循环。
比如主线程里,有

Looper.prepareMainLooper();
...
Looper.loop();

在loop()方法里,启动了一个无限轮训的获取Message队列的循环。

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

当取到Message后,会调用

msg.target.dispatchMessage(msg);

这里,Target就是发送这个Message的Handler,在调用Handler发送Message时,会将Message的Target设置为发送的Handler。

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

首先会回调msg自己的Callback,没有就回调初始化Handler时给的Callback,再不行才回调重写的Handler的handleMessage()方法。

其中,msg自己的Callback,是调用Handler的post方法时,设置给Message的。就完成了Handler.post(runnable)里的run回调。
这里有个知识点,调用Activity.runOnUIThread(),其实内部就是用主线程的Handler.post()方法实现的。

5、ThreadLocal用法和原理

作用是把参数存储在线程相关的map里,在不同的线程里存储,当在相应的线程里能读取出当前线程存储的参数。

下面的例子里,在ThreadLocal里存储String,则在不同的线程里存储不同的String,在不同的线程里,就能读取出这个线程存储的String。

        val threadLocal = object : ThreadLocal() {
            override fun initialValue(): String? {
                return "默认值"
            }
        }
        threadLocal.set("胡军")
        println("当前Thread:${Thread.currentThread().name},get=${threadLocal.get()}")

        Thread(Runnable {
            println("当前Thread:${Thread.currentThread().name},get=${threadLocal.get()}")
            //当使用完成后,建议remove掉。否则不用的线程越来越多,占用内存越来越多
            threadLocal.remove()
        },"子线程1").start()

        Thread(Runnable {
            threadLocal.set("胡军2")
            println("当前Thread:${Thread.currentThread().name},get=${threadLocal.get()}")

            //当使用完成后,建议remove掉。否则不用的线程越来越多,占用内存越来越多
            threadLocal.remove()
        },"子线程2").start()

ThreadLocal源码分析

    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();
    }

在Looper里,保存了一个ThreadLocal和主线程的Looper,并且都是全局唯一静态的。Looper就是用ThreadLocal来保存不同线程里的Looper的。

    static final ThreadLocal sThreadLocal = new ThreadLocal();
    private static Looper sMainLooper;  // guarded by Looper.class

Handler+Message的原理分析

Handler+Message原理图

1、首先看主线程:
应用启动时,在主线程ActivityThread.class里找到main()方法
ActivityThread.main() --> Looper.prepareMainLooper() --> Looper.prepare() --> Looper.sThreadLocal.set(new Looper(quitAllowed)); --> Looper.sMainLooper = myLooper();

这里就完成了主线程的Looper的生成。之后在主线程里都是调用
Looper.sMainLooper作为主线程Looper。

2、看Looper代码
构造方法:

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

初始化Looper的消息队列mQueue,以及赋值给该Looper运行的线程mThread.
由于主线程Looper只有一个,所以整个主线程只有一个消息队列mQueue。

3、看Handler代码
构造方法:

Handler(){}
Handler(Callback callback){}
Handler(Looper looper){}
Handler(boolean async){}
Handler(Callback callback, boolean async){}
Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async){}

有一堆构造方法,如果不指定Looper,则会从Looper.myLooper()获取。
所以当当前Thread并没有Looper时,则找不到Looper,会报错。只有在当前Thread里调用了Looper.prerare()才可以找到Looper。

发送消息(存储消息):
Handler有很多个发送消息的方法,

sendMessage(@NonNull Message msg)
sendMessageDelayed(@NonNull Message msg, long delayMillis)
sendEmptyMessage(int what)

//post方法,将Runnable赋予Message
post(@NonNull Runnable r)
postDelayed(@NonNull Runnable r, long delayMillis)

实际上,上面所有的发送方法,最后都调用了一个方法:

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

最后调用了MessageQueue的enqueueMessage()方法。

boolean enqueueMessage(Message msg, long when) {

//如果是第一条消息,赋值给MessageQueue里的变量mMessages
mMessages = msg;

//如果不是第一条,或者需要插入之前的消息前面,
//利用message的next来形成链式的排列。

}

取出消息(消费)
调用Looper.loop()方法后,就开始不断轮训消息队列。
其中,主线程的Looper在ActivityThread里main()方法中,

main(){
...
        Looper.prepareMainLooper();
...
        Looper.loop();
}

分析loop()方法

public static void loop() {
//拿到该线程里的Looper
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
//拿到Looper里的MessageQueue
        final MessageQueue queue = me.mQueue;

...
//开始不断轮训MessageQueue
        for (;;) {
//首先拿到Queue里的Message
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
...
try {
                msg.target.dispatchMessage(msg);
...}
}
}

上面分析得到,所有的消息最后在handleMessage或在Callback的run方法里执行,而执行的线程就是loop()方法被调用时处于的线程。
主程序里调用了loop()方法,所有的主线程消息都在主线程中运行了。

问题
loop()启动了一个无限死循环,如何避免导致anr呢?
里面有挺好的垃圾回收机制,开始前调用了Binder.clearCallingIdentity();
循环中调用了

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

一旦需要等待时,或还没有执行到执行的时候,会调用NDK里面的JNI方法,释放当前时间片,这样就不会引发ANR异常了。

你可能感兴趣的:(Handler Message源码分析)