【Android Handler 消息机制】
前言
在Android开发中,我们都知道不能在主线程中执行耗时的任务,避免ANR。
Android中主线程也叫UI线程,那么从名字上我们也知道主线程主要是用来创建、更新UI的,而其他耗时操作,比如网络访问,或者文件处理,多媒体处理等都需要在子线程中操作。
之所以在子线程中操作是为了保证UI的流畅程度,手机显示的刷新频率是60Hz,也就是一秒钟刷新60次,每16.67毫秒刷新一次,为了不丢帧,那么主线程处理代码最好不要超过16毫秒。
当子线程处理完数据后,为了防止UI处理逻辑的混乱,Android只允许主线程修改UI,那么这时候就需要Handler来充当子线程和主线程之间的桥梁了。Handler机制在Android多线程编程中可以说是不可或缺的角色。
消息机制概述
Android系统在设计的初期就已经考虑到了UI的更新问题,由于Android中的View是线程不安全的,然而程序中异步处理任务结束后更新UI元素也是必须的。这就造成了一个矛盾,最简单的解决方法肯定是给View加同步锁使其变成线程安全的。这样做不是不可以,只是会有两点坏处:
- 加锁会有导致效率底下
- 由于可以在多个地方更新UI,开发就必须很小心操作,开发起来就很麻烦,一不小心就出错了。
基于以上两个缺点,谷歌设置一个线程专门处理UI控件的更新,如果其他线程也需要对UI进行更新,必须把想做的告诉那个专门处理UI线程的家伙,让它帮你做。大家各有各的任务,井水不犯河水,各司其职。
消息机制简单概括:其他线程通过给特定线程发送消息,将某项专职的工作,交给这个特定的线程去做。比如说其他线程都把处理UI显示的工作通过发送消息交给UI线程去做。
实现原理呢,我是这么理解的,现在要做的主要工作就是切换线程去操作,怎么切换呢?把两条线程看作是两条并行的公路,如果要从一条公路转到另一条公路上,要怎么做呢?是不是只要找到两条公路交叉或者共用某个资源的地方,如果说交叉路口,比如说加油站。当然了,线程是不存在交叉的地方的,那么可以考虑他们公用资源的地方,不同的进程享用不同的内存空间,但是同一个进程的不同线程享用的是同一片内存空间,那让其他线程把要处理的消息放到这个特定的内存空间上,处理消息的线程来这个内存空间上来取消息去处理不就可以了吗。事实正是如此,在Android的消息机制中,扮演这个特定内存空间的对象就是MessageQueue对象,发送和处理的消息就是Message对象。其他的Handler和Looper都是为了配合线程切换用的。
其实不仅仅是线程之间,不同进程之间进行消息传递(IPC机制),也是这个思路,找到公用的一个资源点,文件系统啊,共享内存啊等等。
Handler
Handler的主要用途
- 推送未来某个时间点将要执行的Message或者Runnable到消息队列。
- 在子线程把需要在另一个线程执行的操作加入到消息队列中去。
废话不多说,通过举例来说明Handler的两个主要用途。
1. 推送未来某个时间点将要执行的Message或者Runnable到消息队列
实例:通过Handler配合Message或者Runnable实现倒计时
- 首先看一下效果图
- 方法一,通过Handler + Message的方式实现倒计时。代码如下:
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding mBinding;
private Handler mHandler ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
//设置监听事件
mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//通过Handler + Message的方式实现倒计时
for (int i = 1; i <= 10; i++) {
Message message = Message.obtain(mHandler);
message.what = 10 - i;
mHandler.sendMessageDelayed(message, 1000 * i); //通过延迟发送消息,每隔一秒发送一条消息
}
}
});
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
mBinding.time.setText(msg.what + ""); //在handleMessage中处理消息队列中的消息
}
};
}
}
通过这个小程序,大家可以了解到Handler的一个作用就是:在主线程中,可以通过Handler来处理一些有顺序的操作,让它们在固定的时间点被执行。
- 方法二,通过Handler + Runnable的方式实现倒计时。代码如下:
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding mBinding;
private Handler mHandler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
//设置监听事件
mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
for (int i = 1; i <= 10; i++) {
final int fadedSecond = i;
//每延迟一秒,发送一个Runnable对象
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mBinding.time.setText((10 - fadedSecond) + "");
}
}, 1000 * i);
}
}
});
}
}
方法二也是通过代码让大家加深Handler处理有序事件的用途,之所以分开Runnable和Message两种方法来实现,是因为很多人都搞不清楚为什么Handler可以推送Runnable和Message两种对象。
其实,无论Handler将Runnable还是Message加入MessageQueue,最终都只是将Message加入到MessageQueue。Handler的post Runnable对象这个方法只是对post Message进行了一层封装,所以最终我们都是通过Handler推送了一个Message罢了。
2. 在子线程把需要在另一个线程执行的操作加入到消息队列中去
实例:通过Handler + Message来实现子线程加载图片,在UI线程显示图片
- 效果图如下
- 代码如下(布局代码也不放出来了)
public class ThreadActivity extends AppCompatActivity implements View.OnClickListener {
private ActivityThreadBinding mBinding = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_thread);
// 设置点击事件
mBinding.clickBtn.setOnClickListener(this);
mBinding.resetBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
// 响应load按钮
case R.id.clickBtn:
// 开启一个线程
new Thread(new Runnable() {
@Override
public void run() {
// 在Runnable中进行网络读取操作,返回bitmap
final Bitmap bitmap = loadPicFromInternet();
// 在子线程中实例化Handler同样是可以的,只要在构造函数的参数中传入主线程的Looper即可
Handler handler = new Handler(Looper.getMainLooper());
// 通过Handler的post Runnable到UI线程的MessageQueue中去即可
handler.post(new Runnable() {
@Override
public void run() {
// 在MessageQueue出队该Runnable时进行的操作
mBinding.photo.setImageBitmap(bitmap);
}
});
}
}).start();
break;
case R.id.resetBtn:
mBinding.photo.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.default_pic));
break;
}
}
/* HttpUrlConnection加载图片 */
public Bitmap loadPicFromInternet() {
Bitmap bitmap = null;
int respondCode = 0;
InputStream is = null;
try {
URL url = new URL("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=1421494343,3838991329&fm=23&gp=0.jpg");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10 * 1000);
connection.setReadTimeout(5 * 1000);
connection.connect();
respondCode = connection.getResponseCode();
if (respondCode == 200) {
is = connection.getInputStream();
bitmap = BitmapFactory.decodeStream(is);
}
} catch (MalformedURLException e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(), "访问失败", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return bitmap;
}
}
Handler推送Message和Runnable的区别
首先我们看看post方法和sendMessage方法的源码:
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0);
}
可见,两个方法都是通过调用sendMessageDelayed方法实现的,所以可以知道它们的底层逻辑是一致的。
但是,post方法的底层调用sendMessageDelayed的时候,却是通过getPostMessage®来将Runnable对象来转为Message,我们点进方getPostMessage()法可以看到:
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
其实,最终runnable最终也是转化为一个Message,而这个Message只有一个被赋值的成员变量,就是Runnable的回调函数,也就是说,这个Message在进入MessageQueue之后,它只是一个“动作”,即我们Runnbale的run方法里面的操作。
要知道,我们的Message类可是有很多参数的,所以你可以理解为它是一个非常丰富的JavaBean,可以看看它的成员变量:
- public int what;
- public int arg1;
- public int arg2;
- public Object obj;
- …
那么讲到这里,大家也应该有所理解为什么Google工程师为什么会封装这两种方法,我总结如为:为了更方便开发者根据不同需要进行调用。
当我们需要传输很多数据时,我们可以使用sendMessage来实现,因为通过给Message的不同成员变量赋值可以封装成数据非常丰富的对象,从而进行传输;
当我们只需要进行一个动作时,直接使用Runnable,在run方法中实现动作内容即可。当然我们也可以通过Message.obtain(Handler h, Runnable callback)来传入callback接口,但这样看起来就没有post(Ruannable callback)那么直观。
API
API是我们学习最好的文档
构造函数
- Handler()
- Handler(Handler.Callback callback):传入一个实现的Handler.Callback接口,接口只需要实现handleMessage方法。
- Handler(Looper looper):将Handler关联到任意一个线程的Looper,在实现子线程之间通信可以用到。
- Handler(Looper looper, Handler.Callback callback)
主要方法
-
void dispatchMessage (Message msg)
一般情况下不会使用,因为它的底层实现其实是作为处理系统消息的一个方法,如果真要用,效果和sendMessage(Message m)效果一样。public void dispatchMessage(Message msg) { if (msg.callback != null) { // 如果有Runnbale,则直接执行它的run方法 handleCallback(msg); } else { //如果有实现自己的callback接口 if (mCallback != null) { //执行callback的handleMessage方法 if (mCallback.handleMessage(msg)) { return; } } //否则执行自身的handleMessage方法 handleMessage(msg); } } private static void handleCallback(Message message) { message.callback.run(); }
-
void dump (Printer pw, String prefix)
主要Debug时使用的一个方法,dump函数只是使用了Printer对象进行了打印,打印出Handler以及Looper和Queue中的一些信息,源码如下:public final void dump(Printer pw, String prefix) { pw.println(prefix + this + " @ " + SystemClock.uptimeMillis()); // 如果Looper为空,输出Looper没有初始化 if (mLooper == null) { pw.println(prefix + "looper uninitialized"); } else { // 否则调用Looper的dump方法,Looper的dump方法也是 mLooper.dump(pw, prefix + " "); } }
通过测试用例大家会了解得更清晰:
//测试代码 Printer pw = new LogPrinter(Log.ERROR, "MyTag"); mHandler.dump(pw, "prefix");
-
Looper getLooper ()
拿到Handler相关联的Looper -
String getMessageName (Message message)
获取Message的名字,默认名字为message.what的值。 -
void handleMessage (Message msg)
处理消息。 -
boolean hasMessages (int what)
判断是否有Message的what值为参数what。 -
boolean hasMessages (int what, Object object)
判断是否有Message的what值为参数what,obj值为参数object。 -
Message obtainMessage (int what, Object obj)
从消息池中拿到一个消息并赋值what和obj,其他重载函数同理。 -
boolean post (Runnable r)
将Runnable对象加入MessageQueue。 -
boolean postAtFrontOfQueue (Runnable r)
将Runnbale加入到消息队列的队首。但是官方不推荐这么做,因为很容易打乱队列顺序。 -
boolean postAtTime (Runnable r, Object token, long uptimeMillis)
在某个时间点执行Runnable r。 -
boolean postDelayed (Runnable r, long delayMillis)
当前时间延迟delayMillis个毫秒后执行Runnable r。 -
void removeCallbacks (Runnable r, Object token)
移除MessageQueue中的所有Runnable对象。 -
void removeCallbacksAndMessages (Object token)
移除MessageQueue中的所有Runnable和Message对象。 -
void removeMessages (int what)
移除所有what值的Message对象。 -
boolean sendEmptyMessage (int what)
直接拿到一个空的消息,并赋值what,然后发送到MessageQueue。 -
boolean sendMessageDelayed (Message msg, long delayMillis)
在延迟delayMillis毫秒之后发送一个Message到MessageQueue。
Handler内存泄漏问题
在上面的例子中,为了展示方便,我都没有考虑内存泄漏的情况,但是在实际开发中,如果不考虑代码的安全性的话,尤其当一个项目到达了一定的规模之后,那么对于代码的维护和系统的调试都是非常困难的。而Handler的内存泄漏在Android中也是一个非常经典的案例。
典型错误的使用示例
public class LeakActivity extends AppCompatActivity {
private int a = 10;
//new Handler匿名内部类,会引用外部
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
a = 20;
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leak);
mHandler.sendEmptyMessageDelayed(0, 5000);
//new Runnable也是匿名内部类,也会引用外部
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
...
}
}, 1000000);
}
}
分析问题:
- 外部类Activity中定义了一个非静态内部类Handler,非静态内部类默认持有对外部类的引用。
如果外部Activity突然关闭了,但是MessageQueue中的消息还没处理完,那么Handler就会一直持有对外部Activty的引用,垃圾回收器无法回收Activity,从而导致内存泄漏。
Handler的生命周期有可能与Activity的生命周期不一致,比如栗子中的sendEmptyMessageDelayed,在5000毫秒之后才发送消息,但是很有可能这时候Activity被返回了,这样会造成Handler比Activity还要长寿,这样会导致Activity发生暂时性的内存泄漏。 - 如上代码,在postDelayed中,我们在参数中传入一个非静态内部类Runnable,这同样会造成内存泄漏。
假如此时关闭了Activity,那么垃圾回收器在接下来的1000000ms内都无法回收Activity,造成内存泄漏。
问题一:
为了解决这个问题,我们可以把Handler改为static的, 改成静态内部类后,对外部类的引用设为弱引用,因为在垃圾回收时,会自动将弱引用的对象回收。
但是这样会造成Handler无法访问Activity的非静态变量a。
private static Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
//a = 20; //不能访问得到
break;
}
}
};
问题二:
通过把Activity作为Handler成员变量,在Handler构造的时候传进来即可。这时候我们不能使用匿名内部类了,需要把Handler单独抽取成一个类,这样就可以访问Activity的非静态变量了。但是我们的问题又回来了,这时候Handler持有了Activity的强引用了,这样不就是回到我们的原点了吗?(内存泄漏问题依然没有解决)
private static class MyHandler extends Handler {
private LeakActivity mActivity;//外部类的强引用
public MyHandler(LeakActivity activity) {
mActivity = activity;
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
mActivity.a = 20;
}
}
解决方案:
把Activity通过弱引用来作为成员变量。虽然我们把Activity作为弱引用,但是Activity不一定就是会在GC的时候被回收,因为可能还有其他对象引用了Activity。在处理消息的时候就要注意了,当Activity回收或者正在finish的时候,就不能继续处理消息了。
public class LeakActivity extends AppCompatActivity {
private int a = 10;
private final MyHandler mHandler = new MyHandler(this);
private static final Runnable mRunnable = new Runnable() {
@Override
public void run() {
// 操作
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fourth);
mHandler.postDelayed(mRunnable, 1000*10);
finish();
}
private static class MyHandler extends Handler {
WeakReference<HandlerActivity> mWeakActivity;
public MyHandler(HandlerActivity activity) {
this.mWeakActivity = new WeakReference<HandlerActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//当使用弱引用的时候,会回收Activity吗?
//虽然用的是弱引用,但是并不代表不存在其他的对象没有引用Activity,因此不一定会被回收
LeakActivity activity = mActivityRef.get();//获取Activity
if (activity == null || activity.isFinishing()) {
//当Activity回收或者正在finish的时候,不能继续处理消息
return;
}
mActivityRef.get().a = 20;
}
}
}
HandlerThread
思考一下,假如我们需要同时下载A和B,下载A需要6s,下载B需要5s,在它们下载完成后Toast信息出来即可,此时HandlerThread便是一种解决方式之一。那么HandlerThread到底是什么?
- HandlerThread就是一种线程。
- HandlerThread和普通的Thread之间的区别就是HandlerThread在创建的时候会提供自己该线程的Looper对象。
我们在Actvity创建时系统会自动帮我们初始化好主线程的Looper,然后这个Looper就会管理主线程的消息队列。但是在我们创建子线程时,系统并不会帮我们创建子线程的Looper,需要我们自己手动创建,如下:
new Thread(){
@Override
public void run() {
super.run();
Looper.prepare();
Handler mHandler = new Handler(Looper.myLooper());
Looper.loop();
}
}.start();
所以HandlerThread就在内部帮我们封装了Looper的创建过程,从源码可以看到,HandlerThread集成于Thread,然后覆写run方法,进行Looper的创建,从而通过getLooper方法暴露出该线程的Looper对象
/* HandlerThread 源码 */
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
...
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;//默认优先级
}
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
@Override
public void run() {
mTid = Process.myTid(); //获取线程的tid
Looper