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方法本身。