前言
相信做應(yīng)用層業(yè)務(wù)開發(fā)的同學(xué),都跟我一樣,對(duì)Framework”深惡痛絕“。確實(shí)如此,如果平日里都在做應(yīng)用層的開發(fā),那么基本上我們很少會(huì)去碰Framework的知識(shí)。但生活所迫,面試總是逃不過這一關(guān)的,所以作為一名合格的打工人,我們還是必須得具備一些Framework的基本知識(shí)。
網(wǎng)上有很多文章或淺或深地講了Framework,有很多源碼大家肯定也都看過不止一兩次,但過一段時(shí)間就忘記了。我這篇文章將會(huì)從應(yīng)用層開發(fā)的視角,羅列下我認(rèn)為需要掌握的Framework基本知識(shí)。為了更方便大家去記憶與鞏固,我更多的是從流程上進(jìn)行講解,而不會(huì)對(duì)源碼進(jìn)行非常深入的解讀,文章會(huì)比較長(zhǎng),大家可以當(dāng)做一個(gè)知識(shí)小冊(cè),根據(jù)目錄針對(duì)性地進(jìn)行瀏覽(基于Android 8.0/9.0)。
那話不多說,我們沖!
一、系統(tǒng)啟動(dòng)流程
Zygote進(jìn)程
Zygote進(jìn)程是非常重要的一個(gè)進(jìn)程,它負(fù)責(zé)Android系統(tǒng)中虛擬機(jī)(DVM或者ART)的創(chuàng)建、應(yīng)用進(jìn)程的創(chuàng)建以及SystemServer進(jìn)程的創(chuàng)建。
系統(tǒng)在開機(jī)后會(huì)啟動(dòng)init進(jìn)程,init進(jìn)程進(jìn)而會(huì)fork出Zygote進(jìn)程。Zygote進(jìn)程在啟動(dòng)時(shí),主要做了這么幾件事情:
- 創(chuàng)建虛擬機(jī)。當(dāng)Zygote以fork自身的方式去創(chuàng)建應(yīng)用進(jìn)程和SystemServer進(jìn)程時(shí),它們就能拿到虛擬機(jī)的一個(gè)副本。
- 作為Socket服務(wù)端監(jiān)聽AMS請(qǐng)求創(chuàng)建進(jìn)程
- 啟動(dòng)SystemServer進(jìn)程
SystemServer進(jìn)程
SystemServer進(jìn)程是我們學(xué)習(xí)Framework中最重要的一個(gè)系統(tǒng)級(jí)進(jìn)程,我們熟知的很多服務(wù)(AMS、WMS、PMS等)都屬于它提供的一個(gè)服務(wù),是與我們APP進(jìn)程通信最頻繁的進(jìn)程。
SystemServer進(jìn)程在啟動(dòng)時(shí),主要做了這么幾件事情:
- 啟動(dòng)Binder線程池,為進(jìn)程間通信做準(zhǔn)備
- 啟動(dòng)各種系統(tǒng)服務(wù),比如AMS、WMS、PMS
Launcher進(jìn)程
Launcher進(jìn)程實(shí)際上就是我們的桌面進(jìn)程,熟悉它的同學(xué)都知道一頁桌面本質(zhì)上就是一個(gè)Grid類型的RecylerView,那它是怎么創(chuàng)建的呢?
這就是系統(tǒng)啟動(dòng)的最后一步,當(dāng)SystemServer進(jìn)程啟動(dòng)了AMS服務(wù)后,AMS會(huì)啟動(dòng)Launcher進(jìn)程。Launcher進(jìn)程通過與PMS服務(wù)通信,獲取到機(jī)器上所有的安裝包信息后,將數(shù)據(jù)(APP圖標(biāo)、APP名稱等)渲染到桌面上。
小結(jié):
系統(tǒng)啟動(dòng)流程可以用下圖概括:

