您的位置:首页 > 移动开发 > Android开发

Android 6.0 View加载流程源码分析

2016-10-04 18:32 537 查看
上一篇文章根据源码分析了下Android的App启动的整个流程,那么App启动起来之后,是需要和用户交互的,所以,界面的显示内容是极其重要的一部分。然而大家都知道,Android的UI基本上都是附属在
Acticity
上的,那么接下来就分析一下,Activity启动后,视图是如何加载出来的。

Window

在开始分析视图创建之前,首先让我们先了解一下
Window
。相信大家之前应该遇见过这个类。但是实际开发中却很少能直接接触到
Window
。其实我们可以顾名思义,可以把
Window
看成是
Activity
或者
Toast
Dialog
的窗口。就是这写组件需要展示的内容,都会附属到
Window
上。然而,
Window
是一个抽象类,其具体的实现类是
PhoneWindow
。那么下面我们要分析
Activity
的视图创建,很显然首先需要创建
Window
,比较
Activity
的视图是需要附属到
Window
上的。

Window的创建

Window
是在哪里被创建的呢?还记得 Android 6.0 View加载流程源码分析 中讲到的
performLaunchActivity
这个方法吗,没错就是从这里开始的。在这里面,当
Activity
被创建之后,会调用其
attach
方法。其里面有如下一段代码:

mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);         mWindow.getLayoutInflater().setPrivateFactory(this);


首先直接new 了一个
Window
的实现类
PhoneWindow
对象,然后为其设置
Callback
接口回调。因为
Activity
实现了
Window
Callback
OnWindowDismissedCallback
接口,所以这里都直接传入了当前的
Activity
。这个
Callback
里面有一系列事件的回调方法,这里面有我们熟悉
dispatchTouchEvent
dispatchKeyEvent
等等的方法。到此,
Avtivity
中的
Window
对象算是创建成功了。

既然
Window
已经创建了,那么我们的View如何附属到
Window
上呢?其实是通过
WindowManager
addView
方法。但是这里我们的视图还没创建呢,就先分析完视图的创建吧。

视图创建

对于视图的创建又该从何开始呢?这个方法其实每一个Android开发者都再熟悉不过了。
setContentView
!嗯,就是从这里开始,下面是这个方法的源码。

public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}


看到了吧,这里又调用了之前在
attach
方法中创建好的
Window
对象的
setContentView
方法。进去看看。

@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
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();
}
}


当然这个是
PhoneWindow
里面的方法,原因上面已经说到了,
Activity
里面创建的就是
PhoneWindow
的对象。然后我们看代码。一进去首先判断
mContentParent
是否为空。这个
mContentParent
其实是
DecorView
里面的内容区域其实就是一个
FrameLayout
相信大家了解这方面的只是,不了解的可以自行谷歌。网上有很多说明
setContentView
为何不叫
setView
的文章。嗯,其实这里的
mContentParent
肯定为空的,如果是第一次加载视图的话。那么就会进入
installDecor
方法。这个方法完成了两件事,调用
generateDecor
方法创建了
DecorView
。然后在
generateLayout
方法里面创建了上面说到 的
mContentParent


View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent =(ViewGroup)findViewById(ID_ANDROID_CONTENT);


其中的
in
其实是和系统版本及主题有关的布局文件所加载出来的View。到底是啥呢,我们先将屏幕分成状态栏,标题栏,和内容栏。状态栏就是那个显示电量啊,运营商之类的区域。然后标题栏就是App当中的显示标题的地方,内容栏就是上面说到的
mContentParent
,然后这个
in
是哪个部分呢?应该就是标题栏加内容栏,将其添加到
DecorView
中,显然
DecorView
就是整个手机屏幕的内容区域了。然后调用
findViewById
创建 了
mContentParent
。嗯,至此,
installDecor
方法也就这样了。接下来就调用
mLayoutInflater.inflate(layoutResID, mContentParent)
将我门传进去的布局文件加载好然后添加到
mContentParent
这个内容栏布局中。至此,视图加载完毕!!!

但是,别着急,还没完呢。没发现少了点什么吗?这视图还没显示出来啊!

视图显示

上面一直讲解的
setContentView
方法相信大家都是在
Activity
onCreate
方法中调用的。但是到这里视图并不会显示出来。那么到哪里的时候才会显示呢?大家在刚刚学习
Activity
的生命周期的时候应该知道,当调用了
onResume
的的时候,
Activity
即将显示。没错就是在这个方法里面,我没之前创建的
DecorView
将会通过
WindowManager
被附属到一开始创建好的
Window
上。这一步骤是在哪里执行的呢?这里我们直接定位到
ActivityThread
handleResumeActivity
方法。

final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
mSomeActivitiesChanged = true;

// TODO Push resumeArgs into the activity for consideration
ActivityClientRecord r = performResumeActivity(token, clearHide);

if (r != null) {
final Activity a = r.activity;

if (localLOGV) Slog.v(
TAG, "Resume " + r + " started activity: " +
a.mStartedActivity + ", hideForNow: " + r.hideForNow
+ ", finished: " + a.mFinished);

final int forwardBit = isForward ?
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;

// If the window hasn't yet been added to the window manager,
// and this guy didn't finish itself or start another activity,
// then go ahead and add the window.
boolean willBeVisible = !a.mStartedActivity;
if (!willBeVisible) {
try {
willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
a.getActivityToken());
} catch (RemoteException e) {
}
}
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;
}

