Handler的用途
Handler主要用来做异步消息的处理,主要解决在子线程中无法访问UI的问题。
Handler的初始化
通常有两种使用方法,第一种是直接初始化一个handler,重写其handleMessage方法,第二种是传一个Handler.Callback对象,同样重写Handler.Callback类中的handleMessage方法。
private Handler handler1 = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};
private Handler handler2 = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
return false;
}
});
在Handler的方法中:
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
发送消息
handler.sendMessage(Message msg)handler.post(Runnable r)最终会将runnable对象封装到Message中。
子线程中创建Handler
网上很多地方说子线程不能创建Handler, 其实严格来讲,这种说法是不准确的,如果直接在子线程中调用new Handler() 会报错:
2021-05-31 16:15:52.447 28913-29688/com.fred.app E/AndroidRuntime: FATAL EXCEPTION: Thread-3
Process: com.fred.app, PID: 28913
java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-3,5,main] that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:207)
at android.os.Handler.<init>(Handler.java:119)
at com.fred.app.MainActivity.lambda$sendMsg$3$MainActivity(MainActivity.java:95)
at com.fred.app.-$$Lambda$MainActivity$wS6JjKtF31xh-2WedxDQY0yoNJA.run(Unknown Source:2)
at java.lang.Thread.run(Thread.java:919)
错误信息也很明了,子线程中不能够创建Handler是因为没有调用Looper.prepare()方法。该异常信息是在Handler的构造方法中抛出来的。所以,如果我们在子线程中先调用一次Looper.prepare()方法,再来new Handler()是不会有问题的。
Handler、Looper、MessageQueue
主线程与Looper的绑定
sequenceDiagram
ActivityThread->>Looper: Looper.prepareLooper()
Looper-->>Looper: prepare()
Looper-->> ThreadLocal: set(new Looper(quitAllowed);
ActivityThread的main方法中会调用Looper.prepareMainLooper();, 该方法调用Looper中的prepare()方法,prepare中会调用ThreadLocal的set(new Looper(quitAllowed)方法,将当前的线程与将创建的Looper绑定起来, new Looper(quitAllowed)的时候会初始化一个消息队列,于是主线程中对应的Looper和MessageQueue就都有了。
Looper和MessageQueue的关系
Looper中会有一个MessageQueue的实例,
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Handler和Looper
我们来看Handler的构造方法:
public Handler(@Nullable Callback callback, boolean async) {
...
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;
}
在Looper中
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
从Handler的构造方法中可以看出:
设置Handler中的Looper
在Handler初始化时,会调用Looper.myLooper()方法去拿一个Looper对象, 最终是会从这个ThreadLocal中去拿,从前面的分析来看,如果是在主线程中调用这个方法时,Looper肯定是已经初始化好了,所以返回值必然不为空。而如果是在子线程,没有提前创建一个Looper对象放到ThreadLocal中,此时调用Looper.myLooper()时必须为null,也就会抛出异常,解决的办法便是在子线程中先调用一次Looper.prepareLooper()。
在Handler初始化时不能直接new一个Looper, 是因为如果new 一个Looper,就不能保证Looper的唯一性。在Looper的prepare方法中,取一个Looper是从ThreadLocal中拿,得到的Looper与当前线程绑定。
设置Handler中的MessageQueue
mQueue = mLooper.mQueue;
消息的发送
调用Handler里面的sendMessage或类似的方法,最终会将消息放到消息队列中
消息的消费
还是以主线程为例,在主线程的main方法中调用了Looper.prepareMainLooper();方法后,会再调用Looper.loop();方法,该方法中有一个死循环,不断地从消息队列中取消息,取到消息后便执行msg.target.dispatchMessage(msg);,我们知道msg.target对应的便是一个Handler对象, 所以该方法最终调用的会是一个Handler的dispatchMessage方法。而dispatchMessage 方法最终会调用Handler的handleMessage方法,完成消息的分发。
Handler和Message
Handler中sendMessage时,会调用enqueueMessage方法,该方法会将Message和Handler进行绑定。
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
//msg 的 target 属性指向的便是当前handler
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
Handler的内存泄露问题
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
@SuppressLint("HandlerLeak")
private Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
startActivity(new Intent(MainActivity.this, OrderActivity.class));
System.out.println("[App Log]-------------------------------->handleMessage" );
}
};
private void initView() {
findViewById(R.id.tv_id).setOnClickListener(v -> sendMsg());
}
private void sendMsg() {
new Thread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message msg = new Message();
msg.what = 1;
handler.sendMessage(msg);
}).start();
}
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeMessages(1);
}
}
如上代码,点击按钮后,触发sendMsg方法, 该方法会先sleep(5000), 然后再发送一个message, handler在接收到message后会跳转到另一个activity。我们在onDestory中移除掉了发送的那个message。如果我们在点了按钮后,按系统的返回键,此时该activity的onDestory会执行,然后handler.removeMessage(1)这个方法会执行,此时因上上面的sendMsg 里面的handler消息还没有发出去,所以removeMessage方法实际上并没有删除消息。最终handleMessage中的方法还是会执行,造成内存泄露。
解决的办法很简单,只要我们在onDestory里面将handler置为null, 然后在发消息的时候做一个非空判断就可以了。
或者也可以用这种方式,如果我们在sendMsg中这样写
private void sendMsg() {
new Thread(() -> {
Message msg = new Message();
msg.what = 1;
handler.sendMessageDelayed(msg, 5000);
}).start();
}
此时msg会压入到handler对应的messgeQueue中,但是在5秒后发出去,但因为我们的onDestory里面又把这个消息从消息队列中给移出去了,所以用这种方式也可以避免。
三种消息类型
同步消息
我们平时的使用,发出的消息都是同步消息。
异步消息
通过msg.setAsynchronous(true)设置的消息为异步消息,该方法是在api level 22(android 5.0之后)才引入。
屏障消息
也称为同步屏障消息,其特点是没有对应的target, 也就是没有对应的handler,需要利用反射才能去调用,属于系统的隐藏api。android对这方面的限制越来越严格,在高版本上不允许反射调用。
插入屏障消息
MessageQueue queue = mHandler.getLooper().getQueue();
Method method = MessageQueue.class.getDeclaredMethod("postSyncBarrier");
method.setAccessible(true);
token = (int)method.invoke(queue);
移除屏障消息
MessageQueue queue = mHandler.getLooper().getQueue();
Method method = MessageQueue.class.getDeclaredMethod("removeSyncBarrier", int.class);
method.setAccessible(true);
method.invoke(queue, token);
当调用postSyncBarrier发出一个屏障消息时,消息队列中的同步消息会被挂起,异步消息正常执行,在调用了removeSyncBarrier后,同步消息会被继续执行。需要注意一点是,在我们插入了屏障消息后,一定要记得移除,否则页面会处于一种挂起状态。比如,页面上的点击事件会无法监听。
HandlerThread
继承自Thread, 是一种可以使用Handler的Thread。其内部会有一个Looper,其run方法开启Looper的消息循环. 它的使用:
Thread t = new Thread(new Runnable() {
@Override
public void run() {
Log.i(TAG, "thread start");
}
});
HandlerThread handlerThread = new HandlerThread("Handler Thread");
Handler handler = new Handler(handlerThread.getLooper());
handler.post(t);
IntentService
IntentService继承自Service并且是一个抽象类。IntentService可以用于执行后台耗时的作务,当作务执行后会自动停止,由于它是服务的原因,因此它的优先级比单纯的线程要高很多,也就意味着不容易被系统杀死。IntentService封装了Handler和HandlerThread
- IntentService的使用场景: 官网的说法是,大多数的Service不需要同时处理多个请求(并法),在这种情况下,我们可以采用IntentService。
- 工作机制:
- 创建一个默认的工作线程,将所有的
intents传递到onStartCommand - 创建一个队列,每次将一个
intent传递到onHandleIntent中,所以我们就不用关心多线程的问题 - 如果所有的request都执行了,就会执行了
stopSelf方法
- 创建一个默认的工作线程,将所有的
- IntentService封装了
HandlerThread和Handler。在它的onCreate方法中会创建一个HandlerThread对象,然后使用该对象的Looper构造一个Handler, 由这个Handler来处理消息,由于这个Handler是由HandlerThread的Looper构造的,而HandlerThread本质上来讲是一个子线程,因此IntentService是可以执行后台作务. - 使用方式:继承自IntentService, 并实现其
onHandleIntent方法
一个Demo
button2.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
startService(new Intent(LearnThreadActivity.this, MyIntentService.class).putExtra("action", "ACTION_1"));
startService(new Intent(LearnThreadActivity.this, MyIntentService.class).putExtra("action", "ACTION_2"));
startService(new Intent(LearnThreadActivity.this, MyIntentService.class).putExtra("action", "ACTION_3"));
startService(new Intent(LearnThreadActivity.this, MyIntentService.class).putExtra("action", "ACTION_4"));
}
}
});
public class MyIntentService extends IntentService {
private final String TAG = "MyIntentService";
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
String action = intent.getStringExtra("action");
Log.i(TAG, "task:" + action + " Thread:" + Thread.currentThread());
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i(TAG, "task:" + action + " handled");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "service destory");
}
}
执行结果
10-22 09:51:10.540 15243-15470/com.fred I/MyIntentService: task:ACTION_1 Thread:Thread[IntentService[MyIntentService],5,main]
10-22 09:51:11.542 15243-15470/com.fred I/MyIntentService: task:ACTION_1 handled
10-22 09:51:11.544 15243-15470/com.fred I/MyIntentService: task:ACTION_2 Thread:Thread[IntentService[MyIntentService],5,main]
10-22 09:51:12.544 15243-15470/com.fred I/MyIntentService: task:ACTION_2 handled
10-22 09:51:12.545 15243-15470/com.fred I/MyIntentService: task:ACTION_3 Thread:Thread[IntentService[MyIntentService],5,main]
10-22 09:51:13.545 15243-15470/com.fred I/MyIntentService: task:ACTION_3 handled
10-22 09:51:13.546 15243-15470/com.fred I/MyIntentService: task:ACTION_4 Thread:Thread[IntentService[MyIntentService],5,main]
10-22 09:51:14.546 15243-15470/com.fred I/MyIntentService: task:ACTION_4 handled
10-22 09:51:14.546 15243-15243/com.fred I/MyIntentService: service destory
由此可以看出IntentService中的任务是依次排队执行。
常见的问题
子线程不能操作UI ?
因为ViewRootImpl里面有checkThread的操作, 如果相应的UI操作中的在checkThread之前就完成,是可以操作UI的,比如在activity的onCreate方法里面去操作UI。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
为什么looper.loop()不会造成主线程卡死?
- 主线程中调用了
looper.loop()方法,该方法中有一个死循环,这样才能保证主线程会一直执行下去,不会被退出。 - 在没有消息的时候,会阻塞在loop的
queue.next()中的nativePollOnce()方法里, 线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生。所以死循环也不会特别消耗CPU资源。 - 真正会卡死的操作是在某个消息处理的时候操作时间过长,导致掉帧、ANR,而不是loop方法本身。