二、應(yīng)用進(jìn)程啟動(dòng)流程
前面我們講了SystemServer進(jìn)程與桌面進(jìn)程是如何啟動(dòng)的,等它們啟動(dòng)好了之后,就可以從桌面啟動(dòng)我們的應(yīng)用進(jìn)程了。
在桌面點(diǎn)擊APP圖標(biāo)后,Launcher進(jìn)程將會(huì)向AMS請(qǐng)求創(chuàng)建APP的啟動(dòng)頁面。AMS識(shí)別到APP進(jìn)程不存在之后,就會(huì)用Socket的方式向Zygote進(jìn)程請(qǐng)求創(chuàng)建APP進(jìn)程。Zygote通過fork的方式創(chuàng)建APP進(jìn)程之后,會(huì)反射調(diào)用android.app.ActivityThread的main()方法,初始化ActivityThread。
ActivityThread,可以理解為管理APP主線程任務(wù)的一個(gè)類。在main()方法中初始化時(shí),我們會(huì)初始化主線程的消息循環(huán),從而接收主線程需要處理的所有消息,保證UI在主線程的渲染(消息機(jī)制后文會(huì)詳細(xì)介紹)。
public static void main(String[] args) {
...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);//注釋1
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler(); //注釋2
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
}
private void attach(boolean system, long startSeq) {
...
if (!system) {
...
final IActivityManager mgr = ActivityManager.getService();
try {
mgr.attachApplication(mAppThread, startSeq); // 注釋3
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
...
} else {
...
try {
mInstrumentation = new Instrumentation();
mInstrumentation.basicInit(this);
ContextImpl context = ContextImpl.createAppContext(
this, getSystemContext().mPackageInfo);
mInitialApplication = context.mPackageInfo.makeApplication(true, null);
mInitialApplication.onCreate();
} catch (Exception e) {
throw new RuntimeException(
"Unable to instantiate Application():" + e.toString(), e);
}
}
...
}
可以看到注釋2處會(huì)創(chuàng)建主線程Handler,這里的Handler類名為H,屬于ActivityThread的內(nèi)部類。后面會(huì)多次提到它。
注釋1處調(diào)用attach方法時(shí),傳入的system參數(shù)為false,所以是會(huì)到注釋3處的代碼的,這里又回到了AMS,調(diào)用AMS的attachApplication方法。attachApplication方法又會(huì)一直調(diào)用到ApplicationThread的bindApplication方法,最終一直到ActivityThread.handleBindApplication方法:
private void handleBindApplication(AppBindData data) {
...
try {
final ClassLoader cl = instrContext.getClassLoader();
// 注釋1
mInstrumentation = (Instrumentation)
cl.loadClass(data.instrumentationName.getClassName()).newInstance();
} catch (Exception e) {
throw new RuntimeException(
"Unable to instantiate instrumentation "
+ data.instrumentationName + ": " + e.toString(), e);
}
final ComponentName component = new ComponentName(ii.packageName, ii.name);
mInstrumentation.init(this, instrContext, appContext, component,
data.instrumentationWatcher, data.instrumentationUiAutomationConnection);
...
try {
...
try {
// 注釋2
app = data.info.makeApplication(data.restrictedBackupMode, null);
...
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
// 注釋3
installContentProviders(app, data.providers);
...
}
}
// 注釋4
mInstrumentation.onCreate(data.instrumentationArgs);
} catch (Exception e) {
throw new RuntimeException(
"Exception thrown in onCreate() of "
+ data.instrumentationName + ": " + e.toString(), e);
}
try {
// 注釋5
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
if (!mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}
}
...
}
注釋1處會(huì)初始化Instrumentation,Instrumentation類非常重要,后文會(huì)介紹到。這里主要是用它在注釋2、注釋4、注釋5處創(chuàng)建Application并調(diào)用Application的onCreate生命周期。創(chuàng)建Application時(shí)會(huì)先調(diào)用Application的attachBaseContext方法。另外,注釋3處我們可以看到,隨著應(yīng)用進(jìn)程的啟動(dòng),ContentProviders也會(huì)被啟動(dòng)。
小結(jié):
應(yīng)用進(jìn)程啟動(dòng)流程可以用下圖概括:

三、Activity啟動(dòng)過程
上一節(jié)我們已經(jīng)將APP進(jìn)程成功啟動(dòng),但我們的頁面還沒有起來,這一節(jié)我們就講一下Activity是如何啟動(dòng)的。
讓我們回憶一下,上一節(jié)最開始是Launcher進(jìn)程請(qǐng)求AMS創(chuàng)建APP的啟動(dòng)頁面,那么Launcher進(jìn)程的桌面實(shí)際上也是一個(gè)Activtiy,它啟動(dòng)我們的啟動(dòng)頁面,也是調(diào)用的Activity#startActivity()方法。一層層進(jìn)去后,我們可以發(fā)現(xiàn),實(shí)際上是調(diào)用的Instrumentation的execStartActivity()方法:
#Activity
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
if (mParent == null) { //注釋1
options = transferSpringboardActivityOptions(options);
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options); //注釋2
……
} else {
if (options != null) {
mParent.startActivityFromChild(this, intent, requestCode, options);
} else {
// Note we want to go through this method for compatibility with
// existing applications that may have overridden it.
mParent.startActivityFromChild(this, intent, requestCode);
}
}
}
注釋1處可以看到,mParent代表著上一個(gè)頁面,打開啟動(dòng)頁面時(shí)mParent為null,調(diào)用注釋2處Instrumentation的execStartActivity()方法。 Instrumentation不止執(zhí)行startActivity,它還負(fù)責(zé)了所有Activity生命周期的調(diào)用:

