Android 关于WindowManager的AppToken、WindowToken

【1】Android窗口类型

Android中存在很多窗台类型,类型值从低到高,值越大显示的层越高,本篇使用以下程序为测试代码

private AlertDialog getAlertDialog(String text,int type) {
        AlertDialog dialog = new AlertDialog.Builder(MainActivity.this)
                .setTitle(text)
                .setCancelable(true)
                .create();
        dialog.getWindow().setType(type);
        return dialog;
    }

总体上分为三大类,以Android-28 API为例,运行环境的android版本也为28,注意,其他android版本设置修改dialog的windowType可能让dialog无法弹出来。

普通窗口

从FIRST_APPLICATION_WINDOW 到 LAST_APPLICATION_WINDOW,这个范围用户可以自行使用,在这个范围内需要 AppToken ,TYPE_APPLICATION_STARTING这个是在Launcher启动前的过渡窗口类型,并不是Launcher的窗口类型。注意,这里有个问题,如果Dialog的窗口类型低于Activity的窗口类型,那它会显示在Activity下方么?我们使用如下程序测试就知道了。

 AlertDialog dialog = getAlertDialog("helloWorld #1",WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW) ;
dialog.show();

AlertDialog dialog2 = getAlertDialog("helloWorld #2",WindowManager.LayoutParams.TYPE_APPLICATION);
dialog2.show();

AlertDialog dialog3 = getAlertDialog("helloWorld #3",WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
dialog3.show();

这里不上图了,结果如下:

  • Dialog的展示层级的优先级受TYPE控制,值越大,越显示在上层。
  • Dialog的TYPE低于Activity的并不影响其展示在上方,主要原因是Dialog依赖Activity,因此总是展示在parentWindow上方。
  • 部分手机上TYPE_APPLICATION_STARTING只能用于Application闪屏之前的Window,且在后续的生命周期中不可再使用
  • 部分手机对于低于Activity的Window类型的逻辑存在问题

重点我们知道了AppToken最终影响这类组建的展示,因此如果不使用dialog,我们可以使用如下代码在WindowManager中添加View

private WindowManager mWindowManager;

public void showWindow(final String text, int type) {
        View layout = LayoutInflater.from(this).inflate(R.layout.window, null);
        TextView vt = (TextView) layout.findViewById(R.id.window_text);
        vt.setText(text);
        IBinder appToken  = getAppToken();

//当然,这里使用appToken==null也是可以的,参考ContextImpl的Context.WINDOW_SERVICE注册逻辑,activity的Window会注入
WindowManager中,在adjustLayoutParamsForSubWindow中能自动复制给params

        WindowManager.LayoutParams windowLayoutParams = createWindowLayoutParams(appToken, type);
        if(mWindowManager==null){
            mWindowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager.addView(layout,windowLayoutParams);
    }

 
private IBinder getAppToken( ) {
        IBinder appToken = null;
        try {
            Field mAppTokenField = Window.class.getDeclaredField("mAppToken");
            mAppTokenField.setAccessible(true);
            appToken = (IBinder) mAppTokenField.get(TestActivity.this.getWindow());

        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return appToken;
    }

 
protected final WindowManager.LayoutParams createWindowLayoutParams(IBinder token,int windowType) {
        final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
        p.gravity = Gravity.CENTER;
        p.flags = computeFlags(p.flags);
        p.type = windowType;
        p.token = token;
        p.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
        p.setTitle("TestWindowManager:" + Integer.toHexString(hashCode()));
        return p;
    }

    private int computeFlags(int flags) {
        flags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
                WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
                WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
                WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
        return flags;
    }

使用方法如下

showWindow("HelloWorld #5",WindowManager.LayoutParams.LAST_APPLICATION_WINDOW);

当然,借道dialog也是可以的

ublic void showWindow2(final String text, int type) {
        View layout = LayoutInflater.from(this).inflate(R.layout.window, null);
        TextView vt = (TextView) layout.findViewById(R.id.window_text);
        vt.setText(text);
        Dialog d = new Dialog(this);
        d.getWindow().getAttributes().type = type;
        d.setContentView(layout);
        WindowManager.LayoutParams lp = createWindowLayoutParams(null, type);
        //注意,这里没有使用
        mWindowManager.addView(d.getWindow().getDecorView(),lp);
    }

子窗口

范围从FISRT_SUB_WINDOW到LAST_SUB_WINDOW

这类窗口一般需要使用WindowToken (注意不是AppToken),所谓WindowToken意味着它属于Activity低级别组建,事实上也比Dialog要低,类似PopWindow,这也意味着这类组建没有单独的WindowState监控,此外意味着他们必须在onResume之后弹出,尽管他们的取值大于Dialog的。

当然WindowToken的获取可以参考PopWindow,重点需要关注的WindowToken,同上面要求的AppToken一样,都可以先不设置WindowToken

改造方法如下:

public void showWindow(final String text, int type) {
        View layout = LayoutInflater.from(this).inflate(R.layout.window, null);
        TextView vt = (TextView) layout.findViewById(R.id.window_text);
        vt.setText(text);
     

        WindowManager.LayoutParams windowLayoutParams = createWindowLayoutParams(null, type);
        if(mWindowManager==null){
            mWindowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager.addView(layout,windowLayoutParams);
    }

高级窗口

范围从FIRST_SYSTEM_WINDOW到LAST_SYSTEM_WINDOW

这类组建属于高级组建,重点是可以做app外弹框、应用内常驻弹框,但是由于这类组建在各个版本变动很大,特点是兼容性差、可能需要权限等。

以TYPE_TOAST为例,虽然开发者可以用,但是无法点击,此外添加FLAG_NOT_FOCUSABLE虽然可以常驻,但是部分手机仍然无法点击。

【2】谈谈Dialog的PhoneWindow

  • Dialog中创建了PhoneWindow,然而,这个组建并不能为Dialog提供Token的,提供Token的仍然是Activity所在的Window。
  • WindowManager并非单例,只为了管理子窗口或组件。Dialog也会为自己创建一个和Activity不同的WindowManager,参考WindowManagerImpl的createLocalWindowManager,这个Manager用来管理Dialog上的子窗口或组件,而非管理Dialog组建,同理,Activity的WindowManager用来管理Activity子窗口或者组件。
  • createPresentationWindowManager是为了Presentation管理子窗口或者组建创建,而Presentation依然是Activity的组件。

综上,获取WindowManager时一定需要注意,xxx.getWindow().getWindowManager() 可能存在层级问题,使用时一定要注意。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值