深入理解Window && WindowManager本质

简介: 引言Window 是类似悬浮窗的东西WM 参与 Window 的 Create 和 管理, WMS 和 WM 共同完成 Window 的 IPC交互Window 是 View 直接 管理者Activity 的 setContenView 底层是由PhoneWindow的installDecor绘制的.

1681519328699.png

引言

  • Window 是类似悬浮窗的东西
  • WM 参与 WindowCreate 和 管理, WMSWM 共同完成 WindowIPC交互
  • WindowView 直接 管理者
  • ActivitysetContenView 底层是由PhoneWindowinstallDecor绘制的.

1. 如何使用 WM 添加一个 Window?

// 将一个Button添加到屏幕位置(100,300)的位置 
Button button = new Button(this);
button.setText("CrazyDailyQuestion");
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSLUCENT);
// 通过Flags控制Window的显示特性
layoutParams.flags = 
// 此模式下,系统会将当前Window区域外的单击事件,传递给底层Window,当前Window区域外的点击事件传递给底层Window,当前Window区域以内的事件则自己处理,如果不开启该事件可能出现其他Window无法捕捉到单击事件
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | 
// Window 不需要获取任何焦点,也不需要接收各种输入事件,此标记会同时开启 FLAG_NOT_TOUCH_MODAL
WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN | 
// Window可以显示在锁屏的界面上
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
        layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
        layoutParams.x = 100;
        layoutParams.y = 300;
// 获取Window的对象        
WindowManager mManager = (WindowManager) getApplicationContext()
                .getSystemService(Context.WINDOW_SERVICE);
// 通过WM将View绘制到指定位置                
mManager.addView(button, layoutParams);

2. layoutParams.type

layoutParams.type examp z-orderered 注意事项
Application Activity (0,100) /
Child Dialog (999,2000) 不能单独存在,需要附属在特定的父 Window
System Toast,StatusBar ,软键盘 (1999,3000) 需要声明权限

3. Method

public interface ViewManager{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}

addView

概念: 创建 Window添加View

  1.检查参数是否合法,如果子Window,那么还需要调整一些布局参数
addJustLayoutParamsForSubWindow(wparams)
2.创建 ViewRootImpl 并将 View 添加到列表中
// 存储所有Window对应的View
private final ArrayList<View> mViews = new ArrayList<View>();
// 存储所有Window对应的ViewRootImpl
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
// 存储所有Window所对应的布局参数
private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();
// 存储了正在删除还未完成的 Window 对象
private final ArraySet<View> mDyingViews = new ArraySet<View>();
// 将 Window 对象添加到列表里面
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
3.通过 ViewRootImpl 来更新界面并完成 Window 的添加过程

  Window 的绘制 由 ViewRootImplsetView 来完成,setView 方法会通过 requestLayout 来完成异步请求

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            // View的绘制入口
            scheduleTraversals();
        }
    }

WindowSession 最终完成 Window 的添加过程,真正实现类是 Session,也是Window 添加过程的一次 IPC 调用.然后底层是Seesion通过WMS 实现Window 添加.

updateViewLayout

  概念: 更新 Window中的View

removeView

  概念: 删除一个 Window

4.  Window 事件操作

// 将 View 设置 OnTouchListener
mButton.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
            // 只需根据手指设置 mLayoutParams 的x,y值更改Window的位置
                int rawX = (int) event.getRawX();
                int rawY = (int) event.getRawY();
                switch (event.getAction()) {
                    case MotionEvent.ACTION_MOVE:
                        mLayoutParams.x = rawX;
                        mLayoutParams.y = rawY;
                        // onTouch 方法不断更新 View的位置
                        manager.updateViewLayout(mButton, mLayoutParams);
                        break;
                    default:
                        break;
                }
                return false;
            }
        });

