【第八关】Android Handler详解
参考:https://www.jianshu.com/p/9fe944ee02f7
https://www.jianshu.com/p/69b550cb7d43
https://www.jianshu.com/p/3855e0aa7900
Handler是什么
-
Handler是一个消息分发对象。handler是Android给我们提供用来更新UI的一套机制,也是一套消息处理机制,我们可以发消息,也可以通过它处理消息。简单定义为一套
Android消息传递机制
-
1:To schedule messages and runnables to be executed as some point in the future 定时任务
2:To enqueue an action to be performed on a different thread than your own 在不同线程中执行任务
Handler作用
在多线程的应用场景中,将工作线程中需要跟新UI的操作信息传递到UI主线程,从而实现工作线程对UI的更新处理,最终事项异步消息的处理
为什么要使用Handler?
-
最根本的目的就是为了
解决多线程并发的问题(多个线程并发更新UI的同时,保证线程安全)
,将工作线程需操作UI的消息传递到主线程,使得主线程可根据工作线程的需求更新UI,从而避免线程操作不安全的问题
- 打个比方,如果在一个activity中有多个线程,并且没有加锁,就会出现界面错乱的问题。但是如果对这些更新UI的操作都加锁处理,又会导致性能下降。处于对性能的问题考虑,Android给我们提供这一套更新UI的机制我们只需要遵循这种机制就行了。不用再去关系多线程的问题,所有的更新UI的操作,都是在主线程的消息队列中去轮询的。
- 它把消息发送给Looper管理的MessageQueue,并负责处理Looper分发给他的消息
相关概念
-
Handler类包含如下方法用于发送,处理消息
-
void handleMessage(Message msg):
处理消息的方法
。通常用于被重写 -
final boolean hasMessages(int what):
检查消息队列中是否包含what属性为指定值的消息
。 - final boolean hasMessages(int what, Object object):检查消息队列中是否包含what属性为指定值且object属性为指定对象的消息。
- 多个重载的Message obtainMessage():获取消息。
- sendEmptyMessage(int what):发送空消息。
- final boolean sendEmptyMessageDelayed(int what, long delayMillis):指定多少毫秒后发送空消息。
- final boolean sendMessage(Message msg):立即发送消息。
- final boolean sendMessageDelayed(Message msg, long delayMillis):指定多少毫秒后发送消息。((经验证,该时间为活动onResume时到handleMessage之间的时间))
-
void handleMessage(Message msg):
-
Message:Handler接收和处理的消息对象
- 2个整型数值(arg1,arg2):轻量级存储int类型的数据
-
1个Object:
任意对象
- replyTo:线程通信时使用
-
what:
用户自定义的消息码,让接收者识别消息
-
MessageQueue:Message队列
-
采用
先进先出
的方式管理Message -
每一个线程最多可以拥有一个
-
采用
-
Looper:消息泵,是MessageQueue的管理者,会
不断从MessageQueue中取出消息,并将消息分给对应的Handler处理
-
每个线程只有一个Looper
- Looper.prepare():为当前线程创建Looper对象
- Looper.myLooper():可以获得当前线程的Looper对象
-
-
Handler:
能把消息发送给MessageQueue,并负责处理Looper分给它的消息
核心类
Handler机制中有3个重要的类:
- 处理器类(Handler)
- 消息队列类(MessageQueue)
- 循环器类(Looper)
类图
:
具体介绍:
工作原理
Handler
机制得到工作流程主要包括4个步骤:
- 异步通信准备
- 消息发送
- 消息循环
- 消息处理
-
具体如下图:
-
工作流程图
-
异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数,执行完成一个消息后则继续循环。
若消息队列为空,线程则会阻塞等待
。 -
Android消息处理机制主要是指Handler的运行机制,Handler运行需要底层的MessageQueue和Looper支撑。其中
MessageQueue采用的是单链表的结构(先进先出)
,Looper可以叫做消息循环。由于MessageQueue只是一个消息存储单元,不能去处理消息,而Looper就是专门来处理消息的,
Looper会已无限循环的形式去查找是否有新消息,如果有的话,就处理,否则就线程阻塞,等待着
。 -
Handler创建的时候会采用当前线程的Looper来构造消息循环系统,
注
:线程默认是没有Looper的,如果需要使用Handler就必须为线程创建Looper,因为默认的UI主线程,也就是ActivityThread,ActivityThread被创建的时候就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。
-
示意图
-
注意
线程(Thread),循环(Looper),处理者(Handler)之间的对应关系如下:- 1个线程(Thread)只能绑定1个循环器(Looper),但可以由多个处理器(Handler)
- 1个循环器(Looper)可绑定多个处理者(Handler)
-
1个处理者(Looper)只能绑定1个循环器(Looper)
使用步骤
-
方式1:使用Handler.sendMessage()
在该使用方式中,又分为两种:新建Handler子类(内部类),匿名Handler子类,但本质相同,即继承了Handler类&创建了子类/** * 方式1:新建Handler子类(内部类) */ // 步骤1:自定义Handler子类(继承Handler类) & 复写handleMessage()方法 class mHandler extends Handler { // 通过复写handlerMessage() 从而确定更新UI的操作 @Override public void handleMessage(Message msg) { ...// 需执行的UI操作 } } // 步骤2:在主线程中创建Handler实例 private Handler mhandler = new mHandler(); // 步骤3:创建所需的消息对象 Message msg = Message.obtain(); // 实例化消息对象 msg.what = 1; // 消息标识 msg.obj = "AA"; // 消息内容存放 // 步骤4:在工作线程中 通过Handler发送消息到消息队列中 // 可通过sendMessage() / post() // 多线程可采用AsyncTask、继承Thread类、实现Runnable mHandler.sendMessage(msg); // 步骤5:开启工作线程(同时启动了Handler) // 多线程可采用AsyncTask、继承Thread类、实现Runnable /* *方式2:新建匿名内部类 */ // 步骤1:在主线程中 通过匿名内部类 创建Handler类对象 private Handler mhandler = new Handler(){ // 通过复写handlerMessage()从而确定更新UI的操作 @Override public void handleMessage(Message msg) { ...// 需执行的UI操作 } }; // 步骤2:创建消息对象 Message msg = Message.obtain(); // 实例化消息对象 msg.what = 1; // 消息标识 msg.obj = "AA"; // 消息内容存放 // 步骤3:在工作线程中 通过Handler发送消息到消息队列中 // 多线程可采用AsyncTask、继承Thread类、实现Runnable mHandler.sendMessage(msg); // 步骤4:开启工作线程(同时启动了Handler) // 多线程可采用AsyncTask、继承Thread类、实现Runnable ```
-
原理总结
-
工作流程总结
-
方式2:使用Handler.post()
// 步骤1:在主线程中创建Handler实例 private Handler mhandler = new mHandler(); // 步骤2:在工作线程中 发送消息到消息队列中 & 指定操作UI内容 // 需传入1个Runnable对象 mHandler.post(new Runnable() { @Override public void run() { ... // 需执行的UI操作 } }); // 步骤3:开启工作线程(同时启动了Handler) // 多线程可采用AsyncTask、继承Thread类、实现Runnable
-
原理总结
-
工作流程总结
两者对比
在Handler中发生的内存泄漏
内存泄漏的定义
:本该被回收的对象不能被回收而停留在堆内存中,
内存泄漏出现的原因
:当一个对象已经不再被使用时,本该被回收但却因为有另外一个正在使用的对象持有它的引用从而导致它不能被回收。这就导致了内存泄漏。
问题描述
- Handler的一般用法 = 新建Handler子类(内部类),匿名Handler内部类
/**
* 方式1:新建Handler子类(内部类)
*/
public class MainActivity extends AppCompatActivity {
public static final String TAG = "carson:";
private Handler showhandler;
// 主线程创建时便自动创建Looper & 对应的MessageQueue
// 之后执行Loop()进入消息循环
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1. 实例化自定义的Handler类对象->>分析1
//注:此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue
showhandler = new FHandler();
// 2. 启动子线程1
new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 1;// 消息标识
msg.obj = "AA";// 消息存放
// b. 传入主线程的Handler & 向其MessageQueue发送消息
showhandler.sendMessage(msg);
}
}.start();
// 3. 启动子线程2
new Thread() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 2;// 消息标识
msg.obj = "BB";// 消息存放
// b. 传入主线程的Handler & 向其MessageQueue发送消息
showhandler.sendMessage(msg);
}
}.start();
}
// 分析1:自定义Handler子类
class FHandler extends Handler {
// 通过复写handlerMessage() 从而确定更新UI的操作
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.d(TAG, "收到线程1的消息");
break;
case 2:
Log.d(TAG, " 收到线程2的消息");
break;
}
}
}
}
/**
* 方式2:匿名Handler内部类
*/
public class MainActivity extends AppCompatActivity {
public static final String TAG = "carson:";
private Handler showhandler;
// 主线程创建时便自动创建Looper & 对应的MessageQueue
// 之后执行Loop()进入消息循环
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1. 通过匿名内部类实例化的Handler类对象
//注:此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue
showhandler = new Handler(){
// 通过复写handlerMessage()从而确定更新UI的操作
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.d(TAG, "收到线程1的消息");
break;
case 2:
Log.d(TAG, " 收到线程2的消息");
break;
}
}
};
// 2. 启动子线程1
new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 1;// 消息标识
msg.obj = "AA";// 消息存放
// b. 传入主线程的Handler & 向其MessageQueue发送消息
showhandler.sendMessage(msg);
}
}.start();
// 3. 启动子线程2
new Thread() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 2;// 消息标识
msg.obj = "BB";// 消息存放
// b. 传入主线程的Handler & 向其MessageQueue发送消息
showhandler.sendMessage(msg);
}
}.start();
}
}
-
测试结果
- 上述例子虽可运行成功,但代码会出现严重警告
警告原因:该Handler类由于无设置为静态类,从而导致了内存泄漏
最终的内存泄漏发生在Handler类的外部类:MainActivity类
那么,该Handler在无设置为静态类时,为什么会造成内存泄漏呢?
原因
-
主线程的Looper对象的生命周期 = 该应用程序的生命周期
;当一个Android应用启动的时候,会自动创建一个供应用主线程使用的Looper实例。Looper的主要工作就是一个一个处理消息队列中的消息对象。在Android中,所有Android框架的事件(比如Activity的生命周期方法调用和按钮点击等)都是放入到消息中,然后加入到Looper要处理的消息队列中,由Looper负责一条一条地进行处理。主线程中的Looper生命周期和当前应用一样长。 -
在Java中,
非静态内部类&匿名内部类都默认持有外部类的引用,静态的内部类不会持有外部类的引用
。 -
原因描述
- 上述的Handler实例的消息队列有2个分别来自线程1,2的消息(分别为延迟1s,6s)
- 在Handler消息队列还有未处理的消息/正在处理消息时,消息队列中的Message持有Handler实例的引用
- 由于Handler = 非静态内部类/匿名内部类(2种使用方式),故又默认持有外部类的引用(即MainActivity实例),引用关系如下图
上述的引用关系会一直保持,直到Handler消息队列种的所有消息被处理完毕
-
在Handler消息队列还有未处理的消息/正在处理消息时,此时若需销毁外部类MainActivity,但由于上述引用关系,垃圾收集器(GC)无法回收MainActivity,从而造成内存泄漏。如下图:
总结
- 当Handler消息队列还有未处理/正在处理消息的时候,存在引用关系:“未被处理/正处理的消息->Handler实例->外部类”,若出现Handler的生命周期 > 外部类的生命周期时(即Handler消息队列还有未处理的消息/正在处理消息而外部类需销毁时),将使得外部类无法被垃圾回收器(GC)回收,从而造成内存泄漏。
解决方案
从上面可看出,造成内存泄漏的原因有2个关键条件:
- 存在“未被处理/正在处理的消息->Handler实例->外部类”的引用关系
- Handler的生命周期>外部类的生命周期
即Handler消息队列还有未处理的消息/正在处理消息而外部类需销毁
解决方案的思路 = 使得上述任一条件不成立即可。
解决方案1:静态内部类 + 弱引用
-
原理
静态内部类不默认持有外部类的引用,从而使得“未被处理/正处理的消息->Handler实例->外部类”的引用关系不复存在。 -
具体方案
将Handler的子类设置成静态内部类,同时,还可加上使用WeakReference弱引用持有Activity实例
原因:弱引用的对象拥有短暂的生命周期。在垃圾回收器线程扫描时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
解决代码:public class MainActivity extends AppCompatActivity { public static final String TAG = "carson:"; private Handler showhandler; // 主线程创建时便自动创建Looper & 对应的MessageQueue // 之后执行Loop()进入消息循环 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //1. 实例化自定义的Handler类对象->>分析1 //注: // a. 此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue; // b. 定义时需传入持有的Activity实例(弱引用) showhandler = new FHandler(this); // 2. 启动子线程1 new Thread() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // a. 定义要发送的消息 Message msg = Message.obtain(); msg.what = 1;// 消息标识 msg.obj = "AA";// 消息存放 // b. 传入主线程的Handler & 向其MessageQueue发送消息 showhandler.sendMessage(msg); } }.start(); // 3. 启动子线程2 new Thread() { @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } // a. 定义要发送的消息 Message msg = Message.obtain(); msg.what = 2;// 消息标识 msg.obj = "BB";// 消息存放 // b. 传入主线程的Handler & 向其MessageQueue发送消息 showhandler.sendMessage(msg); } }.start(); } // 分析1:自定义Handler子类 // 设置为:静态内部类 private static class FHandler extends Handler{ // 定义 弱引用实例 private WeakReference<Activity> reference; // 在构造方法中传入需持有的Activity实例 public FHandler(Activity activity) { // 使用WeakReference弱引用持有Activity实例 reference = new WeakReference<Activity>(activity); } // 通过复写handlerMessage() 从而确定更新UI的操作 @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: Log.d(TAG, "收到线程1的消息"); break; case 2: Log.d(TAG, " 收到线程2的消息"); break; } } } }
解决方案2:当外部类结束生命周期时,清空Handler内消息队列
-
原理:
不仅使得 “未被处理 / 正处理的消息 -> Handler实例 -> 外部类” 的引用关系 不复存在,同时 使得 Handler的生命周期(即 消息存在的时期) 与 外部类的生命周期 同步 -
具体方案:
当 外部类(此处以Activity为例) 结束生命周期时(此时系统会调用onDestroy()),清除 Handler消息队列里的所有消息(调用removeCallbacksAndMessages(null)) - 具体代码
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
// 外部类Activity生命周期结束时,同时清空消息队列 & 结束Handler生命周期
}
使用建议
为了保证Handler中消息队列中的所有消息都能被执行,此处推荐使用解决方案1解决内存泄露问题,即 静态内部类 + 弱引用的方式