android之setContentView,addContentView(),Window,WindowManager,Dialog源码剖析。

setContenView:

任何一个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中。

addContentView

 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

阅览源码后Window.addContentView()最后调用的还是PhoneWindow.addContentView(),所以不再累赘了。

Window,WindowManager##

谈这个之前笔者先分享一下个人对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()

个人理解为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

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的内部运行,还得继续研究啊。。。。

理解如有不正确地方,还希望少侠能提醒一下。 谢谢!!

你可能感兴趣的:(android源码浅析)