《Android 开发艺术探索》笔记:(8)Window 和 WindowManager

28 篇文章 0 订阅
3 篇文章 0 订阅

一、总览

在这里插入图片描述
Window(1):一个抽象类,PhoneWindow 是它的实现类,用于生成和操作 DecorView。
Window(2):一种抽象概念,WindowManager 管理的就是 Window(2),它以一个顶级 View 为载体。在 Activity、Dialog 中,这个顶级 View 是 DecorView;在 Toast 中,这个顶级 View 是一个名为 mNextView 的普通 View。
ViewRootImpl:Window(2) 通过 ViewRootImpl 对 View 进行管理。

以下内容中,Window 特指 Window(2),而 Window(1) 用 PhoneWindow 表示。

二、Window 和 WindowManager

以添加 Window 为例展示 Window 和 WIndowManager 的关系:

WindowManager.addView(view, layoutParams);

可以看到,WindowManager 添加 Window 其实是在添加 View,然后它会生成 View 对应的 ViewRootImpl, View 和 ViewRootImpl 一起就代表了 Window。上面 layoutParams 是 WindowManager.LayoutParams,其中 flags 和 type 两个参数比较重要。

Flags 表示 Window 的属性,三种常用的:

  • FLAG_NOT_FOCUSABLE
    表示 Window 不需要获取焦点,也不需要输入事件。
  • FLAG_NOT_TOUCH_MODAL
    当前 WIndow 区域之外的点击事件会传递给下面的 Window,一般都会开启。
  • FLAG_SHOW_WHEN_LOCKED
    让 Window 显示在锁屏上。

Type 表示 Window 的类型,有三种,每种 Window 都有不同的层级:

  • 应用 Window (Activity) 层级:1-99
  • 子 Window (Dialog) 层级:1000-1999
  • 系统 Window (Toast 和 系统状态栏) 层级:2000-2999

不同层级范围对应着 WindowManager.LayoutParams 的 type 参数,有很多对应的值。若采用系统 Window, 要声明相应权限,如使用TYPE_SYSTEM_ERROR 就要声明

< user-permission android:name=“android.permission.SYSTEM_ALERT_WINDOW” />。

WindowManager 常用的有三个方法,即添加 View,更新View,删除 View。它们定义在 ViewManager 中,WindowManager 继承了 ViewManager。WindowManager 和 ViewManager 都是一个接口。

三、Window 的内部机制

Window 是一个抽象概念,每一个 Window 都对应着一个 View 和 一个 ViewRootImpl。View 是 Window 的实体,通过 ViewRootImpl 与 Window 建立联系。

这个 View 就是 DecorView。

Window

添加、删除和更新都是通过这样的过程来完成的:

WindowManager(接口)→
WindowManagerImpl (类)→
WindowManagerGlobal (工厂)。

3.1 Window 的添加过程

实际上通过 WindowManagerGlobal.addView() 来完成。

过程:

  1. 检查参数是否合法,如果是子 Window 那么还需要调整一些布局参数

  2. 创建 ViewRootImpl 并将 View 添加到列表中

  3. 通过 ViewRootImpl 来更新界面并完成 Window 的添加过程
    View 的绘制过程都是由 ViewRootImpl 来完成的,所以这里也由ViewRootImpl 的 setView 方法来完成,setView 内部会通过 requestLayout 来完成异步刷新请求。其中 scheduleTraversals() 就是绘制的入口。

    public void requestLayout(){
    	if (!mHandlingLayoutInLayoutRequest){
    		checkThread();
    		mLayoutRequested = true;
    		scheduleTraversals();
    	}
    }
    

    接着会通过 WindowSession 最终来完成 Window 的添加过程。

    try {
        mOrigWindowType = mWindowAttributes.type;
        mAttachInfo.mRecomputeGlobalAttributes = true;
        collectViewAttributes();
        res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
    } catch (RemoteException e) {
        ...
    } finally {
        ...
    }
    

    WindowSession 的类型是 IWindowSession,它时一个 Binder 对象,真正的实现是 Session,在 Session 内部会通过 WindowManagerService 来实现 Window (显示)的添加。

3.2 Window 的删除过程

实际上通过 WindowManagerGlobal.removeView() 来完成。

public void removeView(View view, boolean immediate) {
	if (view == null) {
		throw new IlleagalArgumentException("view must not be null");
	}
	
	synchronized (mLock) {
		int index = findViewLocked(view, true);//找到要删除的 view 的索引
		View curView = mRoots.get(index).getView();
		removeViewLocked(index, immediate);//删除
		if (curView == view) {
			return;
		}
		throw new IllegalStateException("Calling with view " + view + " but the ViewAncestor is attached to" + curView);
	}
}

进一步的删除在 removeViewLocked() 中完成,代码如下:

private void removeViewLocked(int index, boolean immediate) {
	ViewRootImpl root = mRoots.get(index);
	View view = root.getView();
	if(view != null) {
		InputMethodManager imm = InputMethodManager.getInstance();
		if (imm != null){
			imm.windowDismissed(mViews.get(index).getWindowToken());
		}
	}
	boolean deferred = root.die(immediate);
	if (view != null){
		view.assignParent(null);
		if (deferred) {
			mDyingViews.add(view);
		}
	}
}

root 的 die 方法根据参数 imediate 判断是异步删除还是同步删除。

  • 异步:die 会发一个消息给 ViemRootImpl 的 Handler, Handler 会处理此消息并调用 doDie 方法。
  • 同步:直接调用 doDie 方法。

