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方法。 ![](https://i-blog.csdnimg.cn/blog_migrate/65451ea626d40f5a06519f2eff037de9.png) 在base/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java的createNavigationBar方法中创建了NavigationBarFramgment如下图所示: ![](https://i-blog.csdnimg.cn/blog_migrate/fac6ad51292d569d188e77adb2c6f232.png) ![](https://i-blog.csdnimg.cn/blog_migrate/a9f3f9a4dd6fe3db9c164c30c9494b7c.png) 这是一个继承自LifecycleFragment的Fragment。在这里面定义了手势和虚拟按键的模式。以及虚拟按键背景透明度变化等情况,base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java。如下图: ![](https://i-blog.csdnimg.cn/blog_migrate/3d2119011b1883ba0cdc8be48879260e.png) 从上面我们可以看出调用了create,接下来看看create的实现,该方法中主要创建了NavigationBarFragment对象,并将该对象通过addView添加到了WMS当中。 ![](https://i-blog.csdnimg.cn/blog_migrate/2a93f74019715a74cb21f760689f372c.png) ![](https://i-blog.csdnimg.cn/blog_migrate/ca2e08e3e4696e7d2af4cec179ed11d6.png) 此处值得注意的是通过事务将该对象替换了navigation_bar_frame。我们看看这个ID在那块定义的,该id实在base/packages/SystemUI/res/layout/navigation_bar_window.xml中定义,具体如下: ![](https://i-blog.csdnimg.cn/blog_migrate/c1283c67d7b11f9bf19e6c8cadcc4290.png) 此处仔细的人可以发现,并不是系统自带的空军,也就是说自定义空军NavigationBarFrame中做了逻辑处理,我们详细的看下base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFrame.java这个类。该类中没有很多逻辑但重写了dispatchTouchEvent方法,如下图: ![](https://i-blog.csdnimg.cn/blog_migrate/3d2119011b1883ba0cdc8be48879260e.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NoZW4zNjQ1Njc2Mjg=,size_16,color_FFFFFF,t_70) 看了这个自定义View我们发现并没有我们想要的。那么我们需要回过头看看NavigationBarFragment中,在他的生命周期中做了什么呢? 在NavigationBarFragment的onCreate方法中我看到获取了需要的对象。 ![](https://i-blog.csdnimg.cn/blog_migrate/ddb43173f0b908045784c6ace519e404.png) 了解fragment的同学应该清楚,Fragment的View创建实在其生命周期的onCreateView当中,因此我们需要关注这块逻辑。在此处加载了base/packages/SystemUI/res/layout/navigation_bar.xml布局。该布局中只放了一个NavigationBarView布局和NavigationBarInflaterView自定义控件。这个自定义View也是我们关注的对象。 ![](https://i-blog.csdnimg.cn/blog_migrate/41c8f06985cebba41ca520a10dcd761c.png) ![](https://i-blog.csdnimg.cn/blog_migrate/98c35552cad2b810cd6d2152237f0a19.png) NavgationBarView是一个继承自Framlayout的布局对象,我们先不关注具体实现,后面详细分析。很显然这个容器对象不是我们想要的,那么先看一下里面的控件NavigationBarInflaterView. ![](https://i-blog.csdnimg.cn/blog_migrate/43038408e47fb8ce381a78e8c869a4c6.png) NavigationBarInflaterView也是继承在FrameLayout的一个容器对象。我们简单看一下其构造方法,看看有没有我们想要的。他的功能是什么? ![](https://i-blog.csdnimg.cn/blog_migrate/c9f2eea4a248e12e57ba376f8b423a6d.png) 在构造方法中创建了布局实例化LayoutInflater对象,这个对象大家应该都很清楚他的作用,是专门用来加载布局的。 ![](https://i-blog.csdnimg.cn/blog_migrate/c9f2eea4a248e12e57ba376f8b423a6d.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NoZW4zNjQ1Njc2Mjg=,size_16,color_FFFFFF,t_70) 并加载了R.layout.navigation_layout和R.layout.navigation_layout_vertical两个布局。 ![](https://i-blog.csdnimg.cn/blog_migrate/fac6ad51292d569d188e77adb2c6f232.png) 重点来了,在inflateLayout方法中加载了配置文件base/packages/SystemUI/res/values/config.xml,此处获取到了对应的字符串,在getDefaultLayout中。 ![](https://i-blog.csdnimg.cn/blog_migrate/ca2e08e3e4696e7d2af4cec179ed11d6.png) 下面是加载的三个字符串,在上面对这些字符串进行split,然后解析出了每个按键的间距和按钮名称。 ![](https://i-blog.csdnimg.cn/blog_migrate/c44c6f58a9e8e1a43c5d0cb4a35e1bf9.png) ![](https://i-blog.csdnimg.cn/blog_migrate/59c433f65a6a029535302ac970797145.png) 后面调用了inflateButtons方法, ![](https://i-blog.csdnimg.cn/blog_migrate/fe558b26933720de7e51b2c9123a13cf.png) 并将createView创建的view添加到父布局中。 ![](https://i-blog.csdnimg.cn/blog_migrate/e0490d1fab549c5b2bc9dd1997a00436.png) createView中是真正加载按键的地方。如下图 ![](https://i-blog.csdnimg.cn/blog_migrate/d52878f224c75d39398c817332ad93c3.png) 同时将 在这里当解析出的字符串是HOME、BACK等按键时加载了不同的布局文件。具体如下: base/packages/SystemUI/res/layout/back.xml ![](https://i-blog.csdnimg.cn/blog_migrate/1f3b0f4f8f465bb15134b5bd31c07df1.png) base/packages/SystemUI/res/layout/home.xml ![](https://i-blog.csdnimg.cn/blog_migrate/40f3dcde5660318d4fdd735b2360afbf.png) base/packages/SystemUI/res/layout/contextual.xml ![](https://i-blog.csdnimg.cn/blog_migrate/2a48ac3f5cb8883095e88958a29e472e.png) 以上三个布局文件中都是一个自定义View,KeyButtonView,另外还有一个共同的属性systemui:keyCode。在这个属性中定义了按键键值。通过修改这块可以改变虚拟按键的返回键和菜单键的位置。当然也可以通过修改config配置文件字符串等方法。 我们来看下KeyButtonView这个自定义控件。base/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java是一个ImageView的子类。 ![](https://i-blog.csdnimg.cn/blog_migrate/57f5127ef3158f3caaf197313641f7dd.png) 构造方法都是重载的我们只看参数最多的构造方法,在该方法中获取了在xml中配置的键值。 ![](https://i-blog.csdnimg.cn/blog_migrate/71a905ce0f9bef76d729c7d33e24bf4a.png) 查看这个类我们还可以发现,这个类没有重写onClick点击事件。但是重写了onTouchEvent方法 ![](https://i-blog.csdnimg.cn/blog_migrate/6883f01e0184e9c8ed3129aaba1bf39c.png) 重点看下对触摸事件的处理 ![](https://i-blog.csdnimg.cn/blog_migrate/23b7727f6dff3b98391c3cdb255d975e.png) 可以看出在ACTION_DOWN在按下的时候注入了对应的ACTION_DOWN事件。 ![](https://i-blog.csdnimg.cn/blog_migrate/df036bb4bece07abf778a33464934c52.png) 同样的在ACTION_CANCEL和ACTION_UP的时候注入了对应的ACTION_UP事件,注意此处没有注入ACTION_CANCEL。到这里整个虚拟按键的添加流程就清楚了。我们看一下sendEvent ![](https://i-blog.csdnimg.cn/blog_migrate/df036bb4bece07abf778a33464934c52.png) ![](https://i-blog.csdnimg.cn/blog_migrate/f107335f555b555736295ae110bd4d19.png) 最后通过InputManagerService注入了实体按键。 因此,虚拟按键的设计是依赖触屏事件的,同样的也依赖上层View长安和短按事件。从某种意义上这种设计是存在缺陷的。具体表现在一下两点: 1、触屏出现问题或者View出现问题会导致没办法响应 2、由于IMS的注入会浪费系统资源,进行二次分发会造成功耗浪费。 |