Android关于Activity屏蔽/拦截Home键

版权声明:转载请注明出处。 https://blog.csdn.net/rentee/article/details/79246776

写在前面:
这篇文章并没有提供屏蔽Home键的方法,仅仅是阐释一些原理,引发一些思考。


1.奇淫技巧的源泉:PhoneWindowManager#interceptKeyBeforeDispatching
拦截home键的思想大致由此发源。Input事件分发时,Service端就会过滤了一些事件,而Home的过滤,就在此方法。方法注释说明了,如果app是keyguard类型,则将home键事件派发给app。源码(API 19)如下:

if (keyCode == KeyEvent.KEYCODE_HOME) {
            ... 
            // If a system window has focus, then it doesn't make sense
            // right now to interact with applications.
            WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null;
            if (attrs != null) {
                final int type = attrs.type;
                if (type == WindowManager.LayoutParams.TYPE_KEYGUARD
                        || type == WindowManager.LayoutParams.TYPE_KEYGUARD_SCRIM
                        || type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) {
                    // the "app" is keyguard, so give it the key
                    return 0;
                }
            }
            ...
        }

2.传说中的在onAttachedToWindow中调用getWindow().setType设置Window Type
方式如下:

    @Override
    public void onAttachedToWindow() {
        getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD);
        super.onAttachedToWindow();
    }

本人基于4.4(API 19)及以上系统版本进行测试,都报异常:

java.lang.IllegalArgumentException: Window type can not be changed after the window is added.

所以,这种方式并不可行。
并且,如果你的compileSdkVersion >= 21,TYPE_KEYGUARD这种Window类型都没有了……

3.思考IllegalArgumentException: Window type can not be changed after the window is added
报完错之后有种想法,既然在onAttachedToWindow方法中设置type不行,那在onCreate中或者onResume中,这两个生命周期方法调用时期早于onAttachedToWindow,可以一试。
然而发现并不行。在onKeyDown中打个断点,发现Window的type是TYPE_BASE_APPLICATION,也就是说,之前虽然该了type,但是没有生效,或者说,在我更改了type之后,系统又进行了更改,于是进入了下一步,撸源码。

4.何处设置了TYPE_BASE_APPLICATION
Activity Window从产生到WindowMnager#addView被调用,详细过程请自行查找,网上有较多的阐释。
我需要了解的核心大致如下:
在Activity#attach方法中产生PhoneWindow。
在ActivityThread#handleResumeActivity将DecorView添加到WindowManager。源码(API 19)如下:

if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                }

            // If the window has already been added, but during resume
            // we started another activity, then don't yet make the
            // window visible.
            }

好吧,找到了原因:add前,android“帮你”重设了type。


5.另有蹊径:WindowManager.LayoutParams privateFlags ???
撸Android 5.1(API 21)源码,发现它的PhoneWindowManager#interceptKeyBeforeDispatching方法home键事件处理逻辑与Android 4.4(API 19)有些许不同,如下:

if (keyCode == KeyEvent.KEYCODE_HOME) {
            ... 
            // If a system window has focus, then it doesn't make sense
            // right now to interact with applications.
            WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null;
            if (attrs != null) {
                final int type = attrs.type;
                if (type == WindowManager.LayoutParams.TYPE_KEYGUARD_SCRIM
                        || type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
                        || (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
                    // the "app" is keyguard, so give it the key
                    return 0;
                }
            }
            ...
        }

于是乎想,能不能在这个privateFlags上做文章。
WindowManager.LayoutParams中,privateFlags和PRIVATE_FLAG_KEYGUARD虽然是public的,但是是@hide注解的,所以访问不到,需要通过反射取值和修改,代码如下:

    public static void changeWindowPrivateFlags(Window window) {
        WindowManager.LayoutParams attrObj = window.getAttributes();
        Class<?> attrCls = attrObj.getClass();
        try {
            //拿PRIVATE_FLAG_KEYGUARD的值
            Field privateFlagConstField = attrCls.getField("PRIVATE_FLAG_KEYGUARD");
            privateFlagConstField.setAccessible(true);
            int privateFlagConst = privateFlagConstField.getInt(attrCls);

            //设置privateFlags为PRIVATE_FLAG_KEYGUARD
            Field field = attrCls.getField("privateFlags");
            field.setAccessible(true);
            field.setInt(attrObj, privateFlagConst);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

在Activity onCreate中调用这个方法。好吧,还是无效。但奇怪的是,我在onKeyDown中打断点,发现privateFlags确实等于PRIVATE_FLAG_KEYGUARD。然后就想不通了,如果博友们有通了的,麻烦回复下,感谢!


至此,拦截home宣告失败,但明晰了一些原理,还是颇有收获的。
敢于怀疑,勇于探索。

猜你喜欢

转载自blog.csdn.net/rentee/article/details/79246776