一、android界面构成
Activity:控制模型,控制Window。
Window:承载模型,负责承载视图(View)。
View:显示模型,用于显示。
ViewRoot(ViewRootImpl):是连接WindowManager和DecorView的纽带。
二、View创建整体流程总结
1、在ActivityThread的performLaunchActivity中调用Activity的attach方法,在attach方法中完成PhoneWindow的创建。
2、在Activity的onCreate方法,setContentView方法,PhoneWindow中创建了一个DecorView,并在DecorView中填充了Activity中传入的layoutId布局。通过LayoutInflater进行布局文件的解析。DecorView,它实际是一个FrameLayout,并通过各种主题样式选择加载不同的视图来填充DecorView。
LayoutInflater一定要做成全局单例的方式,原因很简单就是为了加速view实例化的过程,共用反射的构造函数的缓存。
3、在ActivityThread的handleResumeActivity中,会调用WindowManager的addView方法将DecorView添加到WMS(WindowManagerService)上。addView找那个创建ViewRootImpl,并通过ViewRootImpl的setView方法将view添加到WMS中。
4、 在使用requestWindowFeature来设置样式时,实际上是调用了PhoneWindow的requestFeature方法,会将样式存储在Window的mLocalFeatures变量中,当installDecor时,会应用这些样式。也就是说,当需要通过requestWindowFeature来请求样式时,应该在setContentView方法之前调用,因为setContentView方法的调用会导致DecorView的创建并应用样式,如果在之后调用则会导致不会生效,因为此时DecorView已经创建完成了。
ViewRootImpl的setView方法中主要完成:View渲染(requestLayout)、(performTraservals)以及接收触摸屏幕事件。设置了一系列输入管道,将触摸事件分发给DecorView。
三、View具体创建流程
1、PhoneWindow的创建
ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
···
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
}
···
Window window = null;
if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
}
appContext.setOuterContext(activity);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
···
}
Activity.java
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) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
mUiThread = Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
···
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
}
Activity在其attach方法中创建了Window,实际上创建的是PhoneWindow,并通过各种回调等建立起与Activity的联系,我们在Activity中使用getWindow()所得到的即是这个创建的Window。
2、setContentView (DecorView创建)
通常情况下,一个Activity的界面的创建是通过setContentView来引入布局。
mWindow = new PhoneWindow(this, window);
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
而Activity的setContentView方法是通过调用Window的setContentView方法,而Window是一个抽象对象,它的具体实现类就是PhoneWindow。在PhoneWindow中找到setContentView方法
@Override
public void setContentView(int layoutResID) {
// 如果mContentParent为空时先构建
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 {
// 通过LayoutInflater解析布局
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
至此完成了在PhoneWindow中创建了一个DecorView,并在DecorView中填充了Activity中传入的layoutId布局。但还未被绘制到屏幕上,这一步要到ActivityThread的handleResumeActivity时才会真正执行。
通过LayoutInflater解析布局。主要通过inflate方法解析布局
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
上面的inflate方法所做的操作主要有以下几步:
解析xml的根标签
如果根标签是merge,那么调用rInflate解析,将merge标签下的所有子View直接添加到根标签中
如果不是merge,调用createViewFromTag解析该元素
调用rInflate解析temp中的子View,并将这些子View添加到temp中
通过attachToRoot,返回对应解析的根视图
解析View的时候是通过.来判断是内置的View还是自定义的View的,那么我们就能知道为什么在写布局文件中自定义的View需要完整路径了。
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
// 从缓存中获取view的构造函数
Constructor extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class extends View> clazz = null;
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
// 如果没有缓存
if (constructor == null) {
// 如果前缀不为空构造完整的View路径并加载该类
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
// 获取该类的构造函数
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
// 将构造函数加入缓存中
sConstructorMap.put(name, constructor);
} else {
}
Object[] args = mConstructorArgs;
args[1] = attrs;
// 通过反射构建View
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
}
}
createView相对简单,通过判断前缀,来构建View的完整路径,并将该类加载到虚拟机中,获取构造函数并缓存,再通过构造函数创建该View对象,并返回。这个时候我们就获得了根视图。接着调用rInflateChildren方法解析子View,并最终调用rInflate方法:
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
// 获取树的深度,通过深度优先遍历
final int depth = parser.getDepth();
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} else if (TAG_TAG.equals(name)) {// 解析tag标签
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {// 解析include标签
if (parser.getDepth() == 0) {
throw new InflateException(" cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {// 解析到merge标签,并报错
throw new InflateException(" must be the root element");
} else {
// 解析到普通的子View,并调用createViewFromTag获得View对象
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
// 递归解析
rInflateChildren(parser, view, attrs, true);
// 将View加入到父视图中
viewGroup.addView(view, params);
}
}
if (finishInflate) {
parent.onFinishInflate();
}
}
https://www.jianshu.com/p/c228e65ea1d9
https://www.jianshu.com/p/427c59a70da6
https://blog.csdn.net/qq_28261343/article/details/78817184
3、View的显示绘制
在ActivityThread的handleResumeActivity中,会调用WindowManager的addView方法将DecorView添加到WMS(WindowManagerService)上。
ActivityThread.java
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
// 会调用activity的onResume方法
r = performResumeActivity(token, clearHide, reason);
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
//setContentView中完成的DecorView的创建
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;
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient && !a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
通过WindowManager的addView方法添加DecorView。WindowManager是一个接口,实际通过WindowManagerImpl进行添加。
WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
ViewRootImpl root;
View panelParentView = null;
···
//!!!创建ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
root.setView(view, wparams, panelParentView);
}
}
通过ViewRootImpl的setView方法将view添加到WMS中。
ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
// 会调用scheduleTraversals()方法,后续会执行measure-layout-draw?确保View在添加到Window上显示到屏幕之前,已完成测量和绘制操作。
requestLayout();
···
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
}
}
requestLayout执行布局刷新,调用mWindowSession的方法将View添加到WMS中。
最终通过Binder,远程调用WMS中的addToDisplay完成View的添加。
ViewRootImpl的setView方法中主要完成:View渲染(requestLayout)以及接收触摸屏幕事件。设置了一系列输入管道,将触摸事件分发给DecorView。
ViewRootImpl.java
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//mChoreographer.postCallback主要逻辑是向Handler发送一个异步消息,然后在mTraversalRunnable(是Runnable对象)中处理
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
...
performTraversals();//执行真正的view绘制流程measure-->layout-->draw
...
}
}
四、ViewRootImpl
ViewRoot是一个ViewTree的管理者,而不是ViewTree的根节点。
严格意义上说,ViewTree的根节点只有DecorView。
ViewRoot将DecorView和PhoneWindow(Activity创建的Window实例)“组合”起来。
View的绘制三大流程(measure,layout,draw)均是通过ViewRootImpl来完成的。
在ActivityThread中,当Activity对象被创建完毕后,会将DecorVidew添加到Window中,同时会创建ViewRootImpl对象,利用ViewRootImpl对象来建立Window对象和DecorView之间的关系。
View的绘制是从ViewRootImlp的performTraversals开始的,经过measure.layout.draw三方法最终将一个View绘制出来。measure测量View宽高。layout确定View位置,draw负责绘制。
window持有DecorView,DecorView在被添加到window到时候,会产生一个ViewRootImlp,ViewRootImlp在调用performTraversals(遍历表演)的时候会调用顶级view的测量,布局和绘制,然后顶级view会挨个调用自己子布局的测量布局和绘制,这样一个页面就展现出来了。
五、 attachWindow和 detachWindow
attach流程
当在ActivityThread.handleResumeActivity()方法中调用WindowManager.addView()方法时,最终是调去了
WindowManagerImpl.addView() -->
WindowManagerGlobal.addView()
ViewRootImpl.setView()
ViewRootImpl.performTraversals()
host.dispatchAttachedToWindow(mAttachInfo, 0)
//ViewGroup和View有不同实现,ViewGroup依次分发到子View
detach流程
ActivityThread.handleDestroyActivity() -->
WindowManager.removeViewImmediate() -->
WindowManagerGlobal.removeViewLocked()方法 —>
ViewRootImpl.die() --> doDie() -->
ViewRootImpl.dispatchDetachedFromWindow()
1、onAttachedToWindow方法是在Activity resume的时候被调用的,也就是act对应的window被添加的时候,且每个view只会被调用一次,父view的调用在前,不论view的visibility状态都会被调用,适合做些view特定的初始化操作;
2、onDetachedFromWindow方法是在Activity destroy的时候被调用的,也就是act对应的window被删除的时候,且每个view只会被调用一次,父view的调用在后,也不论view的visibility状态都会被调用,适合做最后的清理操作;
3、这些结论也正好解释了方法名里带有window的原因,有些人可能会想,那为啥不叫onAttachedToActivity/onDetachedFromActivity,因为在Android里不止是Activity,这里说的内容同样适用于Dialog/Toast,Window只是个虚的概念,是Android抽象出来的,最终操作的实体还是View,这也说明了前面的WindowManager接口为啥是从ViewManager接口派生的,因为所有一切的基石归根结底还是对View的操作。
https://www.jianshu.com/p/e7b6fa788ae6
六、Token及其他窗口
1、Token是什么?
类型为IBinder,是一个Binder对象。
主要分两种Token:
指向Window的token: 主要是实现WmS和应用所在进程通信。
指向ActivityRecord的token: 主要是实现WMS和AMS通信的。
2、Token的使用场景?
Activity创建时,AMS中需要根据Token去找到对应的ActivityRecord。
Popupwindow的showAtLocation第一个参数需要传入View,这个View就是用来获取Token的。
3、WindowSession是什么
在WindowManager的addView中会创建ViewRootImpl,内部会通过WMS去获取WindowSession
WindowSession的类型是IWindowSession,本身是Binder对象,真正实现类是Session。
WindowManager.LayoutParams上有三种窗口类型type,对应为:
应用程序窗口:type值在 FIRST_APPLICATION_WINDOW LAST_APPLICATION_WINDOW 须将token设置成Activity的token。
eg: Activity窗口,Dialog
子窗口: type值在 FIRST_SUB_WINDOW ~ LAST_SUB_WINDOW SubWindows与顶层窗口相关联,需将token设置成它所附着宿主窗口的token。
eg: PopupWindow(想要依附在Activity上需要将token设置成Activity的token)
系统窗口 : type值在 FIRST_SYSTEM_WINDOW ~ LAST_SYSTEM_WINDOW SystemWindows不能用于应用程序,使用时需要有特殊权限,它是特定的系统功能才能使用。
eg: Toast,输入法等。
token是用来表示窗口的一个令牌,只有符合条件的token才能被WMS通过添加到应用上。
https://www.jianshu.com/p/bac61386d9bf
参考
https://www.jianshu.com/p/c228e65ea1d9
https://www.jianshu.com/p/427c59a70da6
https://blog.csdn.net/qq_28261343/article/details/78817184
https://www.jianshu.com/p/9da7bfe18374
https://www.jianshu.com/p/e7b6fa788ae6
https://www.jianshu.com/p/bac61386d9bf