当前位置: 首页 > 面试经验 >

安卓系统面经_安卓面经(14/20)输入系统

优质
小牛编辑
66浏览
2024-08-04

安卓系统面经_安卓面经(14/20)输入系统

面试题预览

1. 请解释一下Android中的红外遥控的配置流程是怎样的?⭐⭐⭐⭐⭐

2. 请解释一下Android中的蓝牙遥控的配置流程是怎样的?⭐⭐⭐⭐⭐

3. 请解释一下Android中的按键处理流程是怎样的?⭐⭐⭐⭐

4. 请解释一下Android中的输入法切换是如何实现的?⭐⭐⭐

5. 请解释一下Android中的输入法界面定制是如何实现的?⭐⭐⭐⭐

1 配置红外遥控

1.1 说明

1) 红外遥控主要有2种,按调制方式分为PWM和PPM,分别对应NEC和PHILIPS的RC-5、RC-6、RC-7等协议 2) 红外遥控信号传递的关键节点:红外码----linux scancode----android keycode

1.2 添加步骤

基于RK3399 9.0平台配置

1.2.1 配置红外码值

1) 修改dts,添加遥控配置。可配置多个遥控,每组遥控分别取名为ir_key1/ir_key2等。以ir_key5为例,然后分别填写其头码、红外码值以及对应的linux键值名

