现在的位置: 首页 > 综合 > 正文

Android 4.0按键事件以及系统流程分析

2018年02月15日 ⁄ 综合 ⁄ 共 5939字 ⁄ 字号 评论关闭

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对数据要进行分发还是拦截进行判断,并进行相应的处理。

 

 

 

抱歉!评论已关闭.