Android 4.0中按键的处理流程

按键在Android系统中,有着不同的代表意义。以前的全键盘的手机代码没有阅读过,所以也不是很了解。本人介绍的是在触摸屏的手机上的按键消息的处理流程。

在现在触摸屏成为主流的输入设备的情况下,很多厂商都在努力的做到取消物理按键的工作,但是目前就本人的学习情况来看,完全取消在目前看来还是不是那么现实。

有如下几点原因:

首先,本人说明的是目前原生的Android系统上。

其次,Android系统为了节省电量,在电源管理的过程中设置了休眠的方式。而休眠的时候触摸屏同样进入休眠状态。因此,不能够接收到用户的输入消息。

再次,目前的物理按键(主要指power,volume。home)是通过电源管理芯片进行控制的。触摸屏不是。

因此,如果没有现在的物理按键的情况下,如果想把设备从休眠的状态下唤醒基本上来讲是不可能的。

下面来正式的记录下本人在学习的过程中记录的点点滴滴。

首先,简要的介绍一下按键的处理流程。先简单的分为两大类:一类是虚拟按键。另一类是物理按键。

无论是虚拟按键还是物理按键都是要经过驱动层注册为输入设备,然后上报到kernel/drivers/input/input.c中。这里有相关函数的定义。然后通过、sys上报到frameworks/services/input/EventHub.cpp中,在这里会对设备进行扫描并且判断是哪种设备,然后在InputReader.cpp中对原始数据进行读取。在framewoks/services/input/InputDispatcher.cpp中实现数据的派发。在framework/base/core/jni/android_view_KeyEvent.cpp中实现通过JNI机制向上层的KeyEvent.java提供数据。并且在frameworks/base/core/java/android/view/KeyEvent.java中向上层的APP开发人员提供接口。

当然,虚拟键盘中有一个映射关系,键盘的按键值也会上报给上面的应用层,而对于物理按键往往是在frameworks层就被截取并且加以处理了。

普通的按键事件在Android系统中的调用流程(本人不太会处理visio绘图的保存问题)大致如下:


下图是人家goole的图 是比俺画的好啊……


但是对于物理按键的处理流程,目前主要查阅的代码的结果是在PhoneWindowManager.java中进行截获并处理的。

 

Android 4.0按键事件以及电源管理流程分析

Android是集成了linux内核以及frameworks层的东西而形成为os,其中主要包含了三种语言的编程,主要是c、c++以及java。因此他们之间的通信问题就显得尤为突出。

JAVA与c的通讯主要是通过JNI机制进行的。为了提高效率,在上层都使用java进行编程。因此在阅读源代码的过程中,就需要区分给用户使用的文件,系统内部使用的文件,以及与驱动打交道的文件。

Android获取系统消息概述

1、获取原始的用户消息,包括按键、触摸屏、鼠标、轨迹球等各种输入设备的消息。

2、对原始消息进行一定的加工,使之转化为程序可以理解的消息。比如所有的按键消息都包括“按下、弹起”等原始消息,而对程序来讲可能只关心该按键被“按了一次”或者“长按”,因此需要把原始消息转换为程序可以理解的消息。

3、把转换后的消息发送到相应的用户窗口所在的进程。如果获取线程和用户线程同在一个进程空间中,则传递消息比较简单,但对于多进程系统来讲,消息获取线程和用户线程往往在不同的进程空间中,因此需要使用IPC机制把消息传递到用户窗口所在的线程中。

在接下来的几篇博文中将陆续写如下内容:

WindowManagerService处理消息的时机

上报和分发的消息的处理流程

AIDL简介

 

WindowManagerService处理消息的时机

