简介
Handler的使用过程很简单,通过Handler可以轻松的将一个任务切换到Handler所在的线程中去执行;很多人认为Handler的作用是更新UI,这确实没错,但是更新UI只是Handler的一个特殊使用场景;如子线程做耗时任务,当耗时任务结束之后,可能需要在UI上作出改变,由于Android限制子线程不能更新UI,只能在主线程更新UI,所以通过Handler就可以将更新UI 的操作切换到主线程中执行。因此,Handler并不是专门用于更新UI的,只是开发者常将他用来更新UI。
Handler的消息机制主要指Handler的运行机制,需要MessageQueue和Looper的支撑;MessageQueue译为消息队列,但是他的内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表;Looper译为循环,可以理解为消息循环,由于MessageQueue只是消息存储单元,并不能处理消息,而Looper以无限循环的形式查找是否有新消息,如果有的话就处理,否则一直等待着;Looper中有一个特殊的概念ThreadLocal,ThreadLocal并不是线程,他的作用是可以在每个线程中存储数据,ThreadLocal可以在不同线程中互不干扰地存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper,需要注意的是,线程默认没有Looper,如果使用Handler 必须为线程创建Looper。经常提到的主线程即UI线程,就是ActivityThread,创建时会初始化Looper(Looper.prepareMainLooper())。
Handler的主要作用是将一个任务切换到指定的线程中去执行,Android为什么提供这种功能或者Android为什么需要提供在某个具体的线程中执行这种功能呢?
因为Android规定访问UI只能在主线程中进行,如果在子线程中就会抛出异常。ViewRootImpl中checkThread方法对Ui操作作了验证;由于这一点的限制,导致必须在主线程中访问UI,但是Android建议不要在主线程中做耗时操作,否则会导致ANR,因此系统提供Handler,主要是为了解决子线程无法访问UI的矛盾;
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views.");
}
}
延伸:
1)系统为什么不允许子线程访问UI呢?
因为Android的UI控件是线程不安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态;
2)为什么系统不对UI的访问加上锁机制呢
缺点有两个:1)加上锁机制会让UI访问的逻辑变得复杂;2)锁机制会降低UI访问效率,因为锁机制会阻塞某些线程的执行。
鉴于上面两个缺点,最简单且高效的方法就是采用单线程模型来处理UI操作;
简单概述
Handler 通过post()或者send()方法发送消息,最终都会调用sendMessageAtTime(Message msg,long uptimeMillis),调用enqueueMessage(MessageQueue queue,Message msg,long uptimeMillis)加入消息队列,MessageQueue名为消息队列,实际上是一个单链表,用来存储消息列表;Looper.loop()是个无限循环,不断的通过queue.next()获取消息,只有当MessageQueue中没有消息时,处于阻塞状态,循环中断;否则通过msg.target.dispatchMessage(msg),Handler处理消息;
ThreadLocal的工作原理
为了更好的理解Looper原理,先了解下ThreadLocal;
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储后,只能在指定线程中可以获取到存储的数据,对于其他线程来说,无法获取到数据。应用场景:一般来说,当某些数据是以线程为作用域并且不同的线程具有不同的数据副本的时候,可以考虑用ThreadLocal;比如对于Handler来说,需要获取当前handler的Looper,很明显Looper的作用域就是线程并且不同线程具有不同的ThreadLocal;
threadLocal = new ThreadLocal<>();
threadLocal.set("main");
startThread();
start();
Log.e(TAG, "MainThread threadLocal::" + threadLocal.get());
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
try {
threadLocal.set("MyThread");
Log.e(TAG, "MyThread threadLocal::" + threadLocal.get());
sleep(3000);
} catch (InterruptedException e) {
e.getStackTrace();
}
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
try {
threadLocal.set("MyRunnable");
Log.e(TAG, "MyRunnable threadLocal::" + threadLocal.get());
Thread.sleep(3000);
} catch (Exception e) {
e.getStackTrace();
}
}
}
不同的线程访问同一个ThreadLocal对象,通过ThreadLocal获取的值却不一样,这就是ThreadLocal的奇妙之处;因为不同线程访问同一个ThreadLocal的get()方法,ThreadLocal内部会去各自的线程中取出一个数组,然后在从数组中根据当前ThreadLocal的索引值查找对应的value值;
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table; //Values内部有一个数组
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
//根据当前线程获取对应的Values
Values values(Thread current) {
return current.localValues;
}
Values是ThreadLocal类的静态类,里面有一个数组table;
从ThreadLocal的put和get可以看出来,他们操作的都是当前线程的localValues对象的table数组;
Looper的工作原理
具体的说就是Looper会不停的从messageQueue中查看是否有新消息,有新消息就立即处理,否则就一直阻塞在那;
创建一个Looper
子线程
newThread("thread") {
@Override
public voidrun() {
super.run();
Looper.prepare();
Handler handler =newHandler();
Looper.loop();
}
}.start()
主线程
Looper.prepareMainLooper()
final H mH=new H();//在ActivityThread初始化
Looper.loop();
Looper:quit 直接退出
quitSafely 只是一个标记,把消息队列消息队列中已有消息处理完之后才安全退出。
Handler的用法注意事项
1)防止内存泄露,静态内部类+弱引用;匿名内部类隐性持有外部类的引用;
static class MyHandler extends Handler {
private final WeakReference<BasicThreadActivity> activity;
public MyHandler(BasicThreadActivity act) {
activity = new WeakReference<BasicThreadActivity>(act);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
//todo
break;
default:
break;
}
}
}
2)在ondestory()中清空消息和回调
@Override
protected void onDestroy() {
super.onDestroy();
myHandler.removeCallbacksAndMessages(null);
}