// Get rid of anything left hanging around.
cleanUpPendingRemoveWindows(r);

// The window is now visible if it has been added, we are not
// simply finishing, and we are not starting another activity.
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
if (r.newConfig != null) {
r.tmpConfig.setTo(r.newConfig);
if (r.overrideConfig != null) {
r.tmpConfig.updateFrom(r.overrideConfig);
}
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity "
+ r.activityInfo.name + " with newConfig " + r.tmpConfig);
performConfigurationChanged(r.activity, r.tmpConfig);
freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
r.newConfig = null;
}
if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward="
+ isForward);
WindowManager.LayoutParams l = r.window.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
!= forwardBit) {
l.softInputMode = (l.softInputMode
& (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
| forwardBit;
if (r.activity.mVisibleFromClient) {
ViewManager wm = a.getWindowManager();
View decor = r.window.getDecorView();
wm.updateViewLayout(decor, l);
}
}
r.activity.mVisibleFromServer = true;
mNumVisibleActivities++;
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}

if (!r.onlyLocalRequest) {
r.nextIdle = mNewActivities;
mNewActivities = r;
if (localLOGV) Slog.v(
TAG, "Scheduling idle handler for " + r);
Looper.myQueue().addIdleHandler(new Idler());
}
r.onlyLocalRequest = false;

// Tell the activity manager we have resumed.
if (reallyResume) {
try {
ActivityManagerNative.getDefault().activityResumed(token);
} catch (RemoteException ex) {
}
}

} else {
// If an exception was thrown when trying to resume, then
// just end this activity.
try {
ActivityManagerNative.getDefault()
.finishActivity(token, Activity.RESULT_CANCELED, null, false);
} catch (RemoteException ex) {
}
}
}


代码有点长,需要耐心看一下。首先一开始调用了
performResumeActivity
这个方法的命名格式相信大家已经熟悉了。然后里面会调用,
Activity
performResume
方法,接着里面通过我们之前在讲解App启动流程时候提到的
Instrumentation
去调用

callActivityOnResume
方法, 然后
onResume
就是在这里面被调用的了。由于这里主要是讲解视图的加载,所以这些就一笔带过了。好了,继续往下看,下面果然和前面说到的一样,通过调用
WindowManager
addView
方法要将
DecorView
附属到
Window
上了。这个
WindowManager
是哪来的呢?其实是之前那个
Activity
attach
方法里,再创建完
Window
后就通过
getSystemService
去拿到了
WindowManager


mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);


可以看到,这个
WindowManager
被设置进了
Window
对象里面。接着我们分析
WindowManager
addView
方法。其实
WindowManager
是一个接口,其实现类是
WindowManagerImpl
,下面看看
WindowManagerImpl
里面的
addView
方法。

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}


其里面又将
addView
的具体执行又委托给了
WindowManagerGlobal
,我们直接看看其实现。

public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}

final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}

ViewRootImpl root;
View panelParentView = null;

synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}

int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}

// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}

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;
}
}


方法还是很长的。老规矩,看重点。首先,检查各种参数是否合法。如果是子
Window
的话,调用
adjustLayoutParamsForSubWindow
调整布局参数。接下来创建
ViewRootImpl
对象。这
ViewRootImpl
至关重要,详细不少同学也熟悉它。没错,视图的绘制也是需要经过它去实现的。然后就调用了创建好的
ViewRootImpl
对象的
setView
方法。这个方法有点长,就不贴代码了。里面主要会调用
requestLayout
去绘制或者刷新视图。

这里先补充一下,
mViews
mRoots
,
mParams
分别是装载需要附属到
Window
View
ViewRootImpl
,
Window
对于的布局参数的集合。

@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}


其中
scheduleTraversals
方法便是View的绘制入口了。这个里面的操作是异步的。接下来就是通过调用
WindowSession
addToDisplay
方法,去最终完成
Window
或者说是
View
的添加过程。其实是调用了Session的
addToDisplay
的这个方法。

@Override
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
addWindow
方法,这里需要通过IPC。至此,所以的工作就交给了系统服务
WindowManagerService
了,我们也就分析到这里吧。然后往下就是将该
ViewRootImpl
对象设置进了现在操作的这个
View
,上面说过了,视图的绘制都是要进过
ViewRootImpl
这个类的。那么将
ViewRootImpl
设置进
View
是使得在
View
的内部能调用到
ViewRootImpl
requestLayout
方法进行是视图的刷新。

好了,视图附属到
Window
的过程也分析完毕了。我们回到刚刚那个
handleResumeActivity
方法继续往下看。往下调用了
Activity
makeVisible
方法。

void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}


可以看到,这里调用了
DecorView
setVisibility
将其显示出来。好了,大功告成。至此,整个视图的加载流程就分析到这里了。

该文章一来是作为自己的学习总结和记录,二来是分享给有需要的开发人员,如有不正之处,望不吝指出。深表感激!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android ui 源码 界面