目前对于用户的输入消息分析的文章大都是划分为两种类型,一种是key消息,另一种是motion消息。

        对于motion消息,Android原生系统中对其处理都是直接上报的。WindowManagerService没有对其做过多的处理。而对于key消息,则会首先回调WmS中的Key消息处理函数,在WindowManagerService中不处理该消息时才把消息发往客户窗口中。在一般情况下,WindowManagerService中仅处理一些系统Key消息,比如“HOME”键、照相按键、声音按键等。

        在WindowManagerService中注册服务通道时,调用了Java环境中的InputManager对象mInputManager,而该类的构造函数中创建了一个callbacks变量,然后再nativeInit(mCallbacks)进行了初始化。变量mCallbacks作为初始化的参数传递到native环境中的InputManager对象中,从而使得在InputDispatcher进行回调时首先回调到Java环境中的InputManager.callbacks子类中。比如Callbacks中包含了interceptKeyBeforeDispatching()函数,当InputDispatcher()接收到Key消息时,首先回调该函数,而该函数的内部代码中,又调用了WindowManagerService对象中的InputMonitor的同名函数。

 

AIDL简介

通常每个应用程序都在他自己的进程内运行,但有时需要在进程之间传递对象(IPC通信)。此时可以通过应用程序UI的方式写一个运行在不同进程中的service。在Android平台中,一个进程通常不能访问其他进程中的内存区域。所以它们需要把对象拆分成操作系统能理解的简单形式,以便伪装成对象跨边界访问。而要完成这些需要AIDL机制。

        AIDL(Android接口描述语言)是一个IDL语言,它可以生成一段代码,可以是一个在Android设备上运行的两个进程使用内部通信进程进行交互。如果你想在一个进程中(例如在一个Activity中)访问另一个进程中(例如service)某个对象的方法,你就可以使用AIDL来生成这样的代码来伪装传递各种参数。

        要使用AIDL,service需要以AIDL文件的方式提供服务接口,AIDL工具将生成一个相应的java接口,并且在生成的服务器接口中包含一个功能调用的stub()服务桩类。service的onBind方法会返回实现类的对象,之后你就可以使用它了。

        AIDL文件

        framework中包含的aidl是在frameworks/base/Android.mk中定义的。该文件定义了两处aidl文件列表。

        第一处是给LOCAL_SRC_FILES变量中使用 “+=” 进行赋值,该变量将包含在framework.jar目标中的所有源文件,包括aidl文件和java文件。

        第二处是给aidl_files变量使用“:=”赋值符号进行赋值,该变量仅仅包含android.jar目标中所有的aidl文件。

        因此,当给Frameworks中添加新的aidl文件时,需要考虑文件是否要公开到SDK中。如果需要,则需要把该文件路径同时添加到以上两个变量中;如果不需要公开到SDK中,则只需要把文件路径添加到LOCAL_SRC_FILES变量中。

      在完成这些操作后编译仍会出现问题,此时需要运行,make update-api命令,此时改变的文件是current.txt改变的内容如下:

+  public abstract interface IEneaService implements android.os.IInterface {

+  }

+

+  public static abstract class IEneaService.Stub extends android.os.Binder implements android.os.IEneaService {

+    ctor public IEneaService.Stub();

+    method public android.os.IBinder asBinder();

+    method public static android.os.IEneaService asInterface(android.os.IBinder);

+    method public boolean onTransact(int, android.os.Parcel, android.os.Parcel, int) throws android.os.RemoteException;

+  }

+

        这时,如果希望在写的Activity中使用的情况下,需要首先获取服务,这个步骤是通过"ServiceManager.getService()"这个API实现。然后使用service handle来调用service暴露出来的函数。ITestService om = ITestService.Stub.asInterface(ServiceManager.getService("Test"));

       下面是个简单的例子:

/** HelloServer.java*/package com.Test.helloserver;import android.app.Activity;import android.os.Bundle;import android.os.ServiceManager;import android.os.ITestService;import android.util.Log;public class HelloServer extends Activity {    private static final String DTAG = "HelloServer";    /** Called when the activity is first created. */    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.main);                ITestService om = ITestService.Stub.asInterface(ServiceManager.getService("Test"));        try {    Log.d(DTAG, "Going to call service");    om.setValue(20);    Log.d(DTAG, "Service called succesfully");        }        catch (Exception e) {    Log.d(DTAG, "FAILED to call service");    e.printStackTrace();        }    }}