ir_key5 { rockchip,usercode = <0x4cb3>;
 rockchip,key_table =
     <0x23    KEY_POWER>,
     <0x72    KEY_VOLUMEUP>,
     <0x7E    KEY_ZOOM_MINUS>,
     <0x63    KEY_ZOOM_PLUS>,
     <0x7f    KEY_VOLUMEDOWN>,
     <0x35    KEY_UP>,
     <0x66    KEY_LEFT>,
     <0x31    KEY_ENTER>,

1.2.2 配置Linux keycode

1. 修改/kernel/include/uapi/linux/input-event-codes.h,该文件定义了对应的linux层键值

#define KEY_MUTE 113 
#define KEY_VOLUMEDOWN 114 
#define KEY_VOLUMEUP 115 
#define KEY_POWER 116 

2. 修改kl,该文件定义了linux和android层之间键值映射的关系,如上0x2f1即等于十进制的753

key 113 VOLUME_MUTE 
key 114 VOLUME_DOWN 
key 115 VOLUME_UP 
key 116 DIR_POWER 

 

3. kl文件的名称由该输入设备名称来决定。可输入“dumpsys input”命令来查看,如下图所示。

我们通过getevent可以知道遥控器的event编号为0,根据Path字段来映射,发现其设备名称 为ff420030.pwm,则其会优先解析使用ff420030_pwm.kl;若该文件不存在或文件内容不正 确,则会使用Vendor, Product,Version组合而成的kl文件(如Vendor_005d_Product_0001.kl);若仍旧不存在,则会使用系统自带的Generic.kl文件

1.2.3-配置android keycode

修改/frameworks/base/core/java/android/view/KeyEvent.java,最终Android上层即可接收到该值的 按键

public static final int KEYCODE_ZOOM_PLUS = 295;

public static final int KEYCODE_ZOOM_MINUS = 296;

public static final int KEYCODE_MIC_CTL = 297;

public static final int KEYCODE_AI = 298;

public static final int KEYCODE_ENDCALL = 299;

public static final int KEYCODE_CALL = 300;

public static final int KEYCODE_DIR_POWER = 5000;

1.2.4-其他

当更改了framwork层的api或是按键键值后,都需要更新到此文件

1.frameworks/base/api/current.txt

field public static final int KEYCODE_AI = 298; // 0x12a 
field public static final int KEYCODE_CALL = 300; // 0x12c 
field public static final int KEYCODE_ENDCALL = 299; // 0x12b 
field public static final int KEYCODE_MIC_CTL = 297; // 0x129 
field public static final int KEYCODE_ZOOM_MINUS = 296; // 0x128 
field public static final int KEYCODE_ZOOM_PLUS = 295; // 0x127 
field public static final int KEYCODE_DIR_POWER = 5000; 

2. frameworks/base/core/res/res/values/attrs.xml

3. frameworks/native/include/android/keycodes.h

AKEYCODE_ZOOM_PLUS = 295, 
AKEYCODE_ZOOM_MINUS = 296, 
AKEYCODE_MIC_CTL = 297, 
AKEYCODE_AI = 298, 
AKEYCODE_ENDCALL = 299, 
AKEYCODE_CALL = 300, 
AKEYCODE_DIR_POWER = 5000,

4. frameworks/native/include/input/InputEventLabels.h

DEFINE_KEYCODE(MAXHUB_POWER), 
DEFINE_KEYCODE(MAXHUB_MINI_ZOOM_PLUS), 
DEFINE_KEYCODE(MAXHUB_MINI_ZOOM_MINUS), 
DEFINE_KEYCODE(MAXHUB_MINI_MIC_CTL), 
DEFINE_KEYCODE(MAXHUB_MINI_AI), 
DEFINE_KEYCODE(MAXHUB_MINI_ENDCALL), 
DEFINE_KEYCODE(MAXHUB_MINI_CALL), 
DEFINE_KEYCODE(SEEWO_POWER), 

1.3 调试方法

1.3.1-确认IR驱动安装成功

开机后,查看串口打印,有如下内容即安装成功

[ 1.729169] input: ff420030.pwm as /devices/platform/ff420030.pwm/input/input0

1.3.2-确认红外码值识别准确

1.3.2.1. 串口命令查看红外码值(不要关闭kernel打印,否则看不到)
#使能码值打印 
echo 1 > /sys/module/rockchip_pwm_remotectl/parameters/code_print 
<enum name="KEYCODE_ZOOM_PLUS" value="295" /> 
<enum name="KEYCODE_ZOOM_MINUS" value="296" /> 
<enum name="KEYCODE_MIC_CTL" value="297" /> 
<enum name="KEYCODE_AI" value="298" /> 
<enum name="KEYCODE_ENDCALL" value="299" /> 
<enum name="KEYCODE_CALL" value="300" /> 
<enum name="KEYCODE_MAXHUB_RECOVERY" value="295" /> 
<enum name="KEYCODE_DIR_POWER" value="5000" />#按键后即有打印 
130|console:/ # [ 296.206852] USERCODE=0x4cb3 
[ 296.233841] RMC_GETDATA=23 

1.3.2.2. 查看板卡中的kl文件,防止键值映射错误或配置错遥控
-rw-r--r-- 1 root root 1994 2020-12-07 11:32 Vendor_22b8_Product_093d.kl 
-rw-r--r-- 1 root root 968 2020-12-07 11:32 Vendor_2378_Product_1008.kl 
-rw-r--r-- 1 root root 955 2020-12-07 11:32 Vendor_2378_Product_100a.kl 
-rw-r--r-- 1 root root 4800 2020-12-07 11:32 ff420030_pwm.kl 
-rw-r--r-- 1 root root 796 2020-12-07 11:32 ff680030_pwm.kl 
-rw-r--r-- 1 root root 2410 2020-12-07 11:32 qwerty.kl 
-rw-r--r-- 1 root root 176 2020-12-07 11:32 rk29-keypad.kl 
console:/system/usr/keylayout # 

1.3.2.3. 确认连续码是否可用。RK的红外遥控解码使用的是软解,且一些关键波形的周期卡的很严,适配一 些遥控时,很有可能在连续码上会出现异常。此时可放开驱动打印来判别是哪个周期除出了问题来调试

#使能关键波形周期的打印

echo 1 > sys/module/rockchip_pwm_remotectl/parameters/dbg_level

1. 示例:适配某款蓝牙红外一体遥控,发现连续码不能识别;放开打印,发现

RK_PWM_TIME_SEQ1_MAX阈值太小导致

 

1.3.2.4.确认长按是否生效

#logcat -c

#logcat | grep -i keycode

看到repeatCount

在长按的时候有在不断累加即为正常

 

1.3.3-确认linux keycode正确

串口输入命令,查看对应的event事件,中间的数值即为linux keycode,最后的数值1/0分别表示按下/弹起

console:/system/usr/keylayout # getevent 
add device 1: /dev/input/event8 
name: "cvte_touchscreen" 
add device 2: /dev/input/event7 
name: "cvte_mouse" 
add device 3: /dev/input/event6 
name: "cvte_keyboard" 
add device 4: /dev/input/event5 
name: "TC02B_4MIC_3fd2_V0.0.0.0017 Built-in Audio" 
add device 5: /dev/input/event4 
name: "ILITEK Touch Device,21.5-10P" 
add device 6: /dev/input/event1 
name: "rk29-keypad" 
add device 7: /dev/input/event3 
name: "gyro sensor" 
add device 8: /dev/input/event0 
name: "ff420030.pwm" 
add device 9: /dev/input/event2 
name: "accel sensor" 
/dev/input/event0: 0001 0074 00000001 
/dev/input/event0: 0000 0000 00000000 
/dev/input/event0: 0001 0074 00000000 
/dev/input/event0: 0000 0000 00000000

1.3.4-确认android keycode正确

1. 使用logcat抓取打印 
12-07 11:43:03.091 521 622 D WindowManager: interceptKeyTq keycode=5000 
interactive=true keyguardActive=false policyFlags=22000000 
2. 由于android framwork最先处理键值的是interceptKeyBeforeQueueing函数,其内有如下打印 
//PhoneWindowManager.java 
if (DEBUG_INPUT) { 
Log.d(TAG, "interceptKeyTq keycode=" + keyCode + " interactive=" + interactive + 
" keyguardActive=" + keyguardActive + " policyFlags=" + 
Integer.toHexString(policyFlags)); 
} 

1.3.5-确认应用收到键值

1. 如果framwork中没有拦截处理,一般会抛给上层应用来处理,抓取应用本身的打印即可 
2. 也可以模拟发送keycode来测试app 
input keyevent 3 //HOME 
input keyevent 4 //BACK 
input keyevent 23 //DPAD_CENTER 
input keyevent 66 //ENTER 

1.4 NEC红外遥控协议的知识

NEC连续码

针对红外码,一般企标要求是精度做到±20%,可以对比标准和波形来实际调试

 

1.5 注意事项

1. 一般不要直接修改linux层键值,有些系统的原生键值会有特殊用处,不能修改(例如3399平台只

能识别原生linuxpower键值来遥控开机)

2. https://www.cnblogs.com/klb561/p/11029446.html

2 配置蓝牙遥控

2.1 蓝牙遥控器的原理

蓝牙遥控器的原理如下图

从流程上我们可以发现,我们遥控器的码值被转化了几次: HID码值--→Linux event--→ 根据Vendor、Product转化为Android事件。 

2.2 蓝牙遥控按键知识

将蓝牙遥控与Android设备机子连接后,可以通过getevent 命令获取按键输入事件:

 

从上图可以知道

●  名字:蓝牙遥控器的名字为 BT_Smart_RC001

● 007004a:其中,高位为 usage page  (07代表普通健,0c 代码多媒体健);低位为健值

●  0066:表示down的值,这个后面需要在 kl 文件中,转换成十进制的值

2.3 修改已有按键值

例如:某个遥控,按下Back 建,HOME 建不起作用。

先使用 getevent 按下Back健,拿到event 的值

这种通用的按键,在KeyEvent 是有的,我们要做的,就是修改 kl 的值即可。

使用 dumpsys input 命令拿到 kl 的位置:

可以看到 kl 在 Android 系统映射的位置,修改里面的值,0x9e 转成十进制为 158,同理拿到HOME的值,修改如下

修改后重启就发现已经起作用了。

2.4添加新的按键值

例如某个遥控有个按键,键值为0X59,我们想把它定义为新的健,所以需要走一遍按键添加流程;

2.4.1 添加键值和上层映射

方法一:HID_UP_CONSUMER类型

去到 linux 映射表,hid-input.c ,位置在 kernel/drivers/hid/hid-input.c ,去到 HID_UP_CONSUMER 这个方法,把0x59 添加进去,并新增 KEY_SOURCE 这个字符串,后续给Android使用的

方法二:HID_UP_KEYBOARD类型

去到 linux 映射表,hid-input.c ,位置在 kernel/drivers/hid/hid-input.c ,去到 HID_UP_KEYBOARD这个方法。

可以见到里面keyboard类型有个表格映射的。

因此可以根据蓝牙键值的Usage ID去修改给到Keylayout的Linux ScanCode。例如Usage ID为0x96,那么对应到表格的第十行第七列(表格是十六进制形式的,每行从左到右以此是0~F,列也是),那么可以修改第十行第七列的值为你定义的值(我这里定义的是0x2ea),那么送到Keylayout中这个按键的Linux code就是0x2ea,然后在转成Android KeyEvent即可。

2.4.2 头文件中添加字符串

去到 common/include/uapi/linux/input-event-codes.h ,添加刚才的KEY_SOURCE,注意不要重复即可

 

2.4.3 增加Android KeyCode的定义

在framework 的 KeyCodes.h , InputEventLabels.h 和KeyEvent.java

去到 frameworks/native/include/android ,在 KeyCodes.h 增加刚才的SOURCE,注意不要重复即可

然后再去到 frameworks/native/include/input,修改 InputEventLabels.h,定义刚才的source

最后,再去到 frameworks/base/core/java/android/view , 修改给Android 上层使用的 KeyEvent.java 即可:

这里,我们的按键值,就从 linux 传到 KeyEvent.java 了。

2.4.4 PhoneWindowManager 添加按键功能

接下来就是处理自己的功能了,此时按键已经通过 onKeyEvent()拿到,如果你想全局处理,可以在 PhoneWindowManager 的 interceptKeyBeforeQueueing方法添加功能,如:

 

这样,新建一个按键就完成了

3 Android按键处理流程

上面介绍了怎么添加红外和蓝牙遥控,本节着重结合源码讲述Android的按键处理流程,下面以最常见的Power为例

3.1 简介

Android系统中,一般的按键都可以在应用中处理,但是,对于系统级别的按键上层应用是无法收到消息的,也就是说,你的APP是无法直接处理的。针对这种系统级的按键事件,都是在Event事件分发前处理。Event事件分发后,只有包含有Activity的APP才能处理事件;若APP无法处理,则需要在PhoneWindowManager中处理。

本文所讲的Power键则属于该种情况。即用户触发Power键,底层收到按键会回调InputMonitor的函数dispatchUnhandledKey()。

3.2 按键处理流程

看到上面的简介,是否会有这样的疑问。为何最终处理者是PhoneWindowManager?

通过上文可知最终事件的处理是由PhoneWindowManager完成的;那么,按键后,系统是如何传递到PhoneWindowManager?下面就从源码的角度分析一下该过程。

● WindowManagerService:Framework 最核心的服务之一,负责窗口管理。

● InputManagerService:输入管理服务。

上述两个服务与Power按键相关,但是两者是如何关联的,就要从它们的创建说起.我们都知道,Android系统中的核心进程是system_server,对应SystemServer类,在其run()方法中会启动一堆的service,当然包括上述两个服务。具体源码分析如下:

3.2.1 先创建inputManager,再创建WindowManagerService对象时,可发现作为参数引用了上述inputManager,且创建了PhoneWindowManager实例:

//源码路径:frameworks/base/services/java/com/android/server/SystemServer.javaprivate void startOtherServices() {
       ......
       inputManager = new InputManagerService(context);   //输入系统服务   【step_SystemServer_1】
       
       ......
    //【step_SystemServer_2】
       wm = WindowManagerService.main(context, inputManager,
                 mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
                 !mFirstBoot, mOnlyCore, new PhoneWindowManager());   //PhoneWindowManager实例
       ServiceManager.addService(Context.WINDOW_SERVICE, wm);
       ServiceManager.addService(Context.INPUT_SERVICE, inputManager);

}   
//源码路径:frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

