概要
在Android开发中,通常我们作了startActivity之后,一会就加载出来了Activity页面,那么,这个Activity是到底是如何显示出来的呢?Activity的显示主要包括以下几个方面。
-
Activity的显示原理(Window/DecorView,ViewRoot)
-
Activity的UI刷新机制(Vsync/Choreographer)
-
UI的绘制原理(Measure/Layout/onDraw)
-
Surface原理(Surface/SurfaceFlinger)
Activity的显示原理
Activity的显示原理主要从以下几个方面进行分析:
1、Activity的setContentView()原理分析
首先来看看Activity的setConentView长什么样的
public void setContentView(int layoutResID) {
//attach中初始化window
getWindow().setContentView(layoutResID);
initActionBar();
}
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) {
...
mWindow = PolicyManager.makeNewWindow(this)
}
public Window makeNewWindow(Context context) {
return new PhoneWindow(context);
}
从上面看起来还是比较简单的,这里不详细分析,简单说明一下,getWindow()实际上get出来的PhoneWindow。继续关注PhoneWindow中的setContentView()方法是什么样的。
@Override
public void setContentView(int layoutResID) {
//这里mContentParent就是我们setContentView进来的view的父容器,DecorView是整个手机屏幕的父容器
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
private void installDecor() {
if (mDecor == null) {
//这里就是new DecorView(getContext(), -1);
mDecor = generateDecor();
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
} else {
//处理ActionBar的,这里我们不关心
}
}
protected ViewGroup generateLayout(DecorView decor) {
...
//layoutResource即为整个屏幕的布局文件,包括顶的通知栏,statusBar,navgationBar以及中间的contentView的parent
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;
}
以上步骤还只是把我们的contentView加载到decorView中,还没有显示。要Activity的onResume之后才会显示,下面看看onResume之后显示的原因。
2、Activity在onResume()之后显示原因分析
前面我们分析Activity的启动生命周期时有讲到加载Activity时,在调用ActivityThread中的handleLauncheActivity之后会走handleResumeActivity中,从而回调Activity的onResume()方法,这里我们再来看看handleResumeActivity的方法是什么样的
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
boolean reallyResume) {
//触发Activity的onResume()回调
ActivityClientRecord r = performResumeActivity(token, clearHide);
final Activity a = r.activity;
if (r.window == null && !a.mFinished) {
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) {
a.mWindowAdded = true;
//拿到Activity的PhoneWindow的WindowManager,
// 通过WindowManager的addView将Activity的ContentView添加,从而显示
wm.addView(decor, l);
}
}
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}
以上的步骤我们看到最核心的操作就是把contentView通过WindowManager的方法addView进去,那么add进去是之后是如何对该View的显示啊,布局,绘制,以及事件处理的呢,重点就在addView里面。进一步看看WindowManager的addView方法,实际上是在WindowManager作为一个接口,在它的实现类WindowManagerImpl中
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
//WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
//WindowManagerGlobal是全局管理window
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root = new ViewRootImpl(view.getContext(), display);
//ViewRootImpl管理View,一个ViewRoolImpl只能管理一个ViewTree
root.setView(view, wparams, panelParentView);
}
这里我们需要进一步分析ViewRootImpl中setView是如何处理的,代码块如下
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
...
// 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();
...
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mInputChannel);
}
}
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
//核心方法
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
//核心方法,该Callback会在下一次Vsync信号来的时候调用
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
//这里面执行measure, layout, draw
performTraversals();
}
}
private void performTraversals() {
...
//下面四大核心方法,这里直观的描述一下这个显示过程:
//在这个方法之前,Surface实际上还是一个空的,处于不可用状态
//用来向WMS申请Surface,实际上就是通过binder对象向WMS请求一个可以用的Surface
//有了可用的surface之后,绘制就会有buffer,在buffer上绘制完成之后,就可以把这个buffer提交到SurfaceFlinger,
//SurfaceFlinger将图像合成好之后,下一步即可写到屏幕的中缓存区,从而页面显示出来。
relayoutWindow(params, viewVisibility, insetsPending);
...
//对应onMeasure()
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//对应onLayout()
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
//对应onDraw()
performDraw();
}
从上面我们已经跟到ViewRootImpl中的performTraversals()函数,在这个函数中我们发现了平常我们常用的measure,layout,draw的回调地方。这上面步骤中作完后,界面好像就显示出来了,那么这个到底是怎么显示出来的呢?
答案是上面的mWindowSession.addToDisplay()方法。这里mWindowSession实际是一个WMS的binder对象,实际上应用会和WMS双向绑定,相互调用。addToDisplay()最终调用到的是Session中的方法
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets,
InputChannel outInputChannel) {
//通过wms对象添加window,并对window进行管理
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outInputChannel);
}