写在前面:
这篇文章并没有提供屏蔽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宣告失败,但明晰了一些原理,还是颇有收获的。
敢于怀疑,勇于探索。

更多相关文章

  1. Unity头像上传功能实现 二
  2. android中actionBar中字体颜色设置
  3. 【Android(安卓)UI设计与开发】第07期:底部菜单栏(二)Fragment的详
  4. Android(安卓)使用SQLiteDatabase操作SQLite数据库(一)
  5. Android笔记之广播Broadcast
  6. android 与服务器用 http Post方法通迅
  7. uniapp原生插件开发之调用原生方法(android)
  8. 初识Android(安卓)回调机制
  9. android 笔记 --- 相机应用

随机推荐

  1. Android 文件选择器,单选,多选
  2. Android 链接https出现 javax.net.ssl.SS
  3. Android在线源码查看
  4. android获取当前Canvas位图代码
  5. 【Android】菜单功能的实现:上下文菜单
  6. android 学习 ----控件学习 选项卡。
  7. Android 系统调用 汇总
  8. android 谷歌发音
  9. Android调用百度地图API实现――实时定位
  10. 客户要求整个android设备只有一个APP