但它也不是真正的執(zhí)行者,它只是包裝了一下,為什么要這樣包裝呢?個(gè)人理解是因?yàn)镮nstrumentation需要對(duì)這些行為加一下監(jiān)控,它的成員變量mActivityMonitors就是這個(gè)作用。
我們?cè)龠M(jìn)去看Instrumentation的execStartActivity()方法:
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
……
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
int result = ActivityManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options); //注釋1
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
注釋1處實(shí)際上是獲取了ActivityManager.getService()去調(diào)用的startActivity。那這個(gè)ActivityManager.getService()又是何方神圣呢?再往下走
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);//注釋1
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
注釋1處我們可以看到ActivityManager.getService()最終拿到的就是IActivityManager。這段代碼采用的是AIDL(不了解的同學(xué)可以自行學(xué)習(xí)一下),拿到AMS在APP進(jìn)程的代理對(duì)象IActivityManager。那么最終調(diào)用的startActivity也就是AMS的startActivity了。
AMS在startActivity的過程中也會(huì)進(jìn)行一個(gè)比較長(zhǎng)的鏈路,主要是校驗(yàn)權(quán)限、處理ActivityRecord、Activity任務(wù)棧等,這里不細(xì)說,最終會(huì)調(diào)用到app.thread.scheduleLaunchActivity方法。app.thread實(shí)際上是IApplicationThread,跟之前的IActivityManager一樣,它也是一個(gè)代理對(duì)象,代理的是app進(jìn)程的ApplicationThread。ApplicationThread屬于ActivityThread的內(nèi)部類,我們可以看它的scheduleLaunchActivity方法:
@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
updateProcessState(procState, false);
ActivityClientRecord r = new ActivityClientRecord(); // 注釋1
r.token = token;
r.ident = ident;
...
updatePendingConfiguration(curConfig);
sendMessage(H.LAUNCH_ACTIVITY, r); // 注釋2
}
注釋1處會(huì)將啟動(dòng)Activity的參數(shù)封裝成ActivityClientRecord。注釋2處發(fā)送消息給H。之所以發(fā)給H是因?yàn)锳pplicationThread本身是一個(gè)Binder對(duì)象,它執(zhí)行scheduleLaunchActivity時(shí)處于Binder線程中,所以我們需要通過H轉(zhuǎn)換到主線程中來。
H接收到LAUNCH_ACTIVITY消息后,會(huì)調(diào)用handleLaunchActivity方法:
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
...
Activity a = performLaunchActivity(r, customIntent);//注釋1
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
reportSizeConfigurations(r);
Bundle oldState = r.state;
handleResumeActivity(r.token, false, r.isForward,//注釋2
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
if (!r.activity.mFinished && r.startsNotResumed) {
...
performPauseActivityIfNeeded(r, reason);//注釋3
} else {
...
}
}
看注釋2與注釋3處的代碼,可以聯(lián)想到它們必然與Activity生命周期有關(guān),并且這里也解釋了為什么上一個(gè)頁面的onPause生命周期是在下一個(gè)頁面的onResume之后調(diào)用的。注釋1處performLaunchActivity方法很重要,我們來看一下:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
try {
// 注釋1
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
...
} catch (Exception e) {
...
}
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation); // 注釋2
...
// 注釋3
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
...
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme); // 注釋4
}
activity.mCalled = false;
// 注釋5
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
...
// 注釋6
if (!r.activity.mFinished) {
activity.performStart();
r.stopped = false;
}
...
// 注釋7
if (r.state != null || r.persistentState != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
r.persistentState);
}
} else if (r.state != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
}
}
...
}
注釋1處通過反射創(chuàng)建了Activity實(shí)例。注釋2處調(diào)用r.packageInfo.makeApplication創(chuàng)建Application,如果這里Application已經(jīng)在ActivityThread#handleBindApplication()階段創(chuàng)建了,就會(huì)直接返回。
注釋3處調(diào)用了Activity的attach方法,內(nèi)部會(huì)創(chuàng)建PhoneWindow綁定Activity。注釋4設(shè)置Activity的主題,注釋5調(diào)用Activity的onCreate()生命周期,注釋6處調(diào)用onStart()生命周期,注釋7當(dāng)Activity恢復(fù)時(shí)調(diào)用OnRestoreInstanceState()生命周期。再結(jié)合上面有提到的onResume()生命周期,我們Activity的啟動(dòng)過程就已經(jīng)講完了。當(dāng)然,這里走的是啟動(dòng)Activity的鏈路,非啟動(dòng)Activity的邏輯其實(shí)大差不差,大家有興趣可以自行看一下源碼。
小結(jié)
啟動(dòng)頁面啟動(dòng)過程中各進(jìn)程的交互關(guān)系:

啟動(dòng)頁面啟動(dòng)過程可用下圖概括:

四、Context
APP中到底有多少個(gè)Context呢?答案是Activity的數(shù)量+Service的數(shù)量+1,1是指Application。

