標(biāo)題是偽命題
參考資料 Android中為什么主線程不會因?yàn)長ooper.loop()里的死循環(huán)卡死眉踱? 知乎
之前對這個概念一直處于比較模糊的狀態(tài),也是一直被自己忽略了,認(rèn)為可能涉及的東西過于復(fù)雜,所以不敢對自己問,為什么?
這兩天狀態(tài)不錯,生活還是code比較有趣,簡單而真實(shí),所以曾經(jīng)被忽略的問題不經(jīng)意間又開始出現(xiàn)在腦海.
這個問題在我理解看來可以分為兩個問題
為什么主線程需要阻塞
何謂主線程,和其他線程有什么不同之處
- 何謂主線程
主線程,通常稱之為UI線程,也就是APP進(jìn)程被創(chuàng)建的時候所處的線程,和其他的線程一樣都是一個普通的線程. - 不同之處
從app角度來說,不同之處主要集中在UI界面更新上面,普通線程不能更新UI界面,如此設(shè)計也是為了程序的健壯性,畢竟不同線程同時對對象操作時為了保證其準(zhǔn)確性都要進(jìn)行加鎖,而界面更新不能像對象那樣僅僅保證準(zhǔn)確性,還要保證其連貫性,一個按鈕在同一段時間同步(假同步)
發(fā)生了向左又向右的滑動自然是不可取的.
線程的生命周期
一個進(jìn)程或線程在CPU看來無非就是一段的可執(zhí)行代碼,代碼執(zhí)行完畢,線程的生命也就到頭了.
APP的生命周期
從使用手機(jī)的角度來看,從點(diǎn)開APP圖標(biāo)開始,到完全退出APP結(jié)束.
主線程在哪里進(jìn)行了阻塞
我們知道APP的入口是在ActivityThread,一個Java類,有著main方法,而且main方法中的代碼也不是很多.
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
AndroidKeyStoreProvider.install();
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
這就是main方法的全部代碼了,23的源碼.在其中Looper進(jìn)行了初始化,但是并不是常規(guī)的prepare(),而是prepareMainLooper(),其實(shí)差別不大,只不過是給靜態(tài)成員對象成員sMainLooper進(jìn)行了初始化賦值,并不準(zhǔn)予同進(jìn)程中sMainLooper初始化第二次而已.
然后在代碼末尾Looper.loop進(jìn)行阻塞.
標(biāo)題為什么是偽命題
我們都知道主線程是隨著APP的啟動而啟動,隨著APP的結(jié)束而結(jié)束的(多進(jìn)程對應(yīng)多主線程的情況我就將其看做一個統(tǒng)一的主線程)
.
APP要一直運(yùn)行直到用戶退出,那么主線程就必然不能代碼運(yùn)行完畢而終止,所以需要進(jìn)行阻塞,直到用戶退出了APP,才能停止阻塞,讓CPU執(zhí)行完剩下的代碼,爾后代碼執(zhí)行完畢,主線程從而壽終正寢.
為什么主線程阻塞還能更新UI
既然線程是在Looper中阻塞了,那么與Looper配合著出現(xiàn)的Handler肯定是少不了的.
至于Handler是如何進(jìn)行線程切換不了解的同學(xué)請戳這
ActivityThread.H
很容易就在Activity中找到了繼承自Handler的內(nèi)部類H,并且重寫了handleMessage方法,代碼就不列出了.
ActivityThread.H怎么和Looper交互的
光有Handler是不行的,關(guān)鍵要有調(diào)用Handler的地方,然后Handler才能去處理,才會在主線程調(diào)用一個又一個方法.
答案在這!
ActivityThread thread = new ActivityThread();
thread.attach(false);
進(jìn)一步來看attach()方法
private void attach(boolean system) {
...
if (!system) {
...
final IActivityManager mgr = ActivityManagerNative.getDefault();
try {
mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
// Ignore
}
...
} else {
...
}
...
}
恩,想必你們都知道我想看什么了,這里傳入了對象mAppThread,我只關(guān)心mAppThread對象,而他作為參數(shù)最終通過IPC傳遞到哪里去,能力有限就不再繼續(xù)跟進(jìn)了.
ApplicationThread
上面我們說到了mAppThread對象,那么這個對象是哪個對象呢?就是ApplicationThread的實(shí)例化對象,代碼不多,也就500來行,我就截取一點(diǎn)點(diǎn)來示例一下
public final void schedulePauseActivity(IBinder token, boolean finished,
boolean userLeaving, int configChanges, boolean dontReport) {
sendMessage(
finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY,
token,
(userLeaving ? 1 : 0) | (dontReport ? 2 : 0),
configChanges);
}
public final void scheduleStopActivity(IBinder token, boolean showWindow,
int configChanges) {
sendMessage(
showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,
token, 0, configChanges);
}
public final void scheduleCreateService(IBinder token,
ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
updateProcessState(processState, false);
CreateServiceData s = new CreateServiceData();
s.token = token;
s.info = info;
s.compatInfo = compatInfo;
sendMessage(H.CREATE_SERVICE, s);
}
public final void scheduleBindService(IBinder token, Intent intent,
boolean rebind, int processState) {
updateProcessState(processState, false);
BindServiceData s = new BindServiceData();
s.token = token;
s.intent = intent;
s.rebind = rebind;
if (DEBUG_SERVICE)
Slog.v(TAG, "scheduleBindService token=" + token + " intent=" + intent + " uid="
+ Binder.getCallingUid() + " pid=" + Binder.getCallingPid());
sendMessage(H.BIND_SERVICE, s);
}
聰明的同學(xué)已經(jīng)明了,主線程阻塞之后生命周期等方法是如何啟用的.
這一個個形似各種聲明周期的方法,最終還調(diào)用了sendMessage()方法,讓我們再來看看sendMessage方法的是怎么操作的
private void sendMessage(int what, Object obj) {
sendMessage(what, obj, 0, 0, false);
}
private void sendMessage(int what, Object obj, int arg1) {
sendMessage(what, obj, arg1, 0, false);
}
private void sendMessage(int what, Object obj, int arg1, int arg2) {
sendMessage(what, obj, arg1, arg2, false);
}
private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
if (DEBUG_MESSAGES) Slog.v(
TAG, "SCHEDULE " + what + " " + mH.codeToString(what)
+ ": " + arg1 + " / " + obj);
Message msg = Message.obtain();
msg.what = what;
msg.obj = obj;
msg.arg1 = arg1;
msg.arg2 = arg2;
if (async) {
msg.setAsynchronous(true);
}
mH.sendMessage(msg);
}
可以看到最終調(diào)用了mH.sendMessage()方法,而mH是誰呢?ActivityThread的成員變量是ActivityThread.H的實(shí)例化對象.
總結(jié)
到此想必各位也就明了,主線程確實(shí)是阻塞的,不阻塞那APP怎么能一直運(yùn)行,所以說主線程阻塞是一個偽命題,只不過是沒有弄明白既然阻塞了,為什么還能調(diào)用各種聲明周期而已.
調(diào)用生命周期是因?yàn)橛蠰ooper,有MessageQueue,還有溝通的橋梁Handler,通過IPC機(jī)制調(diào)用Handler發(fā)送各種消息,保存到MessageQueue中,然后在主線程中的Looper提取了消息,并在主線程中調(diào)用Handler的方法去處理消息.最終完成各種聲明周期.
文章到此結(jié)束,順便給自己打個小廣告,深圳求職,目前在職招人頂缸中(ps:找個人頂缸真不好招...全是假簡歷)
簡歷戳我
擴(kuò)展的知識點(diǎn)
IPC機(jī)制下的startService流程分析
為什么選擇Binder為什么 Android 要采用 Binder 作為 IPC 機(jī)制挤忙?