子曰:溫故而知新,可以爲師矣。 《論語》-- 孔子
本篇文章會從一個個問題出發,然後結合源碼去講解 UI 的繪製流程
。
一、Android 程序如何啓動?Activity 生命週期如何調用?
衆所周知,Java
程序的入口是 main()
方法,那麼 Android
程序的入口在哪裏,稍微開發時間久一點的都應該知道 ActivityThread
這個類,這個類中的 main()
方法也就是我們 Android
程序的入口。
在這個 main()
方法中,有這麼兩行代碼需要關注:
//ActivityThread 類
public static void main(String[] args) {
//...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
//....
}
見名知意,這邊是開啓了一個 Activity
的線程,調用 attach()
方法進行了一個綁定操作。我們點擊進入 attach()
方法中。可以看到裏面代碼比較多,我們千萬不要一行行代碼都看,要帶着目的去看,那麼下面的代碼就進入到我們的視線中了:
//ActivityThread 類中的 attach() 方法
@UnsupportedAppUsage
private void attach(boolean system, long startSeq) {
//....
final IActivityManager mgr = ActivityManager.getService();
try {
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
//....
}
這邊看命名猜一下,ActivityManager 管理類調用 getService() 方法(也就是所謂的 AMS
)獲取一個接口 IActivityManager
,然後再綁定 Application。接口的實現類是 ActivityManagerService
,我們就要到 ActivityManagerService 類中的 attachApplication()
方法:
// ActivityManagerService 類
@Override
public final void attachApplication(IApplicationThread thread, long startSeq) {
synchronized (this) {
int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
attachApplicationLocked(thread, callingPid, callingUid, startSeq);
Binder.restoreCallingIdentity(origId);
}
}
這邊不用想,要點擊 attachApplicationLocked()
方法:
// ActivityManagerService 類
@GuardedBy("this")
private final boolean attachApplicationLocked(IApplicationThread thread,
int pid, int callingUid, long startSeq) {
//....
thread.bindApplication(一堆參數)
//....
}
這個方法裏面代碼非常多,我們只要看圖上面這行代碼,它又執行綁定 Application 操作,這個 thread 是 IApplicationThread 接口
,實現類就是 ActivityThread 類中的內部類 ApplicationThread
,所以我們又要跳回 ActivityThread 類:
//ActivityThread 類
public final void bindApplication(一堆參數){
//...
sendMessage(H.BIND_APPLICATION, data);
}
在這個方法中,我們看到了發送消息,這個 H 是內部類,繼承 Handler 類,那麼我們就要找到 handleMessage()
方法:
// ActivitThread類中的 內部類 Handler 類的 handleMessage() 方法
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
//....
}
裏面有個 handleBindApplication()
方法,我們點擊進入:
// ActivityThread 類
//...
try {
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
if (!mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}
//...
裏面的代碼又是很多,我們還是那句老話,對着目標找線索,我們可以看到,這邊終於有我們差不多熟悉的代碼了,就是這個 callApplicationOnCreate()
方法,看着非常像創建 Application ,我們點擊進入:
// Instrumentation 類
public void callApplicationOnCreate(Application app) {
app.onCreate();
}
看到這兒,應該不言而喻了,調用了 Application
對象的 onCreate()
方法。
好了,第一個問題說完了,下面說一說 Activity 是如何啓動的?
在 ActivityThread 類中有一個 handleLaunchActivity()
的方法,看名字是啓動 Activity,那麼這個方法是如何調用的,中間過程過於複雜了,這邊就不詳細說了,還是 ActivityManageService 這個類中的 attachApplication() 方法經過一系列調用
,最終調用了 ActivityThread 類中的 handleLaunchActivity()
方法,這個方法裏面又調用了 performLaunchActivity()
方法,在 performLaunchActivity() 方法中調用了 mInstrumentation.callActivityOnCreate()
方法,是不是很熟悉,之前 Application 的 onCreate() 方法也是這個類調用的,最終創建了 Activity,調用了 Activity 類的 onCreate() 方法。
二、在 Activity 的 onCreate() 方法中,setContentView 是如何將 UI 文件加載?
以繼承 Activity
爲例:
我們點擊 setContentView()
方法:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
調用 getWindow()
方法返回的對象再調用 setContentView()
方法,傳入我們自己寫的資源 id
,那麼我們就需要知道 getWindow()
方法是什麼?
// Activity 類
public Window getWindow() {
return mWindow;
}
此方法返回一個 Window
對象,這是一個抽象類,我們從類最上面的註釋可以知道唯一實現類是 PhoneWindow
類,我們就來到 PhoneWindow
類中的 setContentView()
方法:
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//...
}
// 是否有轉場動畫
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//....
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
//.....
}
在這裏,關注 installDecor()
和 mLayoutInflater.inflate(layoutResID, mContentParent)
這兩行代碼,我們先看 installDecor()
方法:
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
//...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
if (decorContentParent != null) {
mDecorContentParent = decorContentParent;
mDecorContentParent.setWindowCallback(getCallback());
if (mDecorContentParent.getTitle() == null) {
mDecorContentParent.setWindowTitle(mTitle);
}
//...
} else {
//...
}
//...
}
}
}
在這個方法中,可以看到有兩個類 mDecor
和 mContentParent
,那麼這兩個類是什麼?
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
ViewGroup mContentParent;
從文檔中給出的註釋,可以知道 mDecor
是一個頂層容器,而 mContentParent
可能是 mDecor
,也可以是它的 子類
。
既然知道這兩個類是什麼,那麼我們回到之前的 installDecor()
方法中,首先判斷 mDecor 是否爲空,爲空則調用 generateDecor(-1)
方法,通過new
的方式創建出來 mDecor 對象。
接着往下看,判斷 mContentParent
是否爲空, 爲空則調用 generateLayout(mDecor)
方法傳入之前創建的 mDecor
對象,我們點擊進入此方法:
protected ViewGroup generateLayout(DecorView decor) {
//.... 都是一些系統主題的判斷,不用看
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
}else if(){
//....
}
//....
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent =(ViewGroup)findViewById(ID_ANDROID_CONTENT);
//....
return contentParent;
}
這邊的 layoutResource
不是我們傳入的,可以在下面的代碼中看到是系統的資源佈局,往下看,點擊 onResourcesLoaded()
方法:
// DecorView 類
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
if (mBackdropFrameRenderer != null) {
//...
}
mDecorCaptionView = createDecorCaptionView(inflater);
// 將資源佈局轉換成 View 對象
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
// Put it below the color views.
// 添加到 DocorView 中
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
}
需要注意的是,此時已經來到了 DecorView
類中,else
裏面的 addView()
方法,會將之前系統的佈局加載到 DocorView
容器中。然後
好了,我們回到 generateLayout()
方法裏,這個方法返回的就是 contentView
,也就是 ID
爲 content
的 FrameLayout
。
我們現在來對 installDecor()
方法做一個總結:
- 創建
DecorView
。 - 初始化
mContentParent
,根據對應的主題特性加載相應的系統佈局,將系統佈局初始化,通過addView
方法加載到DecorView
中。
接着我們就來看一下 mLayoutInflater.inflate(layoutResID, mContentParent)
方法,一路點擊 inflate
方法到底:
// LayoutInflater 類
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
//....
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}
//....
}
這邊的 rootView 就是 那個 mContentParent
,也就是幀佈局 FrameLayout
,這邊會把我們自己寫的佈局添加到這個 FrameLayout
中。
對上面的源碼閱讀做一下總結,即總結一下 View 是如何被添加到屏幕窗口上的:
- 創建頂層佈局容器
DecorView
。 - 在頂層佈局容器中加載基礎佈局
ViewGroup
(系統的)。 - 將我們自己寫的佈局添加到基礎佈局中的
FrameLayout
中。
這邊稍微說一下如果繼承的是 AppCompatActivity
,View 是如何被添加到屏幕窗口上的:
- 通過代理類的實現類
AppCompatDelegateImpl
在原來的容器中在套一層佈局。 - 通過容器 ID 替換,用新的容器 ID 替換原來的
FrameLayout
(填之前的坑,做適配)。 - 將我們自己寫的佈局添加到基礎佈局中的
ContentFrameLayout
中。
這個問題最後用一張圖來總結一下:
三、View 的整體繪製流程是什麼?
在 ActivityThread
類中的 handleResumeActivity()
方法中:
// ActivityThread 類
public void handleResumeActivity(一堆參數){
//...
final Activity a = r.activity;
//...
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
//....
wm.addView(decor, l);
//....
}
在這邊有個 wm.addView(decor, l)
方法,這個 wm
是 a.getWindowManager()
獲取的WindowManager
接口,它的實現類是 WindowManagerImpl
,所以我們就來到 WindowManagerImpl
類中的 addView()
方法:
// WindowManagerImpl 類
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
在這邊可以看到有一個 mGlobal.addView()
方法 ,那麼點擊進入 WindowManagerGlobal
的 addView()
方法中:
// WindowManagerGlobal 類
public void addView(
//...
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) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
在這裏,我們可以看到 root
也就是 ViewRootImpl
調用了 setView()
方法,我們點擊進入:
// ViewRootImpl 類
public void setView(一堆參數){
//....
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
//....
}
在這裏,我們終於看到了熟悉的代碼,一般調用 requestLayout()
方法,會重新佈局,我們點擊此方法:
// ViewRootImpl 類
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
這邊檢查線程後,調用 scheduleTraversals()
方法,點擊進入:
// ViewRootImpl 類
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
這邊有個 postCallback
方法,有個 mTraversalRunnable
線程,一般人可能沒注意,我們看這個線程:
// ViewRootImpl 類
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
它會執行 doTraversal()
方法,點擊進入:
// ViewRootImpl 類
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
這邊調用 performTraversals()
方法 ,點擊調用:
// ViewRootImpl 類
private void performTraversals() {
//...
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//...
performLayout(lp, mWidth, mHeight);
//...
performDraw();
}
在這邊,就看到了熟悉的 UI
繪製流程三步,測量,佈局,繪製。
寫在文末
紙上得來終覺淺,絕知此事要躬行。 《冬夜讀書示子聿》-- 陸游
至此,UI
繪製整體流程的源碼算是梳理了一遍,各位看官食用愉快。