5.  Activity && Window

  ViewAndroid 中视图的程序方式,View 不能单独存在,必须依附 Window 这个抽象概念上,因此有视图的地方就有Window,下面我就带大家来看一下,ActivtyWindwow 创建过程.

  Activity 启动过程很复杂,最终是交给ActivityThreadperformLauchActivity() 来完成整个启动过程,在整个方法内部会通过类加载器创建 Activity 的实例对象,并通过 attach 方法关联一切所需要的上下文环境.

  private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
  java.lang.ClassLoader cl = appContext.getClassLoader();
  activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
// -------为了避免浪费篇幅,省略无关代码--------
          if (activity != null) {
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                if (r.overrideConfig != null) {
                    config.updateFrom(r.overrideConfig);
                }
                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                        + r.activityInfo.name + " with config " + config);
                Window window = null;
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }
                appContext.setOuterContext(activity);
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
 // -------为了避免浪费篇幅,省略无关代码--------
 }
  final void attach(IBinder windowToken) {
        // window 对象通过 PolicyManager.makeNewWindow 创建
        mWindow = PolicyManager.makeNewWindow(this);
        // window 的 Callback接口里面有我们熟悉的dispaTouch方法以及onAttachToWindow方法
        mWindow.setCallback(this);
        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
        mWindow.setBackgroundDrawable(new ColorDrawable(0xFF000000));
        WindowManager.LayoutParams lp = mWindow.getAttributes();
        lp.type = WindowManager.LayoutParams.TYPE_DREAM;
        lp.token = windowToken;
        lp.windowAnimations = com.android.internal.R.style.Animation_Dream;
  }

PolicyManager的具体实现接口方法都在策阅接口IPolicy里面完成.其中makeNewWindow实际上是创建了一个Window对象.

public interface IPolicy {
    public Window makeNewWindow(Context context);
    public LayoutInflater makeNewLayoutInflater(Context context);
    public WindowManagerPolicy makeNewWindowManager();
    public FallbackEventHandler makeNewFallbackEventHandler(Context context);
}

然后我们去找 makeNewWindow 的接口实现,我们发现,Window 具体实现实际上是 PhoneWindow.

 public PhoneWindow makeNewWindow(Context context) { 
       return new PhoneWindow(context); 
   } 

整个过程 Window就已经创建完毕了,接下来我们只要看Activity是怎样将具体实现交给Window处理,而Window的具体实现是PhoneWindow,所以我们来看setContentView的具体实现.

1681519742503.png1681519806517.png

