背景:
针对上一篇文章给各位vip学员布置一个input相关的常见需求作业:
重学Framework Input模块:如何实现按键一键启动Activity-学员作业
这个需求也像学员朋友群里的说的,属于做机顶盒相关的开发的常见需求,所以整体的作业需求的难度算一般,既然一般难度的需求,那么更需求各位学员朋友可以进行实战实现啦。
GlobalKeyManager基础理论知识部分:
GlobalKeyManager涉及代码部分剖析:
frameworks/base/services/core/java/com/android/server/policy/GlobalKeyManager.java
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
frameworks/base/core/res/res/xml/global_keys.xml
各自的职责如下:
global_keys.xml
–主要负责定义好要处理的global keycode,而且这个key按下后会触发发送哪个广播的定义
GlobalKeyManager.java
–主要负责解析global_keys.xml中定义的keycode和对应的broadcast的component,方便后续处理时候有重要的依据
PhoneWindowManager.java
–这里面主要在interceptKeyBeforeDispatching方法中会调用GlobalKeyManager的handle方法会处理相关的按键事件
1、global_keys.xml定义好按键和启动的广播组件
frameworks/base/core/res/res/xml/global_keys.xml
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
** Copyright 2013, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<!-- Mapping of keycodes to components which will be handled globally.
Modify this file to add global keys.
A global key will NOT go to the foreground application and instead only ever be sent via targeted
broadcast to the specified component. The action of the intent will be
android.intent.action.GLOBAL_BUTTON and the KeyEvent will be included in the intent as
android.intent.extra.KEY_EVENT.
-->
<global_keys version="1">
<!-- Example format: keyCode = keycode to handle globally. component = component which will handle this key. -->
<!-- <key keyCode="KEYCODE_VOLUME_UP" component="com.android.example.keys/.VolumeKeyHandler" /> -->
<key keyCode="KEYCODE_VOLUME_UP" component="com.example.remotesubmix/.MyKeyReceiver" />
</global_keys>
2、解析这个xml变成mKeyMapping
private void loadGlobalKeys(Context context) {
try (XmlResourceParser parser = context.getResources().getXml(
com.android.internal.R.xml.global_keys)) {
XmlUtils.beginDocument(parser, TAG_GLOBAL_KEYS);
int version = parser.getAttributeIntValue(null, ATTR_VERSION, 0);
if (GLOBAL_KEY_FILE_VERSION == version) {
while (true) {
XmlUtils.nextElement(parser);
String element = parser.getName();
if (element == null) {
break;
}
if (TAG_KEY.equals(element)) {
String keyCodeName = parser.getAttributeValue(null, ATTR_KEY_CODE);
String componentName = parser.getAttributeValue(null, ATTR_COMPONENT);
String dispatchWhenNonInteractive =
parser.getAttributeValue(null, ATTR_DISPATCH_WHEN_NON_INTERACTIVE);
if (keyCodeName == null || componentName == null) {
Log.wtf(TAG, "Failed to parse global keys entry: " + parser.getText());
continue;
}
int keyCode = KeyEvent.keyCodeFromString(keyCodeName);
if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
mKeyMapping.put(keyCode, new GlobalKeyAction(
componentName, dispatchWhenNonInteractive));
} else {
Log.wtf(TAG, "Global keys entry does not map to a valid key code: "
+ keyCodeName);
}
}
}
}
} catch (Resources.NotFoundException e) {
Log.wtf(TAG, "global keys file not found", e);
} catch (XmlPullParserException e) {
Log.wtf(TAG, "XML parser exception reading global keys file", e);
} catch (IOException e) {
Log.e(TAG, "I/O exception reading global keys file", e);
}
}
这里完全可以通过dumpsys相关命令检测是否xml配置成功
使用命令如下:
dumpsys window
输出结果如下:
可以看到有我们mKeyMapping相关的数据
3、在按键触发后,会PhoneWindowManager的interceptKeyBeforeDispatching方法处理
处理的方法是handleGlobalKey
boolean handleGlobalKey(Context context, int keyCode, KeyEvent event) {
if (mKeyMapping.size() > 0) {
GlobalKeyAction action = mKeyMapping.get(keyCode);
if (action != null) {
final Intent intent = new GlobalKeyIntent(action.mComponentName, event,
mBeganFromNonInteractive).getIntent();
context.sendBroadcastAsUser(intent, UserHandle.CURRENT, null);
if (event.getAction() == KeyEvent.ACTION_UP) {
mBeganFromNonInteractive = false;
}
return true;
}
}
return false;
}
handleGlobalKey调用堆栈如下:
handleGlobalKey:80, GlobalKeyManager (com.android.server.policy)
interceptSystemKeysAndShortcuts:3813, PhoneWindowManager (com.android.server.policy)
interceptKeyBeforeDispatching:3356, PhoneWindowManager (com.android.server.policy)
interceptKeyBeforeDispatching:183, InputManagerCallback (com.android.server.wm)
interceptKeyBeforeDispatching:2417, InputManagerService (com.android.server.input)
总结图如下:
有了以上的理论基础,那再来实战实现就简单多了
实战及调试:
步骤1:
global_keys.xml中加入keycode及会触发的Broadcast组件ComponentName
diff --git a/core/res/res/xml/global_keys.xml b/core/res/res/xml/global_keys.xml
index 8fa6902b0e4c..992c63086129 100644
--- a/core/res/res/xml/global_keys.xml
+++ b/core/res/res/xml/global_keys.xml
@@ -28,4 +28,6 @@
<global_keys version="1">
<!-- Example format: keyCode = keycode to handle globally. component = component which will handle this key. -->
<!-- <key keyCode="KEYCODE_VOLUME_UP" component="com.android.example.keys/.VolumeKeyHandler" /> -->
+ <key keyCode="KEYCODE_VOLUME_UP" component="com.example.remotesubmix/.MyKeyReceiver" />
+
</global_keys>
这里为了测试方便,使用KEYCODE_VOLUME_UP来模拟,而且这里的global_key.xml大家也可以采用overlay方式集成这样减少耦合。
步骤2:
编写对应广播的实现,而且在收到广播后启动对应的Activity
MyKeyReceiver.java
public class MyKeyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
android.util.Log.i("lsm666888","MyKeyReceiver intent " + intent);
Intent intent1 = new Intent(context,MyKeyActivity.class);
intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent1);
}
}
而且在onReceive方法中需要对Activity进行启动,大家注意这里需要添加FLAG_ACTIVITY_NEW_TASK。
MyKeyActivity.java
package com.example.remotesubmix;
import android.app.Activity;
import android.os.Bundle;
import androidx.annotation.Nullable;
public class MyKeyActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mykey);
}
}
AndroidManifest.xml
<activity
android:name=".MyKeyActivity"
android:label="@string/app_name" android:exported="true"
android:theme="@style/AppTheme.NoActionBar">
</activity>
<receiver android:name=".MyKeyReceiver"
android:exported="true"
>
<intent-filter>
<action android:name="android.intent.action.GLOBAL_BUTTON"/>
</intent-filter>
</receiver>
实战测试验证:
上面主要对系统进行整编,然后安装apk(注意不要点击启动这个app),点击音量上按键,但是发现没有任何的反应,只有log输出,并没有Activity的跳转。
07-19 23:20:36.005 2517 2517 I lsm666888: MyKeyReceiver intent Intent { act=android.intent.action.GLOBAL_BUTTON flg=0x10000010 cmp=com.example.remotesubmix/.MyKeyReceiver (has extras) }
07-19 23:20:36.009 546 1110 E ActivityTaskManager: Background activity launch blocked! [callingPackage: com.example.remotesubmix; callingPackageTargetSdk: 31; callingUid: 10153; callingPid: 2517; appSwitchState: 2; callingUidHasAnyVisibleWindow: false; callingUidProcState: RECEIVER; isCallingUidPersistentSystemProcess: false; forcedBalByPiSender: BSP.NONE; intent: Intent { flg=0x10000000 cmp=com.example.remotesubmix/.MyKeyActivity }; callerApp: ProcessRecord{7de681b 2517:com.example.remotesubmix/u0a153}; inVisibleTask: false; balAllowedByPiCreator: BSP.ALLOW_BAL; balAllowedByPiCreatorWithHardening: BSP.ALLOW_BAL; resultIfPiCreatorAllowsBal: BAL_BLOCK; hasRealCaller: true; isCallForResult: false; isPendingIntent: false; autoOptInReason: notPendingIntent; realCallingPackage: com.example.remotesubmix; realCallingPackageTargetSdk: 31; realCallingUid: 10153; realCallingPid: 2517; realCallingUidHasAnyVisibleWindow: false; realCallingUidProcState: RECEIVER; isRealCallingUidPersistentSystemProcess: false; originatingPendingIntent: null; realCallerApp: ProcessRecord{7de681b 2517:com.example.remotesubmix/u0a153}; realInVisibleTask: false; balAllowedByPiSender: BSP.ALLOW_BAL; resultIfPiSenderAllowsBal: BAL_BLOCK; balImproveRealCallerVisibilityCheck: true; balRequireOptInByPendingIntentCreator: true; balRequireOptInSameUid: false; balRespectAppSwitchStateWhenCheckBoundByForegroundUid: true; balDontBringExistingBackgroundTaskStackToFg: true]
07-19 23:20:36.013 546 1110 I ActivityTaskManager: START u0 {flg=0x10000000 cmp=com.example.remotesubmix/.MyKeyActivity} with LAUNCH_MULTIPLE from uid 10153 (BAL_BLOCK) result code=102
可以看到其实是Activity启动报错了,报错原因如下:
Background activity launch blocked!
简单说高版本aosp是不允许有后台广播中直接启动Activity,这种情况下没有任何的app的可见窗口显示,故被系统拦截直接在广播中启动Activity。
那么这个问题该如何解决呢?
相关答案会在Vip群中进行讨论解决哈,各位vip学员可以先自己尝试,vip群里积极讨论,敬请期待解决方法。
更多fw实战干货,请关注下面“千里马学框架”