之前,我们研究了Android应用的启动过程和退出过程,以及布局文件的加载过程,今天我们仍然照着这条线路,继续研究在布局文件加载之后,View是如何被显示到屏幕上来的,也就是研究View的显示过程。下面将从以下两个方面做介绍:1、从加载layout文件到view的测量、布局和绘制的全过程。2、surface、surfaceFlinger与view是如何关联起来的。
在Activity的onCreate方法中,我们通过setContentView(view)来初始化定义好的一个view,而此时view还没有显示出来,在Activity执行了onResume方法后,界面才显示完成,这说明了view的显示经历了Activity的这两个过程。下面是启动Activity与加载view的一个简单过程图。
一、过程分析
下面将会以ActivityThread类为起点,一步步跟踪view的显示过程。
在ActivityThread类中有一个Handler对象,它负责处理服务端传过来的消息。在接收到LAUNCH_ACTIVITY消息后,执行handleLaunchActivity方法,也就是启动一个Activity。
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
ActivityClientRecord r = (ActivityClientRecord)msg.obj;
r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
在此方法中,首先解析package信息,然后调用handleLaunchActivity方法来执行启动Activity。
我们继续跟踪handleLaunchActivity方法。
// 上面代码省略
// Make sure we are running with the most recent config.
handleConfigurationChanged(null, null);
if (localLOGV) Slog.v(TAG, "Handling launch of " + r);
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
Bundle oldState = r.state;
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed);
if (!r.activity.mFinished && r.startsNotResumed) {
// The activity manager actually wants this one to start out
// paused, because it needs to be visible but isn't in the
// foreground. We accomplish this by going through the
// normal startup (because activities expect to go through
// onResume() the first time they run, before their window
// is displayed), and then pausing it. However, in this case
// we do -not- need to do the full pause cycle (of freezing
// and such) because the activity manager assumes it can just
// retain the current state it has.
try {
r.activity.mCalled = false;
mInstrumentation.callActivityOnPause(r.activity);
// We need to keep around the original state, in case
// we need to be created again. But we only do this
// for pre-Honeycomb apps, which always save their state
// when pausing, so we can not have them save their state
// when restarting from a paused state. For HC and later,
// we want to (and can) let the state be saved as the normal
// part of stopping the activity.
if (r.isPreHoneycomb()) {
r.state = oldState;
}
if (!r.activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onPause()");
}
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException(
"Unable to pause activity "
+ r.intent.getComponent().toShortString()
+ ": " + e.toString(), e);
}
}
r.paused = true;
}
} else {
// If there was an error, for any reason, tell the activity
// manager to stop us.
try {
ActivityManagerNative.getDefault()
.finishActivity(r.token, Activity.RESULT_CANCELED, null);
} catch (RemoteException ex) {
// Ignore
}
}
此方法注释比较多,过程非常简单,教容易理解,主要是执行performLaunchActivity和handleResumeActivity方法,他们分别执行Activity的onCreate,onStart和onResume生命周期。
我们继续跟踪performLaunchActivity方法
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
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 app = r.packageInfo.makeApplication(false, mInstrumentation);
if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
if (localLOGV) Slog.v(
TAG, r + ": app=" + app
+ ", appName=" + app.getPackageName()
+ ", pkg=" + r.packageInfo.getPackageName()
+ ", comp=" + r.intent.getComponent().toShortString()
+ ", dir=" + r.packageInfo.getAppDir());
if (activity != null) {
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
+ r.activityInfo.name + " with config " + config);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config);
if (customIntent != null) {
activity.mIntent = customIntent;
}
r.lastNonConfigurationInstances = null;
activity.mStartedActivity = false;
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme);
}
activity.mCalled = false;
mInstrumentation.callActivityOnCreate(activity, r.state);
if (!activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onCreate()");
}
r.activity = activity;
r.stopped = true;
if (!r.activity.mFinished) {
activity.performStart();
r.stopped = false;
}
if (!r.activity.mFinished) {
if (r.state != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
}
}
if (!r.activity.mFinished) {
activity.mCalled = false;
mInstrumentation.callActivityOnPostCreate(activity, r.state);
if (!activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onPostCreate()");
}
}
}
r.paused = true;
mActivities.put(r.token, r);
}
在此方法中,首先调用mInstrumentation.newActivity来创建一个Activity的实例,然后调用makeApplication创建application对象,接着通过activity.attach来调用activity对象的attach方法,然后执行mInstrumentation.callActivityOnCreate,也就是调用activity对象的onCreate方法。在onCreate里我们调用setContentView设置我们预定义好的View,我们便接着跟踪Activity的setContentView方法。
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
方法中getWindow()返回的是一个android.app.Window对象,这个对象就是刚刚在attach方法中赋值的mWindow成员变量,它的类型是PhoneWindow,继续查看PhoneWindow的setContentView方法。
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mContentParent.addView(view, params);
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
PhoneWindow中有两个和视图相关的成员变量,一个是DecorView mDecor,另一个是ViewGroup mContentParent。mDecor是一个FrameLayout,而mContentParent是mDecor中的一个子视图,我们继续跟踪下面的代码便会很清晰了。
继续查看installDecor方法
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
// 下面代码省略
}
}
继续查看generateLayout方法的代码
该方法会做如下事情:
根据窗口的风格修饰类型为该窗口选择不同的窗口布局文件(根视图)。这些窗口修饰布局文件指定一个用来存放Activity自定义布局文件的ViewGroup视图,一般为FrameLayout 其id 为: android:id=”@android:id/content”。例如窗口修饰类型包括FullScreen(全屏)、NoTitleBar(不含标题栏)等。选定窗口修饰类型有两种:1、指定requestFeature()指定窗口修饰符,PhoneWindow对象调用getLocalFeature()方法获取值;2、为我们的Activity配置相应属性,即android:theme=“”,PhoneWindow对象调用getWindowStyle()方法。
确定好窗口风格之后,选定该风格对应的布局文件,这些布局文件位于 frameworks/base/core/res/layout/ ,典型的窗口布局文件有:R.layout.dialog_titile_icons 、R.layout.screen_title_icons、R.layout.screen_progress 、R.layout.dialog_custom_title、R.layout.dialog_title 、R.layout.screen_title (最常用的Activity窗口修饰布局文件)、R.layout.screen_simple (全屏的Activity窗口布局文件)。
下面是Activity最常用的一种窗口布局文件,R.layout.screen_title
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:fitsSystemWindows="true">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
style="?android:attr/windowTitleBackgroundStyle">
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
布局中上面部分,id为title的TextView显示窗口的标题,下面部分id为content的FrameLayout是装载上层应用的layout的容器,也就是代码中的mContentParent对象。
在这儿小结一下Activity的层级结构
1.一个Activity对应着一个PhoneWindow对象,是一对一的关系,如果从Activity A启动到Activity B,那么Activity B会创建一个自己的PhoneWindow对象。
2.PhoneWindow管理着整个屏幕的内容,不包括屏幕最顶部的系统状态条。所以,PhoneWindow或者Window是与应用的一个页面相关联。
3.PhoneWindow同时管理着ActionBar和下面的内容主题,setContentView()方法是用来设置内容主体的,而setTitle()等其他方法就是操作ActionBar的,Window中定义的requestFeature()等方法,有很多与ActionBar属性相关的设置。
4.PhoneWindow自己并不是一个视图(View),它的成员变量mDecor才是整个界面的视图,mDecor是在generateLayout()的时候被填充出来的,而actionBar和contentParent两个视图都是通过findViewById()直接从mDecor中获取出来的。
第一部分performLaunchActivity就执行完成了,下面继续执行android.app.ActivityThread.handleResumeActivity方法。在此方法中,首先调用performResumeActivity执行Activity的onResume方法。下面接着执行这段代码非常关键
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) {
a.mWindowAdded = true;
wm.addView(decor, 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是a.getWindowManager();获取到的,a是Activity,getWindowManager()返回它的mWindowManager对象,而这个对象是WindowManagerImpl类型,它的内部方法大部分是代理的WindowManagerGlobal。通过跟踪wm.addView(decor, l)这句代码,下面我们继续查看WindowManagerGlobal类中的addView方法。
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// 省略代码
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
// 省略代码
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.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
其中root从名称可以看出,它是根节点,它是ViewRootImpl类型,也就是说把传进来的DecorView设置给根节点,继续查看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();
...
view.assignParent(this);
...
}
}
}
首先将传进来的参数view赋值给mView,这里要说明一下,ViewRootImpl其实并不是一个View的子类,mView将是这个对象所认识的root节点,也是整个Activity的root节点。现在整个view的树形结构中有了根节点,也就是ViewRootImpl,那么requestLayout()就有效了,就可以进行后面的measure,layout,draw三步操作了。
下面继续查看requestLayout方法
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
首先检查了是否在主线程,然后就执行scheduleTraversals()方法。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
scheduleConsumeBatchedInput();
}
}
这里主要是post一个mTraversalRunnable成员变量。
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
不多说了,接着执行doTraversal()方法,在doTraversal方法中只是简单的调用performTraversals方法,这个方法非常长,可以研究一下源码,依次调用了performMeasure(),performLayout(),performDraw()三个方法,终于开始了控件层的测量,布局,绘制三个步骤。
完成draw后整个view就显示出来了,在View的onDraw里实际操作的是Canvas对象,那么Canvas对象是哪儿来的?它是怎么通知SurfaceFlinger来显示图形的呢? 下面将介绍surface与surfaceFlinger。
二、Surface与SurfaceFlinger
1、什么是Surface、SurfaceFlinger?
Surface:surface相当于一个容器,存放需要显示的内容。surface是双缓冲,一个buffer用于绘制,一个buffer用于显示。
SurfaceFlinger:SurfaceFlinger是一个系统服务,主要是负责合成各窗口的Surface,然后通过OpenGLES显示到硬件帧缓冲区上。
2、为什么需要SurfaceFlinger?
系统的硬件帧缓冲区一般只有一个,并且不应该是随便访问的,需要一个服务来统一管理访问它,surfaceFlinger就是这个角色。
3、canvas如何和surface关联起来的?
我们查看ViewRootImpl类的源码
// These can be accessed by any thread, must be protected with a lock.
// Surface can never be reassigned or cleared (use Surface.clear()).
private final Surface mSurface = new Surface();
// 在drawSoftware方法里:
Canvas canvas;
canvas = mSurface.lockCanvas(dirty);
....
mView.draw(canvas);
// mView 就是DecoView,执行这个view树的绘制。Canvas是Surface的一个成员变量
4、surface如何和surfaceFlinger联系起来的?
我们首先看一下ViewRootImpl的构造函数
public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();
mDisplay = display;
mBasePackageName = context.getBasePackageName();
...
其中,getWindowsession将建立和WindowManagerService的关系,我们简单看一下getWindowSession方法的源码
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
imm.getClient(), imm.getInputContext());
float animatorScale = windowManager.getAnimationScale(2);
ValueAnimator.setDurationScale(animatorScale);
} catch (RemoteException e) {
Log.e(TAG, "Failed to open window session", e);
}
}
return sWindowSession;
}
我们接着上面的流程中doTraversal之后执行performTraversals方法
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
boolean initialized = false;
boolean contentInsetsChanged = false;
boolean visibleInsetsChanged;
try {
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
// 以下代码省略
ViewRoot也有一个Surface成员变量,叫mSurface,这个就是代表SurfaceFlinger的客户端。ViewRoot在这个Surface上作画,最后将由SurfaceFlinger来合成显示。
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
//relayOut是跨进程调用,mSurface做为参数传进去了,离真相越来越近了!
int relayoutResult = sWindowSession.relayout(
mWindow, params,
(int) (mView.mMeasuredWidth * appScale + 0.5f),
(int) (mView.mMeasuredHeight * appScale + 0.5f),
viewVisibility, insetsPending, mWinFrame,
mPendingContentInsets, mPendingVisibleInsets,
mPendingConfiguration, mSurface); // mSurface做为参数传进去了。
}
下面就进入到服务端查看WindowManagerService的relayoutWindow方法。
public int relayoutWindow(Session session, IWindow client,
WindowManager.LayoutParams attrs, int requestedWidth,
int requestedHeight, int viewVisibility, boolean insetsPending,
Rect outFrame, Rect outContentInsets, Rect outVisibleInsets,
Configuration outConfig, Surface outSurface){
.....
try {
//其中win是最初创建的WindowState!
Surface surface = win.createSurfaceLocked();
if (surface != null) {
//先创建一个本地surface,然后把这个surface复制给传进来的客户端surface
outSurface.copyFrom(surface);
win.mReportDestroySurface = false;
win.mSurfacePendingDestroy = false;
} else {
outSurface.release();
}
}
}
客户端的surface终于和服务端分配的surface联系起来了,接下来就是view使用surface的mCanvas绘制了。
好了,view在上层的显示过程就串联起来了,其余的工作就交给底层的Display系统展示出来即可。