第8章-理解Window和WindowManager

Window和WindowManager

​ window表示一个窗口的概念,是抽象类,唯一的子类是PhoneWindow。操作window需要使用WindowManager,WindowManager是外界访问Window的入口。window的具体实现是 在WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。

​ android中的视图都是window来呈现的,不管是Activity,Dialog,Toast,他们的视图都是附着在Window上的,因此Window是view的直接管理者。

添加addView

  1. 获取WindowManager,使用WindowManager wm = getSystemService(Context.WINDOW_SERVICE)
  2. 调用wm的addView方法即可添加一个window
  3. 注意添加window时,需要申请权限,静态申请和动态申请
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
private fun addWindow() {
        val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager

        //指定view的宽高
        val w = 500
        val h = 100

        //指定type,如果是8.0以上的使用WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
        //如果是8.0以下的使用WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
        val type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY

        //指定flag
        val flag = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or//不需要焦点
                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or//一般都会设置这个,否则其他window无法接收单击事件
                WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON//可以让window显示在锁屏界面上
        
         layoutParams = WindowManager.LayoutParams(w, h, type, flag, PixelFormat.TRANSPARENT)

        //设置显示的位置
        layoutParams.x = 0
        layoutParams.y = 0

        //设置显示的重心,默认是center
        layoutParams.gravity = Gravity.LEFT or Gravity.TOP

        button.setOnClickListener {
            Toast.makeText(this, "点击了window", Toast.LENGTH_SHORT).show()
        }
        windowManager.addView(button, layoutParams)
    }

更新updateViewLayout

b.setOnTouchListener { v, event ->
            if (event.action==MotionEvent.ACTION_MOVE){
                layoutParams.x=event.rawX.toInt() - b.width/2
                layoutParams.y=event.rawY.toInt() - b.height
                layoutParams.gravity=Gravity.START or Gravity.TOP
                windowManager.updateViewLayout(b,layoutParams)
            }
            true
        }

删除removeView

private fun removeWindow() {
        if (button.isAttachedToWindow) {
            val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
            windowManager.removeView(button)
        }
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DbjX35a2-1639613276018)(/Users/wuzehao/Library/Application Support/typora-user-images/image-20211211211357188.png)]

由此看来WindowManager操作Window的过程更像是WindowManger操作view的过程

Window的内部机制

​ WindowManager的实现类是WindowManagerImpl,查看addView方法,发现调用的是WindowManagerGlobal中的addView。从这里看出WindowManagerImpl将添加,更新,删除的方法都委托给了WindowManagerGlobal处理了。

window的添加过程

​ 在WindowManagerGlobal中的addView方法中,创建了ViewRootImpl,接着调用了ViewRootImpl的setView方法,接着执行view的测量,布局,绘制等工作

window的更新过程

​ 调用WindowManagerGlobal的updateViewLayout方法,首先更新view的LayoutParams并替换老的LayoutParams,然后调用ViewRootImpl的setLayoutParams方法,对view重新布局,测量,布局和重绘

window的删除过程

​ 同样的,也是调用WindowManagerGlobal中的removeView方法

Window创建过程

​ 有视图的地方就有window,view必须依附在window上

Activity的Window创建过程

​ 在Activity创建过程中调用了attach方法,这个方法中就创建了Window

@UnsupportedAppUsage
    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, IBinder assistToken) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

      //这里创建PhoneWindow
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
      ...
      ...

​ Activity中的视图是如何附着在Window上的?

需要查看Activity中的setContentView方法,这个方法又调用了Window的setContentView方法

PhoneWindow中的setContentView

@Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
          //创建DecorView
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
          //根据样式布局decorView,添加到mContentParent ViewGroup中
            mContentParent = generateLayout(mDecor);

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();

  • 创建DecorView
  • 把DecorView添加到contentParent中,还没有被WindowManager添加到Window中
  • 在ActivityThread方法中的handleResumeActivity方法中,首先会调用Activity的onResume方法,接着会调用Activity中的makeVisible方法,在这个方法中DecorView真正完成了添加和显示这两个过程,到这里Activity的试图才能被用户看到
void makeVisible() {
        if (!mWindowAdded) {
          //获取WindowManager
            ViewManager wm = getWindowManager();
          //使用WindowManager唯一入口添加window
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

Dialog的Window创建过程

​ Dialog中的Window也是PhoneWindow,在构造函数中即可看出

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        if (createContextThemeWrapper) {
            if (themeResId == Resources.ID_NULL) {
                final TypedValue outValue = new TypedValue();
                context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
                themeResId = outValue.resourceId;
            }
            mContext = new ContextThemeWrapper(context, themeResId);
        } else {
            mContext = context;
        }

        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

  //这里创建了PhoneWindow
        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        w.setCallback(this);

Dialog中的setContentView方法和Activity一样也是调用PhoneWindow中的setContentView方法

public void setContentView(@LayoutRes int layoutResID) {
        mWindow.setContentView(layoutResID);
    }

Dialog的show方法中,decorView会被显示出来,并且被添加到Window上

public void show() {
        if (mShowing) {
            if (mDecor != null) {
                if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
                    mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
                }
           //decorView显示     mDecor.setVisibility(View.VISIBLE);
            }
            return;
        }
  //调用了onStart,这个方法居然是在这里调用的
  onStart();
  //创建了DecorView
  mDecor = mWindow.getDecorView();
  //添加到window上
   mWindowManager.addView(mDecor, l);
}

Dialog的dimissDialog方法中,使用WindowManager的removeView进行移除

创建Dialog必须使用Activity的context,不能使用Application的context,不然就会报没有应用token导致的,而应用的token一般只有activity有。系统的dialog比较特殊,可以不需要token,只要将dialog的type设置成系统的类型即可,与上面提到的一样,注意,必须在清单文件中申请权限和动态申请

dialog.getWindow().setType(...)

Toast的Window创建过程

​ 这个过程比较复杂,涉及到IPC过程,暂不分析,最终调用的方法是在TN内部类中的handleShow方法中,完成了window的添加,在handleHide中,调用的window的remove

 public void handleShow(IBinder windowToken) {
try {
                    mWM.addView(mView, mParams);
                    trySendAccessibilityEvent();
                } catch (WindowManager.BadTokenException e) {
                    /* ignore */
                }
 }
public void handleHide() {
            if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
            if (mView != null) {
                // note: checking parent() just to make sure the view has
                // been added...  i have seen cases where we get here when
                // the view isn't yet added, so let's try not to crash.
                if (mView.getParent() != null) {
                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                    mWM.removeViewImmediate(mView);
                }

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值