0%

Handler原理与HandlerThread

本文主要介绍Handle的原理、如何处理Handler可能导致的内存泄漏问题,需要深入理解Handler的机制,当我们在创建Handler的时候,要明白现在用的是哪个Looper?其他线程如何使用Looper进行通信,释放Looper的区别,以及HandlerThread的原理与用法等等内容。

Handler 使用方式

假若子线程允许访问 UI,则在多线程并发访问情况下,会使得 UI 控件处于不可预期的状态。传统解决办法:加锁,但会使得UI访问逻辑变的复杂,其次降低 UI 访问的效率。于是在Android中采用单线程模型处理 UI 操作,通过 Handler 切换到 UI 线程,解决子线程中无法访问 UI 的问题。

1
2
3
4
5
6
7
8
9
10
11
12
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = findViewById(R.id.main_tv);
new Thread(()-> {
textView.setText("");
}).start();
}
}

上面的代码会抛出CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

方式一 post(Runnable)

创建一个 Handler,通过 handler.post/postDelay,投递创建的 Runnable,在 run 方法中进行更新 UI 操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MainActivity extends AppCompatActivity {

Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = findViewById(R.id.main_tv);
new Thread(()-> {
handler.post(()-> {
textView.setText("");
});
}).start();
}
}

方式二 sendMessage(Message)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class MainActivity extends AppCompatActivity {

private static final int UPDATE_CONTENT_CODE = 101;

Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case UPDATE_CONTENT_CODE:
textView.setText("");
break;
default:
break;
}
}
};

private TextView textView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.main_tv);
new Thread(()-> {
Message msg = Message.obtain();
msg.what = UPDATE_CONTENT_CODE;
// handler.sendEmptyMessage(UPDATE_CONTENT_CODE);
handler.sendMessage(msg);
}).start();
}
}

Handler 存在的问题

当我们直接写出如下代码时:

1
2
3
4
5
6
Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};

AndroidStudio会提示我们:This Handler class should be static or leaks might occur (anonymous android.os.Handler)即可能会发生内存泄漏问题!

此时就相当于这个Handler是一个内部类,非static内部类或者匿名内部类都会持有外部类的引用(textView)。如果此时Activty退出了, handler持有他的引用,则这个Activity 并不会被销毁,其实还是在内存中,所以就造成了内存泄漏 (Memory Leak) 的问题。

可以使用静态的内部类 + 虚引用可以解决这个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class MainActivity extends AppCompatActivity {
private static final int UPDATE_CONTENT_CODE = 101;

private TextView textView;

private final Handler handler = new MyHandler(this);
// 设置为静态内部类
static class MyHandler extends Handler {

private final WeakReference<MainActivity> mActivityWR;
// 在构造方法中传入需持有的Activity实例
public MyHandler(MainActivity activity) {
// 使用WeakReference弱引用持有Activity实例
mActivityWR = new WeakReference<>(activity);
}

@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
MainActivity activity = mActivityWR.get();
if(activity != null) {
switch (msg.what) {
case UPDATE_CONTENT_CODE:
activity.textView.setText("");
break;
default:
break;
}
}
}
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.main_tv);
new Thread(()-> {
Message msg = Message.obtain();
msg.what = UPDATE_CONTENT_CODE;
handler.sendMessage(msg);
}).start();
}

@Override
protected void onDestroy() {
super.onDestroy();
// 清除子线程提交的任务
handler.removeCallbacks(postRunnable);
// 清除对应的消息
handler.removeMessages(UPDATE_CONTENT_CODE);
}
}

另外还有一种解决方式:

1
2
3
4
5
6
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
// 外部类Activity生命周期结束时,同时清空消息队列 & 结束Handler生命周期
}

这种方式会导致外部类Activity生命周期结束时消息队列中的所有消息被清空,所以更推荐使用静态内部类 + 弱引用的方式。

为什么这两种方式可以解决呢?先来看看Handler 通信机制吧:

Handler 通信机制

1、创建Handler,并采用当前线程的Looper创建消息循环系统;

2、Handler通过sendMessage(Message)或Post(Runnable)发送消息,调用enqueueMessage把消息插入到消息链表中;

