任何一个Activity在onCreat()方法里要执行一次setContentView,而setContentView作用笔者总结为两大类
ONE:第一次setContentView时创建一个Decorview并关联到当前的PhoneWindows(Windows的具体实现类)
TWO:从DecorView中找到合适的FrameLayout,并将我们传入的自定义布局的id实例化放入其中,如果非第一次调用则清空这个FrameLayout中第一次载入的子View就是第一次我们传入的自定义布局,然后重新载入。
setContentView则是调用PhoneWindows的setContentView, 如下:
public void setContentView(int layoutResID) {
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();
}
mContentParentExplicitlySet = true;
}
mContentParent就是TWO中我们提到的FrameLayout(方便说明见后续)。这里调用了installDecor(); 而mContentView!=null则调用mContentParent.removeAllViews();清空了mContenParent中所有组件。说明非第一次调用则清空。
if (mContentParent == null) {
installDecor();
}
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(-1); //截取关键
}
else{
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
}
这里主要执行了两个方法generateDecor(),generateLayout(mDecor);
前者实例化了DecorView,后者实例化了mContentParent 先看下generateDecor()
protected DecorView generateDecor(int featureId) {
return new DecorView(context, featureId, this, getAttributes());
//截取关键
}
ok这里说明了确实第一次setContentView时确实创建了DecorView,否则就关联到当前Windwos(),顺带看一下DecorView构造函数:
DecorView(Context context, int featureId, PhoneWindow window,
WindowManager.LayoutParams params) {
setWindow(window); //截取部分
}
所以generateDecor(-1)后DecorView存在实例并且关联到Windows上了,然后接着看 generateLayout(mDecor);
protected ViewGroup generateLayout(DecorView decor) {
layoutResource = R.layout.screen_simple;
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;
//截取部分
}
这里给DecorView添加了一个子合适的子VIew然后通过
findViewById()得到一个VIewGroup然后返回
public T findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
所以findViewById()就在DecorView中查找ID为ID_ANDROID_CONTENT的ViewGroup,而这个ViewGroup肯定就在上述DecorView添加的子View中。
看一下ID_ANDROID_CONTENT的值
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
看一下 R.layout.screen_simple;的XML文件
这里确实看见了一个id为content的FrameLayout ,这个就是mContentParent。ok到这里就剩下了将我们传入的ID实例化放入其中了。看下setContentView中剩下的代码
public void setContentView(int layoutResID) {
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
}
这里就将我们传入布局的ID实例化并放入mContentParent中。
public void addContentView(View view, ViewGroup.LayoutParams params) {
getWindow().addContentView(view, params);
initWindowDecorActionBar();
}
依然调用的是PhoneWIndows中的addContentView();
public void addContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
// TODO Augment the scenes/transitions API to support this.
Log.v(TAG, "addContentView does not support content transitions");
}
mContentParent.addView(view, params);
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
可以看到addContentVIew同样可以执行 installDecor(),也就说没有执行setContentView并不影响addContentView正常使用;与setContentView不同的是若多次调用setContentView,则下次会清空上一次的mContentParent,而addContentVIew中只是调用了mContentParent.addVIew() 也就说可以多次添加。
阅览源码后Window.addContentView()最后调用的还是PhoneWindow.addContentView(),所以不再累赘了。
谈这个之前笔者先分享一下个人对Window,WindowManager的理解和看法,
Window:
Window是个抽象类,具体的实现类为PhoneWindow,它的的作用就是创建一个DecorView以及保留相关参数
Window本身并没有保存要存储的View(DecorView),只是一个引用。
*Window只能关联一个View。
*Window与Window之间效果模型是层级关系,即高层会默认抢占焦点,但可通过窗口参数来控制
WindowManager
WindowManager由Window来实例化每个Window内部都含有一个WindowManager的引用,
由于所有WIndow关联的View都存放在一个静态类中,而WindowManager实现类都含有这个静态类,所以不同的WindowManager可管理相同的View即Window
Activity在创建的过程会默认实现一个的Window类
public class Activity{
private Window mWindow;
final void attach(......){
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowManager(....)
}
}
可以看到在attach()方法里面Window就已经创建完成了,而WindowManager是一个接口,具体实现类为WindowManagerImpl,从上述可看到在Activty中也同样存在一个WindowManager的引用,那这个类主要负责什么呢? 看继承关系
public interface WindowManager extends ViewManager{}
继承了ViewManager所以就可以对View增加或移除,当然由于DecorView的绑定所以移除会报错,所以这样针对的就只能是我们调用了WindowManager.addView()添加的View。那么WindowManager的具体实现类在那实例化呢? 注意上述调用了mWindow.setWindowManager(…) 看源码:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
appToken,appName暂时不提,因为IBinder机制笔者暂时还未研究所以不太清楚后续有机会再写,当然这里我们姑且将它们认为一个标记Window的东西,因为某些窗口只需要在当前Activity内显示,那么为了标记这些Window,则会将token添加到窗口参数
。继续看上述源码,通过传入的wm(其他Window的WindowManagerImpl)有重新创建的一个自己的WindowManagerImpl,
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
最后WIndow内部就有了这个WindowManagerImpl的引用,这样实例化就ok了,所以先有了Window实例对象才会有WindowManager实例对象。所以一个Window可以对应多个WindowManager,而这些WindowManager内部都有这个Window的引用,但是这个Window内只能有一个WindowManager。
那么Activity内部的WindowManager是不是就是本身Window内的WindowManager
mWindowManager = mWindow.getWindowManager();
Activity内的WindowManager引用直接从Window内取WindowManager,ok就是这样。
然后我们研究一下如何获取Window或者WindowManager;
获取Window:在Activity内部调用getWindow()
public Window getWindow() {
return mWindow;
}
直接返回Activity内的Window对象。
获取WindowManager:在Activity内部调用getWindowManager()或者调用context.getSystemService((WINDOW_SERVICE)
public WindowManager getWindowManager() {
return mWindowManager;
}
直接返回Activity内的WindowManager引用,
public Object getSystemService(@ServiceName @NonNull String name) {
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
}
}
最后还是返回了Activity内的WindowManager,当然这里要注意不同的context最后返回的WindowManager是不同的,这里的不同有两种:
ONE:由Window产生的WindowManager对象不同
TWO:Activity对应的WindowManager里的token!=null,而其他的token==null,继而导致WIndowManager不同
token的是否为null会对添加的窗口产生影响,看源码:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
这里是WindowManager.addView()在添加窗口之前applyDefaultToken(params)会对窗口参数微调,其实就是将自身token赋予params。
上述能反应出什么问题?这里我做个小例子:
public int onStartCommand(Intent intent, int flags, int startId) {
WindowManager.LayoutParams la=new WindowManager.LayoutParams();
la.flags=WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
la.format= PixelFormat.RGBA_8888;
la.type=WindowManager.LayoutParams.TYPE_APPLICATION;
la.width=200;
la.height=200;
la.x=0;
la.y=0;
la.gravity= Gravity.LEFT|Gravity.TOP;
Button button=new Button(this);
WindowManager windowManager1= (WindowManager)getSystemService(WINDOW_SERVICE);
windowManager1.addView(button,la);
return null
}
这个是我在Service里写的一个给Window里添加button的实例注意 这里的la.type=WindowManager.LayoutParams.TYPE_APPLICATION;
指的就是在当前的Activity对应的Window里显示如果这个Activity不在栈顶那么则不显示
结果运行报错:
就是提示了token为null,由于是Service本身并没有WindowManager实例,所以应该来自其他的Context实现类但它们并非Activity所以token==null继而报错。
如果上述代码放在一个Activity里则运行正常。所以token应该就是标识了Activity。
但是WindowManager.LayoutParams.TYPE_APPLICATION;换为其他某些则显示正常,例如WindowManager.LayoutParams.TYPE_PHONE;这个就不用在意是否脱离了Activity只要Application运行则就在屏幕上显示。
个人理解为WindowManager.addView()添加的才算实质意义添加了,因为包括上述的添加View或者通过VIewGroup添加的都只是依托了Parent,而这些Parent最顶部的就是Decorview,这些VIew直接或间接与DecorVIew相连,形成了树状结构因此可以在Window显示出来,并且执行相关操作。所以实质添加的只是一个DecorView。
而我们若直接调用WindowManager.addVIew()就不同了。看源码:
WindowManager只是接口具体实现类为WindowWindowManagerImpl
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
WindowManagerImpl通过调用mGlobal来操作VIew即操作窗口,自身只是一个代理。mGlobal则是一个已经实例化的WindowManagerGlobal对象。注意:WindowManagerGlobal对象整个app只有一个 看源码:
private static WindowManagerGlobal sDefaultWindowManager;
public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}
是吧,看看它的addView()
public final class WindowManagerGlobal {
private final ArrayList mViews = new ArrayList();
private final ArrayList mRoots = new ArrayList();
private final ArrayList mParams =
new ArrayList();
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
ViewRootImpl root;
View panelParentView = null;
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
笔者的理解mGlobal的才与WindowManagerService打交道,继而生成,销毁一个窗口,上述代码中 mViews,mRoots, mParams 三个集合对象,mRoots笔者猜测记录窗口的层级关系并且与WMS交流。mVIews则保存了所有窗口的View,mParams则保存了所有窗口的参数。最后调用 root.setView(view, wparams, panelParentView);将窗口显示了出来。
dialog其实就是通过WindowManager添加的一个View而已,
public class Dialog .......{
private final WindowManager mWindowManager;
final Context mContext;
final Window mWindow;
View mDecor;
Dialog(Context context ,........){
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final Window w = new PhoneWindow(mContext);
mWindow = w;
}
}
可以看到在DIalog内部同样存放一个Window,WindowManager,以及一个DecorView。 在初始化过程中mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 则拿到了Activity的WindowManager,所以token!=null,然后new了一个PhoneWindow 正如我们上述提到的Window的目的只是产生一个Decor以及保存窗口参数而已。然后看一下show()方法:
public void show() {
mDecor = mWindow.getDecorView();
WindowManager.LayoutParams l = mWindow.getAttributes();
.....
mWindowManager.addView(mDecor, l);
}
其实过程很简单,从Window中拿到DecorView,Params,然后调整Params,最后添加窗口就ok了。 dismiss()也很简单:
mWindowManager.removeViewImmediate(mDecor);
其实就是调用了removeViewImmediate(),而在WindowManager里:
@Override
public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
}
又交给mGloba了然后与WMS交流表示移除窗口。至于WMS的内部运行,还得继续研究啊。。。。
理解如有不正确地方,还希望少侠能提醒一下。 谢谢!!