原始碼分析第三篇,Activity啟動後的View繪製流程的原始碼分析,由於View繪製牽扯內容太多,因此此篇文章略長,也許許多小夥伴看到部分就會失去耐心,但我相信若能全部看完,或多或少也會有點感悟,若能看完整有那些不足和有誤的地方,請留言告知,感謝!!! (注:若有什麼地方闡述有誤,敬請指正。)
View繪製基礎概念
說到View繪製首先要先了解幾個概念:
-
Window:是一個抽象類,具有視窗管理的功能,實現類為PhoneWindow。Window有3類,應用層Window、子Window、系統Window。應用層Window對應的比如說Activity,而子Window必須附著在父Window上,如Dialog、PopupWindow。系統Window有如Toast、System Alert等。其層級對應區間如下:
應用層Window: 1 - 99 子Window: 1000 - 1999 系統Window: 2000 - 2999 複製程式碼
毫無疑問,層級越高的顯示的越靠上。
-
PhoneWindow類:PhoneWindow這個類是Framework為我們提供的Android視窗的具體實現。我們平時呼叫setContentView()方法設定Activity的使用者介面時,實際上就完成了對所關聯的PhoneWindow的 ViewTree(視窗所承載的控制元件樹)的設定。我們還可以通過Activity類的requestWindowFeature()方法來定製Activity關聯PhoneWindow的外觀,這個方法實際上做的是把我們所請求的視窗外觀特性儲存到了PhoneWindow的mFeatures成員中,在視窗繪製階段生成外觀模板時,會根據mFeatures的值繪製特定外觀。該類繼承於Window類,是Window類的具體實現,即我們可以通過該類具體去繪製視窗。並且,該類內部引用一個DecorView物件,該DectorView物件是所有應用視窗(Activity介面)的根View。簡而言之,PhoneWindow類是把一個FrameLayout類即DecorView物件進行一定的包裝,將它作為應用視窗的根View,並提供一組通用的視窗操作介面。它是Android中的最基本的視窗系統,每個Activity均會建立一個PhoneWindow物件,是Activity和整個View系統互動的介面。
-
DecorView類:是一個應用視窗的根容器,它本質上是一個FrameLayout。DecorView有唯一一個子View,它是一個垂直LinearLayout,包含兩個子元素,一個是TitleView(ActionBar的容器),另一個是ContentView(視窗內容的容器)。關於ContentView,它是一個FrameLayout(android.R.id.content),我們平常用的setContentView就是設定它的子View。
-
ViewRootImpl類:ViewRootImpl是實際管理Window中所以View的類,每個Activity中ViewRootImpl數量取決於呼叫mWindowManager.addView的呼叫次數。
注:Activity是由中心控制器ActivityManagerService來管理控制的。和Activity類似,UI層的內容是由另一個控制器WindowManagerService(WMS)來管理的。
setContentView初始化佈局
View的繪製要從Activity建立後,執行setContentView方法開始分析:
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
複製程式碼
這裡的MainActivity繼承自Activity類,Activity類是AppCompatActivity、FragmentActivity等 Activity的父類,因此直接繼承自Activity更便於分析。
- Activity類:
public void setContentView(@LayoutRes int layoutResID) {
// getWindow()獲取的是Window
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public Window getWindow() {
return mWindow;
}
複製程式碼
getWindow()中直接返回mWindow,那麼mWindow是什麼時候被賦值的呢,這就要說到Activity啟動過程啦,Activity啟動最後會呼叫ActivityThread類中的handleLaunchActivity方法(若不清楚Activity啟動流程的,請參考我上一篇文章),而handleLaunchActivity方法裡又會呼叫performLaunchActivity方法建立並返回一個Activity,看下performLaunchActivity方法裡部分程式碼:
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
handleConfigurationChanged(null, null);
//初始化 WindowManagerService,主要是獲取到 WindowManagerService 代理物件
WindowManagerGlobal.initialize();
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
// 回撥onResume()
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed);
...
}
...
}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
// 建立Activity
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component+ ": " + e.toString(), e);
}
}
try {
// 建立 Application 物件
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
...
if (activity != null) {
...
// 回撥Activity的attach方法
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);
...
// 回撥Activity的onCreate()方法
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity,
r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
...
}
} catch (SuperNotCalledException e) {
throw e;
...
return activity;
}
複製程式碼
從程式碼中可以看出在performLaunchActivity方法裡不僅建立了Activity還呼叫了attahc方法,而 mWindow就是在attahc方法中賦值的。
// Activity類的attach方法:
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,
Window window, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
// mWindow賦值 PhoneWindow繼承自Window物件,是Window類的具體實現
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...
// 設定WindowManagerImpl物件
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
// 獲取WindowManagerImpl物件
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
mWindow.setColorMode(info.colorMode);
}
複製程式碼
回到setContentView方法,在該方法中getWindow()也呼叫了setContentView方法,而getWindow()返回的真實類則是PhoneWindow,所以此時呼叫的是PhoneWindow類中的setContentView方法。
- PhoneWindow類setContentView方法:
/**
* 什麼是Transition?
* 安卓5.0中Activity和Fragment變換是建立在名叫Transitions的安卓新特性之上的。
* 這個誕生於4.4的transition框架為在不同的UI狀態之間產生動畫效果提供了非常方便的API。
* 該框架主要基於兩個概念:場景(scenes)和變換(transitions)。
* 場景(scenes)定義了當前的UI狀態,
* 變換(transitions)則定義了在不同場景之間動畫變化的過程。
*/
@Override
public void setContentView(int layoutResID) {
// contentParent是mDecor(DecorView)兩部分中的ContentView部分
if (mContentParent == null) {
// 初始化DecoView
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
// 是否有特徵,需要動態設定,預設沒有(動畫效果)
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
// 本文不是重點先不分析
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
// 一般會走這裡
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// 生成mDecor
mDecor = generateDecor(-1);
...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
}
protected DecorView generateDecor(int featureId) {
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext().getResources());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
// new DecorView
return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) {
...
// android.R.id.content
// 系統內部定義的佈局,contentParent指的是DecorView的ContentView部分
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
int layoutResource;
...
mDecor.startChanging();
// 根據不同的Feature設定不同的佈局檔案
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
...
return contentParent;
}
複製程式碼
hasFeature(FEATURE_CONTENT_TRANSITIONS)是用來判斷是否來啟用Transition Api,Transition是什麼程式碼上方有所說明。想具體瞭解Transition以及用法,點選下方連結。
www.jcodecraeer.com/a/anzhuokai…
先初始化了DecoView(根佈局),然後會呼叫mLayoutInflater.inflate()方法來填充佈局,inflate方法會使用Xml解析器,解析我們傳入的xml檔案,並儲存到mContentParent裡。Xml解析的具體原始碼就不分析啦,感興趣的小夥伴自行檢視吧。到這裡,setContentView()的整體執行流程我們就分析完了,至此我們已經完成了Activity的ContentView的建立與設定工作。
onResume介面可見繪製之關聯Window和ViewRootImpl
- View的繪製和Activity的啟動息息相關,此時Activity已經走完回撥onCreate,而在Activity流程中已經走完performLaunchActivity方法,繼續往下走則會走入handleResumeActivity方法中然後呼叫 performResumeActivity
// ActivityThread類:
final void handleResumeActivity(IBinder token,boolean clearHide
, boolean isForward, boolean reallyResume, int seq, String reason) {
...
// 回撥onStart和onResume方法
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
final Activity a = r.activity;
boolean willBeVisible = !a.mStartedActivity;
...
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) {
// 1.主要此變數,下面要用
a.mWindowAdded = true;
// 2.Window和DecorView關聯
wm.addView(decor, l);
}
...
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
...
mNumVisibleActivities++;
if (r.activity.mVisibleFromClient) {
// 3.顯示DecorView
r.activity.makeVisible();
}
}
...
}
}
複製程式碼
WindowManager是個介面,它的實現類是WindowManagerImpl類,而WindowManagerImpl又把相關邏輯交給了WindowManagerGlobal處理。WindowManagerGlobal是個單例類,它在程式中只存在一個例項,是它內部的addView方法最終建立了我們的核心類ViewRootImpl。先看上面程式碼1處,設定當前Activity成員變數mWindowAdded為true表明Window已經新增過啦,2處程式碼和3處程式碼:
// WindowManagerImpl類:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
// WindowManagerGlobal類:
// 2處
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...
// new出實際管理Window中所以View的類ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
// mRoots為ViewRootImpl的集合,即執行過多少次addView就有多少ViewRootImpl
mRoots.add(root);
mParams.add(wparams);
try {
// 執行ViewRootImpl的setView--View繪製的起點
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
// Activity類:
// 3處
void makeVisible() {
// 第1處程式碼,mWindowAdded已為true
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
// mDecor設定為顯示
mDecor.setVisibility(View.VISIBLE);
}
複製程式碼
這個過程建立一個 ViewRootImpl,並將之前建立的 DecoView 作為引數傳入,以後 DecoView 的事件都由 ViewRootImpl 來管理了,比如,DecoView 上新增 View,刪除 View。ViewRootImpl 實現了 ViewParent 這個介面,這個介面最常見的一個方法是 requestLayout()。
// ViewRootImpl類:
public ViewRootImpl(Context context, Display display) {
...
// 從WindowManagerGlobal中獲取一個IWindowSession的例項。它是ViewRootImpl和WMS進行通訊的代理
mWindowSession = WindowManagerGlobal.getWindowSession();
...
mWindow = new W(this);//建立了一個W本地Binder物件,作用為將WMS的事件通知到應用程式程式
...
mChoreographer = Choreographer.getInstance();//Choreographer物件,用於統一排程視窗繪圖
...
}
// WindowManagerGlobal類:
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
// 獲取WindowManagerService的Binder代理 windowManager
IWindowManager windowManager = getWindowManagerService();
// 通過Binder代理 windowManager呼叫openSession函式
// 獲取例項sWindowSession:表示活動的客戶端會話。每個程式通常有一個 Session物件與windowManager互動。
// 通過openSession函式來與WMS建立一個通訊會話,後面繼續細說
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
},
imm.getClient(), imm.getInputContext());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
複製程式碼
該段程式碼中引入兩個概念:mWindowSession和mWindow,分別是IWindowSession和IWindow,具體作用下面細講。
onResume介面可見繪製之IWindowSession和IWindow
- 關聯完Window和ViewRootImpl後,ViewRootImpl立馬執行了setView,開始了View繪製的征程。
// ViewRootImpl類:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
...onResume介面可見繪製之IWindowSession和IWindow
requestLayout();
...
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
// 呼叫IWindowSession的addToDisplay方法,第一個引數是IWindow
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mInputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
}
}
}
複製程式碼
ViewRootImpl類的setView方法主要做了3個事:
- 儲存傳入的view引數為mView,這個mView只向PhoneWindow的DecorView
- 執行了開始繪製的方法requestLayout();
- 呼叫IWindowSession的addToDisplay函式,這是一個跨程式的Binder通訊,第一個引數事mWindow,它事W型別,從IWindow.Stub派生的。
從上面程式碼可發現,ViewRoot和遠端程式SystemServer的WMS是有互動的,總結一下互動流程:
- ViewRootImpl初始化時WindowManagerGlobal呼叫getWindowSession,經IWindowManager呼叫openSession,得到IWindowSession物件。
- setView方法中,呼叫IWindowSession的addToDisplay函式,把一個IWindow物件作為引數傳入。
看一下openSession方法:
// WindowManagerService類:
@Override
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
IInputContext inputContext) {
...
// 返回一個Session物件,它支援Binder通訊,並且屬於Bn端。
// Bn意味著Binder Native 端,Bp是Binder Proxy端
// 這兩端會實現相同的介面,但Proxy端只是通過Binder ipc傳送一個Binder Transaction,
// native端是真正做事情,再將結果返回。
Session session = new Session(this, callback, client, inputContext);
return session;
}
// Session類:
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
Rect outOutsets, InputChannel outInputChannel) {
// 呼叫WMS的addWindow方法
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outOutsets, outInputChannel);
}
// WindowManagerService類:
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
InputChannel outInputChannel) {
......
synchronized(mWindowMap) {
...
// 建立WindowToken
WindowToken token = displayContent.getWindowToken(
hasParent ? parentWindow.mAttrs.token : attrs.token);
...
// 呼叫attach方法
win.attach();
}
...
return res;
}
// WindowToken類:
void attach() {
if (localLOGV) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
mSession.windowAddedLocked(mAttrs.packageName);
}
// Session類:
void windowAddedLocked(String packageName) {
...
if (mSurfaceSession == null) {
...
// 建立SurfaceSession物件
mSurfaceSession = new SurfaceSession();
...
}
mNumWindow++;
}
複製程式碼
上面程式碼是按照IWindowSession有關的邏輯順序排列的,這裡又出現了一個重要物件mSurfaceSession,不過還是先講解IWindowSession和IWindow,先來看一張ViewRootImpl和WMS關係圖:
根據這張圖先來總結一下:- ViewRootImpl通過IWindowSession和WMS進行跨程式通訊,IWindowSession定義在IWindowSession.aidl檔案中
- ViewRootImpl內部有一個W內部類,它也是一個基於Binder的通訊類,W是IWindow的Bn端,用於請求響應。 我們來看一下W類內,都有哪些方法:
static class W extends IWindow.Stub {
private final WeakReference<ViewRootImpl> mViewAncestor;
private final IWindowSession mWindowSession;
W(ViewRootImpl viewAncestor) {
mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
mWindowSession = viewAncestor.mWindowSession;
}
@Override
public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
MergedConfiguration mergedConfiguration, Rect backDropFrame, boolean forceLayout,
boolean alwaysConsumeNavBar, int displayId) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.dispatchResized(frame, overscanInsets, contentInsets,
visibleInsets, stableInsets, outsets, reportDraw, mergedConfiguration,
backDropFrame, forceLayout, alwaysConsumeNavBar, displayId);
}
}
@Override
public void moved(int newX, int newY) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.dispatchMoved(newX, newY);
}
}
@Override
public void dispatchAppVisibility(boolean visible) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.dispatchAppVisibility(visible);
}
}
@Override
public void dispatchGetNewSurface() {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.dispatchGetNewSurface();
}
}
@Override
public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.windowFocusChanged(hasFocus, inTouchMode);
}
}
private static int checkCallingPermission(String permission) {
try {
return ActivityManager.getService().checkPermission(
permission, Binder.getCallingPid(), Binder.getCallingUid());
} catch (RemoteException e) {
return PackageManager.PERMISSION_DENIED;
}
}
...
/* Drag/drop */
@Override
public void dispatchDragEvent(DragEvent event) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.dispatchDragEvent(event);
}
}
@Override
public void updatePointerIcon(float x, float y) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.updatePointerIcon(x, y);
}
}
...
}
複製程式碼
可以看到W內部有一個ViewRootImpl的弱引用,從W內繼承的方法可以看出,W即IWindow會通知ViewRootImpl一些事件。這裡的事件指的就是按鍵、觸屏等事件。一個按鍵是如何被分發的呢?其大致流程如下:
- WMS所在的SystemServer程式接收到按鍵事件
- WMS找到UI位於螢幕位於頂端的程式所對應的IWindow物件,這是一個Bp端物件。
- 呼叫這個IWindow物件的dispatchKey,IWindow物件的Bn端位於ViewRootImpl中,ViewRootImpl再根據內部View的位置資訊找到真正處理這個事件的View,最後呼叫dispatchKey方法完成按鍵處理。
到此位置,應該大概能明白IWindowSession和IWindow的用處了吧,再總結一下:
- IWindowSession:用於和WMS通訊,每個App程式都會和WMS建立一個IWindowSession會話用於通訊。
- IWindow:用於回撥WMS事件,IWindow是WMS用來進行事件通知的,每當發生一些事件時,WMS就會把這些事件告訴某個IWindow,然後IWindow再回撥回ViewRootImpl中的某個View,來響應這些事件。
onResume介面可見繪製之同步屏障--VSYNC同步
還有Surface和SurfaceSession沒有正式介紹呢,不過在此之前先來繼續介紹requestLayout():
// ViewRootImpl類:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
// 檢查是否在非UI執行緒更新UI
checkThread();
mLayoutRequested = true;
// 遍歷
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
// 如果不是UI執行緒則丟擲異常
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// postSyncBarrier方法,被稱為同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
複製程式碼
MessageQueue#postSyncBarrier方法,被稱為同步屏障,在這裡主要為了不影響主執行緒UI的繪製。同步屏障可以理解為:在MessageQueue中新增一個特殊的msg,將這個msg作為一個標記,在這個標記被移除之前,當前MessageQueue佇列中排在它後面的其它(非async:即同步) 的message不會被handler處理。因此此處的程式碼繼續向下執行postCallback,我們來看看這個方法裡幹了些什麼:
// Choreographer類:Choreographer就是負責獲取Vsync同步訊號並控制App執行緒(主執行緒)完成影象繪製的類。
public void postCallback(int callbackType, Runnable action, Object token) {
// 傳遞的引數 delayMillis 為 0
postCallbackDelayed(callbackType, action, token, 0);
}
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
...
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
...
synchronized (mLock) {
// 從開機到現在的毫秒數(手機睡眠的時間不包括在內)
final long now = SystemClock.uptimeMillis();
// 從上面方法可知,delayMillis == 0
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) { // true
// 執行該方法,從方法名可以看出此方法中處理跟幀相關的邏輯
scheduleFrameLocked(now);
} else {
// 非同步回撥延遲執行
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
// 是否允許動畫和繪製的垂直同步,預設是為true
if (USE_VSYNC) {
...
// If running on the Looper thread, then schedule the vsync immediately,
// otherwise post a message to schedule the vsync from the UI thread
// as soon as possible.
// 從上面註釋中可知,如果執行在Looper執行緒,即主執行緒,View繪製走到這基本就是主執行緒
if (isRunningOnLooperThreadLocked()) { // true
// 執行該方法
scheduleVsyncLocked();
} else {
// 切換到主執行緒,排程vsync
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
// 如果沒有VSYNC的同步,則傳送訊息重新整理畫面
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
if (DEBUG) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
// DisplayEventReceiver類:
public void scheduleVsync() {
if (mReceiverPtr == 0) {
...
} else {
nativeScheduleVsync(mReceiverPtr);
}
}
複製程式碼
現在跟隨原始碼到了mDisplayEventReceiver.scheduleVsync();方法,而mDisplayEventReceiver是什麼時候初始化的呢,通過原始碼可以看到是在Choreographer類初始化時被new出來的,那Choreographer類是什麼時候初始化的,再通過原始碼可以看到是ViewRootImpl類初始化時被new出來的。
public ViewRootImpl(Context context, Display display) {
...
mChoreographer = Choreographer.getInstance();
...
}
// Choreographer類:Choreographer就是負責獲取Vsync同步訊號並控制App執行緒(主執行緒)完成影象繪製的類。
//每個執行緒一個Choreographer例項
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
Looper looper = Looper.myLooper();
if (looper == null) {
throw new IllegalStateException("The current thread must have a looper!");
}
return new Choreographer(looper);
}
};
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
//建立handle物件,用於處理訊息,其looper為當前的執行緒的訊息佇列
mHandler = new FrameHandler(looper);
//建立VSYNC的訊號接受物件 USE_VSYNC預設為true
mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;
//初始化上一次frame渲染的時間點
mLastFrameTimeNanos = Long.MIN_VALUE;
//計算幀率,也就是一幀所需的渲染時間,getRefreshRate是重新整理率,一般是60
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
//建立訊息處理佇列
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
}
// DisplayEventReceiver類:子類FrameDisplayEventReceiver
public DisplayEventReceiver(Looper looper, int vsyncSource) {
...
mMessageQueue = looper.getQueue();
// //初始化native的訊息佇列
// 接受數量多少等於looper中訊息的多少
mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
vsyncSource);
mCloseGuard.open("dispose");
}
// JNI--nativeInit方法(該方法定義在android_view_DisplayEventReceiver.cpp中)
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
jobject messageQueueObj) {
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
...
sp<NativeDisplayEventReceiver> receiver = new NativeDisplayEventReceiver(env,
receiverWeak, messageQueue);
status_t status = receiver->initialize();
...
receiver->incStrong(gDisplayEventReceiverClassInfo.clazz);
// 把c++中NativeDisplayEventReceiver物件地址返回給Java層
// 通過這種方式將Java層的物件與Native層的物件關聯在了一起。
return reinterpret_cast<jlong>(receiver.get());
}
複製程式碼
現在讓我們回到mDisplayEventReceiver.scheduleVsync();方法中:
// DisplayEventReceiver類:
public void scheduleVsync() {
// 從上面分析可知,此時mReceiverPtr儲存著
// c++中NativeDisplayEventReceiver物件的地址
// 從名字就知道此物件為:原生顯示接收器(作用:請求VSYNC的同步)
if (mReceiverPtr == 0) {
...
} else {
// 該方法也為native方法
// 傳入NativeDisplayEventReceiver物件的地址,請求VSYNC的同步
nativeScheduleVsync(mReceiverPtr);
}
}
複製程式碼
VSYNC的同步:其作用主要是讓顯示卡的運算和顯示器重新整理率一致以穩定輸出的畫面質量。VSYNC的同步具體如何用,具體如何,不是本文的重點,感興趣的小夥伴自行搜尋吧。 我們是執行mChoreographer.postCallback方法進入的JNI,因此其最終會回撥到引數mTraversalRunnable(TraversalRunnable)的類方法內。想具體瞭解VSYNC的小夥伴不妨看看: dandanlove.com/2018/04/13/…
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 移除同步障礙
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
...
}
}
複製程式碼
從原始碼中可以看到TraversalRunnable類內,執行了方法doTraversal();而doTraversal裡先是移除了同步障礙,緊接著執行了performTraversals方法。
繪製流程三部曲
- 重量級方法來啦,performTraversals方法中,執行了我們所熟知的Measure、Layout、Draw的方法:
private void performTraversals() {
final View host = mView;// 這就是DecorView
...
boolean newSurface = false;
...
// 27原始碼1901行
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
...
......
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
// 獲取根View的MeasureSpec的方法
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
// 27原始碼2167行
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
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;
}
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(mTag,
"And hey let's measure once more: width=" + width
+ " height=" + height);
// 27原始碼2193行
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
}
if (didLayout) {
// 27原始碼2212行
performLayout(lp, mWidth, mHeight);
...
}
......
// 決定是否讓newSurface為true,導致後邊是否讓performDraw無法被呼叫,而是重新scheduleTraversals
if (!hadSurface) {
if (mSurface.isValid()) {
// If we are creating a new surface, then we need to
// completely redraw it. Also, when we get to the
// point of drawing it we will hold off and schedule
// a new traversal instead. This is so we can tell the
// window manager about all of the windows being displayed
// before actually drawing them, so it can display then
// all at once.
newSurface = true;
.....
}
}
......
if (!cancelDraw && !newSurface) {
// 27原始碼2359行
performDraw();
} else {
if (isViewVisible) {
// 再執行一次 scheduleTraversals,也就是會再執行一次performTraversals
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
mIsInTraversal = false;
}
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
...
int relayoutResult = mWindowSession.relayout(
mWindow, mSeq, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f),
viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame,
mPendingMergedConfiguration, mSurface);// 將mSurface引數傳入
...
return relayoutResult;
}
複製程式碼
performTraversals()方法先呼叫了relayoutWindow()方法,而relayoutWindow()方法中呼叫了IWindowSession的relayout()方法並傳入了mSurface,暫切記住這個方法,我們放到最後再說。
接下來需要分3個模組講解:繪製流程三部曲之Measure
這裡有個重要的類MeasureSpec:在Measure流程中,系統會將View的LayoutParams根據父容器所施加的規則轉換成對應的MeasureSpec,然後在onMeasure方法中根據這個MeasureSpec來確定View的測量寬高。它的高兩位用來表示模式SpecMode,低30位用來表示大小SpecSize。 SpecMode共有以下三種型別:
- UNSPECIFIED:父容器不作限制,子View想多大就多大,一般用於系統內部,如:ScrollView。
- EXACTLY:精確模式,父容器完全決定子View的大小,當寬或高設為確定值時:即width=20dp,height=30dp,或者為match_parent。
- AT_MOST:最大模式,大小不能大於SpecSize,也就是子View的大小有上限,對應於LayoutParams中的warp_content。看一看其部分原始碼:
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* UNSPECIFIED 模式:
* 父View不對子View有任何限制,子View需要多大就多大
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* EXACTYLY 模式:
* 父View已經測量出子Viwe所需要的精確大小,這時候View的最終大小
* 就是SpecSize所指定的值。對應於match_parent和精確數值這兩種模式
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* AT_MOST 模式:
* 子View的最終大小是父View指定的SpecSize值,並且子View的大小不能大於這個值,
* 即對應wrap_content這種模式
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
//將size和mode打包成一個32位的int型數值
//高2位表示SpecMode,測量模式,低30位表示SpecSize,某種測量模式下的規格大小
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
//將32位的MeasureSpec解包,返回SpecMode,測量模式
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
//將32位的MeasureSpec解包,返回SpecSize,某種測量模式下的規格大小
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
//...
}
複製程式碼
可以看出,該類的思路是相當清晰的,對於每一個View,包括DecorView,都持有一個MeasureSpec,而該MeasureSpec則儲存了該View的尺寸規格。在View的測量流程中,通過makeMeasureSpec來將size和mode打包成一個32位的int型數值,在其他流程通過getMode或getSize得到模式和寬高。
我們重新看回上面ViewRootImpl#getRootMeasureSpec方法的實現:根據不同的模式來設定MeasureSpec,如果是LayoutParams.MATCH_PARENT模式,則是視窗的大小,WRAP_CONTENT模式則是大小不確定,但是不能超過視窗的大小等等。對於DecorView來說,它已經是頂層view了,沒有父容器,因此DecorView的 MeasureSpec使用的是螢幕視窗的大小windowSize和DecorView的LayoutParams來確認MeasureSpec的。那麼到目前為止,就已經獲得了一份DecorView的MeasureSpec,它代表著根View的規格、尺寸,在接下來的measure流程中,就是根據已獲得的根View的MeasureSpec來逐層測量各個子View。
接下來分析測量的邏輯:
// ViewRootImpl類
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);
}
}
// View 類
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
// 回撥onMeasure,在自定義View時也經常會重寫此方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
// 若不重寫此方法,系統會設定一個預設的大小給子View
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
// 無論是EXACTLY還是AT_MOST,都按照測量結果進行設定。
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;
break;
}
return result;
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
// 儲存到成員變數mMeasuredWidth和mMeasuredHeight中
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
複製程式碼
根據原始碼的追蹤,最終將測量的結果儲存在自己的mMeasuredWidth和mMeasuredHeight成員變數中。ViewGroup的測量流程和此一致,只是其在onMeasure時需要測量子View。我們看一看FrameLayout的onMeasure:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 獲取子View的個數
int count = getChildCount();
// 判斷當前佈局的寬高是否是match_parent模式或者指定一個精確的大
// 如果是則置measureMatchParent為false.
// 因為如果是,則當前父控制元件則寬高大小已經確定,不受子View的限制
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
// 清空需要測量match_parent寬或高的子檢視的集合
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
// 遍歷子View,獲取修正自己的最大寬高
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 對child進行測量,方法裡child會呼叫自己的child.measure()方法測量自己
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 尋找子View中寬高的最大者,因為如果FrameLayout是wrap_content屬性
// 那麼它的大小取決於子View中的最大者
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
// 如果FrameLayout是wrap_content模式,那麼往mMatchParentChildren中
// 新增寬或者高為match_parent的子View,
// 因為該子View的最終測量大小會影響到FrameLayout的最終測量大小影響
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// Account for padding too
// 子View中的最大的寬高和自身的padding值都會影響到最終的大小
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
// 根據有無背景來算出最大的寬高,getSuggestedMinimumHeight/Width下面有說明
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
// 重新給自己(FrameLayout)設定測量結果(儲存測量結果)
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
// 讀取需要再測量的子檢視數量(設定為match_parent的子View)
count = mMatchParentChildren.size();
// 此處判斷必須大於1,若不大於1,則不會發生子View改變
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
/**
* 如果子View的寬度是match_parent屬性,那麼對當前FrameLayout的MeasureSpec修改:
* 把widthMeasureSpec的寬度規格修改為:總寬度 - padding - margin,這樣做的意思是:
* 對於子Viw來說,如果要match_parent,那麼它可以覆蓋的範圍是FrameLayout的測量寬度
* 減去padding和margin後剩下的空間。
*
* 以下兩點的結論,可以檢視getChildMeasureSpec()方法:
*
* 如果子View的寬度是一個確定的值,比如50dp,那麼FrameLayout的widthMeasureSpec的寬度規格修改為:
* SpecSize為子View的寬度,即50dp,SpecMode為EXACTLY模式
*
* 如果子View的寬度是wrap_content屬性,那麼FrameLayout的widthMeasureSpec的寬度規格修改為:
* SpecSize為子View的寬度減去padding減去margin,SpecMode為AT_MOST模式
*/
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
final int childHeightMeasureSpec;
// 同理對高度進行相同的處理
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
// 這部分的子View需要重新進行measure過程
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
// getSuggestedMinimumHeight同理
protected int getSuggestedMinimumWidth() {
//如果沒有給View設定背景,那麼就返回View本身的最小寬度mMinWidth
//如果給View設定了背景,那麼就取View本身最小寬度mMinWidth和背景的最小寬度的最大值
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
複製程式碼
總結一下這段程式碼:首先,FrameLayout根據它的MeasureSpec來對每一個子View進行測量,即呼叫measureChildWithMargin方法;對於每一個測量完成的子View,會尋找其中最大的寬高,那麼FrameLayout的測量寬高會受到這個子View的最大寬高的影響(wrap_content模式),接著呼叫setMeasureDimension方法,把FrameLayout的測量寬高儲存。最後則是特殊情況的處理,即當FrameLayout為wrap_content屬性時,如果其子View是match_parent屬性的話,則要重新設定FrameLayout的測量規格,然後重新對該部分View測量。我們看看它是如何測量child的,程式碼如下:
// ViewGroup類
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
// 子View的LayoutParams,你在xml的layout_width和layout_height,
// layout_xxx的值最後都會封裝到這個個LayoutParams。
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
複製程式碼
由原始碼可知,裡面呼叫了getChildMeasureSpec方法,把父容器的MeasureSpec以及自身的layoutParams屬性傳遞進去來獲取子View的MeasureSpec,這也印證了“子View的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同決定”這個結論。現在我們看下這個getChildMeasureSpec的實現:
// ViewGroup類
// spec為父View的MeasureSpec
// padding為父View在相應方向的已用尺寸加上父View的padding和子View的margin
// childDimension為子View的LayoutParams的值
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
// size表示子View可用空間:父容器尺寸減去padding
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
// 表示子View的LayoutParams指定了具體大小值(xx dp)
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 子View想和父View一樣大
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 子View想自己決定其尺寸,但不能比父View大
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
複製程式碼
這裡根據ViewGroup的SpecMode不同,給child設定了不同的模式和大小,以保證child能正確完成測量的過程。現在我們返回measureChildWithMargins方法,接著就會執行child.measure方法,此時如果child還是ViewGroup,則依舊會走一遍上面舉的Fragment的onMeasure流程,直到child為單純的View為止。這就回到了更上面講的View的measure方法啦。
我們來個關於MeasureSpec的例子總結:(純屬個人理解,若有誤之處,敬請指正)
- 先明確一個概念,現在有三種佈局,分別為:DecorView、FrameLayout、若干childView
- MeasureSpec的最初來源是DecorView,使用螢幕視窗的大小windowSize和DecorView的LayoutParams來確認firstWidthMeasureSpec和firstHeightMeasureSpec的,因此DecorView的大小一開始就是確認的
- DecorView根佈局,FrameLayout為DecorView的子View,若干childView為FrameLayout子View
- 每個子View在measure自己前都會先根據父佈局給的widthMeasureSpec和heightMeasureSpec以及padding和margin、自己的寬高來計算出適合自己的widthMeasureSpec和heightMeasureSpec
- 若FrameLayout的寬高都為match_parent,則代表FrameLayout的大小和DecorView一樣大,則DecorView和FrameLayout的widthMeasureSpec和heightMeasureSpec一樣。
- 若FrameLayout的寬高都為wrap_content或者有一樣為wrap_content,則FrameLayout的大小就不一定和DecorView一樣,根據原始碼ViewGroup的getChildMeasureSpec方法可知,wrap_content情況的佈局寬高大小等於父佈局寬高(DecorView)-自身padding,所以此時兩者的widthMeasureSpec和heightMeasureSpec是不一樣的,至少mode不一致。假設此時沒有設定padding以及FrameLayout寬為match_paren高為wrap_content,因此此時FrameLayout的寬高會和螢幕大小一致,但heightMeasureSpec模式會為AT_MOST。
- 根據第5條的分析(即同上面情況一致),此時的FrameLayout的大小不確定,而此時需要先測量FrameLayout的子View(若干childView),並且此時將使用size大小和螢幕大小一致但mode模式為AT_MOST的widthMeasureSpec和heightMeasureSpec傳入childView中。
- childView中根據第6步傳入的widthMeasureSpec和heightMeasureSpec生成的自己的widthMeasureSpec和heightMeasureSpec,此時測量出來的各個childView的大小。
- FrameLayout在childView測量完成後需要根據若干childView中的最大寬高和自身的padding值以及有無背景圖引起的大小來設定自身最終的寬高。
- 若在眾多childView中存在寬高為match_parent的View時,則需要對這部分View重新測量,因為上一次測量是使用size大小和螢幕大小一致但mode模式不一致的widthMeasureSpec和heightMeasureSpec,而現在最終的FrameLayout的大小已經確定,因此FrameLayout的widthMeasureSpec和heightMeasureSpec已經發生改變(mode發生改變),因此需要遍歷這部分childView(這部分子View大小可能會因FrameLayout的高固定而改變自己的大小),由於第8步已經知道了父佈局的精確大小,所以此時只需要根據每個childView的允許的最大寬或高和MeasureSpec.EXACTLY形成適合每個childView的MeasureSpec值,然後重新measure這部分childView。
- 若干childView在測量自己前也需要先結合引數中給出的widthMeasureSpec和heightMeasureSpec,以及padding、margin、自己的寬高計算出適合自己的MeasureSpec值,然後傳出measure中,最後在onMeasure中進一步測量。
- 就是由於會出現第9步的情形,因此View有時會多次onMeasure。
// 該佈局中FrameLayout有兩個TextView,都帶有match_parent的屬性
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="100dp"
android:layout_height="wrap_content"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="Hello World!Hello World" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Hello World!" />
</FrameLayout>
// TextView類:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
......
if (heightMode == MeasureSpec.EXACTLY) {
// Parent has told us how big to be. So be it.
height = heightSize;
mDesiredHeightAtMeasure = -1;
} else {
// 獲取所需高度
int desired = getDesiredHeight();
height = desired;
...
}
...
setMeasuredDimension(width, height);
}
複製程式碼
上述佈局繪製的效果圖,注:兩個TextView高是一樣的(不信可以自己試試)
說明:該情況符合上述說的第5步,而經過第6和第7步測量後,heightMeasureSpec的mode為AT_MOST,兩個TextView的高度根據原始碼(感興趣)來看是不一致的,雖然兩者的measure之前的heightMeasureSpec一致,但是最終測量出來的高度不同,可將第一個TextView去掉可顯示出,第二個TextView第一次測量後的高度,而FrameLayout確定高度後heightMeasureSpec的mode為EXACTLY,從程式碼中可看出height被賦值為FrameLayout高度-padding值,由於無padding,則和FrameLayout高度一致。
疑問:不過上述程式碼中為什麼要判斷match_parent的子View必須大於1,才重新測量子View? 將第一個TextView寬和高都設定為wrap_content,果真第二個TextView高度會變為一行高度。
繪製流程三部曲之Layout
接下來看一看View的onLayout佈局,layout的主要作用:根據子檢視的大小以及佈局引數將View樹放到合適的位置上。此時回到ViewRootImpl類的performLayout方法:
// ViewRootImpl類
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
...
try {
// 先呼叫mView的layout方法
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
// 請求重新佈局的集合(繪製流程一般為空)
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
// 獲取到需要進行layout的View的個數
// requestLayout() was called during layout.
// If no layout-request flags are set on the requesting views, there is no problem.
// If some requests are still pending, then we need to clear those flags and do
// a full request/measure/layout pass to handle this situation.
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,false);
if (validLayoutRequesters != null) {
mHandlingLayoutInLayoutRequest = true;
// Process fresh layout requests, then measure and layout
int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
// 呼叫它們的requestLayout方法,
view.requestLayout();
}
// 再次進行測量
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
mInLayout = true;
// 重新layout
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mHandlingLayoutInLayoutRequest = false;
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null) {
final ArrayList<View> finalRequesters = validLayoutRequesters;
// 再次檢查是否仍有需要layout的View,如果有,就到下一幀再繼續
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during second layout pass: posting in next frame");
view.requestLayout();
}
}
});
}
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
boolean requestLayoutDuringLayout(final View view) {
if (view.mParent == null || view.mAttachInfo == null) {
// Would not normally trigger another layout, so just let it pass through as usual
return true;
}
if (!mLayoutRequesters.contains(view)) {
mLayoutRequesters.add(view);
}
...
}
// View類:
@CallSuper
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
...
}
複製程式碼
performLayout方法中首先呼叫了View的layout方法佈局控制元件,而後出現了一個集合mLayoutRequesters,從原始碼中可知mLayoutRequesters集合資料是在requestLayoutDuringLayout方法中新增的,而requestLayoutDuringLayout方法確是被View中的requestLayout方法呼叫。View類中的requestLayout方法和invalidate方法主要用於自定義View。
- requestLayout方法會導致View的onMeasure、onLayout、onDraw方法被呼叫;
- invalidate方法則只會導致View的onDraw方法被呼叫
因此,現在這種情況程式碼不會走到if (numViewsRequestingLayout > 0) 判斷內,所以只需要看View的layout方法即可。
// View類:
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
// 判斷view和和其父view的佈局模式情況,若兩者不同步,則進行子view的size大小的修改
// 即有兩種情況會進入到該if條件:
// 一是子view有特殊的光學邊界,而父view沒有,此時optical為true,
// 另一種是父view有一個特殊的光學邊界,而子view沒有,此時optical為false
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
// 回撥onLayout()方法
onLayout(changed, l, t, r, b);
...
}
...
}
public static boolean isLayoutModeOptical(Object o) {
return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
}
// ViewGroup類:
private int mLayoutMode = LAYOUT_MODE_UNDEFINED;
boolean isLayoutModeOptical() {
return mLayoutMode == LAYOUT_MODE_OPTICAL_BOUNDS;
}
複製程式碼
mLayoutMode的值預設是LAYOUT_MODE_UNDEFINED,也就是說:isLayoutModeOptical(mParent)返回false,所以會呼叫setFrame方法,並把四個位置資訊傳遞進去,這個方法用於確定View的四個頂點的位置,即初始化mLeft,mRight,mTop,mBottom這四個值,當初始化完畢後,ViewGroup的佈局流程也就完成了。 接下來layout方法裡會回撥onLayout()方法,該方法在ViewGroup中呼叫,用於確定子View的位置,即在該方法內部,子View會呼叫自身的layout方法來進一步完成自身的佈局流程。由於上面Measure是對FrameLayout進行分析,則現在Layout也使用FrameLayout分析:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
//把父容器的位置引數傳遞進去
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
// 以下四個值會影響到子View的佈局引數
// parentLeft由父容器的padding和Foreground決定
final int parentLeft = getPaddingLeftWithForeground();
// parentRight由父容器的width和padding和Foreground決定
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 獲取子View的測量寬高
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
//當子View設定了水平方向的layout_gravity屬性時,根據不同的屬性設定不同的childLeft
//childLeft表示子View的 左上角座標X值
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
/* 水平居中,由於子View要在水平中間的位置顯示,因此,要先計算出以下:
* (parentRight - parentLeft -width)/2 此時得出的是父容器減去子View寬度後的
* 剩餘空間的一半,那麼再加上parentLeft後,就是子View初始左上角橫座標(此時正好位於中間位置),
* 假如子View還受到margin約束,由於leftMargin使子View右偏而rightMargin使子View左偏,所以最後
* 是 +leftMargin -rightMargin .
*/
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
//水平居右,子View左上角橫座標等於 parentRight 減去子View的測量寬度 減去 margin
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
//如果沒設定水平方向的layout_gravity,那麼它預設是水平居左
//水平居左,子View的左上角橫座標等於 parentLeft 加上子View的magin值
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
//當子View設定了豎直方向的layout_gravity時,根據不同的屬性設定同的childTop
//childTop表示子View的 左上角座標的Y值
//分析方法同上
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
//對子元素進行佈局,左上角座標為(childLeft,childTop),右下角座標為(childLeft+width,childTop+height)
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
複製程式碼
onLayout方法內部直接呼叫了layoutChildren方法,而layoutChildren則是具體的實現。首先獲取父容器的padding值,然後遍歷其每一個子View,根據子View的layout_gravity屬性、子View的測量寬高、父容器的padding值、來確定子View的佈局引數,然後呼叫child.layout方法,把佈局流程從父容器傳遞到子元素。如果子View是一個ViewGroup,那麼就會重複以上步驟,如果是一個View,那麼會直接呼叫View#layout方法。
繪製流程三部曲之Draw
現在就剩下最後的Draw啦,我們繼續回到ViewRootImpl中看performDraw方法:
// ViewRootImpl類:
private void performDraw() {
...
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
...
if (mReportNextDraw) {
...
try {
mWindowSession.finishDrawing(mWindow);
} catch (RemoteException e) {
}
}
}
複製程式碼
裡面又呼叫了ViewRootImpl#draw方法,並傳遞了fullRedrawNeeded引數,而該引數由mFullRedrawNeeded成員變數獲取,它的作用是判斷是否需要重新繪製全部檢視,如果是第一次繪製檢視,那麼顯然應該繪製所以的檢視,如果由於某些原因,導致了檢視重繪,那麼就沒有必要繪製所有檢視。
// ViewRootImpl類:
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
...
if (!sFirstDrawComplete) {
synchronized (sFirstDrawHandlers) {
sFirstDrawComplete = true;
final int count = sFirstDrawHandlers.size();
for (int i = 0; i< count; i++) {
mHandler.post(sFirstDrawHandlers.get(i));
}
}
}
// 滑動到指定區域
scrollToRectOrFocus(null, false);
// 分發OnScrollChanged事件
if (mAttachInfo.mViewScrollChanged) {
mAttachInfo.mViewScrollChanged = false;
mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
}
boolean animating = mScroller != null && mScroller.computeScrollOffset();
final int curScrollY;
if (animating) {
curScrollY = mScroller.getCurrY();
} else {
curScrollY = mScrollY;
}
// RootView滑動回撥
if (mCurScrollY != curScrollY) {
mCurScrollY = curScrollY;
fullRedrawNeeded = true;
if (mView instanceof RootViewSurfaceTaker) {
((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
}
}
final float appScale = mAttachInfo.mApplicationScale;
final boolean scalingRequired = mAttachInfo.mScalingRequired;
int resizeAlpha = 0;
// 獲取需要繪製的區域
final Rect dirty = mDirty;
if (mSurfaceHolder != null) {
dirty.setEmpty();
if (animating && mScroller != null) {
mScroller.abortAnimation();
}
return;
}
//如果fullRedrawNeeded為真,則把dirty區域置為整個螢幕,表示整個檢視都需要繪製
//第一次繪製流程,需要繪製所有檢視
if (fullRedrawNeeded) {
mAttachInfo.mIgnoreDirtyState = true;
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
...
// 分發onDraw
mAttachInfo.mTreeObserver.dispatchOnDraw();
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
//
} else {
//
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
...
}
複製程式碼
首先滑動到指定區域,然後獲取了mDirty值,該值儲存了需要重繪的區域的資訊,接著根據fullRedrawNeeded來判斷是否需要重置dirty區域,最後呼叫了ViewRootImpl.drawSoftware方法,並把相關引數傳遞進去,包括dirty區域。
// ViewRootImpl類:
final Surface mSurface = new Surface(); // 成員變數
private boolean drawSoftware(Surface surface, AttachInfo attachInfo
, int xoff, int yoff,boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
try {
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
// 從mSurface中鎖定一塊Canvas區域,由dirty區域決定
canvas = mSurface.lockCanvas(dirty);
// The dirty rectangle can be modified by Surface.lockCanvas()
//noinspection ConstantConditions
if (left != dirty.left || top != dirty.top || right != dirty.right
|| bottom != dirty.bottom) {
attachInfo.mIgnoreDirtyState = true;
}
// TODO: Do this in native
canvas.setDensity(mDensity);
} catch (Surface.OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
} catch (IllegalArgumentException e) {
...
mLayoutRequested = true; // ask wm for a new surface next time.
return false;
}
try {
...
try {
canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
attachInfo.mSetIgnoreDirtyState = false;
// 正式開始繪製
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
...
}
} finally {
try {
// 解鎖Canvas,螢幕上馬上就回出現繪製的畫面長相
surface.unlockCanvasAndPost(canvas);
} catch (IllegalArgumentException e) {
...
}
...
}
return true;
}
複製程式碼
首先是例項化了Canvas物件,然後從mSurface鎖定一塊Canvas的區域,由dirty區域決定,接著對canvas進行一系列的屬性賦值,然後呼叫了mView.draw(canvas)方法繪製,最後解鎖顯示畫面。我們在上面“onResume介面可見繪製之同步屏障--VSYNC同步”部分提到了Surface和SurfaceSession兩個概念。而從ViewRootImpl.drawSoftware方法中也看到了Surface的身影。 現在來總結一下目前出現的和Surface有關的身影:
- 在ViewRootImpl初始化(構造時),會建立一個Surface,上面程式碼可見,直接在成員變數new出來的。
- ViewRootImpl通過IWindowSession和WMS互動,而WMS呼叫的一個attahc方法會構造一個SurfaceSession,忘記的小夥伴請回看“onResume介面可見繪製之IWindowSession和IWindow”部分。
- ViewRootImpl在performTransval的處理過程中會呼叫IWindowSession的relayout方法,回看“繪製流程三部曲”部分。
- ViewRootImpl.drawSoftware方法中呼叫Surface的lockCanvas方法,得到一塊畫布
- ViewRootImpl.drawSoftware方法中呼叫Surface的unlockCanvasAndPost方法,釋放這塊畫布
現在通過跟Surface有關的身影來說一下Surface,先來一張精簡流程圖:
圖中出現了幾個新類,主要看一下SurfaceComposerClient如何得到的,上面說到SurfaceSession類的初始化,回看“onResume介面可見繪製之IWindowSession和IWindow”// SurfaceSession類:
public SurfaceSession() {
// 會呼叫native本地方法
mNativeClient = nativeCreate();
}
// android_view_SurfaceSession.cpp檔案
static jlong nativeCreate(JNIEnv* env, jclass clazz) {
// 初始化SurfaceComposerClient,該物件會和SurfaceFlinger互動
SurfaceComposerClient* client = new SurfaceComposerClient();
client->incStrong((void*)nativeCreate);
return reinterpret_cast<jlong>(client);
}
複製程式碼
通過精簡流程圖和上述程式碼,我們可以發現一下幾點:
- SurfaceComposerClient是在初始化SurfaceSession時才顯示初始化的
- 在“onResume介面可見繪製之IWindowSession和IWindow”裡我們講解ViewRootImpl類中setView方法時,可以看到requestLayout()在mWindowSession.addToDisplay()之前呼叫,並且上面也說明了SurfaceSession初始化時機是在mWindowSession.addToDisplay中
- 所有三部曲的邏輯都是由requestLayout()而來
- 精簡流程圖上顯示需要SurfaceComposerClient,才會有後面的mSurface.lockCanvas和mSurface.unlockCanvasAndPost
那麼是什麼讓在走三部曲邏輯前,使得SurfaceSession初始化的呢?
其實,這個問題的答案就在requestLayout()裡,我們上面“onResume介面可見繪製之同步屏障--VSYNC同步”講到同步障礙:mHandler.getLooper().getQueue().postSyncBarrier(),就是因為這個,其傳送了一個同步障礙訊息後,會阻止訊息佇列中其它訊息的執行,但是並不會停止程式碼的執行,此時程式碼會先越過requestLayout()繼續向下走,此時就會初始化SurfaceSession...,等Looper輪詢到同步障礙訊息後繼續走,直到回撥mTraversalRunnable裡的doTraversal()方法。這就類似於handler.post裡程式碼和普通主執行緒程式碼:
// 主執行緒中:
Handler handler = new Handler();
handler.post(new Runnable() {
@Override
public void run() {
Log.e("handler.post","handler.post:111111111111111111111111111111");
}
});
for(int i = 0; i <10000; i++){
Log.e("handler.post","handler.post:"+i);
}
//列印結果為:
......
handler.post:9998
handler.post:9999
04-18 21:29:26.521 6249-6249/com.android.sourcecodeanalysis E/handler.post: handler.post:111111111111111111111111111111
複製程式碼
因此來簡單總結一下Surface:整個Activity的繪圖流程就是從mSurface中lock一塊Canvas,然後交給mView去自由發揮畫畫才能,最後unlockCanvasAndPost釋放這塊Canvas。 由於Surface這個系統非常複雜,包含Layer(顯示層)、FrameBuffer(幀緩衝)、PageFlipping(畫面交換:FrontBuffer前一幀緩衝畫面和BackBuffer後一幀緩衝畫面)、SurfaceFlinger(影象混合:多個顯示層混合一起顯示畫面)以及Linux共享記憶體等知識點,這裡就不再深究,有興趣的小夥伴,可以參考“深入理解Android卷1(鄧凡平)”一書第8章Surface系統,去進一步研究(切記閱讀原始碼需謹慎,c/c++自我感覺很不錯的小夥伴可以嘗試深入一下)。
接著看我們最後的draw的邏輯:// View類:
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
...
}
複製程式碼
原始碼中已經給出了draw的步驟:
- 對View的背景進行繪製
- 儲存當前的圖層資訊(可跳過)
- 繪製View的內容
- 對View的子View進行繪製(如果有子View)
- 繪製View的褪色的邊緣,類似於陰影效果(可跳過)
- 繪製View的裝飾(例如:滾動條) 原始碼中提示,其中第2步和第5步是可以跳過的,是常見的情況。
// View類:
// 繪製背景
private void drawBackground(Canvas canvas) {
// mBackground是該View的背景引數,比如背景顏色
final Drawable background = mBackground;
if (background == null) {
return;
}
// 根據View四個佈局引數來確定背景的邊界
setBackgroundBounds();
...
// 獲取當前View的mScrollX和mScrollY值
final int scrollX = mScrollX;
final int scrollY = mScrollY;
// 考慮到了view的偏移引數:scrollX和scrollY
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
// 如果scrollX和scrollY有值,則對canvas的座標進行偏移,再繪製背景
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
protected void onDraw(Canvas canvas) {
// 該方法是一個空實現,因為不同的View有著不同的內容,需要自己去實現
// 即在自定義View中重寫該方法來實現。
}
// View中無子佈局,所有dispatchDraw為空實現,該方法主要針對ViewGroup有子View情況
protected void dispatchDraw(Canvas canvas) {
}
複製程式碼
如果該View是一個ViewGroup,則其繪製呼叫dispatchDraw(canvas);
// ViewGroup類:
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
// 遍歷了所以子View
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
......
}
複製程式碼
可以看到dispatchDraw方法中會遍歷所有子View,並且呼叫drawChild方法:
// ViewGroup類:
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
// 這裡呼叫了View的draw方法,但這個方法並不是上面所說的,因為引數不同
return child.draw(canvas, this, drawingTime);
}
// View類:
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
......
if (!drawingWithDrawingCache) {
if (drawingWithRenderNode) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
} else {
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
}
}
} else if (cache != null) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
if (layerType == LAYER_TYPE_NONE) {
// no layer paint, use temporary paint to draw bitmap
Paint cachePaint = parent.mCachePaint;
if (cachePaint == null) {
cachePaint = new Paint();
cachePaint.setDither(false);
parent.mCachePaint = cachePaint;
}
cachePaint.setAlpha((int) (alpha * 255));
canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
} else {
// use layer paint to draw the bitmap, merging the two alphas, but also restore
int layerPaintAlpha = mLayerPaint.getAlpha();
mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
mLayerPaint.setAlpha(layerPaintAlpha);
}
}
}
複製程式碼
首先判斷是否已經有快取,即之前是否已經繪製過一次了,如果沒有,則會呼叫draw(canvas)方法,開始正常的繪製,即上面所說的六個步驟,否則利用快取來顯示。ViewGroup繪製過程:dispatchDraw遍歷繪製子View,若子View依舊為ViewGroup則接著dispatchDraw遍歷繪製,直到不是ViewGroup為止。 最後就只剩下了前景繪製(onDrawForeground):所謂的前景繪製,就是指View除了背景、內容、子View的其餘部分,例如滾動條等.
// View類:
public void onDrawForeground(Canvas canvas) {
onDrawScrollIndicators(canvas);
onDrawScrollBars(canvas);
final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
if (foreground != null) {
if (mForegroundInfo.mBoundsChanged) {
mForegroundInfo.mBoundsChanged = false;
final Rect selfBounds = mForegroundInfo.mSelfBounds;
final Rect overlayBounds = mForegroundInfo.mOverlayBounds;
if (mForegroundInfo.mInsidePadding) {
selfBounds.set(0, 0, getWidth(), getHeight());
} else {
selfBounds.set(getPaddingLeft(), getPaddingTop(),
getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
}
final int ld = getLayoutDirection();
Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
foreground.setBounds(overlayBounds);
}
foreground.draw(canvas);
}
}
複製程式碼
和一般的繪製流程非常相似,都是先設定繪製區域,然後利用canvas進行繪製。
至此,View整體繪製流程完成。
說下感受,此篇文章前前後後準備加寫,用了5天空閒時間,最大的收穫並不是只是知道了View的繪製流程,而是理解了自定義View什麼時候需要呼叫什麼方法和為什麼這樣呼叫的原理,並且以前有些看不懂的地方,突然感覺豁然開朗。雖然過程很辛苦,並且熬了幾個晚上,但是感覺都是值得的。也許沒有幾個人能看完整篇,但還是希望看的小夥伴能在此文章中收穫一點,一點點也是好的。最後告誡自己一句:深入原始碼需謹慎,能力不是很足時,不要太過深入,否則會迷失在裡面。
參考連結:
深入理解Android卷1(鄧凡平)
...
若覺得文章不錯或者不足之處,歡迎點贊留言,謝謝