16.原始碼閱讀(View的繪製-androidapi-26)
今天帶著一個問題來看Android View的繪製流程
View的繪製入口在哪?
很多時候,在進入到一個頁面的時候,會需要動態的獲取到佈局中某一個view的寬度或者高度,但是我們發現如果直接在onCreate方法或者onResume方法中通過這種方式去取高度值得到的是0
int measuredHeight = mTextView.getMeasuredHeight();
而呼叫post方法才可以得到正確的值
mTextView.post(new Runnable() {
@Override
public void run() {
int measuredHeight1 = mTextView.getMeasuredHeight();
System.out.println("post measuredHeight:"+measuredHeight1);
}
});
所以回到我們的第一個問題,view的繪製入口在哪裡,只有view繪製完成經過測量才能得到寬高值,是否在onCreate和onResume方法中,view還沒有完成繪製測量呢?
在Activity的啟動流程中已經瞭解到,最終要啟動Activity並且開始執行Activity生命週期的位置是在ActivityThread的handleLaunchActivity中,我們直接來到這裡看,對Activity啟動流程不熟悉可以看另一篇https://www.jianshu.com/p/bd5208574430
handleLaunchActivity
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
......
//這裡會回撥onCreate方法
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
reportSizeConfigurations(r);
Bundle oldState = r.state;
//這裡會回撥onResume方法
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
if (!r.activity.mFinished && r.startsNotResumed) {
// The activity manager actually wants this one to start out paused, because it
// needs to be visible but isn`t in the foreground. We accomplish this by going
// through the normal startup (because activities expect to go through onResume()
// the first time they run, before their window is displayed), and then pausing it.
// However, in this case we do -not- need to do the full pause cycle (of freezing
// and such) because the activity manager assumes it can just retain the current
// state it has.
performPauseActivityIfNeeded(r, reason);
// We need to keep around the original state, in case we need to be created again.
// But we only do this for pre-Honeycomb apps, which always save their state when
// pausing, so we can not have them save their state when restarting from a paused
// state. For HC and later, we want to (and can) let the state be saved as the
// normal part of stopping the activity.
if (r.isPreHoneycomb()) {
r.state = oldState;
}
}
} else {
//如果啟動Activity失敗,交給ActivityManager處理
// If there was an error, for any reason, tell the activity manager to stop us.
try {
ActivityManager.getService()
.finishActivity(r.token, Activity.RESULT_CANCELED, null,
Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
}
performLaunchActivity
ClassLoader載入出Activity後執行了callActivityOnCreate,會回撥onCreate方法
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
......
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
......
activity.mCalled = false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
return activity;
}
public void callActivityOnCreate(Activity activity, Bundle icicle) {
prePerformCreate(activity);
//呼叫activity中的performCreate,onCreate方法就在裡面
activity.performCreate(icicle);
postPerformCreate(activity);
}
可以看到,activity建立出來後就開始回撥onCreate,而onCreate中做了什麼呢,呼叫了setContentView方法載入我們的佈局檔案,setContentView原始碼分析https://www.jianshu.com/p/2f87ebe77f4e
從原始碼中可以看到,setContentView做了以下的操作:
1.new了一個DecorView
2.載入了一個id為android.R.id.content的佈局並把它加入DecorView
3.我們設定的佈局檔案被載入到id為android.R.id.content的ViewGroup中
也就是說,setContentView後只是載入了佈局,但是還沒有進行測量繪製,那麼在onResum方法呼叫時是否完成了測量繪製呢?
performLaunchActivity之後系統開始執行handleResumeActivity
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
....
//回撥onResum方法
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
......
//獲取到WindowManager,將包含了我們我們自己的佈局的
//DecorView加入到WindowManager中(ViewManager是一個
//介面,WindowManager實現了它)
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 (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
//如果Activity已經可見了,就將DecorView加入Window
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
}
......
// The window is now visible if it has been added, we are not
// simply finishing, and we are not starting another activity.
if (!r.activity.mFinished && willBeVisible
......
WindowManager.LayoutParams l = r.window.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
!= forwardBit) {
l.softInputMode = (l.softInputMode
& (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
| forwardBit;
if (r.activity.mVisibleFromClient) {
ViewManager wm = a.getWindowManager();
View decor = r.window.getDecorView();
//window可見之後,開始真正的繪製view了,這個方法很關鍵
wm.updateViewLayout(decor, l);
}
}
......
r.onlyLocalRequest = false;
// Tell the activity manager we have resumed.
if (reallyResume) {
try {
ActivityManager.getService().activityResumed(token);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
}
}
也就是說在onCreate和onResume方法之心的時候,還沒有完成View的測量,所以在這兩個方法中無法得到實際的高度,具體繪製的程式碼先不看,我們看看為什麼post方法中可以得到高度
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
//把Runnable加入了佇列
getRunQueue().post(action);
return true;
}
public void post(Runnable action) {
postDelayed(action, 0);
}
public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
//看得出這是一個陣列實現的佇列,加入佇列後也沒有執行
//那麼是什麼時候執行的
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
public static <T> T[] append(T[] array, int currentSize, T element) {
assert currentSize <= array.length;
if (currentSize + 1 > array.length) {
T[] newArray = (T[]) Array.newInstance(array.getClass().getComponentType(),
growSize(currentSize));
System.arraycopy(array, 0, newArray, 0, currentSize);
array = newArray;
}
array[currentSize] = element;
return array;
}
dispatchAttachToWindow,這個佇列是在這個方法中執行的,後邊我們會找到這個方法執行的時機,然後就會知道為什麼這個方法中執行就可以得到高度了
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
......
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
performCollectViewAttributes(mAttachInfo, visibility);
onAttachedToWindow();
......
}
我們再回到上邊ViewManager的addView和updateViewLayout方法,ViewManager和WindowManager都是介面,找到它的實現類WindowManagerImpl(可以通過PhoneWindow找到WindowManager的實現類是他)
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
看一下WindowManagerGlobal這個類中做了什麼,這裡建立了一個ViewRootImpl,並把DecorView加入了這個View中
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
......
ViewRootImpl root;
View panelParentView = null;
......
root = new ViewRootImpl(view.getContext(), display);
......
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
}
}
}
//這個方法是給view設定引數的,所以關鍵還是在addView方法
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
......
requestLayout();
}
}
這裡又來到了曾經在invalidate方法中看到的原始碼,重新整理view
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
//執行這個Runnable
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
private void performTraversals() {
....省略大量程式碼
// Ask host how big it wants to be 回撥到measure了
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
....
//回撥到layout了
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
......
//回撥draw了
performDraw();
}
總結一下:
1.onCreate和onResume方法呼叫時還沒有進行view的測量
2.onResume方法呼叫之後將包含著當前佈局檔案的DecorView交給了WindowManager(實現類WindowManagerImpl)處理
3.WindowManager中將DecorView設定到ViewRootImpl中,在ViewRootImpl中呼叫requestLayout開始重新整理View,執行measure layout 和 draw方法
4.post方法中能得到高度是因為post將runnable加入佇列,在view attachToWindow時後才去獲取高度,此時view已經完成了測量
相關文章
- View繪製流程原始碼分析View原始碼
- LayoutInflater建立View原始碼閱讀View原始碼
- Android View 原始碼解析(三) – View的繪製過程AndroidView原始碼
- Android View繪製原始碼分析 MeasureAndroidView原始碼
- Android原始碼分析之View繪製流程Android原始碼View
- View的繪製二:View的繪製流程View
- 基於原始碼分析 Android View 繪製機制原始碼AndroidView
- 【原始碼閱讀】AndPermission原始碼閱讀原始碼
- Android系統原始碼分析--View繪製流程之-inflateAndroid原始碼View
- Android系統原始碼分析–View繪製流程之-setContentViewAndroid原始碼View
- Android系統原始碼分析--View繪製流程之-setContentViewAndroid原始碼View
- Android自定義View之(一)View繪製流程詳解——向原始碼要答案AndroidView原始碼
- 【原始碼閱讀】Glide原始碼閱讀之with方法(一)原始碼IDE
- 【原始碼閱讀】Glide原始碼閱讀之into方法(三)原始碼IDE
- View 的繪製過程View
- 【原始碼閱讀】Glide原始碼閱讀之load方法(二)原始碼IDE
- View繪製——畫多大?View
- View 繪製流程分析View
- View繪製——畫在哪?View
- Android View的繪製過程AndroidView
- ReactorKit原始碼閱讀React原始碼
- Vollery原始碼閱讀(—)原始碼
- NGINX原始碼閱讀Nginx原始碼
- ThreadLocal原始碼閱讀thread原始碼
- 原始碼閱讀-HashMap原始碼HashMap
- Runtime 原始碼閱讀原始碼
- RunLoop 原始碼閱讀OOP原始碼
- AmplifyImpostors原始碼閱讀原始碼
- stack原始碼閱讀原始碼
- CountDownLatch原始碼閱讀CountDownLatch原始碼
- fuzz原始碼閱讀原始碼
- HashMap 原始碼閱讀HashMap原始碼
- delta原始碼閱讀原始碼
- AQS原始碼閱讀AQS原始碼
- Mux 原始碼閱讀UX原始碼
- ConcurrentHashMap原始碼閱讀HashMap原始碼
- HashMap原始碼閱讀HashMap原始碼
- IDEA閱讀原始碼的技巧Idea原始碼