AndroidHandler百问百答

Android百问百答-《那些年被问过的Handler原理》#

文章迁移自我的csdn博客

关于Handler,安卓面试最热门的知识点之一。本篇文章将围绕3点展开:

可以提问哪些Questions?

面试官会怎样follow up?

以及怎样寻找答案。

Handler常见提问#

  1. 哪些场景使用到了Handler?用Handler做什么业务?
  2. 用Handler遇到什么问题?怎么解决这些问题的?
  3. 说一说Handler原理?
  4. 能自己实现一个Handler吗?
  5. 说一说Handler延时原理?
  6. Handler延时有哪些缺陷?造成这些缺陷的原因?
  7. 你知道Handler#handleMessage原理吗?
  8. Handler的post与sendMessage有哪些区别?
  9. 子线程能使用Handler吗?
  10. 子线程能创建Handler吗?
  11. 了解过HandlerThread吗?
  12. 了解过IdleHandler吗?

Handler常见Follow Up#

  1. 你刚才提到了Message,消息屏障听过吗?有几种Message?
  2. Message有什么用?存储了哪些信息?以什么数据结构存储?
  3. APP内最多能有几个Handler?
  4. App内最多能有多少Message?
  5. App内最多能有几个Looper?
  6. App内最多能有几个MessageQueue?
  7. Message如何知道发给哪一个MessageQueue?发给哪个Handler?
  8. MessageQueue如何存储消息,以什么结构存储?
  9. 你提到了Looper,请问子线程如何获取Looper?
  10. 你提到了Looper,说一说Looper的消息队列模型?
  11. 主线程Looper为什么不会阻塞?为什么不会ANR?
  12. 子线程跟主线程如何通过Handler通信?
  13. 子线程创建Handler这么麻烦,有什么替代方法吗?
  14. 主线程Looper什么时候启动的?
  15. 对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有如下公有属性,供程序员调用:

1
2
3
4
5
6
7
public int what;//消息标示,
public int arg1; //简单int类型数据
public int arg2;//简单int类型数据
public Object obj;//简单Object类型数据
public Messenger replyTo;//跨进程信使
public int sendingUid = -1;//Messenger消息标示

Message有如下私有属性,用途如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*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持有以下信息:

  1. 用于传递的数据,如what、arg1、arg2、obj
  2. 用于执行当前Message的Handler
  3. 用于执行当前Message的回调接口CallBack、子线程Runnable
  4. 当前Message的属性,如延时时间、执行标识、Bundle数据,下一个Message引用。这种结构构成了链表。

Handler#post的原理#

post函数入口接收一个子线程Runnable对象

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

getPostMessage()做了如下工作:

1
2
3
4
5
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}

sendMessageDelayed做了如下工作:

1
2
3
4
5
6
7
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

sendMessageAtTime做了如下工作:

1
2
3
4
5
6
7
8
9
10
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

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

这一步总结如下

  1. getPostMessage将Message与Handler绑定
  2. 通过SystemClock.uptimeMillis() + delayMillis计算延时时间,delayMillis默认为0
  3. 将Message与计算得出的时间值,传递给MessageQueue#enqueueMessage,交由MessageQueue处理Message。

Handler#sendMessage原理#

入口接收Message对象

1
2
3
4
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}

后续执行过程与post相同,最终都将Message交由MessageQueue#enqueueMessage处理。

Handler#postDelayed原理#

与post相比,postDelayed函数入口除了接收Runnable子线程对象,还接收一个时间戳,用于延时时间的计算。其他过程与post相同。

1
2
3
4
public final boolean postDelayed(Runnable r, long delayMillis)
{
return sendMessageDelayed(getPostMessage(r), delayMillis);
}

Handler#sendMessageDelayed原理#

与sendMessage类似,多了一个时间戳,用于计算延时时间。其他过程与sendMessage、相同。

1
2
3
4
5
6
7
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