若是异步,则 die 方法后 view 并没有被删除,view 将被放入 mDyingViews 中等待删除。

3.3 Window 的更新过程

实际上通过 WindowManagerGlobal.updateViewLayout() 来完成。

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
	if (view == null) {
		throw new IllegalArgumentException("view must not be null");
	}
	if (!(params instanceof WindowManager.LayoutParams)) {
		throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
	}
	final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;

	view.setLayoutParams(wparams);
	
    synchronized (mLock) {
        int index = findViewLocked(view, true);
        ViewRootImpl root = mRoots.get(index);
        mParams.remove(index);
        mParams.add(index, wparams);
        root.setLayoutParams(wparams, false);
    }
}

updateViewLayout 方法比较简单,先更新 View 的 LayoutParams 并替代老的 LayoutParams,接着通过 ViewRootImpl 的 setLayoutParams 方法来更新 ViewRootImpl 的 LayoutParams。

四、Window 的显示过程

4.1 Activity 的 Window 显示过程

4.1.1 创建 PhoneWindow

Activity 的启动过程很复杂,最终会由 ActivityThread 中的 performLaunchActivity() 来完成整个启动过程,在这个方法内部会通过类加载器创建 Activity 的实例对象,并调用其 attach() 方法为其关联运行过程中所依赖的一系列上下文环境变量。

在 Activity 的 attach() 方法中,系统会通过 PolicyManager.makeNewWindow() 创建 Activity 所属的 PhoneWindow 对象并为其设置回调接口。

大致过程如下(==> 为内部,—> 为之后):

performLaunchActivity() ===>
activity = mInstrumentation.newActivity(…) —>
activity.attach() ===>
mWindow = PolicyManager.makeNewWindow(this) ===>
return new PhoneWindow(context);

生成 PhoneWindow 之后,会为其生成 WindowManger。

mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

4.1.2 创建和填充 DecorView

在 Activity 的 onCreate 中会调用 setContentView() 方法,在里面会通过 PhoneWindow 创建 DecorView,并填充它的内容:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}

setContentView() 的内部逻辑:

Activity.setContentView() ==>
getWindow.setContentView() ==>
PhoneWindow.setContentView()

PhoneWindow.setContentView() 方法大致有以下步骤:

  1. 如果没有 DecorView , 就创建它
  2. 将 View 添加到 DecorView 的 mContentParent 中
  3. 回调 Activity 的 onContentChanged 方法通知 Activity 视图已经发生变化

4.1.3 显示 DecorView

DecorView 已经被创建且配置完毕,但此时 Window 此时还没有显示出来。在 ActivityThread 的 handleResumeActivity 中,会调用 Activity 的 onResume 方法,接着调用了 Activity 的 makeVisible 之后,Window 才会出现:

void makeVisible(){
	if (!mWindowAdded) {
		ViewManager vm = getWindowManager();
		vm.addView(mDecor, getWindow().getAttributes());
		mWindowAdded = true;
	}
	mDecor.setVisibility(View.VISIBILE);
}

通过 WindowManager.addView() 后,显示上的 Window 才正式出现。

4.2 Dialog 的 Window 显示过程

Dialog 的 Window 显示过程与 Activity 类似,有一下几个步骤:

  1. 创建 PhoneWindow
  2. 创建并填充 DecorView(将 Dialog 的视图添加到 DecorView 中)
  3. 显示 DecorView(通过 WindowManager)

Dialog 的 Window 创建过程与 Activity 很类似,几乎没什么区别。当 Dialog 被关闭,它会通过 WindowManager 来移除 DecorView:mWindowManager.removeViewImmediate(mDecor)(同步)。
普通的 Dialog 必须采用 Activity 的 Context,因为需要 Activity 的应用 Token(具体可见这里)。

4.3 Toast 的 Window 显示过程

Toast 的 Window 的显示过程比较复杂。由于 Toast 具有定时取消这一功能,所以系统采用了 Handler。

Toast 提供了 show 和 cancel 分别用于显示和隐藏 Toast,它们内部是一个 IPC 过程。

Toast 的显示:

INotificationManager service = getService() —>
service.enqueueToast(pkg, tn, mDuration) ===>
将 Toast 请求封装为 ToastRecord 添加到 mToastQueue 队列中 —>
showNextToastLocked() ===>
ToastRecord.callback.show() (显示) —>
scheduleTimeoutLocked(record) (延时)

Toast 的隐藏与显示类似,也是通过 ToastRecord 的 callback 来完成的。

ToastRecord.callback.hide() (隐藏)

callback 实际上是 Toast 中的 TN 对象的远程 Binder,通过 callback 来访问 TN 中的方法是需要跨进程来完成的,最终被调用的 TN 中的方法会运行在发起 Toast 请求的应用的 Binder 线程池中。

所以,Toast 的显示和隐藏过程实际上是通过 Toast 中 TN 这个类来实现的,它有两个方法 show 和 hide,即显示和隐藏。由于这两个方法被 NotificationManagerService 以跨进程的方式调用的,因此它们运行在 Binder 线程池中。为了将执行环境切换到 Toast 请求所在线程,在它们内部使用了 Handler。

Handler 收到 show 消息,会调用 handlerShow() 方法,里面会通过 WindowManger 来显示 View:

public void handleShow(IBinder windowToken) {
	...
	mWM.addView(mView, mParams);
	...
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值