上报和分发消息的流程

概论

        Android系统中,大体上分为三个层次kernel、framework、app层。对于kernel层,我们主要关心的是驱动,驱动层上报的事件都是原始数据。这些原始数据通过相应的机制上传到framework层的frameworks\base\service\input文件夹下的EventHub文件中对设备进行扫描区分具体的设备,并交由InputReader.cpp进行对数据的读取和分类。到达MotionEvent或者KeyEvent进行处理。此处这样说明总感觉到奇怪。        (2012-5-14 update)        在android中我们采用了一种从下而上,又从上而下的过程。在这个过程中我们首先是通过对输入设备的操作,从而使得硬件上报数据,而运行在系统中的线程等待接收数据,从而形成一种服务的关系。而从上而下的过程就是一种管理的过程,在这个过程中,我们上报的事件会得到合理的处理。         实际在代码中WindowManagerService中对InputManager进行了初始化,并且调用了start函数进行了启动。        此时大家一定想找到InputManager问个究竟,但是定睛一看,赫然显示两个InputManager。这可叫人如何是好?        本人猜想,应该此处的调用应该是InputManager.java。因为通常的调用都是从Java开始的。可能此处说法不严谨,但是鄙人认为科学都是“大胆假设、小心验证”。当然就从程序阅读本身也能找到根据。 因为此处的构造函数是有参数的。java中有而cpp中没有。(2012年5月7日修订)此处我们往下看的时候,发现在C++里面有函数的重载,而重载的构造函数中就有带有参数的构造函数。那到底此处我们是调用的哪个构造函数呢?当我们开始了解java和其他编程语言的相互调用之后我们发现在Android中我们都会通过JNI机制将本地的函数通过函数指针的形式提供给JAVA层进行调用。所以在命名上一般都以native开头。这样看来我们似乎对这一问题有了更深入的理解。         接着往下走发现在java中有回调函数和nativeInit,相信大家也是对这个函数比较感兴趣。但是此处还是要简单的说明下此回调函数。该函数则会利用windowManagerService,调用windowManagerService内部的其他方法。我们接着往下走,在nativeInit函数中将会创建一个此处会创建一个EventHub对象,以及InputManager对象。InputManager对象创建InputReaderThread以及InputDispatcherThread线程。在InputManager的构造函数中初始化了EventHub对象,通过传递参数给InptReader的构造函数。此处调用InputListener对事件进行监听,并且将消息放到消息队列中。自此WindowManagerService中对InputManager的初始化过程完成。        我们在通过EventHub获取数据之后,在inputreader函数中对原始的数据进行cook处理,处理之后形成应用程序能够识别的数据。这时候通过copyfrom函数将数据放到input中。最终通过inputlistener的flush函数进行监听,此过程就是调用notifyMotionevent函数此函数即通过inputdiapatcher进行分发。在数据分发的过程中,是通过flag对数据要进行分发还是拦截进行判断,并进行相应的处理。

 

 

 

更多相关文章

  1. Android(安卓)NDK开发之数组类型的操作
  2. Android(安卓)数独解码器 初级版(只能解简单数独)
  3. Android中关于JNI 的学习(一)对于JNIEnv的一些认识
  4. Android(安卓)Studio3.0开发JNI流程------JNI接口函数和指针
  5. Mac下使用Eclipse实现Android中调用C/C++(NDK)基础详细教程
  6. 浅浅的介绍一下android里面Handler、Looper、Message和MessageQu
  7. 关于监听Android的静音键以及音量按键
  8. android 安全讲座第五层(三) android so注入的研究
  9. 《第一行代码》第二版 学习总结26 Android中子线程更新UI的三种

随机推荐

  1. android 接触
  2. Android(安卓)总结4种线程中操作UI界面的
  3. Android(安卓)的进程与线程
  4. 本人自学Android技术
  5. android 模拟器和电脑服务器端用socket通
  6. android view框架总结
  7. android电源管理资料整理
  8. android launchmode(四种启动模式)应用场
  9. Android(安卓)笔记 03
  10. 手机的软件形态