看完源码,我们得出几个结论:

  1. 无论是Handler#post或者是Handler#sendMessage,Messag都会交由MessageQueue#enqueueMessage执行
  2. MessageQueue#enqueueMessage接收两个参数Message和long型的时间戳
  3. 时间戳计算方式是SystemClock.uptimeMillis() + delayMillis
  4. post与sendMessage的区别是入参参数不一样,post接收Runnable子线程,将子线程绑定到Message上;sendMessage持有的是主线程

那么我们心中很自然会产生疑问,MessageQueue#enqueueMessage是如何执行的?

MessageQueue源码分析#

源码分析的思路是构造函数和enqueueMessage

MessageQueue#构造函数原理#

首先来看看构造函数

1
2
3
4
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}

构造函数之上定义了很多native方法

1
2
3
4
5
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、数组

1
2
3
4
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#构造函数原理#

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

Looper构造函数做了两件事情,初始化消息队列MessageQueue对象,记录当前线程信息。

Looper#myLooper()原理#

1
2
3
4
5
6
7
/**
* 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

1
2
3
4
5
6
// 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原理#

1
2
3
4
5
6
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原理

1
2
3
4
5
6
7
8
9
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#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 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子线程

1
2
3
private static void handleCallback(Message message) {
message.callback.run();
}

图2 触发了 Handler#Callback接口,Callback是Handler构造函数初始化的时候传递进来的。参考Handler#构造函数原理

1
2
3
4
5
6
7
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方法,这是个空实现,一般由开发者复写实现。

1
2
3
4
5
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}

在Looper这一节,我们暂停脚步总结一下:

  1. 主线程和子线程都可以使用Handler,Handler使用方式都是要Looper.prepare+Lopper.loop,

  2. 子线程Handler用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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秒后,跳转到主页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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,串行的处理单个耗时任务,如单任务下载

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
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。

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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定时处理任务的案例来讲

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
  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处理最好不过了,处理完就销毁,不会有多余的占用

1
2
3
4
5
6
7
8
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的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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,减少电量的消耗

1
2
3
4
5
6
7
8
9
public class WorkService extends IntentService {
...
@Override
protected void onHandleIntent(Intent intent) {
Log.e(WakeCPUReceiver.TAG, "WorkService is working");
// TODO 处理我们的任务
WakeCPUReceiver.completeWakefulIntent(intent);
}
}

completeWakefulIntent源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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休眠、屏幕休眠、设备等待外部输入)时间就会停止,但是不会受到时钟缩放、空闲或者其他节能机制的影响。

使用其他延时方式

  1. 用concurrent包的TimeUnit类延时sleep()方法延时
  2. Timer+TimeTask
  3. AlarmManager
  4. ScheduledExecutorService

问题:子线程创建Handler失败#

解决:参考Looper#子线程用例部分

问题:非静态类导致的内存泄漏#

解决:static+WeakReference

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
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

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
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#

  1. 在Looper中,我们用一个ThreadLocal存储当前Looper的相关数据
  2. 定义了一个消息队列,用来管理消息
  3. 在prepare()时,用ThreadLocal存储Looper的数据;在myLooper时,读取ThreadLocal存储的Looper数据
  4. 在loop()时,用一个死循环来不断的接受和分发消息
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
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#

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
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#

1
2
3
4
5
6
7
8
public class MyMessage {
public int msg1;
public int msg2;
public int what;
public Object obj;
public MyHandler target;
public Runnable runnable;
}

写一个网络请求的测试用例#

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
60
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

1
2
3
4
5
6
7
8
9
10
11
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 用途:

  1. IdleHandler 是 Handler 提供的一种在消息队列空闲时,执行任务的时机;
  2. 当 MessageQueue 当前没有立即需要处理的消息时,会执行 IdleHandler;
  3. Activity界面绘制结束的回调时机

IdleHandler 缺点:

  1. 但它执行的时机依赖消息队列的情况,那么如果 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

点击查看
-------------------本文结束 感谢您的阅读-------------------