Android的消息机制
Android的消息机制
Handler是Android中消息机制的上层接口,开发过程中只需要和Handler交互即可。通过Handler就可以将一个任务切换到Handler所在线程中执行。其中的一个应用场景就是在子线程中执行耗时操作例如读取文件访问网络等以后可以通过handler将更新UI(UI非线程安全,android当中不允许在子线程中更新;而耗时操作如果在主线程进行会造成ANR)的操作切换回主线程中执行。所以Android中的消息机制主要是指Handler的运行机制。
Handler创建时会采用当前线程的Looper来构建内部消息循环系统,然后可以通过Handler的一系列post方法(内部也是通过send方法完成的)将一个Runnable对象投递到Handler内部的Looper中去处理,或者通过Handler的一系列send方法发送一个消息到Looper中去处理,它会调用MessageQueue的enqueueMessage方法将这个消息放入消息队列待Looper处理。
ThreadLocal的作用
定义:ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后只能在指定的线程中获取到存储的数据,其他线程无法获取。
使用场景:(1)当某些数据是以线程为作用域并且不同线程具有不同的数据副本时就可以采用ThreadLocal。(2)复杂逻辑下的对象传递,比如监听器的传递,有的时候一个线程中的任务过于复杂,可能表现为函数调用栈比较深以及代码入口的多样性,我们又需要监听器能够贯穿整个线程的执行过程,这个时候可以采用ThreadLocal。
原理:
工作过程:不同的线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自的线程中取出一个ThreadLocalMap(不是Map,可以理解成Map),然后再从ThreadLocalMap中以当前ThreadLocal的作为key去查找对应的value值,不同线程中的ThreadLocalMap是不同的,所以可以通过ThreadLocal在不同线程中维护一套数据的副本而互不干扰。
内部实现:
-
ThreadLocal是一个泛型类public class ThreadLocal
-
public void set(T value) { Thread t = Thread.currentThread(); //取出当前线程的ThreadLocalMap //ThreadLocalMap不是Map,但是可以理解为Map ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value);//key是ThreadLocal当前变量,value是我们需要的值 else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } //Thread内定义了成员threadLocals //设置到ThreadLocal中的数据也就是写入了threadLocals这个ThreadLocalMap ThreadLocal.ThreadLocalMap threadLocals = null; //threadLocals本身就保存了当前线程所有“局部变量”,也就是一个ThreadLocal变量的集合。
-
public T get() { Thread t = Thread.currentThread(); //进行get操作的时候也就是把当前线程的ThreadLocalMap取出 ThreadLocalMap map = getMap(t); if (map != null) { //将ThreadLocal自己本身作为key去取出内部的实际数据 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
-
从set/get方法的实现可知,这些变量是维护在Thread类内部的ThreadLocalMap当中,只要线程不退出,对象的引用将一直存在,而当线程退出时,Thread类做的清理工作就包括了清理ThreadLocalMap。
但如果使用线程池,线程未必会退出,如果将一些大的对象设置到ThreadLocal中,可能会出现内存泄漏,所以如果希望及时回收对象,可以使用ThreadLocal.remove()方法将变量移除。当然将ThreadLocal赋为null也可以加速回收,原因与ThreadLocalMap的实现使用了弱引用有关,内部由一系列的Entry构成,static class Entry extends WeakReference<ThreadLocal<?>>。
Message
-
消息对象
Message包括了 int what(消息类别)、longwhen(消息触发时间)、int arg1、int arg2(参数2)、Object obj(消息内容)、Handler target(消息响应方)、Runable callback(回调方法)。
-
消息池
静态变量
sPool
的数据类型为Message,通过next成员变量,维护一个消息池;静态变量MAX_POOL_SIZE
代表消息池的可用大小;消息池的默认大小为50。Message.obtain(),从消息池取Message,都是把消息池表头的Message取走,再把表头指向next。
Message#recycle(),将Message加入到消息池的过程,都是把Message加到链表的表头。
消息队列的工作原理
- MessageQueue通过单链表数据结构来维护消息列表;
- boolean enqueueMessage(Message msg, long when) 插入消息,也就是单链表的插入;
- Message next() 取出一条消息并从消息队列中删除,next是一个无限循环的方法,如果消息队列中没有消息就会阻塞在这里,而有消息到来就会返回这条消息并从单链表中删除。
Looper的工作原理
消息循环:会不停地从MessageQueue中查看是否有新消息,如果有新消息就会立刻处理,否则会阻塞。
- public static void prepare() 为当前线程创建Looper,保证Handler可以工作,给主线程ActivityThread创建Looper还可以使用prepareMainLooper方法,getMainLooper可以在任何地方获取到主线程的Looper。
- private Looper(boolean quitAllowed) 构造方法中创建消息队列MessageQueue,并保存当前线程对象;
- public static void loop() 调用了这个方法,消息系统才会真正起作用。loop是个死循环,只有当messageQueue.next()返回null才退出循环,也就是消息队列退出时。
- 在子线程中,如果手动创建了Looper,最好是在任务完成不再需要的时候调用quit或者quitSafely方法终止消息循环,否则这个子线程会一直处于等待状态,而如果退出Looper以后,这个线程就会立即终止。quit或者quitSafely方法被调用的时候,Looper也会调用MessageQueue的quit或者quitSafely方法通知消息队列退出,next方法也会返回null。
- Looper处理一条消息,msg.target.dispatchMessage(msg),也就是handler的dispatchMessage方法,Handler发送的消息最终又交给自己的方法来处理了,只是dispatchMessage方法是在创建Handler时使用的Looper中执行的,这样就将代码逻辑切换到指定的线程中执行了。
public static void prepare() 为当前线程创建Looper,保证Handler可以工作;
private Looper(boolean quitAllowed) 构造方法中创建消息队列MessageQueue,并保存当前线程对象;
public static void loop() 调用了这个方法,消息系统才会真正起作用。loop是个死循环,只有当messageQueue.next()返回null才退出循环,也就是消息队列退出时。
Handler的工作原理
Handler的一系列post方法(内部也是通过send方法完成的)将一个Runnable对象投递到Handler内部的Looper中去处理,或者通过Handler的一系列send方法发送一个消息到Looper中去处理。
handler发送消息的典型过程如下,可以看出这就是向消息队列中插入了一条消息,然后MessageQueue的next方法会返回这条消息给Looper处理,Looper处理消息会调用msg.target.dispatchMessage(msg),最终交给Handler处理,也就是Handler的dispatchMessage方法会被调用,Handler就进入了处理消息的阶段。
public final boolean sendMessage(Message msg)
public final boolean sendMessageDelayed(Message msg, long delayMillis)
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)
public void dispatchMessage(Message msg)
public void handleMessage(Message msg)//子类重写或者是用Callback内部的handleMessage方法
另外关于构造方法,handler内部的消息队列就是赋值了Looper当中的消息队列。
public Handler()
public Handler(Callback callback, boolean async) //在这个方法内会去判断当前线程有无Looper,如果没有的话就会抛出异常。
关于Handler的内存泄露问题
-
原因:
首先Java中非静态内部类 &匿名内部类都默认持有外部类的引用 ,另外主线程中Looper的生命周期和应用的声明周期是一致的。那么也就是Handler 的生命周期和 Activity 是不一致的,所以经常会带来内存泄漏的问题, 比如这样的情况:在 Activity 中定义了一个继承自Handler 的非静态内部类,并通过它发送了一条消息,该消息会在 10 分钟之后返回当前时间,接着将 Activity 退出,但是此时 Activity 并不会被 GC 回收掉的,因为我们的消息任务还在 MessageQueue 中排队,Message当中定义了Handler target,也就是含有Handler的引用,那么 Handler 是无法释放的,而 Handler本身又持有外部类 Activity 的引用,那么也就导致了 Activity 不能被回收释放了,造成内存泄漏。
-
解决:静态内部类+弱引用,将非静态内部类设置成 static 类型的,同时为了高效率的回收,我们可以将所引用的外部类的实例在内部类中设置成 WeakReference 类型,也就是弱引用类型。此外为了防止 Looper对象的内存泄漏,我们可以在Activity销毁的时候调用removeCallbackAndMessages 方法,移出 MessageQueue 里面的所有消息。
例如:
private Handler handler = new Handler() { public void handleMessage(Message msg) {//在主线程执行 if (msg.what == 1) { //4. 在handleMessage()中处理消息 String result = (String) msg.obj; et_handler1_result.setText(result); pb_handler1_loading.setVisibility(View.INVISIBLE); } } }; //可以修改为: //通过使用Static和弱引用WeakReference结合解决, Handler handler=new MyHandler(this); private static class MyHandler extends Handler { private final WeakReference<HandlerTestActivity> activity; private MyHandler(HandlerTestActivity activity) { this.activity = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); HandlerTestActivity mActivity = activity.get(); if (mActivity != null) { mActivity.doHandleMessage(msg); } } } private void doHandleMessage(Message msg) { if (msg.what == 1) { //4. 在handleMessage()中处理消息 String result = (String) msg.obj; et_handler1_result.setText(result); pb_handler1_loading.setVisibility(View.INVISIBLE); } }
主线程的消息循环
Android的主线程就是ActivityThread,主线程的入口方法为main,在main方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()来开启主线程的消息循环。主线程消息循环开始以后,ActivityThread还需要一个Handler来和消息队列进行交互,这个Handler就是ActivityThread.H,它内部定义了一组消息类型,主要包含了四大组件的启动和停止等过程。ActivityThread通过ApplicationThread和AMS进行进程间通信,AMS以进程间通信的方式完成ActivityThread的请求后会回调ApplicationThread的Binder方法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread中的逻辑切换到ActivityThread中去执行,即切换到主线程中执行,这个过程就是主线程消息循环模型。