private WindowManagerService(Context context, InputManagerService inputManager,
            boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore,
            WindowManagerPolicy policy) {
            ......

             mPolicy = policy;    //实例: PhoneWindowManager对象      【step_InputMonitor_2】       ......}

3.2.2 启动inputManager之前,设置了一个回调接口:

//消息分发之前回调--->查看InputManagerService      inputManager.setWindowManagerCallbacks(wm.getInputMonitor());        
inputManager.start();

3.2.3 InputMonitor.java底层收到按键会回调InputManagerService的dispatchUnhandledKey()--->InputMonitor的函数dispatchUnhandledKey()。具体由底层InputDispatcher.cpp调用。

//源码路径: frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java

  //【Power键属于系统级按键,因此处理方法是dispatchUnhandledKey】
    /* Provides an opportunity for the window manager policy to process a key that
     * the application did not handle. */
    @Override
    public KeyEvent dispatchUnhandledKey(
            InputWindowHandle focus, KeyEvent event, int policyFlags) {
        WindowState windowState = focus != null ? (WindowState) focus.windowState : null;
        
        //此处 mservice: WindowManagerService    【step_InputMonitor_0】
        return mService.mPolicy.dispatchUnhandledKey(windowState, event, policyFlags);
    }

由这三步可知,最终由PhoneWindowManager处理。将上述整理成时序图:

3.3 Power按键触发后的具体执行逻辑分析

列出几种常见的触发Power键的情况:

情况一:长按Power键

情况二:单独短按Power键

情况三:Power + 音量键(-)

以下也以这三种情况结合源码分析流程。

由上文可知,真正的处理逻辑在PhoneWindowManager类中,该类有两个方法:interceptKeyBeforeDispatching和interceptKeyBeforeQueueing,包括了几乎所有按键的处理。

●  interceptKeyBeforeDispatching:主要处理Home键、Menu键、Search键等。

● interceptKeyBeforeQueueing:主要处理音量键、电源键(Power键)、耳机键等。

3.3.1 当前Power键处理流程:

dispatchUnhandledKey()------>interceptFallback()---->interceptKeyBeforeQueueing()

下面从interceptKeyBeforeQueueing(KeyEvent event, int policyFlags)分析。而一个按键包含两个动作Down和UP,因此从这两个方面分析interceptKeyBeforeQueueing()的执行流程。

● 按下: interceptPowerKeyDown(KeyEvent event, boolean interactive)

● 释放: interceptPowerKeyUp(KeyEvent event, boolean interactive, boolean canceled)

参数含义:

interactive:是否亮屏

KeyEvent.FLAG_FALLBACK:不被应用处理的按键事件或一些在 键值映射中不被处理的事件(例:轨迹球事件等)。

根据操作按键时系统是否亮屏,代码执行的逻辑也不同,因此每个事件下分别从亮灭屏来分析,具体如下:

(下述代码的执行过程中有对一些变量的判断,而这些值都是系统配置的,在config.xml中,因此具体执行哪个流程以当前平台配置为准)

涉及到的配置信息的相关源码路径:
frameworks/base/core/java/android/view/ViewConfiguration.java
frameworks/base/core/res/res/values/config.xml

1、 按下(ACTION_DOWN):先上源码PhoneWindowManager.java中的interceptPowerKeyDown函数。

private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
         //FACE_UNLOCK_SUPPORT start
         Slog.i("FaceUnlockUtil", "interceptPowerKeyDown interactive = " + interactive);
         Settings.System.putInt(mContext.getContentResolver(), "faceunlock_start", 1);
         //FACE_UNLOCK_SUPPORT end
         // Hold a wake lock until the power key is released.
         if (!mPowerKeyWakeLock.isHeld()) {
             mPowerKeyWakeLock.acquire();          //获得唤醒锁
         }
 
         // Cancel multi-press detection timeout.
         if (mPowerKeyPressCounter != 0) {
             mHandler.removeMessages(MSG_POWER_DELAYED_PRESS);
         }
 
         // Detect user pressing the power button in panic when an application has
         // taken over the whole screen.
         boolean panic = mImmersiveModeConfirmation.onPowerKeyDown(interactive,
                 SystemClock.elapsedRealtime(), isImmersiveMode(mLastSystemUiFlags),
                 isNavBarEmpty(mLastSystemUiFlags));
         if (panic) {
             mHandler.post(mHiddenNavPanic);
         }
 
         // Latch power key state to detect screenshot chord.
         if (interactive && !mScreenshotChordPowerKeyTriggered
                 && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {   
             mScreenshotChordPowerKeyTriggered = true;                //标记按下power key,用于组合键截屏,具体参考下述3
             mScreenshotChordPowerKeyTime = event.getDownTime();
             interceptScreenshotChord();
         }
 
         // Stop ringing or end call if configured to do so when power is pressed.
         TelecomManager telecomManager = getTelecommService();
         boolean hungUp = false;
         if (telecomManager != null) {
             if (telecomManager.isRinging()) {
                 // Pressing Power while there\'s a ringing incoming
                 // call should silence the ringer.
                 telecomManager.silenceRinger();
             } else if ((mIncallPowerBehavior
                     & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0
                     && telecomManager.isInCall() && interactive) {
  
 类似资料: