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. Window、View、ViewRootImpl关系
  6. 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<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>()

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还没真正的绘制显示,后面需要将DecorView交给ViewRootImpl,进行视图绘制,这涉及到后面的添加Window到WindowManager中。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容