关于Window的问题有哪些

发布时间:2021-10-19 17:29:45 作者:iii
来源:亿速云 阅读:147

本篇内容主要讲解“关于Window的问题有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“关于Window的问题有哪些”吧!

如果你遇到这些问题

Window是什么

窗口。你可以理解为手机上的整个画面,所有的视图都是通过Window呈现的,比如Activity、dialog都是附加在Window上的。Window类的唯一实现是PhoneWindow,这个名字就更加好记了吧,手机窗口呗。

Window到底在哪里呢?我们看到的View是Window吗?是也不是。

不知道你晕没有,我总结下,Window是个概念性的东西,你看不到他,如果你能感知它的存在,那么就是通过View,所以View是Window的存在形式,有了View,你才感知到View外层有一个皇帝的新衣——window。 

WindowManager是什么?和WMS的关系?

WindowManager就是用来管理Window的,实现类为WindowManagerImpl,实际工作会委托给WindowManagerGlobal类中完成。

而具体的Window操作,WM会通过Binder告诉WMS,WMS做最后的真正操作Window的工作,会为这个Window分配Surface,并绘制到屏幕上。 

怎么添加一个Window?

    var windowParams: WindowManager.LayoutParams = WindowManager.LayoutParams()
    windowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
    windowParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG
    var btn = Button(this)
    windowManager.addView(btn, windowParams)
 

简单贴了下代码,加了一个Button。

有的朋友可能会疑惑了,这明明是个Button,是个View啊,咋成了Window?

刚才说过了,View是Window的表现形式,在实际实现中,添加window其实就是添加了一个你看不到的window,并且里面有View才能让你感觉得到这个是一个Window。

所以通过windowManager添加的View其实就是添加Window的过程。

这其中还有两个比较重要的属性:flags和type,下面会依次说到。 

Window怎样可以显示到锁屏界面

Window的flag可以控制Window的显示特性,也就是该怎么显示、touch事件处理、与设备的关系、等等。所以这里问的锁屏界面显示也是其中的一种Flag。


// Window不需要获取焦点,也不接受各种输入事件。
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;

// @deprecated Use {@link android.R.attr#showWhenLocked} or
// {@link android.app.Activity#setShowWhenLocked(boolean)} instead to prevent an
// unintentional double life-cycle event.


// 窗口可以在锁屏的 Window 之上显示
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
   

Window三种类型都存在的情况下,显示层级是怎样。

Type表示Window的类型,一共三种:

可以看到,区别就是有个Window层级(z-ordered),层级高的能覆盖住层级低的,离用户更近。

 

Window就是指PhoneWindow吗?

如果有人问我这个问题,我肯定心里要大大的疑惑了????。

可不就是PhoneWindow吗?都唯一实现类了,净问些奇怪问题。

但是面试的时候遇到这种问题总要答啊?这时候就要扯出Window的概念了。

PhoneWindow什么时候被创建的?

熟悉Activity启动流程的朋友应该知道,启动过程会执行到ActivityThread的handleLaunchActivity方法,这里初始化了WindowManagerGlobal,也就是WindowManager实际操作Window的类,待会会看到:

 public Activity handleLaunchActivity(ActivityClientRecord r,
                                         PendingTransactionActions pendingActions, Intent customIntent) {
        //...
        WindowManagerGlobal.initialize();
        //...
        final Activity a = performLaunchActivity(r, customIntent);
        //...
        return a;
    }
 

然后会执行到performLaunchActivity中创建Activity,并调用attach方法进行一些数据的初始化(伪代码):

    final void attach() {
        //初始化PhoneWindow
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        mWindow.setCallback(this);

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

        mWindowManager = mWindow.getWindowManager();
}
 

可以看到,在Activity的attach方法中,创建了PhoneWindow,并且设置了callback,windowManager

这里的callback待会会说到,跟事件分发有关系,可以说是当前Activity和PhoneWindow建立联系。

 

要实现可以拖动的View该怎么做?

还是接着刚才的btn例子,如果要修改btn的位置,使用updateViewLayout即可,然后在ontouch方法中传入移动的坐标即可。

        btn.setOnTouchListener { v, event ->
            val index = event.findPointerIndex(0)
            when (event.action) {
                ACTION_MOVE -> {
                    windowParams.x = event.getRawX(index).toInt()
                    windowParams.y = event.getRawY(index).toInt()
                    windowManager.updateViewLayout(btn, windowParams)
                }
                else -> {
                }
            }
            false

        }
   

Window的添加、删除和更新过程。

Window的操作都是通过WindowManager来完成的,而WindowManager是一个接口,他的实现类是WindowManagerImpl,并且全部交给WindowManagerGlobal来处理。下面具体说下addView,updateViewLayout,和removeView。

 

1)addView

//WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {

        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        }
            
            ViewRootImpl root;
            View panelParentView = null;

            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            try {
                root.setView(view, wparams, panelParentView);
            } 
        }
    }
 
