Android R中虚拟按键的详细设计与实现 虚拟按键被Google设计在SystemUi当中,他的入口是NavigationBarFragment.java类。在Google的设计中,虚拟按键并不是在驱动测直接上报,而是利用触屏事件转化为按键事件,然后将这些事件当成实体按键去处理。这种设计用很小的性能、功耗换功能的设计是存在一定的缺陷的。 整个虚拟按键的原理描述如下:在systemUI加载中向系统中添加了虚拟按键的窗口,在窗口中加载了对应的按钮布局。当用手指点击HOME键区域时,系统会将触屏事件,通过驱动写入的dev/input下。系统通过InputReader和InputDispathcer对触屏事件进行分发。直到事件分发给对应的KeyButtonView的onTouchEvent中,在该方法中利用IMS调用InputDispathcer的inject的注入事件的接口,将HOME、BACK、菜单等按键,注入到系统中,进行二次分发,将具体事件进行消费。 整个虚拟按键的创建和添加是从base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java开始的,在这个类中最终调用了createNavigationBar方法。 在base/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java的createNavigationBar方法中创建了NavigationBarFramgment如下图所示: 这是一个继承自LifecycleFragment的Fragment。在这里面定义了手势和虚拟按键的模式。以及虚拟按键背景透明度变化等情况,base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java。如下图: 从上面我们可以看出调用了create,接下来看看create的实现,该方法中主要创建了NavigationBarFragment对象,并将该对象通过addView添加到了WMS当中。 此处值得注意的是通过事务将该对象替换了navigation_bar_frame。我们看看这个ID在那块定义的,该id实在base/packages/SystemUI/res/layout/navigation_bar_window.xml中定义,具体如下: 此处仔细的人可以发现,并不是系统自带的空军,也就是说自定义空军NavigationBarFrame中做了逻辑处理,我们详细的看下base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFrame.java这个类。该类中没有很多逻辑但重写了dispatchTouchEvent方法,如下图: 看了这个自定义View我们发现并没有我们想要的。那么我们需要回过头看看NavigationBarFragment中,在他的生命周期中做了什么呢? 在NavigationBarFragment的onCreate方法中我看到获取了需要的对象。 了解fragment的同学应该清楚,Fragment的View创建实在其生命周期的onCreateView当中,因此我们需要关注这块逻辑。在此处加载了base/packages/SystemUI/res/layout/navigation_bar.xml布局。该布局中只放了一个NavigationBarView布局和NavigationBarInflaterView自定义控件。这个自定义View也是我们关注的对象。 NavgationBarView是一个继承自Framlayout的布局对象,我们先不关注具体实现,后面详细分析。很显然这个容器对象不是我们想要的,那么先看一下里面的控件NavigationBarInflaterView. NavigationBarInflaterView也是继承在FrameLayout的一个容器对象。我们简单看一下其构造方法,看看有没有我们想要的。他的功能是什么? 在构造方法中创建了布局实例化LayoutInflater对象,这个对象大家应该都很清楚他的作用,是专门用来加载布局的。 并加载了R.layout.navigation_layout和R.layout.navigation_layout_vertical两个布局。 重点来了,在inflateLayout方法中加载了配置文件base/packages/SystemUI/res/values/config.xml,此处获取到了对应的字符串,在getDefaultLayout中。 下面是加载的三个字符串,在上面对这些字符串进行split,然后解析出了每个按键的间距和按钮名称。 后面调用了inflateButtons方法, 并将createView创建的view添加到父布局中。 createView中是真正加载按键的地方。如下图 同时将 在这里当解析出的字符串是HOME、BACK等按键时加载了不同的布局文件。具体如下: base/packages/SystemUI/res/layout/back.xml base/packages/SystemUI/res/layout/home.xml base/packages/SystemUI/res/layout/contextual.xml 以上三个布局文件中都是一个自定义View,KeyButtonView,另外还有一个共同的属性systemui:keyCode。在这个属性中定义了按键键值。通过修改这块可以改变虚拟按键的返回键和菜单键的位置。当然也可以通过修改config配置文件字符串等方法。 我们来看下KeyButtonView这个自定义控件。base/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java是一个ImageView的子类。 构造方法都是重载的我们只看参数最多的构造方法,在该方法中获取了在xml中配置的键值。 查看这个类我们还可以发现,这个类没有重写onClick点击事件。但是重写了onTouchEvent方法 重点看下对触摸事件的处理 可以看出在ACTION_DOWN在按下的时候注入了对应的ACTION_DOWN事件。 同样的在ACTION_CANCEL和ACTION_UP的时候注入了对应的ACTION_UP事件,注意此处没有注入ACTION_CANCEL。到这里整个虚拟按键的添加流程就清楚了。我们看一下sendEvent 最后通过InputManagerService注入了实体按键。 因此,虚拟按键的设计是依赖触屏事件的,同样的也依赖上层View长安和短按事件。从某种意义上这种设计是存在缺陷的。具体表现在一下两点: 1、触屏出现问题或者View出现问题会导致没办法响应 2、由于IMS的注入会浪费系统资源,进行二次分发会造成功耗浪费。 |