借助孙群的源码结构图,整个setContentView源码结构如上,接下来我们来看一下PhoneWindowsetContentView源码


    @Override
   public void setContentView(int layoutResID) {
    if (mContentParent == null) {
    // installDecor()方法会调用generateDecor()和generateLayout()方法
           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;
   }
1. DecorView 创建

   这里我们着重需要了解的是 generateDecor() 方法实现

    protected DecorView generateDecor(int featureId) {
       Context context;
       if (mUseDecorContext) {
           Context applicationContext = getContext().getApplicationContext();
           if (applicationContext == null) {
               context = getContext();
           } else {
               context = new DecorContext(applicationContext, getContext().getResources());
               if (mTheme != -1) {
                   context.setTheme(mTheme);
               }
           }
       } else {
           context = getContext();
       }
       return new DecorView(context, featureId, this, getAttributes());
   }

DecorView其实就是一个FrameLayout,它的创建过程由 installDecor 完成,然后installDecor内部方法通过 generateDecor 来创建 DecorView,这个时候 DecorView 还是一个空白的 FrameLayout.

mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

其中具体的布局文件和主题都在generateDecor完成,然后我们需要注意的是ID_ANDROID_CONTENT多对应的id其实就是ViewGroup 对应的MContentParent

    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
2. View 添加到 DecorViewmContenParnent
mLayoutInflate.inflate(layoutResID,mContenParnent);
3. 回调 Activity 的 onContentchanged 方法 通知 Activity 视图已经发生改变.
     if (cb != null && !isDestroyed()) {
        // 最后触发内容变化的回调
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

DecorView 绘制完成,绘制主要是在onResume完成的,具体可以查看ActivityThread -> handleResumeActivity -> makeVisible -> addView

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

6. Dialog && Window

   Dialog 的创建和 Activty 类似,需要注意两个事情.第一点, Dialog 使用的是 Activity 的 token,而不是 Application 的token,所以上下文注意不要用错了,第二点,因为Dilaog是子Window,所以需要申请系统Window权限.

7. Toast && Window

原理:
  • IPC 机制
  • Toast 访问 NMS
  • NMS 回调 Toast TN 接口
  • Handler 定时系统

8. View && Window

  任何一个 View 都是依附在Window上的,Window 是 View 直接 管理者

9. 注意事项

  • 华为手机需要手动赋权,才能开启Toast.建议自定义Toast
  • 频繁请求Toast,系统有权拒绝NMS,防止网络攻击



















相关文章
|
3天前
|
API 开发工具 Android开发
调用Android原生@SystemApi、@Hide方法
调用Android原生@SystemApi、@Hide方法
8 1
|
设计模式 缓存 前端开发
理清Activity、View及Window之间关系
理清Activity、View及Window之间关系
|
Android开发
Android | View & Fragment & Window 的 getContext() 一定返回 Activity 吗?
Android | View & Fragment & Window 的 getContext() 一定返回 Activity 吗?
117 0
Android | View & Fragment & Window 的 getContext() 一定返回 Activity 吗?
|
缓存 Android开发
Android 深入了解 Window 、Activity、 View 三者关系(下)
addView 成功有一个标志就是能够接收触屏事件,通过对 setContentView 流程的分析,可以看出添加 View 的操作实质上是 PhoneWindow 在全盘操作,背后负责人是 WMS,反之 Activity 自始至终没什么参与感。但是我们也知道当触屏事件发生之后,Touch 事件首先是被传入到 Activity,然后才被下发到布局中的 ViewGroup 或者 View(Touch事件分发 了解一下)。那么 Touch 事件是如何传递到 Activity 上的呢?
216 0
|
XML 开发工具 Android开发
Android 深入了解 Window 、Activity、 View 三者关系(上)
Window、Activity、View都经常用到,但三者关系还是没有系统的理清,今天咱们就开始整理整理这三者的关系: Window:顶级窗口外观和行为策略的 抽象基类 。唯一实现是 PhoneWindow类。 Activity:四大组件之一,它提供一个界面让用户点击和各种滑动操作。 View:代表用户界面组件的基本构建块,UI 组件。
229 0
Android 深入了解 Window 、Activity、 View 三者关系(上)
为什么会有window.window这种设计
为啥要搞这个这个看起来貌似很奇葩的设计。 要解答这个问题,还得请出this,我们经常说浏览器中的全局对象是window, 这句话对了,也还没完全对。 全局对象的真实身份应该是全局作用域的this。 window只是为了便于访问this,弄出来的一个属性。
274 0
为什么会有window.window这种设计
|
前端开发 JavaScript 数据安全/隐私保护
58、window 对象
浏览器里面,window对象(注意,w为小写)指当前的浏览器窗口。它也是当前页面的顶层对象,即最高一层的对象,所有其他对象都是它的下属。一个变量如果未声明,那么默认就是顶层对象的属性。
200 0
|
Android开发
Activity、Window、View三者关系
目录介绍 01.Window,View,子Window 02.什么是Activity 03.什么是Window 04.什么是DecorView 05.什么是View 06.关系结构图 07.Window创建过程 08.
981 0
|
Android开发
从源码角度分析Activity、Window和DecorView的关系
前言 最近想出一篇Android事件分发机制的文章,但是根据很多小伙伴反馈在理解Android事件分发机制之前都不是很明白Activity、Window和DecorView之间的关系,导致在学习Android事件分发机制上理解很费劲,本文将从源码角度带你分析Activity、Window和DecorView之间的关系,让你彻彻底底搞明白。
1393 0