WindowManagerService Window View 关系(一)详解手机开发

对我来说,Android的Window窗口机制是一大难点,特别是里面涉及到的类与类都非常相似,所以花了一段时间来理解梳理Window、 ViewRootImpl、WindowManagerService(后文简称WMS)之间的关系。

按照习惯,先抛出自己一开始的问题:

  1. Android window 是怎么使用的?
  2. Window、View、WMS以及ViewRootImpl之间什么关系?

这是我一开始的疑惑,后面随着学习深入,慢慢地把第二个问题分解成几个部分:

  1. Activity、WindowManager、Window之间怎么关联
  2. DecorView、PhoneWindow之间怎么关联(setContentView)
  3. Activity 怎么创建添加一个Window(从App进程的角度看)
  4. WMS 怎么创建添加一个Window (从WMS进程的角度看)
  5. Token的创建和作用

先给一张最后的流程图
在这里插入图片描述


利用WindowManager创建Window

受《Android艺术开发探索》启发,从创建Window的例子开始窥探Android的窗口机制。

// MainActivity 
private void checkAndCreate() {
    
    if (Build.VERSION.SDK_INT >= 23) {
    
        if (!Settings.canDrawOverlays(this)) {
    
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); 
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 
            startActivityForResult(intent, 1); 
        } else {
    
            createFloatButton(); 
        } 
    } 
} 
// MainActivity    
private void createFloatButton() {
    
    Button mFloatingButton = new Button(this); 
    mFloatingButton.setText("click me"); 
    WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams( 
            LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0, 
            PixelFormat.TRANSPARENT); 
    mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL 
            | LayoutParams.FLAG_NOT_FOCUSABLE 
            | LayoutParams.FLAG_SHOW_WHEN_LOCKED; 
    mLayoutParams.type = LayoutParams.TYPE_APPLICATION_OVERLAY; 
    mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; 
    mLayoutParams.x = 100; 
    mLayoutParams.y = 300; 
    getWindowManager().addView(mFloatingButton, mLayoutParams); 
} 

记得加上权限

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> 

上面的例子创建了一个浮动的按钮,逻辑很简单,创建Button和LayoutParams,利用getWindowManager().addView()增加一个Window。

getWindowManager().addView()就是Activity操作窗口的入口了。

getWindowManager()从ContextImpl获得一个WindowManager接口,真正实现是WindowManagerImpl。

// WindowManagerImpl 
public final class WindowManagerImpl implements WindowManager {
    
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); 
    private IBinder mDefaultToken; 
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    
        applyDefaultToken(params); 
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); 
    } 
     
    public void removeView(View view) {
    
        mGlobal.removeView(view, false); 
    } 
} 

WindowManagerImpl实现了WindowManager,内部实际操作都是转调WindowManagerGlobal,是一个单例,app进程内的window操作都是由WIndowMangerGlobal控制。

WindowManagerImpl 会跟Context进行关联,而WindowManagerGlobal跟Context不关联

图片

Activity、WindowManager、Window之间怎么关联

从上面的例子知道,Activity添加window,是由WindowManagerImpl和WIndowManagerGlobal控制的。以下是以Activity所在进程的角度,去看Window、Activity、ViewRootImpl之间怎么相互关联,各自负责怎样的工作。

1. Activity

在Activity中,有三个重要变量:

Window mWindow; // PhoneWindow
WindowManager mWindowManager; // WindowManagerImpl
View mDecor = null; // DecorView

mWindow是PhoneWindow,表示Activity的具体窗口;
mWindowManager 是WindowManagerImpl,负责窗口的管理;
mDecor是DecorView,继承自FrameLayout,是最顶层的View;ViewRootImpl存的View就是DecorView;

既然有了DecorView为什么需要PhoneWIndow
在这里插入图片描述

2. WindowManagerImpl

提供给客户端直接操作Window的类,关联了Context,内部实际委托了WindowManagerGlobal进行操作

3. WindowManagerGlobal

WindowManagerGlobal管理了所有的DecorView、ViewRootImpl。并且负责了跟AMS通信,这部分后续再讨论。

private final ArrayList mViews = new ArrayList();
private final ArrayList mRoots = new ArrayList();
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
private final ArraySet mDyingViews = new ArraySet()

mViews 存储了所有的DecorView,是 view 的根布局;

mRoots 存储了所有的ViewRootImpl,它在View的绘制发挥重要的作用,所有View的绘制以及事件分发等交互都是通过它来执行或传递的;可参考《View/ViewGroup 绘制流程和疑惑》

mParams 存储对应的window的属性;

mDyingView 存储待删除的View;

4. attach()关联Activity、Window、WindowManager

上面讨论了Activity内部的Window是PhoneWindow,WIndowManager指向WindowManagerImpl,由WindowManagerImpl管理窗口机制,那Activity的WIndow和WindowManager是什么时候创建并且关联的呢?

参考《Activity 启动流程分析》,在Activity的启动过程中,最后在ActivityThread的主线程中调用了handleLaunchActivity()performLaunchActivity()启动Activity,内部生成ContextImpl,通过ClassLoader创建Activity等以外,还调用了Activity的attach()方法