3、Looper循环检测消息队列中的消息,若有消息则取出该消息,并调用该消息持有的handler的dispatchMessage方法,回调到创建Handler线程中重写的handleMessage里执行。

1、Handler 发送消息

1
handler.sendMessage(msg);

调用链如下:sendMessage -> sendMessageDelayed -> sendMessageAtTime -> enqueueMessage -> queue.enqueueMessage。其实就是Handler的MessageQueue插入了一个新的的Message对象。

对于postRunnable而言,通过post投递该runnable,调用getPostMessage,通过该runnable构造一个message,再通过 sendMessageDelayed投递,接下来和sendMessage的流程一样了。

1
2
3
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}

2、消息入队列

在enqueueMessage中,通过MessageQueue入队列,并为该message的target赋值为当前的handler对象,记住msg.target很重要,之后Looper取出该消息时,还需要由msg.target.dispatchMessage回调到该handler中处理消息:

1
2
3
4
5
6
7
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

在MessageQueue中,由Message的消息链表进行入队列。

3、Looper 处理消息

再说处理消息之前,先看Looper是如何构建与获取的:

1、构造Looper时,构建消息循环队列,并获取当前线程

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

但该函数是私有的,外界不能直接构造一个Looper,而是通过Looper.prepare来构造的:

1
2
3
4
5
6
7
8
9
10
public static void prepare() {
prepare(true);
}

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

这里创建Looper,并把Looper对象保存在sThreadLocal中,那sThreadLocal是什么呢?

1
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

它是一个保存Looper的TheadLocal实例,而ThreadLocal是线程私有的数据存储类,可以来保存线程的Looper对象,这样Handler就可以通过ThreadLocal来保存于获取Looper对象了。

那么Looper在loop中如何处理消息呢?

在loop中,一个循环,通过next取出MessageQueue中的消息,若取出的消息为null,则结束循环,返回。设置消息为空,可以通过MessageQueue的quit和quitSafely方法通知消息队列退出。若取出的消息不为空,则通过msg.target.dispatchMessage回调到handler中去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;

// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();

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

// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}

msg.target.dispatchMessage(msg);

if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}

// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
Long.toHexString(ident) + " to 0x"
Long.toHexString(newIdent) + " while dispatching to "
msg.target.getClass().getName() + " "
msg.callback + " what=" + msg.what);
}

msg.recycleUnchecked();
}
}

4、handler处理消息

Looper把消息回调到handler的dispatchMessage中进行消息处理:

若该消息有callback,即通过Post(Runnable)的方式投递消息,因为在投递runnable时,把runnable对象赋值给了message的callback。

若handler的mCallback不为空,则交由通过callback创建handler方式去处理。

否则,由最常见创建handler对象的方式,在重写handlerMessage中处理。

1
2
3
4
5
6
7
8
9
10
11
12
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

Handler 进行子线程通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class OtherActivity extends AppCompatActivity {
private static final String TAG = "OtherActivity";
Handler handler1;
Handler handler2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_other);

new ThreadA().start();
new ThreadB().start();
}

class ThreadA extends Thread {
@Override
public void run() {
super.run();
Looper.prepare();
handler1 = new Handler(Looper.myLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
Log.i(TAG, "handleMessage: ThreadName = " + Thread.currentThread().getName()
+ " msg.what = " + msg.what);
}
};
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(handler2 != null) handler2.sendEmptyMessage(2) ;
Looper.loop();
}
}

class ThreadB extends Thread {
@Override
public void run() {
super.run();
Looper.prepare();
handler2 = new Handler(Looper.myLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
Log.i(TAG, "handleMessage: ThreadName = " + Thread.currentThread().getName()
+ " msg.what = " + msg.what);
}
};
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(handler1 != null) handler1.sendEmptyMessage(5) ;
Looper.loop();
}
}
}

1、调用Looper类的 prepare() 方法可以为当前线程创建一个消息循环,调用loop() 方法使之处理信息,直到循环结束。

2、Handler有几个构造重载,如果构造时不提供Looper类对象参数,默认会获取当前线程的Looper对象,即将当前线程的消息循环作为Handler关联的消息循环。

3、消息处理机制中,消息存放在一个消息队列中,而线程围绕这个队列进入一个无限循环,直到程序退出。

如果队列中有消息,线程就会把消息取出来,并分发给相应的Handler进行处理;

