前言
不知何时起,大约是21年初的时候首次听说客三消这个词;意思是客户端将在三年内消失; 移动互联网发展了十年,客户端技术几乎非常成熟;流量往头部的app汇集的趋势不可逆, 市面上招聘客户端最多的应该是字节了,客户端求职也激烈了起来;各种八股轮番轰炸, 本着好记性不如烂笔头的原则,记录android面试常见的八股知识点;所谓八股就是在 实际开发中用的极少,但是还是需要知道起技术原理以提升自己面试时的气度。
1、骨灰级的Handler
记得13年的时候,能够聊一些Handler机制;就算是厉害的开发了,如今再问Handler
,心底一万头草泥马奔跑,真的太卷啦。
无论如何,Handler是UiFrameWork的核心机制消息的核心实现;掌握其运行原理还是很有必要滴。
1.1、 核心关系,构造和准备工作
Handler: 消息处理和接受者,Looper: 消息调度者,MessageQueue: 消息存储的队列
Handler的构造函数
Handler的6个构造函数,核心构造函数就下面这个:
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
干了两件事:
1、Handler是匿名内部类,非静态内部类,方法中定义的局部类都可能导致内存泄露,打印一点日志。
2、对核心字段mLooper,mQueue赋值;
经验: 从mLooper = Looper.myLooper()可以推测从子线程空参构造创建Hanndler会抛出异常;建议创建Handler的时候显式的指定Looper。
Looper的构造函数
Looper的沟通函数是private修饰的,实际在prepare方法中调用。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
public static void prepare() {
prepare(true);
}
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));
}
MessageQueue的构造函数
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
nativeMessageQueue->incStrong(env);
return reinterpret_cast<jlong>(nativeMessageQueue);
}
构造方法中干了两件事
1、quitAllowed设置了是否允许Message退出。
一般在自定义子线程开启Looper设置true;
比如Looper.prepare()方法默认传参数quitAllowed传的是true;
在Ui线程中的消息是不可退出的,ActivityThread的main方法中会调用;
Looper.prepareMainLooper,quitAllowed传的就是false,表示不可退出。
2、调用nativeInit方法初始化native层的nativeMessageQueue,之后再将生成的nativeMessageQueue,
通过reinterpret_cast方法将指针转化为long值返回给Framework层保存。
1.2、 消息入队
日常使用的new Handler().post()还是new Handler().postDelayed(),
都会调用到sendMessageDelayed(Message msg, long delayMillis),Runnable赋值给Message的callback字段;
继而调用enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis),核心实现都在enqueueMessage。
// Handler.sendMessageDelayed
public final boolean sendMessageDelayed(Message msg, long delayMillis){
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// Handler.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);
}
// Handler.enqueueMessage
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
// MessageQueue.enqueueMessage
boolean enqueueMessage(Message msg, long when) {
// 一些异常校验的逻辑
。。。
synchronized (this) {
//健壮性代码,Message正在退出
if (mQuitting) {
。。。
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// 当前消息队列空闲的
。。。
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
入队的核心实现干了两件事:(可以看出MessageQueue并不是一个严格意义上的队列结构;而是封装了消息入队存储相关逻辑的而已)
1、使用Message.next保存下一个Message,从而按照时间将所有的Message排序,从而达到入队的效果。
2、如果需要唤醒,则调用nativeWake方法来唤醒之前等待的线程。
1.3、消息分发
Looper.loop;
public static void loop() {
//...省略一波代码
final MessageQueue queue = me.mQueue;
//...省略一波代码
for (;;) {
Message msg = queue.next(); //取出Message
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//...省略一波代码
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
//省略一波代码target
msg.recycleUnchecked();
}
}
整个方法核心就干了两件事:
1、取出消息,Message msg = queue.next();
next实现优先取异步消息(View的绘制就用异步消息),上层应用只能使用同步消息。
2、用msg.target也就是Handler调dispatchMessage方法把消息分发下去。
1.3、消息处理Handler.dispatchMessage
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
public void handleMessage(@NonNull Message msg) {
}
private static void handleCallback(Message message) {
message.callback.run();
}
可以看出dispatchMessage核心就干了两件事
1、优先处理msg.callback,其实就是个Runnable;直接调run方法。
2、其实处理外面override或者直接传入的消息实现,override handleMessage和传入CallBack等价的。
1.4、Native部分的补充
经常翻阅Android源码的同学会发现,源码存在大量的native方法;深入系统原理很难脱离NDK编程;
native层的逻辑跟framework一致类似。弱化了NativeMessageQueue;基本实现都在NativeLooper中;
native层主要负责的是消息调度,何时阻塞线程、唤醒线程,避免不停的空循环导致的性能消耗过大。
native层消息接收
在 framework层的MessageQueue.next方法中,会调用一个native方法;
nativePollOnce(ptr, nextPollTimeoutMillis)来阻塞线程。
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jlong ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
mPollEnv = env;
mPollObj = pollObj;
mLooper->pollOnce(timeoutMillis);
mPollObj = NULL;
mPollEnv = NULL;
if (mExceptionObj) {
env->Throw(mExceptionObj);
env->DeleteLocalRef(mExceptionObj);
mExceptionObj = NULL;
}
}
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
while (mResponseIndex < mResponses.size()) {
//...省略一波代码
}
//...省略一波代码
result = pollInner(timeoutMillis);
}
}
pollInner(timeoutMillis)方法,这里就是最终的调用。
int Looper::pollInner(int timeoutMillis) {
//...
int result = POLL_WAKE;
//...
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
// No longer idling.
mPolling = false;
// Acquire lock.
mLock.lock();
//...省略一大波代码
for (int i = 0; i < eventCount; i++) {
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
if (fd == mWakeEventFd) {
if (epollEvents & EPOLLIN) {
awoken();//通过awoken唤醒线程
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
}
} else {
//...
}
}
// Release lock.
mLock.unlock();
//...
return result;
}
这里的核心逻辑非常简单,干了两件事:
1、epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis)方法获取到事件到个数;如果个数为0,就继续阻塞;
2、如果不为0;则会遍历每一个事件,如果有mWakeEventFd并且是EPOLLIN事件,就会通过awoken方法真正地唤醒线程。
总结
- Handler消息机制是framework的核心机制之一,面试经常遇到;
- 好记性不如烂笔头,写博客记录下胜过理论温习一次。