Android 6.0 View加载流程源码分析
2016-10-04 18:32
537 查看
上一篇文章根据源码分析了下Android的App启动的整个流程,那么App启动起来之后,是需要和用户交互的,所以,界面的显示内容是极其重要的一部分。然而大家都知道,Android的UI基本上都是附属在
首先直接new 了一个
既然
看到了吧,这里又调用了之前在
当然这个是
其中的
但是,别着急,还没完呢。没发现少了点什么吗?这视图还没显示出来啊!
代码有点长,需要耐心看一下。首先一开始调用了
可以看到,这个
其里面又将
方法还是很长的。老规矩,看重点。首先,检查各种参数是否合法。如果是子
这里先补充一下,
其中
可以看到,接下来又调用了
好了,视图附属到
可以看到,这里调用了
该文章一来是作为自己的学习总结和记录,二来是分享给有需要的开发人员,如有不正之处,望不吝指出。深表感激!
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应用程序窗口框架学习(2)-view绘制流程源代码解析-setContentView与LayoutInflater加载解析机制源码分析
- android 6.0 SystemUI源码分析(3)-Recent Panel加载显示流程
- android 6.0 SystemUI源码分析(3)-Recent Panel加载显示流程
- android 6.0 SystemUI源码分析(3)-Recent Panel加载显示流程
- Android应用setContentView与LayoutInflater加载解析机制源码分析(超级棒!)
- Android应用层View绘制流程与源码分析(棒的不行)
- Android应用层View绘制流程与源码分析
- 从源码分析Android的Glide库的图片加载流程及特点
- Android应用层View绘制流程与源码分析
- Android大图加载,缩放,滑动浏览--SubsamplingScaleImageView 源码分析<一>大图加载
- Android源码/框架源码分析及Actviity/View等的启动流程
- Android应用层View绘制流程与源码分析
- Android应用层View绘制流程与源码分析
- Android setContentView与LayoutInflater加载解析机制源码分析
- Android视图View绘制流程及源码分析
- Android应用层View绘制流程与源码分析(转)
- Android View绘制流程(结合源码分析)上
- Activity加载view6.0源码分析---setContentView
- Android Launcher加载流程源码分析
- Android应用层View绘制流程与源码分析