如上圖所示,ContextImpl與ContextWrapper都繼承于Context,并且Context具體的實(shí)現(xiàn)類是ContextImpl,作為ContextWrapper的成員變量mBase存在。Activity、Service、Application都繼承于ContextWrapper,都屬于Context,但它們都是靠ContextImpl去實(shí)現(xiàn)Context相關(guān)的功能的。
ContextImpl具體的創(chuàng)建時(shí)機(jī)是在Activity、Service、Application創(chuàng)建的時(shí)候,調(diào)用attachBaseContext()方法,具體代碼這邊就不貼了,感興趣的同學(xué)可以自行查閱一下。
Application#getApplicationContext()方法獲取到的是什么呢?
# ContextImpl
@Override
public Context getApplicationContext() {
return (mPackageInfo != null) ?
mPackageInfo.getApplication() : mMainThread.getApplication();
}
最終調(diào)用到ContextImpl#getApplicationContext()方法,可以看到最終返回Context的是Application對(duì)象。而fragment#getContext()返回的Context是Activity對(duì)象。
五、View的工作原理
View與Window是相輔相成的兩個(gè)存在,本節(jié)先介紹View,涉及到Window的知識(shí)點(diǎn)可以先不管,下一節(jié)會(huì)詳細(xì)介紹??梢韵劝裌indow理解成畫布,View理解成畫,畫總是要在畫布上才能渲染的,也就是說View必須要借助于Window才能展示出來。
View的繪制流程
每一個(gè)View都會(huì)有一個(gè)ViewRootImpl對(duì)象,View繪制的起點(diǎn)就是ViewRootImpl的perfromTraversals方法,在perfromTraversals內(nèi)部會(huì)調(diào)用measure、layout、draw這三大流程。
measure是為了測(cè)量出View的尺寸大小,measure又會(huì)調(diào)用onMeasure,在onMeasure中又會(huì)先對(duì)子View進(jìn)行測(cè)量,最終得到整個(gè)View的大小。measure過程之后,我們就已經(jīng)可以調(diào)用View#getMeasuredWidth()與View#getMeasuredHeight()獲取到View的測(cè)量寬高了。
layout過程決定了View在父容器中的位置與實(shí)際寬高,也即是View的四個(gè)頂點(diǎn)坐標(biāo)的位置。layout與measure相反,layout是先得到父View的位置,再onLayout遞歸下去得到子View的位置的。layout過程后,View#getWidth()與View#getHeight()才有值,是View的實(shí)際寬高。
draw是繪制的最后一步,調(diào)用onDraw方法將View繪制在屏幕上。當(dāng)然,子View也是會(huì)一層一層遞歸(通過dispatchDraw方法)調(diào)用onDraw方法往下走的。
Measure
繪制流程中比較重要的個(gè)人感覺是measure方法,所以單拿出來說一說。我們?cè)诳磎easure相關(guān)方法的時(shí)候一定會(huì)看到MeasureSpec這個(gè)參數(shù),它是什么東西呢?
MeasureSpec是一個(gè)32位的int值,高2位代表SpecMode,低30位代表SpecSize。前者表示模式,后者表示大小,用1個(gè)值包含了兩種含義。我們可以通過MeasureSpec#makeMeasureSpec(size, mode)方法合成MeasureSpec,也可以通過MeasureSpec#getMode(spec)與MeasureSpec#getSize(spec)獲取到MeasureSpec中的Mode或者Size。只有生成了子View的MeasureSpec,我們才能夠通過調(diào)用子View的measure方法測(cè)量出子View的大小。
那么MeasureSpec是怎么創(chuàng)建的呢?
SpecMode有三類,分別是UNSPECIFIED、EXACTLY、AT_MOST三種。UNSPECIFIED不用管,是系統(tǒng)內(nèi)部使用的。那么什么時(shí)候是EXACTLY,什么時(shí)候是AT_MOST呢?有些同學(xué)可能知道,它的取值跟LayoutParams是有關(guān)系的。但需要注意的是,它不是完全由自身的LayoutParams決定的,LayoutParams需要和父容器一起才能決定View本身的SpecMode。當(dāng)View是DecorView時(shí),MeasureSpec根據(jù)窗口尺寸和自身的LayoutParams就能確定:
# ViewRootImpl
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
...
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); // 注釋1
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); // 注釋2
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
注釋1與注釋2處獲取的就是窗口尺寸與自身LayoutParams的尺寸。當(dāng)頂層View的MeasureSpec確認(rèn)后,在onMeasure方法中就會(huì)對(duì)下一層的View進(jìn)行測(cè)量,獲取子View的MeasureSpec。我們可以看一下ViewGroup的measureChildWithMargins方法,它在很多ViewGroup的onMeasure中都會(huì)被調(diào)用到:
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); // 注釋1
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width); // 注釋2
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec); // 注釋3
}
我們可以看到注釋1處首先是獲取到子View自身的LayoutParams。然后再在注釋2處根據(jù)父View的MeasureSpec以及padding、margin,生成了子View的MeasureSpec。最后再通過注釋3處調(diào)用measure測(cè)量出了子View的長(zhǎng)度。那么父View的MeasureSpec傳進(jìn)去有什么用呢?后面代碼有點(diǎn)長(zhǎng),我直接寫結(jié)論:
當(dāng)父View的MeasureMode是EXACTLY時(shí),子View的LayoutParams如果是MATCH_PARENT或者寫死的值,子View的MeasureMode是EXACTLY;子View的LayoutParams如果是WRAP_CONTENT,子View的MeasureMode是AT_MOST。
當(dāng)父View的MeasureMode是AT_MOST時(shí),子View的LayoutParams只有是寫死的值時(shí),子View的MeasureMode才會(huì)是EXACTLY,不然這種情況都是AT_MOST。
當(dāng)父View的MeasureMode是AT_MOST時(shí),子View的LayoutParams只有是寫死的值時(shí),子View的MeasureMode才會(huì)是EXACTLY,不然這種情況都是AT_MOST。` </pre>
有一個(gè)比較容易理解的方法是,AT_MOST通常對(duì)應(yīng)的LayoutParams是WRAP_CONTENT。我們可以想想,父View如果是WRAP_CONTENT,子View即使是MATCH_CONTENT,那子View還不是相當(dāng)于尺寸不確定嗎?所以子View這種情況下的MeasureMode仍然是AT_MOST。只有在尺寸確定的情況下,View的MeasureMode才會(huì)是EXACTLY。如果這里混亂的同學(xué),可以再好好琢磨一下。
至于padding和margin,我們大概想一想就知道肯定是在測(cè)量長(zhǎng)度時(shí)用到的。比如在計(jì)算子View長(zhǎng)度時(shí),肯定是需要把它的padding和margin都去掉才能準(zhǔn)確。
另外,我們觀察View的onMeasure方法,發(fā)現(xiàn)它提供了默認(rèn)的實(shí)現(xiàn):
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
通過setMeasuredDimension(width, height)設(shè)置了View的寬高。但這里的getDefaultSize()是準(zhǔn)確的嗎?我們可以看一下:
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize; // 注釋1
break;
}
return result;
}
注釋1處我們可以看到,當(dāng)SpecMode為AT_MOST時(shí),默認(rèn)直接用的是specSize。所以這里是有問題的,因?yàn)檫@個(gè)specSize代表父View的size,這樣會(huì)造成LayoutParams為WRAP_CONTENT的效果是MATCH_PARENT的效果。所以很多官方的自定義View都是重寫了onMeasure方法,自己去計(jì)算尺寸。我們?cè)趯懽远xView時(shí)也需要特別注意這一點(diǎn)。
而ViewGroup是一個(gè)抽象類,它并沒有實(shí)現(xiàn)View的onMeasure方法,那是因?yàn)槊總€(gè)ViewGroup的布局規(guī)則都不一樣,自然測(cè)量的方式也會(huì)不同,需要各個(gè)子類自己去實(shí)現(xiàn)onMeasure。
layout過程
layout過程只需要知道,layout主要是父容器用來確定容易中子View位置的一個(gè)過程。當(dāng)父容器確定位置后,在父容器的onLayout中會(huì)遍歷其所有子元素調(diào)用它們的layout方法。而layout方法實(shí)際上就是確定四個(gè)頂點(diǎn)坐標(biāo)的位置。
我們可以看到onLayout與onMeasure方法類似,View和ViewGroup都沒有實(shí)現(xiàn)它,因?yàn)槊總€(gè)View布局方式不同,需要自己實(shí)現(xiàn)。但因?yàn)樵诖_定坐標(biāo)過程中需要用到長(zhǎng)和寬,所以layout的順序排在measure的后面。
觀察View#getWidth()與View#getHeight()方法:
public final int getWidth() {
return mRight - mLeft;
}
public final int getWidth() {
return mRight - mLeft;
}
可以看到實(shí)際上它們就是坐標(biāo)之間做了減法,所以這兩個(gè)方法要在onLayout方法之后才能獲取到真正的值。也就是View的實(shí)際寬高。一般情況下measureWidth與width是會(huì)相等的,除非我們特意重寫了layout方法(layout方法與measure方法不一樣,它是可以被重寫的):
public void layout(int l, int t, int r, int b) {
super.layout(l, t, r+11, b-11)
}
這樣,實(shí)際寬高與測(cè)量寬高就會(huì)不一致了。
自定義View
自定義View一共分為四種類型:
- 繼承View
- 這種類型常見于要繪制一些不規(guī)則的圖形,需要重寫它的
onDraw方法。需要的注意的是,View的Padding屬性在繪制過程中默認(rèn)是不起作用的,如果要使Padding屬性起作用,就需要自己在onDraw中獲取Padding后繪制。另外,onMeasure時(shí)需要考慮wrap_content和padding的情況,上文也有提到過。
- 這種類型常見于要繪制一些不規(guī)則的圖形,需要重寫它的
- 繼承ViewGroup
- 這種類型比較少,一般用于實(shí)現(xiàn)自定義的布局。那就意味著要自定義布局規(guī)則,也就是自定義
onMeasure、onLayout。在onMeasure中,也需要處理wrap_content和padding的情況。另外在onMeasure和onLayout中,還需要考慮padding與子元素margin共同作用的場(chǎng)景。
- 這種類型比較少,一般用于實(shí)現(xiàn)自定義的布局。那就意味著要自定義布局規(guī)則,也就是自定義
- 繼承某個(gè)特定的View,比如TextView
- 這種類型一般用于擴(kuò)展已有的某個(gè)View的功能,比較容易實(shí)現(xiàn),不需要重寫
onMeasure或者onLayout方法。
- 這種類型一般用于擴(kuò)展已有的某個(gè)View的功能,比較容易實(shí)現(xiàn),不需要重寫
- 繼承某個(gè)特定的ViewGroup,比如FrameLayout
- 這種類型也很常見,一般用于將幾個(gè)View組合到一起,但相對(duì)第二種方式會(huì)簡(jiǎn)單許多,也不需要重寫
onMeasure或者onLayout方法。
- 這種類型也很常見,一般用于將幾個(gè)View組合到一起,但相對(duì)第二種方式會(huì)簡(jiǎn)單許多,也不需要重寫
在自定義View中還需要額外注意的是,如果View中有額外開線程或者是動(dòng)畫的話,需要在合適的時(shí)機(jī)(比如onDetachedFromWindow生命周期)進(jìn)行回收或者暫停,否則容易引起內(nèi)存泄漏。另外,如果涉及到嵌套滑動(dòng)的話可能還需要處理滑動(dòng)沖突。
六、Window與WindowManager
Window相關(guān)類
Window是一個(gè)抽象類,它的具體實(shí)現(xiàn)是PhoneWindow。PhoneWindow在Activity#attach方法中被創(chuàng)建:
#Activity
final void attach(Context context...) {
...
mWindow = new PhoneWindow(this, window, activityConfigCallback); // 注釋1
...
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); // 注釋2
}
注釋1處初始化了PhoneWindow,注釋2處給Window設(shè)置了WindowManager。WindowManager,顧名思義就是用來管理Window的,它繼承了ViewManager接口:
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
從上面的代碼我們可以看出,管理Window,實(shí)際上就是管理View。context.getSystemService(Context.WINDOW_SERVICE)方法最終獲取的是WindowManager的實(shí)現(xiàn)類WindowManagerImpl,我們可以觀察WindowManagerImpl中的這三個(gè)方法,如addView:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
可以看到實(shí)際上addView是由mGlobal去實(shí)現(xiàn)的,WindowManagerImpl只是橋接了一下。這個(gè)mGlobal是WindowManagerGlobal,是個(gè)單例,全局唯一:
#WindowManagerImpl
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
Window類型
Window類型具體為Window的Type屬性。Type屬性其實(shí)是一個(gè)int指,分為三大類型:
- 應(yīng)用程序窗口
- 常見的Activity的Window就屬于應(yīng)用程序窗口,它的int值范圍是1-99。
- 子窗口
- 子窗口代表著要依附于其它窗口才能存在,比如PopupWindow就屬于一個(gè)子窗口,它的int值范圍是1000-1999。
- 系統(tǒng)窗口
- Toast、音量條、輸入法的窗口就屬于系統(tǒng)窗口,int值范圍是2000-2999。當(dāng)我們要?jiǎng)?chuàng)建一個(gè)系統(tǒng)窗口時(shí),需要申請(qǐng)系統(tǒng)權(quán)限android.permission.SYSTEM_ALERT_WINDOW才可以。
一般來說,type屬性值越大,那么Z-Order的排序就越靠前,窗口越接近用戶。
Window的操作
添加View
我們延續(xù)上方的addView代碼,看一下這里面的過程:
# WindowManagerImpl
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params); // 注釋1
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
// Only use the default token if we don't have a parent window.
if (mDefaultToken != null && mParentWindow == null) {
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
// Only use the default token if we don't already have a token.
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (wparams.token == null) {
wparams.token = mDefaultToken;
}
}
}
# WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
root = new ViewRootImpl(view.getContext(), display); // 注釋2
view.setLayoutParams(wparams);
// 注釋3
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
// 注釋4
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
注釋1調(diào)用了applyDefaultToken方法,將token放在了LayoutParams中。我們可以發(fā)現(xiàn)Window的很多方法中都有這個(gè)token,這個(gè)token到底是什么,有什么作用呢?實(shí)際上這個(gè)token是一個(gè)IBinder對(duì)象,是AMS創(chuàng)建Activity時(shí)就會(huì)創(chuàng)建的,用來唯一標(biāo)識(shí)一個(gè)Activity,創(chuàng)建后WMS也會(huì)拿到一份儲(chǔ)存起來。當(dāng)AMS調(diào)用scheduleLauncherActivity方法轉(zhuǎn)回到APP進(jìn)程時(shí),會(huì)將這個(gè)token傳到APP進(jìn)程中。當(dāng)我們?cè)贏PP進(jìn)程對(duì)Window進(jìn)行操作時(shí),就會(huì)用到這個(gè)token。因?yàn)樽罱KWindow的操作是在WMS,所以我們調(diào)用WMS方法時(shí)會(huì)將這個(gè)token傳過去,WMS就會(huì)比對(duì)這個(gè)token和最開始存儲(chǔ)的token,以此來找到這個(gè)Window是屬于哪個(gè)Activity。、
再繼續(xù)回來,我們看注釋2處WindowManagerGlobal#addView方法中每次都會(huì)new一個(gè)ViewRootImpl對(duì)象。ViewRootImpl我們很熟悉,上一節(jié)有講到,它負(fù)責(zé)View的繪制工作。這里它不僅負(fù)責(zé)View的繪制,還負(fù)責(zé)與最終的WMS進(jìn)行通信。具體可以看到注釋4處調(diào)用了ViewRootImpl#setView方法,內(nèi)部再調(diào)用IWindowSession#addToDisplay方法。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
...
}
IWindowSession實(shí)際上是WMS的Session對(duì)象在APP進(jìn)程的代理,這樣我們的邏輯就到了WMS那邊了,WMS會(huì)完成剩下的addView操作,包括為添加的窗口分配Surface,確定窗口顯示次序,最后將Surface交給SurfaceFlinger處理,合成到屏幕上進(jìn)行顯示。并且,addToDisplay方法的第一個(gè)參數(shù)mWindow,是ViewRootImpl的內(nèi)部類W,它是app進(jìn)程的Binder實(shí)現(xiàn)類,WMS可以通過它調(diào)用app進(jìn)程的方法。
另外我們可以看到WindowManagerGlobal在注釋3處維護(hù)了三個(gè)列表,一個(gè)是View,一個(gè)是ViewRootImpl,一個(gè)是Params布局參數(shù),在更新Window/移除Window時(shí)會(huì)用到。
小結(jié)
添加View的過程可用下圖概括:

更新Window
更新Window的過程與添加的過程是類似的,所經(jīng)過的類關(guān)系一模一樣。主要的區(qū)別在WindowManagerGlobal中需要從列表中獲取到ViewRootImpl,并更新布局參數(shù):
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
...
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
ViewRootImpl會(huì)調(diào)用scheduleTraversals方法重新繪制頁面,最終在performTraversals方法中調(diào)用IWindowSession#relayout方法更新Window,并重新觸發(fā)View的三大繪制流程。
Activity渲染
在第三節(jié)我們講了Activity的啟動(dòng)過程,但其實(shí)還沒有完全講完,因?yàn)锳ctivity實(shí)際上是沒有渲染出來的。那Activity是怎么渲染出來的呢?當(dāng)然也是靠Window。
當(dāng)界面可與用戶進(jìn)行交互時(shí),AMS會(huì)調(diào)用ActivityThread的handleResumeActivity方法:
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
...
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); // 注釋1
...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l); // 注釋2
} else {
...
}
...
注釋1處調(diào)用performResumeActivity方法,內(nèi)部會(huì)觸發(fā)Activity的onResume生命周期。注釋2處用WindowManager#addView方法將decorView繪制到了Window上,這樣整個(gè)Activity就渲染出來了。這也是Activity在onResume生命周期才會(huì)顯示出來的原因。
Dialog、PopupWindow、Toast
最后,還是覺得有必要搞明白Dialog、PopupWindow、Toast這三個(gè)東西到底是什么玩意兒。毫無疑問,它們都是通過Window渲染的,但Dialog屬于應(yīng)用程序窗口,PopupWindow屬于子窗口,Toast屬于系統(tǒng)級(jí)窗口。
Dialog的創(chuàng)建時(shí),跟Activity一樣會(huì)創(chuàng)建一個(gè)PhoneWindow:
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
···
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final Window w = new PhoneWindow(mContext); // 注釋1
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setOnWindowSwipeDismissedCallback(() -> {
if (mCancelable) {
cancel();
}
});
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
我們看到注釋1處Dialog初始化時(shí)是創(chuàng)建了自己的Window,所以它不依附于其它Window存在,不屬于子窗口,而是一個(gè)應(yīng)用程序窗口。
在Dialog#show()時(shí),就會(huì)用WindowManager將DecorView添加到窗口上,移除時(shí)同樣是將DecorView從窗口上移除。有一個(gè)特殊之處是,普通的dialog的context必須是Activity,否則show時(shí)會(huì)報(bào)錯(cuò),因?yàn)樘砑哟翱跁r(shí)需要校驗(yàn)Activity的token,除非我們把這個(gè)dialog的window設(shè)置成系統(tǒng)窗口,就不需要了。
而PopupWindow為什么就屬于子窗口呢?我們可以查閱PopupWindow的源碼:
# PopupWindow
private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
public PopupWindow(View contentView, int width, int height, boolean focusable) {
if (contentView != null) {
mContext = contentView.getContext();
// 注釋1
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
}
setContentView(contentView);
setWidth(width);
setHeight(height);
setFocusable(focusable);
}
public void showAtLocation(IBinder token, int gravity, int x, int y) {
...
final WindowManager.LayoutParams p = createPopupLayoutParams(token); // 注釋2
preparePopup(p);
...
invokePopup(p);
}
protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
p.gravity = computeGravity();
p.flags = computeFlags(p.flags);
p.type = mWindowLayoutType; // 注釋3
p.token = token; // 注釋4
p.softInputMode = mSoftInputMode;
p.windowAnimations = computeAnimationResource();
...
}
private void invokePopup(WindowManager.LayoutParams p) {
...
final PopupDecorView decorView = mDecorView;
decorView.setFitsSystemWindows(mLayoutInsetDecor);
setLayoutDirectionFromAnchor();
mWindowManager.addView(decorView, p); // 注釋5
...
}
在源碼中,我們可以看到PopupWindow中不會(huì)有任何新建Window的操作,因?yàn)樗蕾嚨氖莿e人的Window。在注釋1處拿到WindowNManager,注釋2處調(diào)用createPopupLayoutParams方法給Window參數(shù)賦值。尤其注意注釋3和注釋4處兩個(gè)字段,type字段即代表了窗口的類型,我們可以看到mWindowLayoutType的值是WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,這就代表了是子窗口。且注釋4處的token,不再是Activity的token了,而是show的時(shí)候傳進(jìn)來的View的token。
Toast也是類似,我們可以看到它的源碼中WindowParams.LayoutParams.type的值是WindowManager.LayoutParams.TYPE_TOAST,對(duì)應(yīng)著系統(tǒng)窗口。具體內(nèi)部機(jī)制這里就不分析了,感興趣的同學(xué)可以自行查閱。
七、Handler消息機(jī)制
Handler消息機(jī)制,最主要的作用就是在多線程的背景下,通過消息機(jī)制,可以從子線程切換到主線程進(jìn)行UI的更新操作,并且保證了線程安全。
首先介紹其中的主要成員以及他們之間的關(guān)系。
- Message
- 消息,可用來存放數(shù)據(jù),通過Message.obtain()可從緩存池中獲取一個(gè)消息對(duì)象。
- MessageQueue
- 用于存儲(chǔ)消息的隊(duì)列。
- Looper
- loop方法會(huì)持續(xù)監(jiān)聽MessageQueue,當(dāng)MessageQueue中有消息時(shí),將消息拿出來進(jìn)行分發(fā)。
- 一個(gè)線程只會(huì)有一個(gè)Looper,一個(gè)Looper對(duì)應(yīng)一個(gè)MessageQueue,在Loope創(chuàng)建時(shí),對(duì)應(yīng)的MessageQueue就會(huì)創(chuàng)建。一個(gè)Handler也只能綁定一個(gè)Looper,但是一個(gè)Looper可以綁定多個(gè)Handler,Looper是以線程為單位的。
- Looper線程隔離,是通過Threadlocal實(shí)現(xiàn)的。Looper下有個(gè)靜態(tài)變量sThreadLocal,每個(gè)線程下調(diào)用Looper.myLooper()時(shí)就是從這個(gè)變量中拿,獲取當(dāng)前線程下的Looper。
- Looper分為子線程Looper與MainLooper。MainLooper在ActivityThread的
main方法中就會(huì)通過Looper.prepareMainLooper()方法準(zhǔn)備好。它們的區(qū)別一個(gè)是可以quit的,一個(gè)是不能quit的。所謂的quit就是調(diào)用looper.quit方法,實(shí)際上是在MessageQueue中進(jìn)行quit,將其中的消息給移除。quit又分是否安全quit。safequit下,會(huì)先將消息隊(duì)列中已有的消息先發(fā)完再quit。
- Handler
- 用于發(fā)送Message與接收Message,作為Message中的target。
- Message.Callback
- 當(dāng)Message.callback存在時(shí),Handler將接收不到Message,而是直接回調(diào)到callback中。 Handler在post一個(gè)Runnable時(shí),實(shí)際上就是發(fā)送一個(gè)消息并將這個(gè)Runnable作為這個(gè)消息的callback存在。
那整個(gè)消息通信的流程又是什么樣的呢?我們可以串起來說一下。
- 首先通信準(zhǔn)備階段,
Looper.prepare()給當(dāng)前線程創(chuàng)建一個(gè)Looper,同時(shí)創(chuàng)建一個(gè)MessageQueue。 new一個(gè)Handler。 -
handler.post或者handler.sendMessage發(fā)送一個(gè)消息,入隊(duì)到MessageQueue。 - Looper將MessageQueue中的隊(duì)列按優(yōu)先級(jí)分發(fā)給Handler。
- Handler獲取到Message,并根據(jù)之中的數(shù)據(jù)處理消息。
在寫業(yè)務(wù)的時(shí)候我們常常會(huì)post/send一個(gè)延時(shí)消息,這個(gè)延時(shí)消息是怎么實(shí)現(xiàn)的呢?每個(gè)Message都有一個(gè)when字段,對(duì)應(yīng)著它什么時(shí)候需要被分發(fā)。在入隊(duì)到MessageQueue時(shí),就會(huì)根據(jù)when的先后進(jìn)行排列。當(dāng)loop取消息時(shí),會(huì)判斷這個(gè)消息的分發(fā)時(shí)間是否到了,如果還沒到就先不分發(fā),等時(shí)間到了再分發(fā)。
所以在消息機(jī)制中,并不是發(fā)一個(gè)消息就能夠保證它馬上會(huì)被處理的,因?yàn)闄C(jī)制內(nèi)部就有優(yōu)先級(jí)排列。那怎么能夠在我們需要的時(shí)候提高消息的優(yōu)先級(jí)呢?我們可以調(diào)用postAtFrontOfQueue以及sendMessageAtFrontOfQueue將消息放到隊(duì)頭,其實(shí)是將這個(gè)消息的when設(shè)置為0?;蛘?,使用異步消息與同步屏障。
異步消息與同步屏障
異步消息與同步屏障其實(shí)我們開發(fā)時(shí)很少會(huì)用到,一般都是系統(tǒng)自己使用,同步屏障的接口也是hide的,我們要調(diào)用只能反射調(diào)用。但因?yàn)槊嬖嚭艹R?,這里也順帶提一下。
異步消息的創(chuàng)建我們可以在創(chuàng)建Message時(shí)調(diào)用setAsynchronous方法將Message設(shè)置為異步消息,或者Handler創(chuàng)建時(shí)也可以傳參選擇是否為異步,如果是異步的話,Handler發(fā)送的所有消息都為異步消息。
那什么又是同步屏障呢?同步屏障的本質(zhì)其實(shí)就是特殊的Message,特殊在于這個(gè)Message的Target不是Handler,而是null,以此與其它Message區(qū)分。我們可以通過反射調(diào)用MessageQueue的postSyncBarrier方法發(fā)送一個(gè)同步屏障,以及removeSyncBarrier方法進(jìn)行移除。
我們可以將同步屏障與異步消息結(jié)合,以此來提高異步消息是被優(yōu)先執(zhí)行的。具體原理是MessageQueue在Loop取出Message時(shí),會(huì)判斷Message是否為同步屏障。若為同步屏障,則需要在隊(duì)列中找到第一個(gè)異步消息進(jìn)行優(yōu)先處理,而不是處理排在前面的同步消息:
#MessageQueue
Message next() {
...
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous()); // 注釋1
}
...
}
注釋1處當(dāng)發(fā)現(xiàn)msg.target為null時(shí),代表著這個(gè)消息是同步屏障,就會(huì)去拿隊(duì)列中第一個(gè)異步消息。
上文中講到過view的更新操作,其中在最后的繪制階段ViewRootImpl會(huì)通過requestLayout()進(jìn)行布局的更新,requestLayout()內(nèi)部又調(diào)用scheduleTraversals()方法從而重新走一遍三大繪制流程。
# ViewRootImpl
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // 注釋1
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); //注釋2
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
在這個(gè)方法中我們沒有看到三大繪制流程的觸發(fā),而是在注釋1處發(fā)了一個(gè)同步屏障,阻塞主線程消息隊(duì)列。同時(shí)在注釋2處監(jiān)聽VSYNC信號(hào)。當(dāng)VSYNC信號(hào)來時(shí),Choreographer會(huì)發(fā)一個(gè)異步消息,這個(gè)異步消息在同步屏障的幫助下將會(huì)得到執(zhí)行,并且觸發(fā)監(jiān)聽回調(diào)。監(jiān)聽回調(diào)中ViewRootImpl將移除同步屏障,并調(diào)用performTraversals()執(zhí)行三大繪制流程刷新UI。
總結(jié)
本篇文章一共介紹了七點(diǎn)Framework知識(shí),分別是:系統(tǒng)啟動(dòng)流程、應(yīng)用進(jìn)程啟動(dòng)流程、Activity啟動(dòng)過程、Context、View的工作原理、Window與WindowManager、Handler消息機(jī)制。這幾點(diǎn)都是筆者目前認(rèn)為Android業(yè)務(wù)開發(fā)需要掌握的知識(shí)點(diǎn),它們有些是面試常問的,有些是平時(shí)開發(fā)或多或少會(huì)接觸到的,甚至我們做Hook與插件化時(shí)都會(huì)用到里面的知識(shí)(后面會(huì)專門出一篇文章講Hook與插件化)。像SystemServer進(jìn)程的代碼本章一律沒提,因?yàn)楣P者自己也不太會(huì),就不獻(xiàn)丑啦,而且平時(shí)的確也沒用到過。當(dāng)然,這篇文章會(huì)隨時(shí)更新,隨時(shí)填補(bǔ)知識(shí)的空白,哈哈~希望以上內(nèi)容可以幫到大家!