在Android 中会有以下5个按键(Back、Home、Menu、Power、Volume)与用户进行交互,Framework 层中实现按键功能,因此,从手机系统定制的角度,可以满足客户的客制化要求。本文主要从Framework层浅析这些客制化需求的实现。
Back、Home、Menu、Power、Volume 按键图
Android 按键修改相关的类PhoneWindowManager 简介如何打开 或者 关闭 Navigation Bar如何长按Home 键启动Google Now如何长按实体Menu键进入多窗口模式如何点击 Menu键进入调出最近任务列表如何让App拿到Power key 值如何修Activity启动是的窗口(app启动白屏,黑屏问题)WindowManagerPolicy 简介
欢迎关注微信公众号:程序员Android
公众号ID:ProgramAndroid
获取更多信息
微信公众号:ProgramAndroid
我们不是牛逼的程序员,我们只是程序开发中的垫脚石。
我们不发送红包,我们只是红包的搬运工。
1. Android 按键修改相关的类
以MTK 平台为例,按键客制化的代码主要存放在以下类中
PhoneWindowManager
PhoneWindowManager 代码路径如下:
\alps\frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
WindowManagerPolicy
PhoneWindowManager 实现 的接口类WindowManagerPolicy 代码路径如下:
alps\frameworks\base\core\java\android\view\WindowManagerPolicy.java
2. PhoneWindowManager 简介
PhoneWindowManager 类实现接口如下:
java.lang.Object↳ android.view.WindowManagerPolicy.java↳ com.android.server.policy.PhoneWindowManager.java
PhoneWindowManager 类实现关系
PhoneWindowManager主要用于实现各种实体或虚拟按键处理,如需特殊处理按键,请修改源码。
3. 如何打开 或者 关闭 Navigation Bar
虚拟导航栏
解决方法:
修改config.xml 文件中
搜索关键字 config_showNavigationBar , 查看 config_showNavigationBar 值
true 表示显示,false 表示不显示
<!-- Whether a software navigation bar should be shown. NOTE: in the future this may beautodetected from the Configuration. --><bool name="config_showNavigationBar">true</bool>
参考路径如下:
alps\frameworks\base\core\res\res\values\config.xml
修改 system.prop 文件
查询关键字 qemu.hw.mainkeys ,并查看值,0 表示关闭 1.表示开启 。
# temporary enables NAV bar (soft keys)qemu.hw.mainkeys=1
不同项目文件存放地址不一样,可以使用以下命令查找
终端下查找文件方法
find 路径 -name "文件名.java"
或者直接查找文件中的字符串
find 路径 -type f -name "文件名" | xargs grep "文件中的字符串"
修改PhoneWindowManager代码
如果上面两个修改都不生效(搜索关键字config_showNavigationBar、qemu.hw.mainkeys),请在PhoneWindowManager 查看setInitialDisplaySize方法中mHasNavigationBar 的值是否被写死,true表示会显示、false 表示不显示导航栏。
@Overridepublic void setInitialDisplaySize(Display display, int width, int height, int density) {...// mHasNavigationBar 值控制是否显示虚拟导航栏mHasNavigationBar = res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);...}
4. 如何长按Home 键启动Google Now
预制 Google Now APK
请自行安装APK
修改 PhoneWindowManager 代码
长按Home键启动Google Now ,实现方法参考launchAssistLongPressAction 功能实现。
private void launchAssistAction(String hint, int deviceId) {sendCloseSystemWindows(SYSTEM_DIALOG_REASON_ASSIST);if (!isUserSetupComplete()) {// Disable opening assist window during setupreturn;}Bundle args = null;if (deviceId > Integer.MIN_VALUE) {args = new Bundle();args.putInt(Intent.EXTRA_ASSIST_INPUT_DEVICE_ID, deviceId);}if ((mContext.getResources().getConfiguration().uiMode& Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION) {// On TV, use legacy handling until assistants are implemented in the proper way.((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)).launchLegacyAssist(hint, UserHandle.myUserId(), args);} else {if (hint != null) {if (args == null) {args = new Bundle();}args.putBoolean(hint, true);}StatusBarManagerInternal statusbar = getStatusBarManagerInternal();if (statusbar != null) {statusbar.startAssist(args);}}}
自己实现常按Home 键吊起Google Now 方法,供在按键分发处理事件时候调用。
private void launchAssistLongPressAction() {performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);sendCloseSystemWindows(SYSTEM_DIALOG_REASON_ASSIST);// launch the search activityIntent intent = new Intent(Intent.ACTION_SEARCH_LONG_PRESS);intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);try {// TODO: This only stops the factory-installed search manager.// Need to formalize an API to handle othersSearchManager searchManager = getSearchManager();if (searchManager != null) {searchManager.stopSearch();}startActivityAsUser(intent, UserHandle.CURRENT);} catch (ActivityNotFoundException e) {Slog.w(TAG, "No activity to handle assist long press action.", e);}}private SearchManager getSearchManager() {if (mSearchManager == null) {mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);}return mSearchManager;}private void startActivityAsUser(Intent intent, UserHandle handle) {if (isUserSetupComplete()) {mContext.startActivityAsUser(intent, handle);} else {Slog.i(TAG, "Not starting activity because user setup is in progress: " + intent);}}
在按键事件分发之前处理
在按键分发处理之前调用自定义长按Home 键的方法
@Overridepublic long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {...} else if (keyCode == KeyEvent.KEYCODE_ASSIST) {if (down) {if (repeatCount == 0) {mAssistKeyLongPressed = false;} else if (repeatCount == 1) {mAssistKeyLongPressed = true;if (!keyguardOn) {launchAssistLongPressAction();}}...}
注意 双击Home 键调出最近任务列表请用以下方法
双击Home 键调出最近任务列表
在 phoneWindowManager.java 的 interceptKeyBeforeQueueing 方法中修改
修改方法如下:
int result = 0; // 原为 int result, 请加入初始值.// 请在类中补充 boolean homeDownDoubleClick = false; 的定义// 请在类中补充 long lastHomeDownTime=0; 的定义// 请在类中补充 long lastHomeUpTime=0; 的定义// 检测原理: 检测上一次按下的 home key 与本次按下的 home key 时间间隔是否 < 500ms// if yes, 则认为是双击 home keyif (keyCode == KeyEvent.KEYCODE_HOME) {if (down) {// this is home downif (((event.getEventTime() - lastHomeDownTime) < 500)) {homeDownDoubleClick = true;} else {homeDownDoubleClick = false;}lastHomeDownTime = event.getEventTime();} else {// then home up comesLog.d(TAG, "homeDownDoubleClick=" + homeDownDoubleClick+ ",lastHomeDownTime=" + lastHomeDownTime+ ",lastHomeUpTime=" + lastHomeUpTime+ ",this home up=" + event.getEventTime());if (homeDownDoubleClick&& ((event.getEventTime() - lastHomeUpTime) < 500)) {Log.d(TAG, "double click on home detected");try {IStatusBarService statusbar = getStatusBarService();if (statusbar != null) {// 调出最近任务列表statusbar.preloadRecentApps();statusbar.toggleRecentApps();}} catch (RemoteException e) {Slog.e(TAG,"RemoteException when preloading recent apps",e);mStatusBarService = null;}result |= ACTION_WAKE_UP;return result;}lastHomeUpTime = event.getEventTime();}}
5. 如何长按实体Menu键进入多窗口模式
Android N上支持Multi-Window,通过recent key 进入多窗口,对于没有打开虚拟导航栏,只有实体menu按键的手机,可以考虑向SystemUI发送广播的形式,进入Android 分屏多任务模式。
解决方案如下:
PhoneStatusBar 里注册广播
PhoneStatusBar 是SystemUI模块的代码,参考路径如下:
alps/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
自定义广播实现可以参考系统mDemoReceiver 的实现方法
动态注册广播方法如下:
context.registerReceiverAsUser(mDemoReceiver, UserHandle.ALL, demoFilter,android.Manifest.permission.DUMP, null);context.registerReceiverAsUser(mAppLongSwitchReceiver, UserHandle.ALL, new IntentFilter("广播的Action"),android.Manifest.permission.DUMP, null);
自定义接收广播后,onReceive处理事件实现分屏方法如下:
private BroadcastReceiver mAppLongSwitchReceiver = new BroadcastReceiver() {public void onReceive(Context context, Intent intent) {if (DEBUG) Log.v(TAG, "onReceive: " + intent);toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS,MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS);}};
PhoneWindowManager 中发送广播
在 PhoneWindowManager 的interceptKeyBeforeDispatching方法中发送广播
@Overridepublic long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {...if (!keyguardOn) {if (down && repeatCount == 1){Intent intent = new Intent("com.app_long_switch");mContext.sendBroadcast(intent);return -1;}if (down && repeatCount == 0) {preloadRecentApps();} else if (!down) {toggleRecentApps();}}...}
destory 方法注销广播
再destory 方法中记得一定要注销广播
mContext.unregisterReceiver(mDemoReceiver);mContext.unregisterReceiver(mAppLongSwitchReceiver);
6. 如何点击 Menu键进入调出最近任务列表
如果想调出最近任务列表,需要拦截menu的事件,在PhoneWindowManager的interceptKeyBeforeDispatching 中处理即可
else if (keyCode == KeyEvent.KEYCODE_MENU) {// Hijack modified menu keys for debugging featuresfinal int chordBug = KeyEvent.META_SHIFT_ON;this.toggleRecentApps();return -1;
如果想长按Menu 调出可以使用以下方法
else if (keyCode == KeyEvent.KEYCODE_MENU) {// Hijack modified menu keys for debugging featuresfinal int chordBug = KeyEvent.META_SHIFT_ON;if(KeyEvent.ACTION_UP == event.getAction()&&event.getEventTime()-event.getDownTime()>500){//long pressthis.toggleRecentApps();return -1;}
7. 如何让 App 拿到Power key 值
一般情况下App 是拿不到Power的Key值,但通过以下方法可以实现。
修改PhoneWindowManager 文件实现
在PhoneWindowManager 中修改interceptKeyBeforeQueueing 方法实现让特定的APP拿到Power key 值
/** {@inheritDoc} */@Overridepublic int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {...case KeyEvent.KEYCODE_POWER: {//com.example.adc为要处理power key的包名if(win != null && win.getAttrs() !=null&&win.getOwningPackage().equals("com.example.adc")){return 1;// return 1事件就传给app处理}}... }
如果只想让某个app的某个Activity 处理
/** {@inheritDoc} */@Overridepublic int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {...case KeyEvent.KEYCODE_POWER: {// 如果只想让power键让某个Activity处理,将以上的if条件改为:if(win != null && win.getAttrs() != null&&win.getAttrs().getTitle().equals("xxx.xxx.xxx.xxxActivity")){return 1;// return 1 就会传给 xxx.xxx.xxx.xxxActivity处理}}... }
8. 如何修Activity启动是的窗口(app启动白屏,黑屏问题)
当用户从主菜单进入其他应用程序例如时钟、联系人、文件管理等时,可能会出现屏幕闪一下黑屏、白屏等问题,这种现象在当前手机主题(Theme)是浅色(例如白色)的情况下比较明显。
此所谓的闪"黑屏",其实是应用程序的启动窗口。
启动窗口出现的条件如下:
仅在要启动的Activity在新的Task或者新的Process时,才可能显示启动窗口
启动窗口先于Activity窗口显示,当Activity窗口的内容准备好之后,启动窗口就会被移除掉,show出真正的activity 窗口
启动窗口和普通的Activity window类似,只是没有画任何内容,默认是一个黑色背景的窗口
正是由于启动窗口默认是黑色背景的,所以在当前的手机主题为浅色调的时候,就比较容易因为颜色的深浅对比而产生一种视觉上的闪动感。
解决方法如下:
1.去掉启动窗口
在 ActivityStack.java中将SHOW_APP_STARTING_PREVIEW 设置为false 既可
修改启动窗口样式在 PhoneWindowManager中的addStartingWindow 方法中添加自定义样式或者背景等
/** {@inheritDoc} */@Overridepublic View addStartingWindow(IBinder appToken, String packageName, int theme,CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,int icon, int logo, int windowFlags, Configuration overrideConfig) {...//添加自定义背景View.setBackgroundColor(...); ...}
9. WindowManagerPolicy 简介
PhoneWindowManager 实现 的接口类如下:
alps\frameworks\base\core\java\android\view\WindowManagerPolicy.java
WindowManagerPolicy 接口实现
WindowManagerPolicy 是一个接口类,主要对外提供一些接口。
常用接口如下:
WindowState 接口
WindowMangerFuncs接口
Screen On 接口
Keyguard 接口
至此,本篇已结束,如有不对的地方,欢迎您的建议与指正。同时期待您的关注,感谢您的阅读,谢谢!
如有侵权,请联系小编,小编对此深感抱歉,届时小编会删除文章,立即停止侵权行为,请您多多包涵。
既然都看到这里,领两个红包在走吧!
以下两个红包每天都可以领取
1.支付宝搜索522398497,或扫码支付宝红包海报。
支付宝扫一扫,每天领取大红包
2.微信红包,微信扫一扫即可领取红包
微信扫一扫,每天领取微信红包
小礼物走一走,来简书关注我