1、前言
在 Android 开发中,Handler 的机制和运行原理这方面的常识能够说是每个人都需求了解的。这不仅是由于 Handler 是 Android 运用的基石之一,也由于 Handler 全体设计上也是十分优秀的。接下来我就整理总结一下常见的 Handler 相关常识点。
2、根本运用(GPT)
-
创立Handler目标:要运用Handler,首要需求创立一个Handler目标。Handler能够在UI线程或其他线程中创立,但一般在UI线程中创立,以便将音讯发送到UI线程。
Handler handler = new Handler();
-
发送音讯:要将音讯发送到Handler,能够运用Handler的
post
办法或sendMessage
办法。一般,您将运用post
办法履行一个Runnable任务。handler.post(new Runnable() { @Override public void run() { // 在UI线程履行的任务 // 能够更新UI元素 } });
或许运用
sendMessage
办法:Message message = handler.obtainMessage(); message.what = MY_MESSAGE_CODE; handler.sendMessage(message);
-
处理音讯:在Handler地点的线程中,能够覆盖
handleMessage
办法来处理音讯。一般,您需求继承Handler
类并重写handleMessage
办法。class MyHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MY_MESSAGE_CODE: // 处理音讯 break; // 能够处理更多不同音讯类型 } } }
-
相关Handler与Looper:Handler需求与Looper(音讯循环)相关,以便能够在音讯行列中接纳和处理音讯。一般,UI线程现已有一个与之相关的Looper,所以在UI线程中创立Handler不需求额外装备。但如果您在其他线程中创立Handler,需求先创立一个Looper。
Looper.prepare(); // 创立一个新的Looper Handler handler = new Handler(); // 相关Handler与新的Looper Looper.loop(); // 开始音讯循环,有必要调用以使Looper活动
-
从后台线程向UI线程发送音讯:一般情况下,Handler最常用于在后台线程履行任务后更新UI线程。例如,如果您在后台线程中进行网络请求,请求完成后,能够运用Handler将成果传递给UI线程以更新UI元素。
new Thread(new Runnable() { @Override public void run() { // 后台线程履行任务 // ... // 任务完成后,运用Handler将成果传递给UI线程 handler.post(new Runnable() { @Override public void run() { // 更新UI } }); } }).start();
这些是Android Handler的根本用法。Handler是Android中处理异步任务和多线程通信的重要工具,能够保证UI更新等操作在UI线程中履行,从而防止运用程序溃散或出现不稳定行为。
3、流程整理
从 2 中能够看出 Handler 有两种发送信息的办法。第一种是发送 Message;第二种是直接 post runnable。咱们分别看下两种办法的源码处理。
3.1 获取 Message 的办法
发送 Message 首要需求获取一个 Message,当然能够直接 new 一个目标出来,可是也能够经过 Message.obtain() 办法来获取一个音讯池里边的音讯目标。
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
可见这种办法能够削减内存分配和废物收回的开支,由于它防止了频频创立和销毁 Message 目标,而是重复运用已有目标。这在Android中的音讯处理机制中非常有用,由于一般会有很多的音讯目标需求创立和处理,如Handler中的音讯行列。因此,Message.obtain 办法的运用办法类似于享元模式,经过同享可复用的目标来削减体系资源的耗费,进步性能。这有助于更有效地办理Android运用程序中的音讯处理。
3.2 压入音讯行列
这儿边接着往下看,会经过 Message.enqueueMessage 将这个音讯压入音讯行列中。这儿就要说下这个 Looper 的获取办法了。检查代码能够看到 Looper 是从 ThreadLocal 里边获取到的。ThreadLocal 保证了在每个线程内只要一个 Looper 目标。到这儿音讯现已进入音讯行列中了。
final MessageQueue mQueue;
public Handler(@Nullable Callback callback, boolean async) {
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;
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
// Looper.java
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
接下来就是取音讯的过程,取音讯的办法是在 Looper.java 的 loop 办法中,如果是在子线程运用的情况下需求自己手动发动 Looper.loop 办法开启轮询。主线程中则是由体系在 ActivityThread.java 的 main 办法里边为咱们开启了轮询。
public static void main(String[] args) {
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
接着看 loop 办法,这儿边是一个死循环,会一直从音讯行列中获取音讯。获取到了后会履行 msg.target.dispatchMessage(msg); 办法。能够看到在 android30 里边现已体系现已集成了检测耗时音讯的机制(logSlowDelivery 相关代码)。
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
me.mInLoop = true;
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;
final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
Object token = null;
if (observer != null) {
token = observer.messageDispatchStarting();
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logSlowDelivery) {
if (slowDeliveryDetected) {
if ((dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
slowDeliveryDetected = false;
}
} else {
if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
msg)) {
// Once we write a slow delivery log, suppress until the queue drains.
slowDeliveryDetected = true;
}
}
}
if (logSlowDispatch) {
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
这儿边 target 在 enqueueMessage 现已设置成了发送 handler。所以履行逻辑会回到 handler 的 dispatchMessage 办法里边。
3.3 音讯履行
这儿边能够先看一下 post runnable 办法。其实仍是发送的 callback 是 runnable 的 Message。所以处理流程都是一致的。
// Handler.java
public void dispatchMessage(@NonNull Message msg) {
// 1、如果 msg 存在 callback 则直接履行,post 办法都会走到这儿
if (msg.callback != null) {
handleCallback(msg);
} else {
// 2、mCallback 不为空则进入这儿处理,这个 mCallback 能够经过构造办法传入
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 3、最后会走到自身 handleMessage 办法,这个办法能够经过继承重写
handleMessage(msg);
}
}
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
走到这儿,能够发现咱们的 Handler 机制在 Java 层现已完全整理一遍了。下面持续看下 native 层的部分。这儿就引入了一个经典问题:那就是主线程 loop 办法是死循环,体系为什么不会卡死呢?
从源码能够看到 loop 办法里边调用了音讯行列的 next 办法,这儿边会调用持续调用 native 的 nativePollOnce(ptr, nextPollTimeoutMillis); 办法。这儿边会经过 epoll 机制,当等候音讯的时候,会开释体系资源,当被唤醒时再持续操作。唤醒操作 nativeWake(mPtr); 。
// 0 当即回来,2000 等候 2s,-1 永久休眠
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);
}
// 底层经过 epoll 的办法监听读端,会进入等候,等候写入端有数据写入
int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
// 刺进音讯的时候,会调用 wake 办法,会写入了 1,唤醒
uint64_t inc = 1;
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
3.4 音讯屏障
音讯屏障的典型用例是在UI线程中履行UI更新,以保证UI更新的操作依照它们被提交的次序履行。例如,如果在后台线程中进行了多次UI更新,并将这些更新音讯发送到UI线程的音讯行列中,能够运用 sendMessageAtFrontOfQueue 办法来保证这些UI更新依照它们被发送的次序履行,从而防止UI显示的不一致性。经过 sendMessageAtFrontOfQueue 办法会将时间设置为 0,在进入音讯行列的过程中,会直接刺进到行列头部,所以能够保证履行优先级较高。
3.5 IdleHandler
IdleHandler
是Android中的一个回调接口,它用于在主线程闲暇时履行任务。当主线程没有处理音讯时(即处于闲暇状况),IdleHandler
中的回调办法将被触发,答应您履行一些耗时较长的任务,而不会影响到UI的响应性。这在某些情况下非常有用,例如在后台预加载数据或履行其他非UI相关的工作。
// 创立一个IdleHandler
MessageQueue.IdleHandler idleHandler = new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
// 在主线程闲暇时履行的任务
// 能够履行一些耗时操作,不会堵塞UI线程
return false; // 回来true表明持续监听,false表明不再监听
}
};
// 注册IdleHandler
Looper.myQueue().addIdleHandler(idleHandler);
这块处理逻辑是在 MessageQueue 的 next 办法内部。
4、总结
到这儿根本整理了 Handler 的一些运用和原理,尽管各种结构和 Kotlin 都能够很便利的履行切换线程的操作了,可是这些原理性的东西仍是值得咱们学习并了解的。