共计 8080 个字符,预计需要花费 21 分钟才能阅读完成。
HandlerThread 置信大家都比拟相熟了,从名字上看是一个带有 Handler 音讯循环机制的一个线程,比个别的线程多了音讯循环的机制,能够说是 Handler+Thread 的联合,从源码上看也是如此的设计,个别状况下如果须要子线程和主线程之间互相交互,能够用 HandlerThread 来设计,这比单纯的 Thread 要不便,而且更容易治理,因为大家都晓得 Thread 的生命周期在一些状况下是不可管制的,比方间接 new Thread().start(), 这种形式在我的项目中是不举荐应用的,实际上 Android 的源码中也有很多中央用到了 HandlerThread, 上面我将剖析一下 HandlerThread 以及波及到的一些其余相干的问题。
HandlerThread 的源码简略剖析
int mPriority;
int mTid = -1;
Looper mLooper;
private @Nullable Handler mHandler;
public HandlerThread(String name) {super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
首先字段有优先级,能够看到 HandlerThread 外面的优先级是默认的,当然了,你也能够批改,调用有二个参数的构造函数就能够了,在构造函数外面有个 String 类型,代表 HandlerThread 名字的,这个个别状况下能够取线程的名称。
/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
protected void onLooperPrepared() {}
@Override
public void run() {mTid = Process.myTid();
Looper.prepare();
synchronized (this) {mLooper = Looper.myLooper();
notifyAll();}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
这外面有一个办法 onLooperPrepared(),在理论中,咱们能够重写这个办法做一些初始化的操作,这个 run() 是重点,能够看到 Looper 进行了初始化并且开始循环接管音讯了,并且唤醒了以后所有期待的线程,因为 run 办法是在 start 之后才会启动的,因而在用 HandlertThread 的时候,在初始化了实例之后就必须调用 start 办法开启音讯循环了。
public Looper getLooper() {if (!isAlive()) {return null;}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {while (isAlive() && mLooper == null) {
try {wait();
} catch (InterruptedException e) {}}
}
return mLooper;
}
这个办法是获取以后的 looper,能够看到如果没有获取的时候就始终期待直到获取,而后面也提到了获取到了就唤醒了所有的线程,看来这是线程的期待 - 唤醒机制利用。
public Handler getThreadHandler() {if (mHandler == null) {mHandler = new Handler(getLooper());
}
return mHandler;
}
这个是获取 HandlerThread 绑定的 Looper 线程的 Handler
public boolean quit() {Looper looper = getLooper();
if (looper != null) {looper.quit();
return true;
}
return false;
}
public boolean quitSafely() {Looper looper = getLooper();
if (looper != null) {looper.quitSafely();
return true;
}
return false;
}
能够看到这两个办法去退出线程的 Looper 循环,那么这两个办法有什么区别呢, 实际上都是调用了 MessageQueue 的 quit() 办法,源码如下:
void quit(boolean safe) {if (!mQuitAllowed) {throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {if (mQuitting) {return;}
mQuitting = true;
if (safe) {removeAllFutureMessagesLocked();
} else {removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
能够看到: 当咱们调用 quit 办法的时候,实际上执行了 MessageQueue 中的 removeAllMessagesLocked 办法,该办法的作用是把 MessageQueue 音讯池中所有的音讯全副清空,无论是提早音讯(提早音讯是指通过 sendMessageDelayed 或通过 postDelayed 等办法发送的须要提早执行的音讯, 只有不是立刻执行的音讯都是提早音讯)还是非提早音讯。
而 quitSafely 办法时,实际上执行了 MessageQueue 中的 removeAllFutureMessagesLocked 办法,通过名字就能够看出,该办法只会清空 MessageQueue 音讯池中所有的提早音讯,并将音讯池中所有的非提早音讯派发进来让 Handler 去解决,quitSafely 相比于 quit 办法平安之处在于清空音讯之前会派发所有的非提早音讯, 一句话,就是革除将来须要执行的音讯。
这两个办法有一个独特的特点就是:Looper 不再接管新的音讯了, 音讯循环就此结束,此时通过 Handler 发送的音讯也不会在放入音讯杜队列了,因为音讯队列曾经退出了。利用这 2 个办法的时候须要留神的是:quit 办法从 API 1 就开始存在了,比拟早,而 quitSafely 直到 API 18 才增加进来.
到此源码剖析完结,上面看一个理论的简略的例子:
private static class UserHandlerThread extends HandlerThread implements Handler.Callback {
private Handler mHandler;
public UserHandlerThread(String name) {super(name);
start();
mHandler = new Handler(getLooper(), this);
}
@Override
protected void onLooperPrepared() {super.onLooperPrepared();
}
@Override
public boolean handleMessage(Message msg) {if (msg.what==0x123){Log.d("[app]","收到音讯了");
}
return false;
}
public Handler getHandler() {return mHandler;}
}
private UserHandlerThread handlerThread;
handlerThread = new UserHandlerThread("handlerThread");
handlerThread.getHandler().sendEmptyMessage(0x123);
OK,大家都晓得后果了吧,个别状况下,在 OnDestroy 办法中须要退出循环,比方上面代码:
@Override
protected void onDestroy() {super.onDestroy();
if (handlerThread != null) {handlerThread.getHandler().removeCallbacksAndMessages(null);
handlerThread.quit();
handlerThread = null;
}
}
//Looper.getMainLooper().quit();
大家先疏忽正文的那句代码, 好了,上面剖析 3 个波及到相干的问题:
● 既然子线程能够退出音讯循环队列,那么 UI 线程,也就是主线程是否也一样能够在 onDestroy 办法退出音讯循环呢, 就是勾销方才代码的正文.
先说答案: 不能退出主线程的音讯队列,不然会抛出 Main thread not allowed to quit. 谬误,是不是很相熟,没错,下面的代码中曾经贴出来了,为什么呢,MessageQueue 有一个字段:mQuitAllowed
// True if the message queue can be quit.
private final boolean mQuitAllowed;
这个字段代表:是否能够退出音讯队列的意思,那么主线程的这个字段是什么呢,是 false,就是不能退出以后音讯的音讯队列的意思,来看主线程的音讯初始化代码: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();}
}
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));
}
private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();}
能够看到 prepare 参数中,主线程传入的是 false,而在 Looper 的构造函数外面被赋值了,也就是主线程的是 false,所以这就决定了主线程是不能退出音讯队列,那为什么子线程就能够退出音讯队列呢,因为子线程传入的是 true,代码如下:
Looper.prepare();
public static void prepare() {prepare(true);
}
看到没,子线程传入的是 true,这阐明子线程是能够退出音讯队列的,实际上真正退出主线程的音讯队列是 FrameWork 层做的事件,上面的第三个问题会提到。
● Handler 外面收回去的音讯,到底是在哪个线程执行的呢?
本文并不打算长篇剖析 Handler 的源码,只简略探讨跟 Handler 线程相干的问题,实际上,对于 Handler,如果能了解上面的图,而后再本人去剖析源码,我想了解会更深
咱们晓得,handler 的构造函数中有一个参数,就是设置以后 Looper 的,代码如下:
/**
* Use the provided {@link Looper} instead of the default one.
*
* @param looper The looper, must not be null.
*/
public Handler(Looper looper) {this(looper, null, false);
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
能够看到传入的 Looper 最终会被赋值到 mLooper 字段, 如果没有调用这个构造函数,那么 mLooper 由以后线程获取,而音讯队列 MessageQueue 是由 Looper 失去的, 换句话说:Handler 的音讯在哪个 Looper 循环,那意味着所有的音讯将会被哪个 Looper 的音讯队列所存储和解决,而一个线程只能绑定一个 Looper, 因而一个 Handler 只能绑定一个 Looper,也就意味着,Handler 绑定了哪个线程的 Looper,那么所有的音讯都会在哪个线程执行,这跟下面的图是统一的,当然了有同学说我在 Handler 外面开线程就另外说了,但 Handler 操作的自身肯定是在对应的 Looper 所在的线程的, 就是如果 Handler 绑定了主线程的 Looper, 那么所有的音讯都会在主线程解决,如果绑定的是子线程,那么所有的音讯都会在子线程解决。
●在 Android 一个应用程序 ” 退出 ” 的真正含意是什么呢?
对于这个问题,有同学可能说我杀掉过程,应用程序挂了,也就相当于退出了。实际上这个说法是不正确的,因为这里的杀掉过程,不仅仅是程序自身,而且连程序的内存空间也一并被解决,咱们后面说了,子线程能够退出音讯队列,意味着子线程就再也无奈接管到任何音讯了,这就是子线程的退出含意,实际上,主线程也是一样的,只是这个过程对咱们开发者不可见而已,在主线程中,如果退出了音讯队列,那么意味着主线程也无奈接管到任何音讯,上面是代码, 在 ActivityThread.java 外面:
public final void scheduleExit() {sendMessage(H.EXIT_APPLICATION, null);
}
case EXIT_APPLICATION:
if (mInitialApplication != null) {mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
看到这里,我想相熟 IPC 过程的同学应该都晓得了,没错,scheduleExit() 不是本地过程调用的,而是由服务端过程 ActivityAManagerService 服务进行调用的,这也是我为什么说退出主线程是由 FrameWork 调用的起因,在 AMS 外面有 2 处中央调用了退出的代码,别离是绑定本地过程和内存回收工作的时候调用的,上面是代码 ( 在 ActivityManagerService.Java 外面):
在 APP 本地过程绑定到 AMS 过程的时候调用
private final boolean attachApplicationLocked(IApplicationThread thread,
int pid) {
// Find the application record that is being attached... either via
// the pid if we are running in multiple processes, or just pull the
// next app record if we are emulating process with anonymous threads.
ProcessRecord app;
if (pid != MY_PID && pid >= 0) {synchronized (mPidsSelfLocked) {app = mPidsSelfLocked.get(pid);
}
} else {app = null;}
if (app == null) {
Slog.w(TAG, "No pending application record for pid" + pid
+ "(IApplicationThread" + thread + "); dropping process");
EventLog.writeEvent(EventLogTags.AM_DROP_PROCESS, pid);
if (pid > 0 && pid != MY_PID) {Process.killProcessQuiet(pid);
//TODO: killProcessGroup(app.info.uid, pid);
} else {
try {
// 这里也调用了
thread.scheduleExit();} catch (Exception e) {// Ignore exceptions.}
}
return false;
}
// 省略一些非必要代码
}
// i 清理内存的时候调用
final void trimApplications() {synchronized (this) {
int i;
// First remove any unused application processes whose package
// has been removed.
for (i=mRemovedProcesses.size()-1; i>=0; i--) {final ProcessRecord app = mRemovedProcesses.get(i);
if (app.activities.size() == 0
&& app.curReceiver == null && app.services.size() == 0) {
Slog.i(
TAG, "Exiting empty application process"
+ app.toShortString() + "("
+ (app.thread != null ? app.thread.asBinder() : null)
+ ")\n");
if (app.pid > 0 && app.pid != MY_PID) {
// 杀过程
app.kill("empty", false);
} else {
try {
// 这里调用了退出主线程音讯队列代码
app.thread.scheduleExit();} catch (Exception e) {// Ignore exceptions.}
}
cleanUpApplicationRecordLocked(app, false, true, -1, false /*replacingPid*/);
mRemovedProcesses.remove(i);
if (app.persistent) {addAppLocked(app.info, false, null /* ABI override */);
}
}
}
// Now update the oom adj for all processes.
updateOomAdjLocked();}
}
看到了吧,在首次 App 绑定过程的时候,如果产生 app==null 这个谬误的时候就调用了退出主线程音讯队列,另一个就是在清理内存的时候调用,这两个过程对咱们开发者不可见,咱们理解就好, 在源码背后,并没有什么是神奇的中央。
感激大家浏览,如果有什么谬误的中央,请欢送指出,谢谢。
相干视频举荐:
【安卓面试合集】5-FrameWork 源码之 handler 源码解析 (一)-01
【安卓面试合集】6-FrameWork 源码之 handler 源码解析 (一)-02
本文转自 https://juejin.cn/post/6844903526468943886,如有侵权,请分割删除。