Android Handler 基础使用
01.Handler常见使用方式
handler机制大家都比较熟悉呢。在子线程中发送消息,主线程接受到消息并且处理逻辑。如下所示
- 一般handler的使用方式都是在主线程中定义Handler,然后在子线程中调用mHandler.sendXx()方法,这里有一个疑问可以在子线程中定义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
35public class MainActivity extends AppCompatActivity {
private TextView tv ;
/**
* 在主线程中定义Handler,并实现对应的handleMessage方法
*/
public static Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 101) {
Log.i("MainActivity", "接收到handler消息...");
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.tv);
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread() {
@Override
public void run() {
// 在子线程中发送异步消息
mHandler.sendEmptyMessage(1);
}
}.start();
}
});
}
}
02.在子线程中定义Handler
直接在子线程中创建handler,看看会出现什么情况?
- 运行后可以得出在子线程中定义Handler对象出错,难道Handler对象的定义或者是初始化只能在主线程中?其实不是这样的,错误信息中提示的已经很明显了,在初始化Handler对象之前需要调用Looper.prepare()方法。
- Handler的工作是依赖于Looper的,而Looper(与消息队列)又是属于某一个线程(ThreadLocal是线程内部的数据存储类,通过它可以在指定线程中存储数据,其他线程则无法获取到),其他线程不能访问。因此Handler就是间接跟线程是绑定在一起了。因此要使用Handler必须要保证Handler所创建的线程中有Looper对象并且启动循环。因为子线程中默认是没有Looper的,所以会报错。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread() {
@Override
public void run() {
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 1) {
Log.i(TAG, "在子线程中定义Handler,接收并处理消息");
}
}
};
}
}.start();
}
});如何正确运行。在这里问一个问题,在子线程中可以吐司吗?答案是可以的,只不过有条件。
- 这样程序已经不会报错,那么这说明初始化Handler对象的时候我们是需要调用Looper.prepare()的,那么主线程中为什么可以直接初始化Handler呢?难道是主线程创建handler对象的时候,会自动调用Looper.prepare()方法的吗?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread() {
@Override
public void run() {
Looper.prepare();
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 1) {
Log.i(TAG, "在子线程中定义Handler,接收并处理消息");
}
}
};
//获取Looper对象
mLooper = Looper.myLooper();
Looper.loop();
//在适当的时候退出Looper的消息循环,防止内存泄漏
mLooper.quit();
}
}.start();
}
});
03.Handler的post方法和view的post方法
Handler的post方法实现很简单,如下所示
1
2
3
4
5
6
7
8
9
10mHandler.post(new Runnable() {
@Override
public void run() {
}
});
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}view的post方法也很简单,如下所示
- 可以发现其调用的就是activity中默认保存的handler对象的post方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
ViewRootImpl.getRunQueue().post(action);
return true;
}
public void post(Runnable action) {
postDelayed(action, 0);
}
public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
04.避免子线程手动创建looper
下面这种使用方式,是非常危险的一种做法
- 在子线程中,如果手动为其创建Looper,那么在所有的事情完成以后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待的状态,而如果退出Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止Looper。(【 Looper.myLooper().quit(); 】)
1
2
3
4
5
6
7
8new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(MainActivity.this, "run on Thread", Toast.LENGTH_SHORT).show();
Looper.loop();
}
}).start();
05.解决Handler内存泄漏
解决Handler内存泄露主要2点
- 有延时消息,要在Activity销毁的时候移除Messages
- 匿名内部类导致的泄露改为匿名静态内部类,并且对上下文或者Activity使用弱引用。
问题代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler();
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.text); //模拟内存泄露
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mTextView.setText("yangchong");
}
}, 2000);
}
}造成内存泄漏原因分析
- 上述代码通过内部类的方式创建mHandler对象,此时mHandler会隐式地持有一个外部类对象引用这里就是MainActivity,当执行postDelayed方法时,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,MessageQueue是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏。
解决方案
第一种解决办法
- 要想避免Handler引起内存泄漏问题,需要我们在Activity关闭退出的时候的移除消息队列中所有消息和所有的Runnable。
- 上述代码只需在onDestroy()函数中调用mHandler.removeCallbacksAndMessages(null);就行了。
1
2
3
4
5
6
7
8@Override
protected void onDestroy() {
super.onDestroy();
if(handler!=null){
handler.removeCallbacksAndMessages(null);
handler = null;
}
}
第二种解决方案
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//自定义handler
public static class HandlerHolder extends Handler {
WeakReference<OnReceiveMessageListener> mListenerWeakReference;
/**
* @param listener 收到消息回调接口
*/
HandlerHolder(OnReceiveMessageListener listener) {
mListenerWeakReference = new WeakReference<>(listener);
}
@Override
public void handleMessage(Message msg) {
if (mListenerWeakReference!=null && mListenerWeakReference.get()!=null){
mListenerWeakReference.get().handlerMessage(msg);
}
}
}
//创建handler对象
private HandlerHolder handler = new HandlerHolder(new OnReceiveMessageListener() {
@Override
public void handlerMessage(Message msg) {
switch (msg.what){
case 1:
TextView textView1 = (TextView) msg.obj;
showBottomInAnimation(textView1);
break;
case 2:
TextView textView2 = (TextView) msg.obj;
showBottomOutAnimation(textView2);
break;
}
}
});
//发送消息
Message message = new Message();
message.what = 1;
message.obj = textView;
handler.sendMessageDelayed(message,time);
即推荐使用静态内部类 + WeakReference 这种方式。每次使用前注意判空。