目录
- 前言
- setContentView()流程
- WindowManager.addView流程
前言
又是一年一度的1024程序员节了,今天不写点什么总感觉对不起这个节日。想来想去,就写点关于View的绘制。本文不会重点讲View绘制三大回调函数:onMeasure、onLayout、onDraw,而是站在Android framework的角度去分析一下View的绘制。
- View是如何被渲染到屏幕中的?
- ViewRoot、DecorView、Activity、Window、WindowManager是什么关系?
- View和Surface是什么关系?
- View和SurfaceFlinger、OpenGL ES是什么关系?
计算机的图像一般是需要经过底层的图像引擎输出GPU需要的数据交给GPU,显示器从GPU从不断的取出渲染好的数据显示到屏幕上。
熟悉Andriod体系架构的人都知道,Android底层渲染图像的引擎是OpenGL ES/Vulkan。那么View是被谁渲染的呢?没错,View最终也是交给底层渲染引擎的,那么从View到OpenGL ES这中间经历了哪些过程呢?
setContentView()流程
在Activity的onCreate中我们一般会通过setContentView来给Activity设置界面布局。这个时候,Activity是否开始渲染了呢?并没有,setContentView只是构建整个DecorView的树。
//android.app.Activity | |
public void setContentView(@LayoutRes int layoutResID) { | |
getWindow().setContentView(layoutResID); | |
initWindowDecorActionBar(); | |
} |
setContentView是调用Window的setContentView,而PhoneWindow是Window的唯一实现类:
//com.android.internal.policy.PhoneWindow | |
public void setContentView(int layoutResID) { | |
if (mContentParent == null) { | |
installDecor();//,安装DecorView | |
} 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);//,解析layoutResID | |
} | |
mContentParent.requestApplyInsets(); | |
final Callback cb = getCallback(); | |
if (cb != null && !isDestroyed()) { | |
cb.onContentChanged(); | |
} | |
mContentParentExplicitlySet = true; |
1处开始安装DecorView,主要是new一个DecorView,并找到其中id等于content的布局,通过mContentParent引用。我们在xml的写的布局就是添加到这个mContentParent容器中。
//com.android.internal.policy.PhoneWindow | |
private void installDecor() { | |
mForceDecorInstall = false; | |
if (mDecor == null) { | |
mDecor = generateDecor(-);//new一个DecorView | |
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); | |
mDecor.setIsRootNamespace(true); | |
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures !=) { | |
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); | |
} | |
} else { | |
mDecor.setWindow(this); | |
} | |
if (mContentParent == null) { | |
mContentParent = generateLayout(mDecor);//找到id==content的ViewGroup | |
... | |
} |
2处通过LayoutInflator解析传入的layoutResID,解析成View并添加到mContentParent中。mContentParent就是我们xml界面的中的id等于content的布局:
综上分析,setContentView主要完成两个功能:
1、构建DecorView
2、解析自定义的xml布局文件,添加到DecorView的content中。
所以setContentView还没有真正开始渲染图像。
思考:如果我们没有调用setContentView,Activity能正常启动吗?为什么?
WindowManager.addView流程
Android的所有View都是通过WindowManager.addView添加到屏幕中的。那么Activity的DecorView是什么时调被添加到屏幕中的呢?
答案在ActivityThread的handleResumeActivity方法中:
//android.app.ActivityThread | |
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, | |
String reason) { | |
... | |
//执行Activity的onResume生命周期 | |
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); | |
... | |
if (r.window == null && !a.mFinished && willBeVisible) { | |
r.window = r.activity.getWindow(); | |
View decor = r.window.getDecorView();//、调用了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(); | |
} | |
} | |
if (a.mVisibleFromClient) { | |
if (!a.mWindowAdded) { | |
a.mWindowAdded = true; | |
wm.addView(decor, l);//、开始调用WindowManager.addView将view添加到屏幕 | |
} else { | |
// The activity will get a callback for this {@link LayoutParams} change | |
// earlier. However, at that time the decor will not be set (this is set | |
// in this method), so no action will be taken. This call ensures the | |
// callback occurs with the decor set. | |
a.onWindowAttributesChanged(l); | |
} | |
} | |
// If the window has already been added, but during resume | |
// we started another activity, then don't yet make the | |
// window visible. | |
} else if (!willBeVisible) { | |
if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set"); | |
r.hideForNow = true; | |
} |
wm.addView才是开始DecorView渲染的入口。而它的触发时机是在Activity的onResume生命周期之后,所以说onResume之后View才会显示在屏幕上,并且渲染完成才可以获取到View的宽度。
主要看下1处调用了window的getDecorView()方法:
//com.android.internal.policy.PhoneWindow | |
@Override | |
public final @NonNull View getDecorView() { | |
if (mDecor == null || mForceDecorInstall) { | |
installDecor(); | |
} | |
return mDecor; | |
} |
这里可以看出,即使我们没有调用setContentView,DecorView也会初始化,只是会显示空白页面。
然后我们重点看下2处的代码,通过WindowManager的addView方法将DecorView添加到window中了:wm.addView(decor, l)
继续分析addView之前先梳理一下必要的基本知识。
上面的wm虽然是ViewManager类型的,它实际就是WindowManager。
WindowManager是一个接口,它继承自ViewManager。
public interface WindowManager extends ViewManager { | |
... | |
} |
可以看到WindowManager的实现类是WindowManagerImpl,后面WindowManager的功能都是靠WindowManagerImpl来实现的。
Window是抽象类,PhoneWindow是它的实现。WindowManager是Window的成员变量,Window和WindowManager都是在Activity的attach方法中初始化的:
//android.app.Activity | |
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, IBinder assistToken) { | |
attachBaseContext(context); | |
mFragments.attachHost(null /*parent*/); | |
mWindow = new PhoneWindow(this, window, activityConfigCallback);//,初始化window | |
...省略无关代码 | |
mWindow.setWindowManager( //、给window初始化windowManager | |
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE), | |
mToken, mComponent.flattenToString(), | |
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) !=); | |
if (mParent != null) { | |
mWindow.setContainer(mParent.getWindow()); | |
} | |
mWindowManager = mWindow.getWindowManager();//、Activity通过mWindowManager引用window中的WindowManager,两个wm是同一个东西。 | |
mCurrentConfig = config; | |
mWindow.setColorMode(info.colorMode); | |
setAutofillOptions(application.getAutofillOptions()); | |
setContentCaptureOptions(application.getContentCaptureOptions()); | |
} |
1处开始初始化window,并赋值给Activity的成员变量mWindow
2处给window设置windowManager
3处Activity通过mWindowManager引用window中的WindowManager,两个wm是同一个东西。
然后重点看下setWindowManager方法的实现:
//android.view.Window | |
public void setWindowManager(WindowManager wm, IBinder appToken, String appName, | |
boolean hardwareAccelerated) { | |
mAppToken = appToken; | |
mAppName = appName; | |
mHardwareAccelerated = hardwareAccelerated; | |
if (wm == null) { | |
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); | |
} | |
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); | |
} |
梳理完基本关系,再回头看下wm.addView过程。
//android.view.WindowManagerImpl | |
@Override | |
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { | |
applyDefaultToken(params); | |
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); | |
} |
可以看到wm.addView交给了mGolbal对象。
mGolbal是WindowManagerGlobal类型的全局单例:
public final class WindowManagerImpl implements WindowManager { | |
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); | |
... |
继续看WindowManagerGlobal.addView是如何实现的。
//android.view.WindowManagerGlobal | |
public void addView(View view, ViewGroup.LayoutParams params, | |
Display display, Window parentWindow) { | |
// ...省略无关代码 | |
ViewRootImpl root; | |
View panelParentView = null; | |
// ...省略无关代码 | |
root = new ViewRootImpl(view.getContext(), display); | |
view.setLayoutParams(wparams); | |
mViews.add(view); | |
mRoots.add(root); | |
mParams.add(wparams); | |
// do this last because it fires off messages to start doing things | |
try { | |
root.setView(view, wparams, panelParentView); | |
} catch (RuntimeException e) { | |
// BadTokenException or InvalidDisplayException, clean up. | |
if (index >=) { | |
removeViewLocked(index, true); | |
} | |
throw e; | |
} | |
} | |
} |
可以看到,这里创建了一个ViewRootImpl对象root,并将view、root、wparams保存到了集合中。最后调用了ViewRootImpl的setView方法设置视图。
继续跟踪ViewRootImpl的setView方法。
//android.view.ViewRootImpl | |
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { | |
synchronized (this) { | |
if (mView == null) { | |
mView = view; | |
//...省略不重要代码 | |
mAdded = true; | |
int res; /* = WindowManagerImpl.ADD_OKAY; */ | |
// 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(); | |
if ((mWindowAttributes.inputFeatures | |
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) ==) { | |
mInputChannel = new InputChannel(); | |
} | |
mForceDecorViewVisibility = (mWindowAttributes.privateFlags | |
& PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) !=; | |
try { | |
mOrigWindowType = mWindowAttributes.type; | |
mAttachInfo.mRecomputeGlobalAttributes = true; | |
collectViewAttributes(); | |
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, | |
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame, | |
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, | |
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel, | |
mTempInsets); | |
setFrame(mTmpFrame); | |
} 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(); | |
} | |
} | |
//...省略不重要代码 | |
if (view instanceof RootViewSurfaceTaker) { | |
mInputQueueCallback = | |
((RootViewSurfaceTaker)view).willYouTakeTheInputQueue(); | |
} | |
if (mInputChannel != null) { | |
if (mInputQueueCallback != null) { | |
mInputQueue = new InputQueue(); | |
mInputQueueCallback.onInputQueueCreated(mInputQueue); | |
} | |
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, | |
Looper.myLooper()); | |
} | |
view.assignParent(this); | |
//...省略不重要代码 | |
} | |
} | |
} |
WMS是Android窗口管理系统,在将View树注册到WMS之前,必须先执行一次layout,WMS除了窗口管理之外,还负责各种事件的派发,所以在向WMS注册前app在确保这棵view树做好了接收事件准备。
ViewRoot起到中介的作用,它是View树的管理者,同时也兼任与WMS通信的功能。
mWindowSession.addToDisplay将View的渲染交给了WindowManagerService。
mWindowSession是IWindowSession类型的变量,在服务端的实现类是Session.java,它是一个Binder对象。
public static IWindowSession getWindowSession() { | |
synchronized (WindowManagerGlobal.class) { | |
if (sWindowSession == null) { | |
try { | |
// Emulate the legacy behavior. The global instance of InputMethodManager | |
// was instantiated here. | |
// TODO(b/): Remove this hack after cleaning up @UnsupportedAppUsage | |
InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary(); | |
IWindowManager windowManager = getWindowManagerService(); | |
sWindowSession = windowManager.openSession( | |
new IWindowSessionCallback.Stub() { | |
public void onAnimatorScaleChanged(float scale) { | |
ValueAnimator.setDurationScale(scale); | |
} | |
}); | |
} catch (RemoteException e) { | |
throw e.rethrowFromSystemServer(); | |
} | |
} | |
return sWindowSession; | |
} | |
} | |
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, | |
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, | |
Rect outOutsets, InputChannel outInputChannel) { | |
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, | |
outContentInsets, outStableInsets, outOutsets, outInputChannel); | |
} |
可以看到最终是通过WindowManagerService完成了Window的添加。