final IWindowSession mWindowSession;

mWindowSession = WindowManagerGlobal.getWindowSession();

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    //
    requestLayout();

    res = mWindowSession.addToDisplay(mWindow,);
}
 

setView方法主要完成了两件事,一是通过requestLayout方法完成异步刷新界面的请求,进行完整的view绘制流程。其次,会通过IWindowSession进行一次IPC调用,交给到WMS来实现Window的添加。

其中mWindowSession是一个Binder对象,相当于在客户端的代理类,对应的服务端的实现为Session,而Session就是运行在SystemServer进程中,具体就是处于WMS服务中,最终就会调用到这个Session的addToDisplay方法,从方法名就可以猜到这个方法就是具体添加Window到屏幕的逻辑,具体就不分析了,下次说到屏幕绘制的时候再细谈。

 

2)updateViewLayout

 public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
//...
        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);
        }
    }
 

这里更新了WindowManager.LayoutParamsViewRootImpl.LayoutParams,然后在ViewRootImpl内部同样会重新对View进行绘制,最后通过IPC通信,调用到WMS的relayoutWindow完成更新。 

3)removeView

public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            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);
        }
    }
    
    
    private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (view != null) {
            InputMethodManager imm = view.getContext().getSystemService(InputMethodManager.class);
            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);
            }
        }
    }    
 

该方法中,通过view找到mRoots中的对应索引,然后同样走到ViewRootImpl中进行View删除工作,通过die方法,最终走到dispatchDetachedFromWindow()方法中,主要做了以下几件事:

Activity、PhoneWindow、DecorView、ViewRootImpl 的关系?

看完上面的流程,我们再来理理这四个小伙伴之间的关系:

关于Window的问题有哪些  
 

Window中的token是什么,有什么用?

token?又是个啥呢?刚才window操作过程中也没出现啊。

token其实大家应该工作中会发现一点踪迹,比如application的上下文去创建dialog的时候,就会报错:

unable to add window --token null 

所以这个token跟window操作是有关系的,翻到刚才的addview方法中,还有个细节我们没说到,就是adjustLayoutParamsForSubWindow方法。

//Window.java
    void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
        if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
            //子Window
            if (wp.token == null) {
                View decor = peekDecorView();
                if (decor != null) {
                    wp.token = decor.getWindowToken();
                }
            }
        } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
            //系统Window
        } else {
            //应用Window
            if (wp.token == null) {
                wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
            }
            
        }
    }
 

上述代码分别代表了三个Window的类型:

然后在WMS中的addWindow方法会验证这个token,下次说到WMS的时候再看看。

所以这个token就是用来验证是否能够添加Window,可以理解为权限验证,其实也就是为了防止开发者乱用context创建window。

拥有token的context(比如Activity)就可以操作Window。没有token的上下文(比如Application)就不允许直接添加Window到屏幕(除了系统Window)。 

Application中可以直接弹出Dialog吗?

这个问题其实跟上述问题相关:

//检查权限
if (!Settings.canDrawOverlays(this)) {
    val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
    intent.data = Uri.parse("package:$packageName")
    startActivityForResult(intent, 0)
}

dialog.window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG)

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

关于事件分发,事件到底是先到DecorView还是先到Window的?

经过上述一系列问题,是不是对Window印象又深了点呢?最后再看一个问题,这个是wanandroid论坛上看到的,

这里的window可以理解为PhoneWindow,其实这道题就是问事件分发在Activity、DecorView、PhoneWindow中的顺序。

当屏幕被触摸,首先会通过硬件产生触摸事件传入内核,然后走到FrameWork层(具体流程感兴趣的可以看看参考链接),最后经过一系列事件处理到达ViewRootImpl的processPointerEvent方法,接下来就是我们要分析的内容了:

//ViewRootImpl.java
 private int processPointerEvent(QueuedInputEvent q) {
            final MotionEvent event = (MotionEvent)q.mEvent;
            ...
            //mView分发Touch事件,mView就是DecorView
            boolean handled = mView.dispatchPointerEvent(event);
            ...
        }

//DecorView.java
    public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            //分发Touch事件
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //cb其实就是对应的Activity
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }


//Activity.java
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

//PhoneWindow.java
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

//DecorView.java
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }    

 

事件的分发流程就比较清楚了:

ViewRootImpl——>DecorView——>Activity——>PhoneWindow——>DecorView——>ViewGroup

(这其中就用到了getCallback参数,也就是之前addView中传入的callback,也就是Activity本身)

但是这个流程确实有些奇怪,为什么绕来绕去的呢,光DecorView就走了两遍。

参考链接中的说法我还是比较认同的,主要原因就是解耦。

到此,相信大家对“关于Window的问题有哪些”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

推荐阅读:
  1. 装window的nginx遇到的问题
  2. window环境 解决端口占用问题

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

window

上一篇:firewall设定是什么

下一篇:spring webmvc请求处理流程中返回值处理是什么

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》