Input模块作业成果分享:手把手带你实现按键一键启动Activity

背景:

针对上一篇文章给各位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实战干货,请关注下面“千里马学框架”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千里马学框架

帮助你了,就请我喝杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值