基於Android 8.0的原始碼分析(View的繪製流程)
據CNBC報道,社交網路巨頭Facebook已承認向61家科技公司提供了其使用者資料的特殊訪問許可權,此前該公司曾在2015年公開表示限制此類訪問。Facebook在上週五晚些時候提交給美國國會的747頁檔案中承認,該公司在2015年5月宣佈限制上述做法後,繼續與61家硬體和軟體製造商分享使用者資訊。
作者簡介本篇來自 豌豆射手_BiuBiu的投稿,分享了Android原始碼分析(View的繪製流程),一起來看看!希望大家喜歡。
豌豆射手_BiuBiu的部落格地址:
正文https://www.jianshu.com/u/a58eb984bda4
原始碼基於安卓8.0分析結果
View是何時開始繪製的?Activity走了onCreate方法嗎?這篇文章就是從程式的入口ActivityThread入口程式,去解釋View中的measure()方法、View中的layout、View中的draw怎麼開始呼叫的,非常有意思!雖然好多的技術文件,在半個月前已經做好了,這篇文章,對我自己來講的話,是個很好的複習~~
為了更好地闡述著這篇文章,我這裡就直接丟擲結論了,為啥會這樣的,在下篇文章會講到,這裡就記住一點,在Activity onResume後,呼叫了View onAttachedToWindow 才會開始View measure
Activity的生命週期和View的生命週期.jpg
為什麼會這樣子?先看ActivityThread類裡面有個內部private class H extends Handler這就是系統的Handler,具體分析請看Android原始碼分析(Handler機制)
https://www.jianshu.com/p/a2c53e96cae6
裡面有個case RESUME_ACTIVITY,獲取焦點
case RESUME_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
SomeArgs args = (SomeArgs) msg.obj;
handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
args.argi3, "RESUME_ACTIVITY" );
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
handleResumeActivity()方法,這裡只截取了關鍵的程式碼
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
return;
}
mSomeActivitiesChanged = true;
//在這裡執行performResumeActivity的方法中會執行Activity的onResume()方法
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
final Activity a = r.activity;
if (localLOGV) Slog.v(
TAG, "Resume " + r + " started activity: " +
a.mStartedActivity + ", hideForNow: " + r.hideForNow
+ ", finished: " + a.mFinished);
final int forwardBit = isForward ?
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
boolean willBeVisible = !a.mStartedActivity;
if (!willBeVisible) {
try {
willBeVisible = ActivityManager.getService().willActivityBeVisible(
a.getActivityToken());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
//PhoneWindow在這裡獲取到
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
//DecorView在這裡獲取到
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//獲取ViewManager物件,在這裡getWindowManager()實質上獲取的是ViewManager的子類物件WindowManager
// TODO: 2018/5/24 WindowManager
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物件
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
}
}
第一點分析得出performResumeActivity()肯定先於wm.addView(decor, l);執行的~這也是為啥我們 Activity先獲取焦點了,才去繪製View。
performResumeActivity(),可以得出呼叫的是r.activity.performResume();
關於r.activity.performResume();;這裡也可以,看出,在activity 中的fragment獲取焦點要晚於activity,雖然這是常識。注意這個方法mInstrumentation.callActivityOnResume(this);;然後才會執行onPostResume;這也就是為什麼,Activity先獲取焦點,後執行onPostResume();
final void performResume() {
performRestart();
mInstrumentation.callActivityOnResume(this);
mCalled = false;
//這裡也可以,看出,在activity 中的fragment獲取焦點要晚於activity,雖然這是常識
mFragments.dispatchResume();
mFragments.execPendingActions();
onPostResume();
}
關注這個方法mInstrumentation.callActivityOnResume(this);果然不出所料,這裡執行了activity.onResume();
既然在上面知道了,activity 獲取焦點,會在上面執行,那麼View的繪製就會在下面的函式中進行。
獲取PhoneWindow; activity.getWindow(),Window類的唯一子類
獲取window.getDecorView();DecorView,PhoneWindow的內部類,private final class DecorView extends FrameLayout ,安卓的事件分發和它密切相關Android原始碼分析(事件傳遞),也就是從Activity 傳遞到 ViewGroup的過程~~
獲取ViewManager wm = a.getWindowManager();,其實也就是activity.getWindowManager(),也就是獲取的是ViewManager的子類物件WindowManager,這裡的知道WindowManager其實也是一個介面.
wm.addView(decor, l);,也就是到這裡來了,WindowManager.addView(decor,l).
//PhoneWindow在這裡獲取到
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
//DecorView在這裡獲取到
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//獲取ViewManager物件,在這裡getWindowManager()實質上獲取的是ViewManager的子類物件WindowManager
// TODO: 2018/5/24 WindowManager
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物件
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
if (!a.mWindowAdded) {
a.mWindowAdded = true;
//在這裡WindowManager將DecorView新增到PhoneWindow中
wm.addView(decor, l);
}
}
分析到這裡來了,會通過WindowManager.addView(decor,l).我們需要去找WindowManager的實現。WindowManagerImpl;
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
}
去尋找WindowManagerGlobal的addView()方法。這裡有個單利模式,在原始碼好多地方使用的單利模式都是這樣,並沒有進行雙重判斷,在老牌的圖片載入框架ImageLoader也是這樣獲取單利物件,如果想了解更多設計模式的姿勢,可以看這片文章二十三種設計模式.
https://www.jianshu.com/p/4e01479b6a2c
public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}
在這裡!就是WindowManagerGlobal.addView()的關鍵的方法,我做了兩個註釋,一個是view.setLayoutParams(wparams);,這個方法非常有意思,最近在研究ViewGroup的原始碼,發現不論什麼情況下,View或者是ViewGroup都會有兩次測量,這裡是根本的原因,我先給結論。
api26:執行2次onMeasure、1次onLayout、1次onDraw。
api25-24:執行2次onMeasure、2次onLayout、1次onDraw,
api23-21:執行3次onMeasure、2次onLayout、1次onDraw,
api19-16:執行2次onMeasure、2次onLayout、1次onDraw,
API等級24:Android 7.0 Nougat
API等級25:Android 7.1 Nougat
API等級26:Android 8.0 Oreo
API等級27:Android 8.1 Oreo
後續我會做一篇文章詳細解釋下,為什麼會這樣,這裡不過多的解釋了,自提一句,非常有意思的程式碼!以前還會有兩次的layout,說明谷歌也在優化安卓 framework。todo
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
...
root = new ViewRootImpl(view.getContext(), display);
//view setLLayoutParams()在這裡
view.setLayoutParams(wparams);
try {
// TODO: 2018/6/4 這裡呢?就是ViewRootImpl 呼叫的setView的方法,就在這裡
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
ok,現在繼續的關注這個方法ViewRootImpl.setView(view, wparams, panelParentView)
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) { try {
// TODO: 2018/6/4 這裡傳入的attrs 決定了View 或者是ViewGroup是否會onMeasure 兩次
mWindowAttributes.copyFrom(attrs);
} catch (RemoteException e) {
// TODO: 2018/5/24 就會調動這裡的來
unscheduleTraversals();
} finally {
if (restore) {
attrs.restore();
} if (res < WindowManagerGlobal.ADD_OKAY) {
// TODO: 2018/5/24 就會調動這裡的來
unscheduleTraversals();}
}
unscheduleTraversals(),沒有Activity獲取焦點的時候,這個方法肯定會執行
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
關注mTraversalRunnable物件
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
doTraversal()方法,Traversal翻譯過來就是遍歷的意思~~
// TODO: 2018/5/24 到這裡來了 ----> Traversal 遍歷
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
performTraversals這裡就是整個View繪製的開始,所有的繪製,都會從這裡開始,雖然這個方法程式碼有點多,但是關鍵的地方我都做了註釋,下面一步一步的分析
如果視窗的型別是有狀態列的,那麼頂層檢視DecorView所需要的視窗的寬度和高度
// 如果視窗的型別是有狀態列的,那麼頂層檢視DecorView所需要的視窗的寬度和高度
//就是除了狀態列
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
//否者頂層檢視DecorView所需要的視窗的寬度和高度就是整個螢幕的寬度
desiredWindowWidth = dipToPx(config.screenWidthDp);
desiredWindowHeight = dipToPx(config.screenHeightDp);
}
獲得view寬高的測量規格
// TODO: 2018/5/25 //獲得view寬高的測量規格,
// TODO: 2018/5/25 mWidth和mHeight表示視窗的寬高,lp.widthhe和lp.height表示DecorView根佈局寬和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
注意這個物件WindowManager.LayoutParams lp ,如果說lp.horizontalWeight > 0.0f或者是lp.verticalWeight > 0.0f,那麼measureAgain =true;horizontalWeight這個標記大概是這個意思指示額外空間的多少將被水平分配。如果檢視指定0不應被拉伸。否則額外畫素將被優先評估。在所有重量大於0的檢視中。一般都指示出還有多少的水平的空間將要被分配。
/**
* 這個WindowMananger 這裡標記了 是
*/
// TODO: 2018/6/4
WindowManager.LayoutParams lp = mWindowAttributes;
// TODO: 2018/5/25 這裡是第一步的 執行測量的操作
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
如果這個measureAgain=true的話,就會再次呼叫performMeasure(),通過程式碼可以發現這就呼叫了兩次performMeasure;
其實我這裡犯了一個錯誤,不是這樣的子,這個標記不一定是為true。
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(mTag,
"And hey let's measure once more: width=" + width
+ " height=" + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
關於performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);方法,其實就是呼叫的是mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);,也就是View第一步是測量。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
第一次繪製的時候,這個標記一定是didLayout一定是true,一定會走到這個方法裡面去performLayout(lp, mWidth, mHeight);
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
// TODO: 2018/5/25 執行佈局操作
performLayout(lp, mWidth, mHeight);
}
}
關於performLayout這個方法,直接會呼叫host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());,也就是View的layout的方法。
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
final View host = mView;
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
//測試層級
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
這兩個標記也是!cancelDraw && !newSurface為true,那麼就會走到performDraw();
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
// TODO: 2018/5/25 執行繪製的操作
performDraw();
}
關於performDraw();方法,直接呼叫的是draw(fullRedrawNeeded);
private void performDraw() {
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
關於draw(fullRedrawNeeded);,會呼叫到這裡來drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)
private void draw(boolean fullRedrawNeeded) {
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}