Android百问百答-《那些年被问过的Handler原理》
关于Handler,安卓面试最热门的知识点之一。本篇文章将围绕3点展开:
可以提问哪些Questions?
面试官会怎样follow up?
以及怎样寻找答案。
文章快速索引
- Android百问百答-《那些年被问过的Handler原理》
Handler常见提问
- 哪些场景使用到了Handler?用Handler做什么业务?
- 用Handler遇到什么问题?怎么解决这些问题的?
- 说一说Handler原理?
- 能自己实现一个Handler吗?
- 说一说Handler延时原理?
- Handler延时有哪些缺陷?造成这些缺陷的原因?
- 你知道Handler#handleMessage原理吗?
- Handler的post与sendMessage有哪些区别?
- 子线程能使用Handler吗?
- 子线程能创建Handler吗?
- 了解过HandlerThread吗?
- 了解过IdleHandler吗?
Handler常见Follow Up
- 你刚才提到了Message,消息屏障听过吗?有几种Message?
- Message有什么用?存储了哪些信息?以什么数据结构存储?
- APP内最多能有几个Handler?
- App内最多能有多少Message?
- App内最多能有几个Looper?
- App内最多能有几个MessageQueue?
- Message如何知道发给哪一个MessageQueue?发给哪个Handler?
- MessageQueue如何存储消息,以什么结构存储?
- 你提到了Looper,请问子线程如何获取Looper?
- 你提到了Looper,说一说Looper的消息队列模型?
- 主线程Looper为什么不会阻塞?为什么不会ANR?
- 子线程跟主线程如何通过Handler通信?
- 子线程创建Handler这么麻烦,有什么替代方法吗?
- 主线程Looper什么时候启动的?
- 对Handler做过哪些优化?
Handler源码分析
Handler#构造函数原理
Handler有7个构造函数
Handler()
Handler(Handler.Callback callback)
Handler(Looper looper)
Handler(Looper looper, Handler.Callback callback)
Handler(boolean async)
Handler(Callback callback, boolean async)
Handler(Looper looper, Callback callback, boolean async)
先从其中一个构造函数看起:
提到了MessageQueue、Looper,Message,CallBack暂且记下。
我们根据经验及面试题,关注Handler几个关键API
- obtainMessage
- post
- sendMessage
- postDelayed
- sendMessageDelayed
接着我们关注这些API的底层实现,一个一个分析吧!
Handler#obtainMessage 原理
Handler#obtainMessage 调用了Message#obtain()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T7alw6p4-1597413794308)(C:\Users\lenovo\AppData\Local\Temp\1597329263919.png)]
可以看到obtain函数从Message链表中获取message,这是一种内存复用,节省了频繁创建内存,如果Message链表为空,则创建一个Message。如果你对Message是个链表有疑问,那么请继续看下面的内容吧!
Message源码分析
Message有如下公有属性,供程序员调用:
public int what;//消息标示,
public int arg1; //简单int类型数据
public int arg2;//简单int类型数据
public Object obj;//简单Object类型数据
public Messenger replyTo;//跨进程信使
public int sendingUid = -1;//Messenger消息标示
Message有如下私有属性,用途如下:
/*package*/ static final int FLAG_IN_USE = 1 << 0;//正在使用中
/*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;//消息同步标识
/*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;//
/*package*/ int flags;//消息执行标识
/*package*/ long when;//执行时间
/*package*/ Bundle data;//装载的数据
/*package*/ Handler target;//目标载体
/*package*/ Runnable callback;//任务线程
/*package*/ Message next;//消息链表,下一个消息
private static final Object sPoolSync = new Object();//锁对象
private static Message sPool;//消息池
private static int sPoolSize = 0;//消息池大小
private static final int MAX_POOL_SIZE = 50;//消息池最大消息数常量
private static boolean gCheckRecycle = true;//循环检查
Message的源码,我们可以得出如下结论,Message是一种链表结构,每个Message持有以下信息:
- 用于传递的数据,如what、arg1、arg2、obj
- 用于执行当前Message的Handler
- 用于执行当前Message的回调接口CallBack、子线程Runnable
- 当前Message的属性,如延时时间、执行标识、Bundle数据,下一个Message引用。这种结构构成了链表。
Handler#post的原理
post函数入口接收一个子线程Runnable对象
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
getPostMessage()做了如下工作:
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
sendMessageDelayed做了如下工作:
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
sendMessageAtTime做了如下工作:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
最终走下了MessageQueue#enqueueMessage
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
这一步总结如下
- getPostMessage将Message与Handler绑定
- 通过SystemClock.uptimeMillis() + delayMillis计算延时时间,delayMillis默认为0
- 将Message与计算得出的时间值,传递给MessageQueue#enqueueMessage,交由MessageQueue处理Message。
Handler#sendMessage原理
入口接收Message对象
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
后续执行过程与post相同,最终都将Message交由MessageQueue#enqueueMessage处理。
Handler#postDelayed原理
与post相比,postDelayed函数入口除了接收Runnable子线程对象,还接收一个时间戳,用于延时时间的计算。其他过程与post相同。
public final boolean postDelayed(Runnable r, long delayMillis)
{
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
Handler#sendMessageDelayed原理
与sendMessage类似,多了一个时间戳,用于计算延时时间。其他过程与sendMessage、相同。
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
看完源码,我们得出几个结论:
- 无论是Handler#post或者是Handler#sendMessage,Messag都会交由MessageQueue#enqueueMessage执行
- MessageQueue#enqueueMessage接收两个参数Message和long型的时间戳
- 时间戳计算方式是SystemClock.uptimeMillis() + delayMillis
- post与sendMessage的区别是入参参数不一样,post接收Runnable子线程,将子线程绑定到Message上;sendMessage持有的是主线程
那么我们心中很自然会产生疑问,MessageQueue#enqueueMessage是如何执行的?
MessageQueue源码分析
源码分析的思路是构造函数和enqueueMessage
MessageQueue#构造函数原理
首先来看看构造函数
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
构造函数之上定义了很多native方法
private native static long nativeInit();
private native static void nativeDestroy(long ptr);// 阻塞
private native static void nativeWake(long ptr); // 唤醒
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
native之上定义了几类数据结构,Message、ArrayList、SparseArray、数组
Message mMessages; // 头结点
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
private IdleHandler[] mPendingIdleHandlers;
接着我们来看看enqueueMessage是如何处理Message的吧
MessageQueue#enqueueMessage原理
- 图中 1处会判断如果 Message 中的 target 没有被设置,则直接抛出异常;
- 图中2和 3 处会按照 Message 的时间 when 来有序得插入 MessageQueue 中,可以看出 MessageQueue 实际上是一个链表维护的有序队列,只不过是按照 Message 的执行时间来排序。
看到这里,思路似乎终止了,我们跟随Handler、MessQueue的脚步,只看到了Message被插入到MessageQueue的私有队列中。那我们产生的Message什么时候会背消费呢?
视角再次回到一开始的地方——Handler的构造函数原理,在那一节我们提到了Handler构造函数初始化Looper.myLooper(),mLooper.mQueue,接下来我们看看Looper吧!
Looper源码分析
查看源码可知,Looper是final类型的,禁止被外部继承修改。
Looper#子线程用例
首先在Looper类的注释上,我们看到了如下信息,提示我们在子线程中用个Looper.prepare()+Looper.looper()的方式使用Handler
为什么需要用这种方式开启Looper呢?
答案是在任何线程要开启Loop,都要用Looper.prepare()+Looper.looper()的方式。以APP主进程为例,APP进程启动入口的main方法,也是通过这种方式开启loop的。与子线程细微不同的是,主线程开启looper用的是prepareMainLooper。
ActivityThread #main方法
带着以下疑问,我们去追看源码:Looper构造函数做了什么?prepare做了什么?loop做了什么?
Looper#构造函数原理
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper构造函数做了两件事情,初始化消息队列MessageQueue对象,记录当前线程信息。
Looper#myLooper()原理
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
可以看到myLooper是从threadLocal中取出Looper对象。在Looper类中定义了如下变量sThreadLocal、mQueue、sMainLooper、mThread
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue;
final Thread mThread;
Looper#prepare原理
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));
}
prepare就是 new 出一个 Looper。核心之处在于将 new 出的 Looper 设置到了线程本地变量 sThreadLocal 中。也就是说创建的 Looper 与当前线程发生了绑定。
Looper#prepareMainLooper原理
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
prepareMainLooper只有在APP进程启动的时候有用,并不推荐开发者调用这个函数。
Looper#loop原理
图1 取出Looper对象
图2 校验当前线程是否持有Looper,是否启动而来Looper.prepare
图3 从Looper中取出对应的MessageQueue,主线程Looper就取出主线程的MessageQueue,子线程就取出子线程MessageQueue
图4 从MessageQueue中取出Message
图5 Message#target属性,即handler,调用Message绑定好的handler#dispatchMessage,处理消息。
也就是说,Message最终交由与Message绑定的Handler处理。Looper只是负责无限循环+从MessageQueue中读取。
Handler#dispatchMessage
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);// 1
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {// 2
return;
}
}
handleMessage(msg);// 3
}
}
可以看到有3处可以处理Message
图 1触发了Message#Runnable的run方法,要知道callback就是个Runnable子线程
private static void handleCallback(Message message) {
message.callback.run();
}
图2 触发了 Handler#Callback接口,Callback是Handler构造函数初始化的时候传递进来的。参考Handler#构造函数原理
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
public boolean handleMessage(Message msg);
}
图3 触发了Handler的handleMessage方法,这是个空实现,一般由开发者复写实现。
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
在Looper这一节,我们暂停脚步总结一下:
-
主线程和子线程都可以使用Handler,Handler使用方式都是要Looper.prepare+Lopper.loop,
-
子线程Handler用法
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop(); } } new LooperThread().start();
回答这些面试题吧
面试题解析
哪些场景使用到了Handler?用Handler做什么业务?
最简单的消息发送
主线程使用Handler, 主线程里或子线程里发送消息,或延迟发送消息的方式更新UI如,启动应用时Splash页面的延迟2,3秒后,跳转到主页面
private static final int WHAT_UPDATE_ICON = 1;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case WHAT_UPDATE_ICON:
Log.e(Tag, "receive message:" + msg.obj);
break;
}
}
};
Message msg = handler.obtainMessage(WHAT_UPDATE_ICON);
msg.obj = "update the imageview";
handler.sendMessage(msg);
使用消息的时候,尽量使用 obtainMessage 的方式来获取Message,避免多次创建Message对象,消耗内存,效率低下。
记住:消息不一定是更新UI的消息,可以再handlerMessage中做很多事情!
结合HandlerThread处理耗时任务
结合HandlerThread,串行的处理单个耗时任务,如单任务下载
class DownloadOneByOne extends HandlerThread {
public DownloadOneByOne() {
super(DownloadOneByOne.class.getSimpleName());
}
@Override
protected void onLooperPrepared() {
super.onLooperPrepared();
// 初始化下载组件
}
}
private HandlerThread mHandlerThread;
private Handler downloadHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
String url = (String) msg.obj;
// 使用下载组件开始下载
}
};
public void initHandler() {
// 初始化Handler
mHandlerThread = new DownloadOneByOne();
mHandlerThread.start();
downloadHandler = new Handler(mHandlerThread.getLooper());
}
private void sendDownloadTask(String downloadUrl) {
// 发送下载任务
Message msg = downloadHandler.obtainMessage(WHAT_DOWNLOAD_TASK);
msg.obj = downloadUrl;
downloadHandler.sendMessage(msg);
}
倒计时View的简易实现
通过Handler我们还可以快速简易,并且不占用太多性能的实现一个简易的倒计时View。
public class CountDownView extends AppCompatTextView {
/**
* 总时间
*/
private long seconds;
/**
* 当前分钟
*/
private long minutes;
/**
* 当前秒数
*/
private int second = 60;
private static final int SECONDS_PER_MINUTE = 60;
private static final int MILLS_PER_SECOND = 1000;
private static final int MILLS_PER_MINUTE = SECONDS_PER_MINUTE * 1000;
private static final int WHAT_DONE = 2;
private static final int WHAT_TICK = 1;
private int marginEnd;
private StringBuilder content = new StringBuilder();
public CountDownView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
DeviceProfile deviceProfile = Launcher.getLauncher(getContext()).getDeviceProfile();
int size = (int) (MeasureSpec.getSize(widthMeasureSpec) / deviceProfile.inv.numColumns);
marginEnd = marginEnd == 0 ? (size - deviceProfile.iconSizePx) / 2 : marginEnd;
setMarginEnd(marginEnd);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private void setMarginEnd(int marginEnd) {
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
layoutParams.setMarginEnd(marginEnd);
layoutParams.resolveLayoutDirection(layoutParams.getLayoutDirection());
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (handler.hasMessages(WHAT_TICK)) {
handler.removeMessages(WHAT_TICK);
}
}
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case WHAT_DONE:
setVisibility(View.GONE);
break;
default:
setText(content.toString());
handler.post(runnable);
break;
}
}
};
/***
* 设置倒计时
* @param millis
*/
public void setCountDownMills(long millis) {
seconds = (long) Math.floor(millis / MILLS_PER_SECOND);
minutes = (long) Math.floor(millis / MILLS_PER_MINUTE) - 1;
// start after one second
handler.postDelayed(runnable, MILLS_PER_SECOND);
}
private Runnable runnable = new Runnable() {
@Override
public void run() {
if (seconds <= 0) {
handler.sendEmptyMessage(WHAT_DONE);
return;
}
seconds--;
if (second <= 0) {
second = SECONDS_PER_MINUTE;
minutes = (long) Math.floor(seconds / SECONDS_PER_MINUTE);
}
second--;
content.delete(0, content.length());
appendZeroWhenLower10(minutes);
content.append(":");
appendZeroWhenLower10(second);
if (handler.hasMessages(WHAT_TICK)) {
handler.removeMessages(WHAT_TICK);
}
handler.sendEmptyMessageDelayed(WHAT_TICK, MILLS_PER_SECOND);
}
};
private StringBuilder appendZeroWhenLower10(long value) {
if (value < 10) {
content.append("0").append(value);
} else {
content.append(value);
}
return content;
}
}
结合IntentService的使用
使用IntentService处理耗时的任务相对比较简单,我们来个有难度的,结合AlarmManager的调度,息屏唤醒IntentService定时处理任务的案例来讲
private static final String ACTION_WAKE_UP = "com.doze.cpu.wakeup";
public static void registerAlarm(Context context, int wakeType) {
type = wakeType;
if (alarmManager == null)
alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if (operation != null) alarmManager.cancel(operation);
schedule(context);
}
private static void schedule(Context context) {
Intent intent = new Intent();
intent.setAction(ACTION_WAKE_UP);
operation = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
switch (type) {
case 0:
AlarmUtils.setRTCWakeup(alarmManager, AlarmUtils.DEFAULT_TRIGGER_AT_MILLIS, operation);
break;
case 1:
AlarmUtils.setElapsedWakeup(alarmManager, AlarmUtils.DEFAULT_TRIGGER_AT_MILLIS, operation);
break;
}
}
定时5分钟发送一个Action为com.doze.cpu.wakeup的广播,我们的广播需要继承 WakefulBroadcastReceiver, 在onReceive里,调用startWakefulService方法,会创建一个1分钟的WakeLock,唤醒cpu处理我们的任务,我们的任务IntentService处理最好不过了,处理完就销毁,不会有多余的占用
public class WakeCPUReceiver extends WakefulBroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent wakefulIntent = new Intent(context, WorkService.class);
startWakefulService(context, wakefulIntent);
schedule(context);
}
startWakefulService的源码
public static ComponentName startWakefulService(Context context, Intent intent) {
synchronized (mActiveWakeLocks) {
int id = mNextId;
mNextId++;
if (mNextId <= 0) {
mNextId = 1;
}
intent.putExtra(EXTRA_WAKE_LOCK_ID, id);
ComponentName comp = context.startService(intent);
if (comp == null) {
return null;
}
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"wake:" + comp.flattenToShortString());
wl.setReferenceCounted(false);
wl.acquire(60*1000);
mActiveWakeLocks.put(id, wl);
return comp;
}
}
在IntentService里,我们在onHandleIntent处理我们的任务后,再调用WakefulBroadcastReceiver的静态方法completeWakefulIntent,释放WakeLock,减少电量的消耗
public class WorkService extends IntentService {
...
@Override
protected void onHandleIntent(Intent intent) {
Log.e(WakeCPUReceiver.TAG, "WorkService is working");
// TODO 处理我们的任务
WakeCPUReceiver.completeWakefulIntent(intent);
}
}
completeWakefulIntent源码
public static boolean completeWakefulIntent(Intent intent) {
final int id = intent.getIntExtra(EXTRA_WAKE_LOCK_ID, 0);
if (id == 0) {
return false;
}
synchronized (mActiveWakeLocks) {
PowerManager.WakeLock wl = mActiveWakeLocks.get(id);
if (wl != null) {
wl.release();
mActiveWakeLocks.remove(id);
return true;
}
// We return true whether or not we actually found the wake lock
// the return code is defined to indicate whether the Intent contained
// an identifier for a wake lock that it was supposed to match.
// We just log a warning here if there is no wake lock found, which could
// happen for example if this function is called twice on the same
// intent or the process is killed and restarted before processing the intent.
Log.w("WakefulBroadcastReceiver", "No active wake lock id #" + id);
return true;
}
}
IdleHandler 用于UI性能优化
先计算任务放在Activity绘制结束完成之后,节省了90Ms计算时间。参考面试题你了解过IdleHandler 吗?
HandlerThread用于单线程消息通知器
在用户操作某些界面元素的时候,如收藏、点赞、转发,有一个小的问题,就是如果有一个操作生成10个快速连续的增删改查操作,那么我们的UI就会收到10次回调,而这种场景下我们其实只需要最后一次回调就够了,中间操作其实不用刷新UI的。如何合并这些频繁操作,只在最后一次操作结束时候响应UI更新呢。
答:HandlerThread+反射MessageQueue+idelHandler
用Handler遇到什么问题?怎么解决这些问题的?
问题:Handler延时不准确,经常到了时间不响应业务
解决:SystemClock.uptimeMillis()表示系统开机到当前的时间总数,单位是毫秒,但是,当系统进入深度睡眠(CPU休眠、屏幕休眠、设备等待外部输入)时间就会停止,但是不会受到时钟缩放、空闲或者其他节能机制的影响。
使用其他延时方式
- 用concurrent包的TimeUnit类延时sleep()方法延时
- Timer+TimeTask
- AlarmManager
- ScheduledExecutorService
问题:子线程创建Handler失败
解决:参考Looper#子线程用例部分
问题:非静态类导致的内存泄漏
解决:static+WeakReference
static class MyHandler extends Handler{
WeakReference<MainActivity> mActivity;
MyHandler(MainActivity activity){
mActivity = new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
int cuttent = msg.what;
MainActivity activity = mActivity.get();
if(activity.currentlayout!=null){
Animation set2 = new TranslateAnimation(0,0,0,-100);
set2.setDuration(500);
activity.currentlayout.setAnimation(set2);
}
activity.linearLayout.removeAllViews();
activity.currentlayout = activity.initView(cuttent);
Animation set1 = new TranslateAnimation(0,0,100,0);
set1.setDuration(500);
activity.currentlayout.setAnimation(set1);
activity.linearLayout.addView(activity.currentlayout);
super.handleMessage(msg);
}
}
说一说Handler原理?
原理的定义是:某个类的提供哪些功能,这些功能是如何实现的?
我认为回答这个问题包含三步:Handler是什么,关键API是什么,关键对象是什么?
第一步:回答Handler是什么,有哪些场景。
答:Handler是Android消息通信组件,用于线程间通信,收发消息,更新UI等参考面试题目1。
第二步:Handler的关键API是什么,用途是什么,如何实现的。
答:关键API有:
构造函数:用于绑定MessageQueue、Looper、Message、Runnable、CallBack等
obtainMessage:用于复用Message
post、sendMessage:不同的执行Message方式,前者是接收Runnable参数,后者是当前线程
sendMessageAtTime:消息延时的实现入口,调用MessageQueue#enqueueMessage
第三步:Handler的关键对象是什么,提供哪些功能,这些功能如何实现的。
答:关键对象有:
Message
Message是一种链表结构的子节点,作为载体可以存储的信息有:公开信息arg1、arg2、handler、Message,私有信息when、Runnable。Message有2种Flag,使用中、同步中,三种消息类型,如普通消息、异步消息、消息屏障。
MessageQueue
MessageQueue提供一种链表数据结构,包括头结点信息,插入节点的方式是按照 Message 的时间 when 顺序,时间小的先插入 。
Looper
开启无限循环,不断从 MessageQueue 中取出 Message,然后处理 Message 中指定的任务。典型的Looper是主线程Looper,在 ActivityThread 的 main 方法中,除了调用 Looper.prepareMainLooper 初始化 Looper 对象之外,还调用了 Looper.loop 方法开启无限循环,Looper 的主要功能就是在这个循环中完成的。
Looper提供了一些native方法用于唤醒阻塞状态如nativePollOnce
Looper不断loop的结果,就是调用msg.target.handleMessage,即执行开发者定义好的Handler#handleMessage方法体中的业务。
能自己实现一个Handler吗?
根据Handler的类图,我们可以抽象出Handler消息组件的基本架构。
简版Handler
概要设计:
首先我们仿照Android的Handler定义了:阻塞队列、处理消息的回调、分发和发送消息的方法
其次然后在创建Handler时,我们获取了当前线程的Looper和MessageQueue
最后,当我们发送消息的时候,将消息添加进之前得到的MessageQueue
public class MyHandler {
private MyMessageQueue queue;// 用于进行线程间通信的阻塞队列
private CallBack callBack; // 处理消息的回调
public MyHandler(CallBack callBack) {
MyLooper looper = MyLooper.myLooper();
if (looper == null) {
throw new RuntimeException("在新开的线程中。创建MyHandler对象需要先调用MyLooper.prepare()方法。");
}
queue = looper.queue;
this.callBack = callBack;
}
//消息接收的回调
public interface CallBack {
void handleMessage(MyMessage msg);
}
//发送消息
public void sendMessage(MyMessage msg) {
msg.target = this;
queue.enqueueMessage(msg);
}
//派发消息
public void dispatchMessage(MyMessage msg) {
callBack.handleMessage(msg);
}
}
简版Looper
- 在Looper中,我们用一个ThreadLocal存储当前Looper的相关数据
- 定义了一个消息队列,用来管理消息
- 在prepare()时,用ThreadLocal存储Looper的数据;在myLooper时,读取ThreadLocal存储的Looper数据
- 在loop()时,用一个死循环来不断的接受和分发消息
public class MyLooper {
private static ThreadLocal<MyLooper> threadLocal = new ThreadLocal<>();
private static MyLooper myLooper;
public MyMessageQueue queue;//一个线程对应一个阻塞队列
private MyLooper() {
queue = new MyMessageQueue();
}
//获取当前线程相对应的Looper对象
public static MyLooper myLooper() {
return threadLocal.get();//当未调用prepare()方法时。ThreadLocal.get()方法返回的为null;
}
//为本线程准备对应的MyLooper对象
public static void prepare() {
if (threadLocal.get() != null) {
throw new RuntimeException( "Only one MyLooper may be created per thread");
}
threadLocal.set(new MyLooper());
}
//这里启动消息循环
public static void loop() {
while (true) {
myLooper = myLooper();
MyMessageQueue mQueue = myLooper.queue;
senior.thread_concurrent.handler.MyMessage msg = mQueue.next();// take()方法是个阻塞方法。线程运行到此会阻塞住。以准备接收发过来的消息
msg.target.dispatchMessage(msg);
}
}
}
简版MessageQueue
public class MyMessageQueue {
private BlockingQueue<MyMessage> queue;
private boolean quit = false;
public MyMessageQueue() {
queue = new LinkedBlockingQueue<>();
queue.clear();
}
//入队
public boolean enqueueMessage(MyMessage msg) {
if (msg.target == null) {
throw new RuntimeException("消息必须有一个消息处理者");
}
try {
queue.put(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
//出队
public MyMessage next() {
MyMessage msg = null;
if (quit) {
return null;
}
try {
msg = queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return msg;
}
//销毁
public synchronized void quit() {
quit = true;
}
}
简版Message
public class MyMessage {
public int msg1;
public int msg2;
public int what;
public Object obj;
public MyHandler target;
public Runnable runnable;
}
写一个网络请求的测试用例
public class TestClient {
private senior.thread_concurrent.handler.MyHandler mainHandler;
public static void main(String[] args) {
new TestClient().test();
}
private void test() {
//初始化主线程Looper
MyLooper.prepare();
mainHandler = new senior.thread_concurrent.handler.MyHandler(new senior.thread_concurrent.handler.MyHandler.CallBack() {
@Override
public void handleMessage(senior.thread_concurrent.handler.MyMessage msg) {
// 刷新界面
String obj = (String) msg.obj;
LogUtil.print("刷新界面:" + obj);
}
});
//发起网络请求
LogUtil.print("在主线程发起一个网络请求");
NetThread netThread = new NetThread("http://baidu.com");
netThread.start();
LogUtil.print("在主线程继续其它操作");
//开始消息循环
MyLooper.loop();
}
//网络线程类
private class NetThread extends Thread {
private String url;
public NetThread(String url) {
this.url = url;
}
@Override
public void run() {
String body = getWebData(url);
senior.thread_concurrent.handler.MyMessage msg = new senior.thread_concurrent.handler.MyMessage();
msg.obj = body;
mainHandler.sendMessage(msg);
}
}
//执行网络请求
private String getWebData(String url) {
LogUtil.print("执行请求网络:" + url);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String body = "这是" + url + "的响应值";
LogUtil.print("请求网络成功:" + body);
return body;
}
}
说一说Handler延时原理?
首先Handler无论是post还是sendMessage方式处理Message过程中,都会产生一个时间戳,计算方式是SystemClock.uptimeMillis() + delayMillis,这个时间戳会赋值给Message.when,影响Message在MessageQueue链表中的位置。时间戳值越大,越晚执行。
Handler延时存在时间不准的问题,问题产生原因以及解决办法以及在面试题2问题:Handler延时不准确,经常到了时间不响应业务提到。
Handler延时有哪些缺陷?造成这些缺陷的原因?
参考面试题2问题:Handler延时不准确,经常到了时间不响应业务
Handler的post与sendMessage有哪些区别?
post需要指定Runnable参数,将传入的Runnable绑定至Handler默认的Message,很多值都为默认值,换言之post方法只是为了执行Runnable子线程的任务。
sendMessage需要传入开发者自定义的Message参数,将Message中的信息载体传递下去,sendMessage方法是为了传递消息。
两者最终都会将Message传递下去,区别是Message中的数据信息赋值数量的不同。
子线程能使用Handler吗?
能,可以使用handler对象以及对应的方法。区别是Handler的创建位置,如果Handler在主线程创建,那么只能在主线程中处理消息。如果在子线程创建Handler,那么才能在子线程处理消息。
子线程能创建Handler吗?
能,前提是需要Looper.prepare+Looper.loop
Looper.prepare是将当前线程添加到sThreadLocal中,Looper.loop是开启无限循环,不断执行Message
子线程创建Handler这么麻烦,有什么替代方法吗?了解过HandlerThread吗?
HandlerThread的run方法中替我们做了Looper.prepare+Looper.loop
HandlerThread handlerThread = new HandlerThread("handler-thread");
handlerThread.start(); // 必须在Handler创建前调用,因为线程start后才会创建Looper
Handler threadHandler = new Handler(handlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 处理消息,因为这个方法是在子线程调用,所以可以在这执行耗时任务
}
};
了解过IdleHandler吗?
IdleHandler 用途:
- IdleHandler 是 Handler 提供的一种在消息队列空闲时,执行任务的时机;
- 当 MessageQueue 当前没有立即需要处理的消息时,会执行 IdleHandler;
- Activity界面绘制结束的回调时机
IdleHandler 缺点:
- 但它执行的时机依赖消息队列的情况,那么如果 MessageQueue 一直有待执行的消息时,IdleHandler 就一直得不到执行,也就是它的执行时机是不可控的,不适合执行一些对时机要求比较高的任务。
IdleHandler场景
如果我们想在界面绘制出来后做点什么,那么在onResume里面是不合适的,它先于measure等流程了**, 有人可能会说在onResume里面post一个runnable可以吗?还是不行,因为那样就会变成这个样子
所以你的行为一样会在绘制之前执行,这个时候我们的主角IdleHandler就发挥作用了,我们前面说了,它是在looper里面message暂时执行完毕了就会回调,顾名思义嘛,Idle就是队列为空的意思,那么我们的onResume和measure, layout, draw都是一个个message的话,这个IdleHandler就提供了一个它们都执行完毕的回调了,大概就是这样
也就是说IdleHandler可以再界面绘制的消息回调之后执行。
优化前:
这个是我们地图的公交详情页面, 进入之后产品要求左边的页卡需要展示,可以看到左边的页卡是一个非常复杂的布局,那么进入之后的效果可以明显看到头部的展示信息是先显示空白再100毫秒左右之后才展示出来的,原因就是这个页卡的内容比较复杂,用数据向它填充的时候花了较长时间,代码如下:
可以看到这个detailView就是这个侧滑的页卡了,填充里面的数据花了90ms,如果这个时间是用在了界面view绘制之前的话,就会出现以上的效果了,view先是白的,再出现,这样就体验不好了。
优化后:如果我们把它放到IdleHandler里面呢?
结果非常明显:顶部的页卡先展示出来了,这样体验是不是会更好一些呢。虽然只有短短90ms,不过我们做app也应该关注这种细节优化的,是吧~ 这个做法也提供了一种思路,android本身提供的activity框架和fragment框架并没有提供绘制完成的回调,如果我们自己实现一个框架,就可以使用这个IdleHandler来实现一个onRenderFinished这种回调了。
代码如下:
特别参考
https://wetest.qq.com/lab/view/352.html
https://blog.csdn.net/u013718120/article/details/51945490