上一篇文章我們使用第三種方法來實(shí)現(xiàn)延遲加載肢娘。不過上一篇寫的比較簡(jiǎn)單,只是講解了如何去實(shí)現(xiàn)舆驶,這一篇就來講一下為何要這么做橱健,以及這么做后面的原理。
其中會(huì)涉及到一些 Android 中的比較重要的類沙廉,以及 Activity 生命周期中比較重要的幾個(gè)函數(shù)畴博。
上一篇中我們最終使用的 DelayLoad 的核心方法是在 Activity 的 onCreate 函數(shù)中加入下面的方法 :
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
myHandler.post(mLoadingRunnable);
}
});
我們一一來看涉及到的類和方法
1. Activity.getWindow 及 PhoneWindow 的初始化時(shí)機(jī)
Activity 的 getWindow 方法獲取到的是一個(gè) PhoneWindow 對(duì)象:
public Window getWindow() {
return mWindow;
}
這個(gè) mWindow 就是一個(gè) PhoneWindow 對(duì)象,其初始化的時(shí)機(jī)為這個(gè) Activity attach 的時(shí)候:
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
attachBaseContext(context);
mFragments.attachActivity(this, mContainer, null);
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
........
// PolicyManager.makeNewWindow(this) 最終會(huì)調(diào)用 Policy 的 makeNewWindow 方法
public Window makeNewWindow(Context context) {
return new PhoneWindow(context);
}
}
這里需要注意 Activity 的 attach 方法很早就會(huì)調(diào)用的蓝仲,是要早于 Activity 的 onCreate 方法的俱病。
總結(jié):
- PhoneWindow 與 Activity 是一對(duì)一的關(guān)系官疲,通過上面的初始化過程你應(yīng)該更加清楚這個(gè)概念
- Android 中對(duì) PhoneWindow 的注釋是 :Android-specific Window ,可見其重要性
- PhoneWindow 中有很多大家比較熟悉的方法亮隙,比如 setContentView / addContentView 等 途凫; 也有幾個(gè)重要的內(nèi)部類,比如:DecorView ;
2. PhoneWindow.getDecorView 及 DecorView 的初始化時(shí)機(jī)
上面我們說到 DecorView是 PhoneWindow 的一個(gè)內(nèi)部類溢吻,其定義如下:
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker
那么 DecorView 是什么時(shí)候初始化的呢维费?DecorView 是在 Activity 的父類的 onCreate 方法中被初始化的,比如我例子中的 MainActivity 是繼承自 android.support.v7.app.AppCompatActivity 促王,當(dāng)我們調(diào)用 MainActivity 的 super.onCreate(savedInstanceState); 的時(shí)候犀盟,就會(huì)調(diào)用下面的
protected void onCreate(@Nullable Bundle savedInstanceState) {
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
由于我們導(dǎo)入的是 support.v7 包里面的AppCompatActivity, getDelegate() 得到的就是AppCompatDelegateImplV7 蝇狼,其 onCreate 方法如下:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mWindowDecor = (ViewGroup) mWindow.getDecorView();
......
}
就是這里的 mWindow.getDecorView() 阅畴,對(duì) DecorView 進(jìn)行了實(shí)例化:
public final View getDecorView() {
if (mDecor == null) {
installDecor();
}
return mDecor;
}
第一次調(diào)用 getDecorView 的時(shí)候,會(huì)進(jìn)入 installDecor 方法迅耘,這個(gè)方法對(duì) DecorView 進(jìn)行了一系列的初始化 贱枣,其中比較重要的幾個(gè)方法有:generateDecor / generateLayout 等,generateLayout 會(huì)從當(dāng)前的 Activity 的 Theme 提取相關(guān)的屬性颤专,設(shè)置給 Window纽哥,同時(shí)還會(huì)初始化一個(gè) startingView,添加到 DecorView上栖秕,也就是我們所說的 startingWindow春塌。
總結(jié)
-
Decor 有裝飾的意思,DecorView 官方注釋為 “This is the top-level view of the window, containing the window decor” , 我們可以理解為 DecorView 是我們當(dāng)前 Activity 的最下面的布局簇捍。所以我們打開 DDMS 查看 Tree Overview 的時(shí)候只壳,可以發(fā)現(xiàn)最根部的那個(gè) View 就是 DecorView:
- 應(yīng)用從桌面啟動(dòng)的時(shí)候,在主 Activity 還沒有顯示的時(shí)候垦写,如果主題沒有設(shè)置窗口的背景吕世,那么我們就會(huì)看到白色(這個(gè)和手機(jī)的Rom也有關(guān)系),如果應(yīng)用啟動(dòng)很慢梯投,那么用戶得看好一會(huì)白色命辖。如果要避免這個(gè),則可以在 Application 或者 Activity 的 Theme 中設(shè)置 WindowBackground , 這樣就可以避免白色(當(dāng)然現(xiàn)在各種大廠都是SplashActivity+廣告我也是可以理解的)
3. Post
當(dāng)我們調(diào)用 DecorView 的 Post 的時(shí)候分蓖,其實(shí)最終會(huì)調(diào)用 View 的 Post 尔艇,因?yàn)?DecorView 最終是繼承 View 的:
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Assume that post will succeed later
ViewRootImpl.getRunQueue().post(action);
return true;
}
注意這里的 mAttachInfo ,我們調(diào)用 post 是在 Activity 的 onCreate 中調(diào)用的么鹤,那么此時(shí) mAttachInfo 是否為空呢终娃?答案是 mAttachInfo 此時(shí)為空。
這里有一個(gè)點(diǎn)就是 Activity 的各個(gè)回調(diào)函數(shù)都是干嘛的蒸甜?是不是平時(shí)自己寫應(yīng)用的時(shí)候棠耕,貌似在 onCreate 里面搞定一切就OK了余佛, onResume ? onStart窍荧?沒怎么涉及到嘛辉巡,其實(shí)不然。
onCreate 顧名思義就是 Create 蕊退,我們?cè)谇懊婵吹浇奸梗珹ctivity 的 onCreate 函數(shù)做了很多初始化的操作,包括 PhoneWindow/DecorView/StartingView/setContentView等瓤荔,但是 onCreate 只是初始化了這些對(duì)象.
真正要設(shè)置為顯示則在 Resume 的時(shí)候净蚤,不過這些對(duì)開發(fā)者是透明了,具體可以看 ActivityThread 的 handleResumeActivity 函數(shù)输硝,handleResumeActivity 中除了調(diào)用 Activity 的 onResume 回調(diào)之外今瀑,還初始化了幾個(gè)比較重要的類:ViewRootImpl / ThreadedRenderer。
ActivityThread.handleResumeActivity:
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) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
主要是 wm.addView(decor, l); 這句腔丧,將 decorView 與 WindowManagerImpl聯(lián)系起來放椰,這句最終會(huì)調(diào)用到 WindowManagerGlobal 的 addView 函數(shù)作烟,
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
......
ViewRootImpl root;
View panelParentView = null;
......
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
......
}
}
我們知道 ViewRootImpl 是 View 系統(tǒng)的一個(gè)核心類愉粤,其定義如下:
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks
ViewRootImpl 初始化的時(shí)候會(huì)對(duì) AttachInfo 進(jìn)行初始化,這就是為什么之前的在 onCreate 的時(shí)候 attachInfo 為空拿撩。ViewRootImpl 里面有很多我們比較熟悉也非常重要的方法衣厘,比如 performTraversals / performLayout / performMeasure / performDraw / draw 等。
我們繼續(xù) addView 中的root.setView(view, wparams, panelParentView); 傳入的 view 為 decorView压恒,root 為 ViewRootImpl 影暴,這個(gè)函數(shù)中將 ViewRootImpl 的mView 變量 設(shè)置為傳入的view,也就是 decorView探赫。
這樣來看型宙,ViewRootImpl 與 DecorView 的關(guān)系我們也清楚了。
扯了一圈伦吠,我們?cè)倩氐酱髽?biāo)題的 Post 函數(shù)上妆兑,前面有說這個(gè) Post 走的是 View 的Post 函數(shù),由于 在 onCreate 的時(shí)候 attachInfo 為空毛仪,所以會(huì)走下面的分支:ViewRootImpl.getRunQueue().post(action);
注意這里的 getRunQueue 得到的并不是 Looper 里面的那個(gè) MessageQueue搁嗓,而是由 ViewRootImpl 維持的一個(gè) RunQueue 對(duì)象,其核心為一個(gè) ArrayList :
private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>();
void post(Runnable action) {
postDelayed(action, 0);
}
void postDelayed(Runnable action, long delayMillis) {
HandlerAction handlerAction = new HandlerAction();
handlerAction.action = action;
handlerAction.delay = delayMillis;
synchronized (mActions) {
mActions.add(handlerAction);
}
}
void executeActions(Handler handler) {
synchronized (mActions) {
final ArrayList<HandlerAction> actions = mActions;
final int count = actions.size();
for (int i = 0; i < count; i++) {
final HandlerAction handlerAction = actions.get(i);
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
actions.clear();
}
}
當(dāng)我們執(zhí)行了 Post 之后 箱靴,其實(shí)只是把 Runnable 封裝成一個(gè) HandlerAction 對(duì)象存入到 ArrayList 中腺逛,當(dāng)執(zhí)行到 executeActions 方法的時(shí)候,將存在這里的 HandlerAction 再通過 executeActions 方法傳入的 Handler 對(duì)象重新進(jìn)行 Post衡怀。
那么 executeActions 方法是什么時(shí)候執(zhí)行的呢棍矛?傳入的 Handler 又是哪個(gè) Handler 呢安疗?
4. PerformTraversals
我們之前講過,ViewRootImpl 的 performTraversals 方法是一個(gè)很核心的方法够委,每一幀繪制都會(huì)走一遍茂契,調(diào)用各種 measure / layout / draw 等 ,最終將要顯示的數(shù)據(jù)交給 hwui 去進(jìn)行繪制慨绳。
我們上一節(jié)講到的 executeActions 掉冶,就是在 performTraversals 中執(zhí)行的:
// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(mAttachInfo.mHandler);
可以看到這里傳入的 Handler 是 mAttachInfo.mHandler ,上一節(jié)講到 mAttachInfo 是在 ViewRootImpl 初始化的時(shí)候一起初始化的:
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
這里的 mHandler 是一個(gè) ViewRootHandler 對(duì)象:
final class ViewRootHandler extends Handler{
......
}
......
final ViewRootHandler mHandler = new ViewRootHandler();
我們注意到 ViewRootHandler 在創(chuàng)建的時(shí)候并沒有傳入一個(gè) Looper 對(duì)象脐雪,這意味著此 ViewRootHandler 的 Looper 就是 mainLooper厌小。
這下我們就清楚了,我們?cè)?onCreate 中 Post 的 runnable 對(duì)象战秋,最終還是在第一個(gè) performTraversals 方法執(zhí)行的時(shí)候璧亚,加入到了 MainLooper 的 MessageQueue 里面了。
繞了一圈終于我們終于把文章最前面的那句話解釋清楚了脂信,當(dāng)然中間還有很多的廢話癣蟋,不過我估計(jì)能耐著性子看到這里的人會(huì)很少,所以如果你看到了這里狰闪,可以在底下的評(píng)論里面將 index ++ 疯搅;這里 index = 0 ;就是看看幾個(gè)人是真正認(rèn)真看了這篇文章的埋泵。
5. UpdateText
接著 performTraversals 我們繼續(xù)說幔欧,話說在第一篇文章 我們有講到,Activity 在啟動(dòng)時(shí)丽声,會(huì)在第二次執(zhí)行 performTraversals 才會(huì)去真正的繪制礁蔗,原因在于第一次執(zhí)行 performTraversals 的時(shí)候,會(huì)走到 Egl 初始化的邏輯雁社,然后會(huì)重新執(zhí)行一次 performTraversals 浴井。
所以前一篇文章的評(píng)論區(qū)有人問為何在 run 方法里面還要 post 一次,如果在 run 方法里面直接執(zhí)行 updateText 方法 霉撵,那么 updateText 就會(huì)在第一個(gè) performTraversals 之后就執(zhí)行磺浙,而不是在第一幀繪制完成后才去執(zhí)行,所以我們又 Post 了一次 喊巍。所以大概的處理步驟如下:
第一步:Activity.onCreate --> Activity.onStart --> Activity.onResume
第二步:ViewRootImpl.performTraversals -->Runnable
第三步:Runnable --> ViewRootImpl.performTraversals
第四步:ViewRootImpl.performTraversals --> UpdateText
第五步:UpdateText
6. 總結(jié)
其實(shí)一路跟下來發(fā)現(xiàn)其實(shí)原理很簡(jiǎn)單屠缭,其實(shí) DelayLoad 其實(shí)只是一個(gè)很小的點(diǎn),關(guān)鍵是教大家如何去跟蹤一個(gè)自己不認(rèn)識(shí)的知識(shí)點(diǎn)或者優(yōu)化崭参,這里面主要用到了兩個(gè)工具:Systrace 和 Method Trace呵曹, 以及源碼編譯和調(diào)試。
關(guān)于 Systrace 和 Method Trace 的使用,之后會(huì)有詳細(xì)的文章去介紹奄喂,這兩個(gè)工具非常有助于理解源碼和一些技術(shù)的實(shí)現(xiàn)铐殃。
代碼和博客
本文涉及到的代碼已經(jīng)上傳到了Github:
https://github.com/Gracker/DelayLoadSample