// Activity 
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) {
    
	// 保存当前ContextImpl 
    attachBaseContext(context); 
    .... 
    // 创建PhoneWindow 
    mWindow = new PhoneWindow(this, window, activityConfigCallback); 
    mWindow.setWindowControllerCallback(this); 
    mWindow.setCallback(this); 
    mWindow.setOnWindowDismissedCallback(this); 
    mWindow.getLayoutInflater().setPrivateFactory(this); 
    .... 
    mToken = token; 
    .... 
    // 设置PhoneWindow的WindowManager 
    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()); 
    } 
    // 保存PhoneWindow的WindowManager到Activity中 
    mWindowManager = mWindow.getWindowManager(); 
} 

Activity的attach()做了很多事情,我们只关心跟窗口机制相关的:

  1. 生成了PhoneWindow
  2. 获取WindowManager(WindowManagerImpl)到本地

至此 Activity就关联了PhoneWindow和WindowManagerImpl,而WindowManagerImpl是在ContextImpl生成的。

5. WidowManagerImpl的创建

在ContextImpl内部变量SystemServiceRegistry的static块中

// SystemServiceRegistry 
registerService(Context.WINDOW_SERVICE, WindowManager.class, 
        new CachedServiceFetcher<WindowManager>() {
    
    @Override 
    public WindowManager createService(ContextImpl ctx) {
    
        return new WindowManagerImpl(ctx); 
    }}); 
}     

SystemServiceRegistry注册了WINDOW_SERVICE服务,实际上是保存到一个hashmap中,当前还没创建WindowManager,等到在ContextImpl的getSystemService()懒加载

// ContextImpl 
public Object getSystemService(String name) {
    
    return SystemServiceRegistry.getSystemService(this, name); 
} 
// SystemServiceRegistry 
public static Object getSystemService(ContextImpl ctx, String name) {
    
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name); 
    return fetcher != null ? fetcher.getService(ctx) : null;  
} 

getSystemService()懒加载创建了WidowManagerImpl

所以说,Activity.getWindowManger()Context.getSystemService(Context.WINDOW_SERVICE)都可以获取WindowManager

小结

  1. Activity的创建过程中,创建了PhoneWindow、WindowManagerImpl
  2. WindowManagerImpl 实际委托WindowMangerGlobal管理,管理DecorView、ViewRootImpl和WMS通信

Activity 中 DecorView、PhoneWindow之间怎么关联(setContentView)

上面一节讨论了Acitiviy创建关联了PhoneWindow、WindowManagerImpl,这一节讨论Window和View之间的关系。

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    
    private DecorView mDecor;  
    private ViewGroup mContentParent; 
    private ViewGroup mContentRoot; 
    ... 
} 

PhoneWindow实现了Window接口,内部包含DecorView,是一个FrameLayout。

mContentParent是DecorView的子View,ContentParent 也是一个FrameLayout,id为ID_ANDROID_CONTENT,是所有自定义layout/view的父View,是Activity的视图区域。
在这里插入图片描述

Activity的onCreate()生命周期中,会调用setContentView()

// Activity 
public void setContentView(@LayoutRes int layoutResID) {
    
     getWindow().setContentView(layoutResID); // 调用了Activity的PhoneWindow.setContentView() 
     initWindowDecorActionBar(); 
} 
 
// PhoneWindow 
public void setContentView(View view, ViewGroup.LayoutParams params) {
    
    if (mContentParent == null) {
    
        installDecor();// 创建DecorView 
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
    
        mContentParent.removeAllViews(); 
    } 
 
    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
    
        ... 
    } else {
    
        // 添加View到ContentParent 
        mContentParent.addView(view, params); 
    } 
    mContentParent.requestApplyInsets(); 
    ... 
} 

setContentView()有几个重载方法,为了方便,只看setContentView(View view, ViewGroup.LayoutParams params)不需要去解析ResID,流程都是一致的。

private void installDecor() {
    
   if (mDecor == null) {
    
     //创建一个DecorView 
     mDecor = generateDecor(); 
     mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 
     mDecor.setIsRootNamespace(true); 
     if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
    
       mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); 
     } 
   } 
   if (mContentParent == null) {
    
     //生成ContentParent,对应id为ID_ANDROID_CONTENT的view 
     mContentParent = generateLayout(mDecor); 
   } 
} 

PhoneWindow创建了DecorView,并找出mContentParent。接下去不再继续深究DecorView的生成,仅抓住窗口机制的脉络。

小结

  1. WindowManager将DecorView加载到PhoneWindow中;
  2. 到目前为止,Acitivty还没真正的绘制显示,ViewRootImpl也还没生成,后面需要将DecorView交给ViewRootImpl,进行视图绘制,这涉及到后面的添加Window到WindowManager中。

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/6290.html

(0)
上一篇 2021年7月17日
下一篇 2021年7月17日

相关推荐

发表回复

登录后才能评论