如果队列中没有消息,线程就会进入空闲等待状态,等待下一个消息的到来;

HandlerThread 本质

在上面利用Handler完成两个线程通信的代码中,需要调用Looper.prepare() 为一个线程开启一个消息循环,默认情况下Android中新诞生的线程是没有开启消息循环的。(主线程除外,主线程系统会自动为其创建Looper对象,开启消息循环。) Looper对象通过MessageQueue来存放消息和事件。一个线程只能有一个Looper,对应一个MessageQueue。 然后通过Looper.loop() 让Looper开始工作,从消息队列里取消息,处理消息。

所以要使用Handler完成线程之间的通信,首先需要调用Looper.prepare() 为该线程开启消息循环,然后创建Handle,然后调用 Looper.loop() 开始工作。这都是很常规的流程,所以:

HandlerThread 已经帮我们做好了这些事情,HandlerThread 本质上就是一个普通Thread,只不过内部建立了Looper。

HandlerThread 用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class OtherActivity extends AppCompatActivity {
private static final String TAG = "OtherActivity";
private Handler handler1;
private Handler handler2;
private HandlerThread handlerThread1;
private HandlerThread handlerThread2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_other);

// 创建HandlerThread
handlerThread1 = new HandlerThread("handle-thread-1");
handlerThread2 = new HandlerThread("handle-thread-2");
// 开启HandleThread
handlerThread1.start();
handlerThread2.start();

handler1 = new Handler(handlerThread1.getLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
Log.i(TAG, "handleMessage: ThreadName = " + Thread.currentThread().getName()
+ " msg.what = " + msg.what);
}
};

handler2 = new Handler(handlerThread2.getLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
Log.i(TAG, "handleMessage: ThreadName = " + Thread.currentThread().getName()
+ " msg.what = " + msg.what);
}
};

handler2.sendEmptyMessage(2);
handler1.sendEmptyMessage(5);
}

// 释放资源
@Override
protected void onDestroy() {
super.onDestroy();
handlerThread1.quit();
handlerThread2.quitSafely();
}
}

创建 Looper 并执行 loop() 的线程在任务结束的时候,需要手动调用 quit。反之,线程将由于 loop() 的轮询一直处于可运行状态,CPU 资源无法释放。更有可能因为 Thread 作为 GC Root 持有超出生命周期的实例引发内存泄漏。

SDK 推荐使用 quitSafely() 去终止 Looper,原因是其只会剔除执行时刻晚于当前调用时刻的 Message。这样可以保证 quitSafely 调用的那刻,满足执行时间条件的 Message 继续保留在队列中,在都执行完毕才退出轮询。

那么主线程需要 quit 吗?其实不需要,在内存不足的时候 App 由 AMS 直接回收进程。因为主线程极为重要,承载着 ContentProvider、Activity、Service 等组件生命周期的管理,即便某个组件结束了,它仍有继续存在去调度其他组件的必要! 换言之,ActivityThread 的作用域超过了这些组件,不该由这些组件去处理它的结束。比如,Activity destroy 了,ActivityThread 仍然要处理其他 Activity 或 Service 等组件的事务,不能结束。

Handler 在系统中的身影

其实Android本身就是一个巨大的消息处理机,ActivityThread类是Android APP进程的初始类,它的main函数是这个APP进程的入口。APP进程中UI事件的执行代码段都是由ActivityThread提供的。也就是说,主线程实例是存在的,只是创建它的代码我们不可见。ActivityThread的main函数就是在这个主线程里被执行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public final class ActivityThread {

//...
private static ActivityThread sCurrentActivityThread;
public static ActivityThread currentActivityThread() {
return sCurrentActivityThread;
}
private void attach(boolean system) {
sCurrentActivityThread = this;
//...
}
public static void main(String[] args) {
//....

// 创建Looper和MessageQueue对象,用于处理主线程的消息
Looper.prepareMainLooper();

// 创建ActivityThread对象
ActivityThread thread = new ActivityThread();

// 建立Binder通道 (创建新线程)
thread.attach(false);

Looper.loop(); //消息循环运行
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}

Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施即可。

  • 本文作者: Tim
  • 本文链接: https://zouchanglin.cn/492643384.html
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!