前言

在网上看到好多关于androidinputdevice流程分析,但是都不全,有的只是从linux内核那边分析,有的从android上层分析,而且分析的代码也比较老,都是在android2.3以下,最近在做android4.0下的多点触摸以及校准程序,多点触摸的驱动很好写,在linux内核里面都有现成的例子,照着改就可以了。但是android下的校准程序比较复杂,一种是在android

Framework层进行,一种是在linux内核层进行。

对于校准程序来说,需要全屏校准。但是在android4.0下面,下面的导航栏是systemui画的,无法去掉,因此在校准程序里面通过display得到分辨率高度比实际的小,差的那部分就是导航栏的高度。如果以小的高度进行校准,但使用实际的高度进行触摸坐标到屏幕坐标转换,就会导致触摸点偏下的问题。

为了解决这个问题,在网上找了很多资料,第一种就是想办法在校准程序里面得到整个屏幕的分辨率,进而让校准程序全屏显示,即把导航栏隐藏,在网上看到又网友用下面例子实现:

//forphone

getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);

//forpadView.SYSTEM_UI_FLAG_SHOW_FULLSCREEN=4

getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_SHOW_FULLSCREEN);

经过自己实验,这两个都无法隐藏下面的导航栏,而且在最新的sdk里面也没有SYSTEM_UI_FLAG_SHOW_FULLSCREEN的定义。第二种就是在jni种通过fb0得到系统的分辨率,这个是真实的分辨率,这种方法需要apkroot或者graphics组权限,才能打开fb0,而且android4.0根据触摸屏类型是否使用外部显示分辨率,如果使用外部display的话,那么就不能用fb0的分辨率。为了解决这个问题,把整个inputtouch流程都看了一边。废话少说,进入正题。

1androidinputtouch流程

Androidinouttouch流程分两部分,一部分是从androidframework开始,如何读取touch设备的事件并分发。一部分是从linux内核开始,如何从触摸屏读取触摸坐标并送给touch设备。

2androidframework层

2.1文件结构

首先看看EventInput文件结构吧,在frameworks/base/services/input之下

2.2模块介绍

lEventhub

它是系统中所有事件的中央处理站。它管理所有系统中可以识别的输入设备的输入事件,此外,当设备增加或删除时,EventHub将产生相应的输入事件给系统。EventHub通过getEvents函数,给系统提供一个输入事件流。它也支持查询输入设备当前的状态(如哪些键当前被按下)。而且EventHub还跟踪每个输入调入的能力,比如输入设备的类别,输入设备支持哪些按键。

lInputReader

InputReader从EventHub中读取原始事件数据(RawEvent),并由各个InputMapper处理之后输入对应的inputlistener.InputReader拥有一个InputMapper集合。它做的大部分工作在InputReader线程中完成,但是InputReader可以接受任意线程的查询。为了可管理性,InputReader使用一个简单的Mutex来保护它的状态。InputReader拥有一个EventHub对象,但这个对象不是它创建的,而是在创建InputReader时作为参数传入的。

lInputDispatcher

InputDispatcher负责把事件分发给输入目标,其中的一些功能(如识别输入目标)由独立的policy对象控制。

lInputManager

InputManager是系统事件处理的核心,它虽然不做具体的事,但管理工作还是要做的,比如接受我们客户的投诉和索赔要求,或者老板的出筒。

InputManager使用两个线程:

1)InputReaderThread叫做"InputReader"线程,它负责读取并预处理RawEvent,appliespolicy并且把消息送入DispatcherThead管理的队列中。

2)InputDispatcherThread叫做"InputDispatcher"线程,它在队列上等待新的输入事件,并且异步地把这些事件分发给应用程序。

InputReaderThread类与InputDispatcherThread类不共享内部状态,所有的通信都是单向的,从InputReaderThread到InputDispatcherThread。两个类可以通过InputDispatchPolicy进行交互。

InputManager类从不与Java交互,而InputDispatchPolicy负责执行所有与系统的外部交互,包括调用DVM业务。

看看下图理解input下面几个模块的关系

2.3线程创建

SystemServer大家熟悉吧,它是androidinit进程启动的,它的任务就是启动android里面很多服务,并管理起来,如果大家不熟悉,请参考andorid启动流程分析

SystemServer.java(frameworks\base\services\java\com\android\server)里面

ServerThread::run调用

Slog.i(TAG,"WindowManager");

wm=WindowManagerService.main(context,power,

factoryTest!=SystemServer.FACTORY_TEST_LOW_LEVEL,

!firstBoot);

ServiceManager.addService(Context.WINDOW_SERVICE,wm);

WindowManagerService.java(frameworks\base\services\java\com\android\server\wm)里面

WindowManagerServicemain调用

WMThreadthr=newWMThread(context,pm,haveInputMethods,allowBootMsgs);

thr.start();

接着调用

WMThread::run调用

WindowManagerServices=newWindowManagerService(mContext,mPM,

mHaveInputMethods,mAllowBootMessages);

接着在WindowManagerService里面调用

mInputManager=newInputManager(context,this);

至此我们创建了一个javainput设备管理器

InputManager.java(frameworks\base\services\java\com\android\server\wm)里面

InputManager调用

nativeInit(mContext,mCallbacks,looper.getQueue());

从下面开始就进入native空间

com_android_server_InputManager.cpp(frameworks\base\services\jni)里面

nativeInit对应android_server_InputManager_nativeInit调用

gNativeInputManager=newNativeInputManager(contextObj,callbacksObj,looper);

NativeInputManager里面调用

sp<EventHub>eventHub=newEventHub();

mInputManager=newInputManager(eventHub,this,this);

这个函数创建一个EventHub对象,然后把它作为参数来创建InputManager对象。特别注意,InputManager是在C++里,具体在InputManager.cpp里。EventHub类在EventHub.cpp里,这个类和input事件获取有关。

至此我们创建了一个nativeinput设备管理器,具体作用见上面说明

首先是去InputManager.cpp(frameworks\base\services\input)文件里面

InputManager::InputManager调用

mDispatcher=newInputDispatcher(dispatcherPolicy);

mReader=newInputReader(eventHub,readerPolicy,mDispatcher);

initialize();

它创建了InputDispatcher对象,同时也创建了InputReader对象。并分别暂存于mDispatcher和mReader变量中。注意eventHub和mDispatcher都作为参数创建InputReader对象。后面还用initialize来初始化。下面是initialize函数的定义:

voidInputManager::initialize(){

mReaderThread=newInputReaderThread(mReader);

mDispatcherThread=newInputDispatcherThread(mDispatcher);

}

它创建两个线程对象,一个是InputReaderThread线程对象,负责input事件的获取;另一个是InputDispatcherThread线程对象,负责input消息的发送。

注:以上两个线程对象都有自己的threadLoop函数,它将在Thread::_threadLoop中被调用,这个Thread::_threadLoop是线程入口函数,线程在Thread::run中被真正地创建

InputDispatcher.cpp(frameworks\base\services\input)里面

InputDispatcher::InputDispatcher做一些准备工作

InputReader.cpp(frameworks\base\services\input)里面

InputReader::InputReader做一些准备工作

2.4线程启动

在上面讲到在WindowManagerService里面调用

mInputManager=newInputManager(context,this);

创建input管理器

紧接着调用

mInputManager.start();

InputManager.java(frameworks\base\services\java\com\android\server\wm)里面

start调用

Slog.i(TAG,"Startinginputmanager");

nativeStart();

从下面开始就进入native空间

com_android_server_InputManager.cpp(frameworks\base\services\jni)里面

nativeStart对应android_server_InputManager_nativeStart调用

status_tresult=gNativeInputManager->getInputManager()->start();

InputManager.cpp(frameworks\base\services\input)文件里面

InputManager::start调用

status_tresult=mDispatcherThread->run("InputDispatcher",PRIORITY_URGENT_DISPLAY);

result=mReaderThread->run("InputReader",PRIORITY_URGENT_DISPLAY);

上面两个线程对象是Thread子类,于是继承它的run方法,在Thread::run中,调用createThreadEtc函数,并以Thread::_threadLoop作为入口函数,以上面的mDispatcherThread或mReaderThread作为userdata创建线程,然后会调用threadLoop(),在Thread类中它是虚函数,得由子类来复写

因此会调用InputReader.cpp(frameworks\base\services\input)里面的threadLoopInputReaderThread::threadLoop调用

mReader->loopOnce();

mReader就是上面创建的inputreader对象,作为参数传给mReaderThread

InputReader::loopOnce调用

count=mEventHub->getEvents(timeoutMillis,mEventBuffer,EVENT_BUFFER_SIZE);

得到input输入事件

processEventsLocked

处理input输入事件

因此会调用InputDispatcher.cpp(frameworks\base\services\input)里面的threadLoopInputDispatcherThread::threadLoop调用

mDispatcher->dispatchOnce();

mDispatcher就是上面创建的InputDispatcher对象,作为参数传给mDispatcherThread

InputDispatcher::dispatchOnce调用

dispatchOnceInnerLocked(&nextWakeupTime)

dispatchOnceInnerLocked函数处理input输入消息,mLooper->pollOnce是等待下一次输入事件。

mLooper->pollOnce(timeoutMillis):

这个请看Looper.cpp文件中的Looper::pollOnce()函数。Looper里主要通过linux管道方式实现进程间通信,通过epoll机制实现外界事件请求作出响应。

至此整个androidinputevent框架已经运转起来了,好像到现在还没有提到touch,别着急,且看下面的分析

2.5event初始化

还记得android_server_InputManager_nativeInit里面创建

sp<EventHub>eventHub=newEventHub();

EventHub.cpp(frameworks\base\services\input)里面

EventHub::EventHub初始化

mOpeningDevices(0)表示需要打开的设备链表,为NULL

mClosingDevices(0)表示需要关闭的设备链表,为NULL

mNeedToSendFinishedDeviceScan(false)表示需要发送设备扫描完成,默认为0

mNeedToReopenDevices(false)表示需要重新打开设备,默认为0

mNeedToScanDevices(true)表示需要扫描设备,默认为1

mPendingEventCount(0)表示需要处理event个数,默认为0

mPendingEventIndex(0)表示当前需要处理event的索引,默认为0

mPendingINotify(false)表示需要处理的通知,默认为0

mEpollFd=epoll_create(EPOLL_SIZE_HINT);epoll实例,在EventHub::EventHub中初始化此例,所有输入事件通过epoll_wait来获取

mINotifyFd=inotify_init();

intresult=inotify_add_watch(mINotifyFd,DEVICE_PATH,IN_DELETE|IN_CREATE);

创建mINotifyFd,用于监控/dev/input目录下删除和创建设备节点的事件

result=epoll_ctl(mEpollFd,EPOLL_CTL_ADD,mINotifyFd,&eventItem)

mINotifyFd注册到mEpollFd里面,通过epoll来监听mINotifyFd的变化

result=pipe(wakeFds);

mWakeReadPipeFd=wakeFds[0];

mWakeWritePipeFd=wakeFds[1];

result=fcntl(mWakeReadPipeFd,F_SETFL,O_NONBLOCK)

result=fcntl(mWakeWritePipeFd,F_SETFL,O_NONBLOCK)

创建唤醒管道,并设置为非阻塞,如果向mWakeWritePipeFd写,那么mWakeReadPipeFd就会有变化

eventItem.data.u32=EPOLL_ID_WAKE;

result=epoll_ctl(mEpollFd,EPOLL_CTL_ADD,mWakeReadPipeFd,&eventItem);

mWakeReadPipeFd注册到mEpollFd里面,通过epoll来监听mWakeReadPipeFd的变化

至此EventHub对象以及构造完成了,mEpollFd监听mINotifyFdmWakeReadPipeFd的变化。

2.6读取事件

在上面2.4节最后我们看到InputReaderThread线程里面会循环调用

InputReader::loopOnce接着调用

count=mEventHub->getEvents(timeoutMillis,mEventBuffer,EVENT_BUFFER_SIZE);

这里的mEventHub就是上节实例化的eventhub,我们来看getEvents

EventHub.cpp(frameworks\base\services\input)里面

EventHub::getEvents

for(;;){

nsecs_tnow=systemTime(SYSTEM_TIME_MONOTONIC);

进入for循环

//Reopeninputdevicesifneeded.

if(mNeedToReopenDevices){

mNeedToReopenDevices=false;

LOGI("Reopeningallinputdevicesduetoaconfigurationchange.");

closeAllDevicesLocked();

mNeedToScanDevices=true;

break;//returntothecallerbeforeweactuallyrescan

}

检查mNeedToReopenDevices是否为ture,如果为true,在closeAllDevicesLocked里面关闭所有打开的硬件设备描述符,并把需要删除的设备放在mClosingDevices链表里面,如果这个设备是在mOpeningDevices里面,就忽略跳过,并删除eventhub层的device对象。然后设置mNeedToScanDevicestrue,因为mNeedToReopenDevices默认为false,所以不会执行这段代码

//Reportanydevicesthathadlastbeenadded/removed.

while(mClosingDevices){

Device*device=mClosingDevices;

LOGV("Reportingdeviceclosed:id=%d,name=%s\n",

device->id,device->path.string());

mClosingDevices=device->next;

event->when=now;

event->deviceId=device->id==mBuiltInKeyboardId?0:device->id;

event->type=DEVICE_REMOVED;

event+=1;

deletedevice;

mNeedToSendFinishedDeviceScan=true;

if(--capacity==0){

break;

}

}

检查mClosingDevices链表是否存在,如果存在,循环把需要删除的设备信息放在event里面,同时设置eventtypeDEVICE_REMOVED并删除eventhub层的device对象。设置mNeedToSendFinishedDeviceScantrue。每循环一次,capacity1capacity等于0,就退出for循环,表明这次getEvents已经取得256event事件了,返回给inputreader处理。因为一开始mClosingDevices不存在,所以不会执行这段代码,只有上面的closeAllDevicesLocked执行了,才会执行这段代码。

if(mNeedToScanDevices){

mNeedToScanDevices=false;

scanDevicesLocked();

mNeedToSendFinishedDeviceScan=true;

}

检查mNeedToScanDevices是否为true,如果为true,就执行设备扫描。在scanDevicesLocked里面,会打开/dev/input目录,并把循环调用openDeviceLocked,在openDeviceLocked里面

intfd=open(devicePath,O_RDWR)

打开一个input设备

//Checktoseeifthedeviceisonourexcludedlist

for(size_ti=0;i<mExcludedDevices.size();i++){

constString8&item=mExcludedDevices.itemAt(i);

if(identifier.name==item){

LOGI("ignoringeventid%sdriver%s\n",devicePath,item.string());

close(fd);

return-1;

}

}

判断这个设备是否已经存在,如果存在,就关闭退出。

下面得到设备一系列信息。

Device*device=newDevice(fd,deviceId,String8(devicePath),identifier);

创建一个eventhub层的device对象

//Loadtheconfigurationfileforthedevice.

loadConfigurationLocked(device);

得到设备的idc配置文件,这就是为什么android4.0需要idc文件

//Figureoutthekindsofeventsthedevicereports.

ioctl(fd,EVIOCGBIT(EV_KEY,sizeof(device->keyBitmask)),device->keyBitmask);

ioctl(fd,EVIOCGBIT(EV_ABS,sizeof(device->absBitmask)),device->absBitmask);

ioctl(fd,EVIOCGBIT(EV_REL,sizeof(device->relBitmask)),device->relBitmask);

ioctl(fd,EVIOCGBIT(EV_SW,sizeof(device->swBitmask)),device->swBitmask);

ioctl(fd,EVIOCGBIT(EV_LED,sizeof(device->ledBitmask)),device->ledBitmask);

ioctl(fd,EVIOCGPROP(sizeof(device->propBitmask)),device->propBitmask);

得到设备各种配置,接下设置deviceclass,就设备的类型

//Seeifthisisatouchpad.

//Isthisanewmodernmulti-touchdriver?

if(test_bit(ABS_MT_POSITION_X,device->absBitmask)

&&test_bit(ABS_MT_POSITION_Y,device->absBitmask)){

//SomejoystickssuchasthePS3controllerreportaxesthatconflict

//withtheABS_MTrange.Trytoconfirmthatthedevicereallyis

//atouchscreen.

if(test_bit(BTN_TOUCH,device->keyBitmask)||!haveGamepadButtons){

device->classes|=INPUT_DEVICE_CLASS_TOUCH|INPUT_DEVICE_CLASS_TOUCH_MT;

}

//Isthisanoldstylesingle-touchdriver?

}elseif(test_bit(BTN_TOUCH,device->keyBitmask)

&&test_bit(ABS_X,device->absBitmask)

&&test_bit(ABS_Y,device->absBitmask)){

device->classes|=INPUT_DEVICE_CLASS_TOUCH;

}

上面就是根据驱动程序里面的设置来判断inputdevice是多点触摸还是单点触摸,现在是不是看到和触摸屏有点关系了

//Determinewhetherthedeviceisexternalorinternal.

if(isExternalDeviceLocked(device)){

device->classes|=INPUT_DEVICE_CLASS_EXTERNAL;

}

判断是不是外部设备,根据两个条件判断,一是在idc文件里面如果有“device.internal”存在,就是内部设备,否则是外部设备。如果没有这个域存在,根据硬件设备的总线判断,如果是usbbluetoothbus,就是外部设备。这个留着后面有作用。

if(epoll_ctl(mEpollFd,EPOLL_CTL_ADD,fd,&eventItem))

将设备加入到mEpollFd监控里面

device->next=mOpeningDevices;

mOpeningDevices=device;

将设备加入需要打开设备链表里面

至此,/dev/input/下面所有的设备对于linux层都已经打开,并且都添加到了mEpollFd监控里面,但是android层面的device还没有添加和初始化,只是放在需要打开设备链表里面。接着设置mNeedToSendFinishedDeviceScantrue。因为mNeedToScanDevices初始化为true,因此第一次进入getEvents就会执行这部分代码。

while(mOpeningDevices!=NULL){

Device*device=mOpeningDevices;

LOGV("Reportingdeviceopened:id=%d,name=%s\n",

device->id,device->path.string());

mOpeningDevices=device->next;

event->when=now;

event->deviceId=device->id==mBuiltInKeyboardId?0:device->id;

event->type=DEVICE_ADDED;

event+=1;

mNeedToSendFinishedDeviceScan=true;

if(--capacity==0){

break;

}

}

检查mOpeningDevices链表是否存在,如果存在,循环把需要添加的设备信息放在event里面,同时设置eventtypeDEVICE_ADDED。设置mNeedToSendFinishedDeviceScantrue。每循环一次,capacity1capacity等于0,就退出for循环,表明这次getEvents已经取得256event事件了,返回给inputreader处理。因为一开始会执行mNeedToScanDevices代码,只要/dev/input下面有设备节点存在,mOpeningDevices也会存在,所以开始就会执行这段代码。

if(mNeedToSendFinishedDeviceScan){

mNeedToSendFinishedDeviceScan=false;

event->when=now;

event->type=FINISHED_DEVICE_SCAN;

event+=1;

if(--capacity==0){

break;

}

}

如果mNeedToSendFinishedDeviceScantrue,就把FINISHED_DEVICE_SCAN信息放在event里面,同时capacity1capacity等于0,就退出for循环,表明这次getEvents已经取得256event事件了,返回给inputreader处理。

至此mEpollFd监听mINotifyFdmWakeReadPipeFd和/dev/input里面所有设备的变化

//Grabthenextinputevent.

booldeviceChanged=false;

while(mPendingEventIndex<mPendingEventCount){

conststructepoll_event&eventItem=mPendingEventItems[mPendingEventIndex++];

上面这段代码通过mPendingEventIndexmPendingEventCount关系判断mEpollFd是否监听到了事件发生,如果有事件发声,从mPendingEventItems取出一个事件

if(eventItem.data.u32==EPOLL_ID_INOTIFY){

if(eventItem.events&EPOLLIN){

mPendingINotify=true;

}else{

LOGW("Receivedunexpectedepollevent0x%08xforINotify.",eventItem.events);

}

continue;

}

判断是否是EPOLL_ID_INOTIFY事件,即mINotifyFd有没有变化,也就是在有没有设备热拔插发声,如果有设置mPendingINotify为true,继续循环取下一个事件。如果不是继续往下走。

if(eventItem.data.u32==EPOLL_ID_WAKE){

if(eventItem.events&EPOLLIN){

LOGV("awokenafterwake()");

awoken=true;

charbuffer[16];

ssize_tnRead;

do{

nRead=read(mWakeReadPipeFd,buffer,sizeof(buffer));

}while((nRead==-1&&errno==EINTR)||nRead==sizeof(buffer));

}else{

LOGW("Receivedunexpectedepollevent0x%08xforwakereadpipe.",

eventItem.events);

}

continue;

}

判断是不是EPOLL_ID_WAKE事件,即mWakeReadPipeFd有没有变化,如果有设置awoken为true,继续循环取下一个事件。如果不是EPOLL_ID_WAKE事件,继续往下走,肯定是有设备输入事件发生。

ssize_tdeviceIndex=mDevices.indexOfKey(eventItem.data.u32);

if(deviceIndex<0){

LOGW("Receivedunexpectedepollevent0x%08xforunknowndeviceid%d.",

eventItem.events,eventItem.data.u32);

continue;

}

得到有事件发生的设备索引,如果deviceIndex<0,有错误发声,继续循环取下一个事件。

Device*device=mDevices.valueAt(deviceIndex);

if(eventItem.events&EPOLLIN){

int32_treadSize=read(device->fd,readBuffer,

sizeof(structinput_event)*capacity);

if(readSize==0||(readSize<0&&errno==ENODEV)){

//DevicewasremovedbeforeINotifynoticed.

LOGW("couldnotgetevent,removed?(fd:%dsize:%dbufferSize:%dcapacity:%derrno:%d)\n",

device->fd,readSize,bufferSize,capacity,errno);

deviceChanged=true;

closeDeviceLocked(device);

}elseif(readSize<0){

if(errno!=EAGAIN&&errno!=EINTR){

LOGW("couldnotgetevent(errno=%d)",errno);

}

}elseif((readSize%sizeof(structinput_event))!=0){

LOGE("couldnotgetevent(wrongsize:%d)",readSize);

}else{

int32_tdeviceId=device->id==mBuiltInKeyboardId?0:device->id;

size_tcount=size_t(readSize)/sizeof(structinput_event);

for(size_ti=0;i<count;i++){

conststructinput_event&iev=readBuffer[i];

LOGV("%sgot:t0=%d,t1=%d,type=%d,code=%d,value=%d",

device->path.string(),

(int)iev.time.tv_sec,(int)iev.time.tv_usec,

iev.type,iev.code,iev.value);

#ifdefHAVE_POSIX_CLOCKS

//Usethetimespecifiedintheeventinsteadofthecurrenttime

//sothatdownstreamcodecangetmoreaccurateestimatesof

//eventdispatchlatencyfromthetimetheeventisenqueuedonto

//theevdevclientbuffer.

//

//Theevent'stimestampfortuitouslyusesthesamemonotonicclock

//timebaseastherestofAndroid.Thekerneleventdevicedriver

//(drivers/input/evdev.c)obtainstimestampsusingktime_get_ts().

//ThesystemTime(SYSTEM_TIME_MONOTONIC)functionweuseeverywhere

//callsclock_gettime(CLOCK_MONOTONIC)whichisimplementedasa

//systemcallthatalsoqueriesktime_get_ts().

event->when=nsecs_t(iev.time.tv_sec)*1000000000LL

+nsecs_t(iev.time.tv_usec)*1000LL;

LOGV("eventtime%lld,now%lld",event->when,now);

#else

event->when=now;

#endif

event->deviceId=deviceId;

event->type=iev.type;

event->scanCode=iev.code;

event->value=iev.value;

event->keyCode=AKEYCODE_UNKNOWN;

event->flags=0;

if(iev.type==EV_KEY&&device->keyMap.haveKeyLayout()){

status_terr=device->keyMap.keyLayoutMap->mapKey(iev.code,

&event->keyCode,&event->flags);

LOGV("iev.code=%dkeyCode=%dflags=0x%08xerr=%d\n",

iev.code,event->keyCode,event->flags,err);

}

event+=1;

}

capacity-=count;

if(capacity==0){

//Theresultbufferisfull.Resetthependingeventindex

//sowewilltrytoreadthedeviceagainonthenextiteration.

mPendingEventIndex-=1;

break;

}

}

}else{

LOGW("Receivedunexpectedepollevent0x%08xfordevice%s.",

eventItem.events,device->identifier.name.string());

}

}

根据设备索引的设备文件句柄,通过read函数读取input_event事件,读取个数为capacity,根据read返回值readSize除以sizeof(structinput_event)得到实际读取的事件个数,然后循环把input_event赋给event,同时capacity减去读取事件个数,如果capacity等于0,就退出循环,表明这次getEvents已经取得256event事件了,返回给inputreader处理。如果不等于0,判断mPendingEventIndexmPendingEventCount关系,如果小于继续循环从mPendingEventItems取下一个事件,如果相等,就表示事件已经取完了,执行下面的代码

if(mPendingINotify&&mPendingEventIndex>=mPendingEventCount){

mPendingINotify=false;

readNotifyLocked();

deviceChanged=true;

}

如果mPendingINotify为true,且mPendingEventIndex>=mPendingEventCount,就表明有设备热拔插事件发生,调用readNotifyLocked()

readNotifyLocked()调用

read(mINotifyFd,event_buf,sizeof(event_buf))

event_buf循环取出inotify_event事件,包括设备节点创建或者删除,以及设备名字

如果是IN_CREATE,就调用openDeviceLocked打开设备,添加打开设备链表。如果是IN_DELETE,就调用closeDeviceByPathLocked关闭设备,添加关闭设备链表。并设置deviceChangedtrue

//Reportaddedorremoveddevicesimmediately.

if(deviceChanged){

continue;

}

如果deviceChangedtrue,结束本次循环,从头执行循环,即立即执行设备添加或删除。

//Returnnowifwehavecollectedanyeventsorifwewereexplicitlyawoken.

if(event!=buffer||awoken){

break;

}

如果event!=buffer,就表示有event事件发生,或者awoken存在,结束循环,立即返回给inputreader处理event事件。

mPendingEventIndex=0;

intpollResult=epoll_wait(mEpollFd,mPendingEventItems,EPOLL_MAX_EVENTS,timeoutMillis);

调用epoll_wait(mEpollFd,mPendingEventItems,EPOLL_MAX_EVENTS,timeoutMillis)之后,读到的epoll_event事件保存在mPendingEventItems,总共的事件数保存在mPendingEventCount

mPendingEventCount=size_t(pollResult);

当然,在调用epoll_wait之前,mPendingEventIndex被清0,直正的事件处理在上面的代码中。epoll_event只表明某个设备上有事件,并不包含事件内容,具体事件内容需要通过read来读取

//Alldone,returnthenumberofeventsweread.

returnevent-buffer;

返回得到的event个数,支持整个getEvents已经执行完成,所有的event事件都保存在inputreader传递的RawEvent里面,看看下面的图,理解数据结构的变化

2.7处理事件

在上面2.4节最后我们看到InputReaderThread线程里面会循环调用

InputReader::loopOnce调用

count=mEventHub->getEvents(timeoutMillis,mEventBuffer,EVENT_BUFFER_SIZE);

读取事件,上一节已经介绍了,得到事件后,接着调用

processEventsLocked(mEventBuffer,count)

处理事件

processEventsLocked里面主要分两步处理:

1)处理来自于事件驱动设备的事件(processEventsForDeviceLocked

2)处理设备增加、删除和修改事件为处理事件做准备

for(constRawEvent*rawEvent=rawEvents;count;){

int32_ttype=rawEvent->type;

进入for循环,取得本次循环头一个rawEvent,然后判断事件type,如果小于FIRST_SYNTHETIC_EVENT,就表示是真正的input事件,如果大于等于,就表示是input设备变化事件。

size_tbatchSize=1;

if(type<EventHubInterface::FIRST_SYNTHETIC_EVENT){

int32_tdeviceId=rawEvent->deviceId;

while(batchSize<count){

if(rawEvent[batchSize].type>=EventHubInterface::FIRST_SYNTHETIC_EVENT

||rawEvent[batchSize].deviceId!=deviceId){

break;

}

batchSize+=1;

}

#ifDEBUG_RAW_EVENTS

LOGD("BatchSize:%dCount:%d",batchSize,count);

#endif

processEventsForDeviceLocked(deviceId,rawEvent,batchSize);

判断下一事件type,从rawEvent数组里面取得属于同一个设备的连续input事件,然后交给设备处理程序去处理,如果后面的事件不属于同一个设备,或者事件typeFIRST_SYNTHETIC_EVENT以后的事件,就终止查询,运行processEventsForDeviceLocked

}else{

switch(rawEvent->type){

caseEventHubInterface::DEVICE_ADDED:

addDeviceLocked(rawEvent->when,rawEvent->deviceId);

break;

caseEventHubInterface::DEVICE_REMOVED:

removeDeviceLocked(rawEvent->when,rawEvent->deviceId);

break;

caseEventHubInterface::FINISHED_DEVICE_SCAN:

handleConfigurationChangedLocked(rawEvent->when);

break;

default:

LOG_ASSERT(false);//can'thappen

break;

}

}

如果事件typeFIRST_SYNTHETIC_EVENT以后的事件,是与Device相关的事件,这些事件是在EventHub::getEvents中产生的,并不是Kernel态的事件输入设备产生的。就调用设备添加,删除,配置变化等函数。

count-=batchSize;

rawEvent+=batchSize;

去掉已经处理的事件,为下一次循环做准备。

}

至此,我们看到inputreadergetEvents得到的事件都有一一对应的处理。

2.7.1处理事件准备设备添加删除

按照程序执行流程,应该是先有设备,然后才会有设备事件,所以先分析设备增加。其代码如下:

InputReader::addDeviceLocked

String8name=mEventHub->getDeviceName(deviceId);

uint32_tclasses=mEventHub->getDeviceClasses(deviceId);

得到设备名字和类型

InputDevice*device=createDeviceLocked(deviceId,name,classes);

得到一个inputreader层的device

device->configure(when,&mConfig,0);

device->reset(when);

进行device配置和reset

if(device->isIgnored()){

LOGI("Deviceadded:id=%d,name='%s'(ignorednon-inputdevice)",deviceId,name.string());

}else{

LOGI("Deviceadded:id=%d,name='%s',sources=0x%08x",deviceId,name.string(),

device->getSources());

}

判断devicemapper是否存在,如果不存在,这个设备就不是inputdevice

mDevices.add(deviceId,device);

新建的InputDevice增加到InputReader::mDevices中

InputReader::createDeviceLocked

InputDevice*device=newInputDevice(&mContext,deviceId,name,classes);

创建一个inputreader层的device

//Externaldevices.

if(classes&INPUT_DEVICE_CLASS_EXTERNAL){

device->setExternal(true);

}

根据类型设置是否是外部设备

接下来就是根据类型给device创建和增加事件转换器,即mapper,我们只分析touch

//Touchscreensandtouchpaddevices.

if(classes&INPUT_DEVICE_CLASS_TOUCH_MT){

device->addMapper(newMultiTouchInputMapper(device));

}elseif(classes&INPUT_DEVICE_CLASS_TOUCH){

device->addMapper(newSingleTouchInputMapper(device));

}

根据多点还是单点分别创建事件转换器。我们只分析单点设备,我们的touch只有一个mapper---SingleTouchInputMapper

SingleTouchInputMapper::SingleTouchInputMapper

它继承自TouchInputMapper---InputMapper

做一些初始化的工作

InputDevice::addMapper

mMappers.add(mapper)

新建的InputMapper增加到InputDevice::mMappers

至此inputreader层的inputdevice创建完成,并且每个device都创建了一个对应的事件转换器。

创建完就要进行配置

device->configure(when,&mConfig,0);

InputDevice::configure

if(!isIgnored()){

if(!changes){//firsttimeonly

mContext->getEventHub()->getConfiguration(mId,&mConfiguration);

}

size_tnumMappers=mMappers.size();

for(size_ti=0;i<numMappers;i++){

InputMapper*mapper=mMappers[i];

mapper->configure(when,config,changes);

mSources|=mapper->getSources();

}

}

判断mapper是否为空,如果不存在,就不需要配置。判断是否是配置改变,不是配置改变,那就是第一次进行配置,需要从eventhub里面得到设备的idc配置文件

接着对mapper进行配置,可能有多个事件转换器,一一对相应的mapper进行转换。

mapper->configure,我们分析的是单点touch,因此mapper对应的是SingleTouchInputMapper,它里面没有configure,继续找TouchInputMapper

TouchInputMapper::configure

InputMapper::configure(when,config,changes)

这个什么也没有做

if(!changes){//firsttimeonly

//Configurebasicparameters.

configureParameters();

//Configurecommonaccumulators.

mCursorScrollAccumulator.configure(getDevice());

mTouchButtonAccumulator.configure(getDevice());

//Configureabsoluteaxisinformation.

configureRawPointerAxes();

//Prepareinputdevicecalibration.

parseCalibration();

resolveCalibration();

}

如果是第一次配置,就进入里面,调用

TouchInputMapper::configureParameters

mParameters.gestureMode=getEventHub()->hasInputProperty(getDeviceId(),INPUT_PROP_SEMI_MT)

?Parameters::GESTURE_MODE_POINTER:Parameters::GESTURE_MODE_SPOTS;

首先从驱动文件里面得到mParameters.gestureMode类型

String8gestureModeString;

if(getDevice()->getConfiguration().tryGetProperty(String8("touch.gestureMode"),

gestureModeString)){

if(gestureModeString=="pointer"){

mParameters.gestureMode=Parameters::GESTURE_MODE_POINTER;

}elseif(gestureModeString=="spots"){

mParameters.gestureMode=Parameters::GESTURE_MODE_SPOTS;

}elseif(gestureModeString!="default"){

LOGW("Invalidvaluefortouch.gestureMode:'%s'",gestureModeString.string());

}

}

如果idc文件有touch.gestureMode存在,使用idc文件的配置

if(getEventHub()->hasInputProperty(getDeviceId(),INPUT_PROP_DIRECT)){

//Thedeviceisatouchscreen.

mParameters.deviceType=Parameters::DEVICE_TYPE_TOUCH_SCREEN;

}elseif(getEventHub()->hasInputProperty(getDeviceId(),INPUT_PROP_POINTER)){

//Thedeviceisapointingdevicelikeatrackpad.

mParameters.deviceType=Parameters::DEVICE_TYPE_POINTER;

}elseif(getEventHub()->hasRelativeAxis(getDeviceId(),REL_X)

||getEventHub()->hasRelativeAxis(getDeviceId(),REL_Y)){

//Thedeviceisacursordevicewithatouchpadattached.

//Bydefaultdon'tusethetouchpadtomovethepointer.

mParameters.deviceType=Parameters::DEVICE_TYPE_TOUCH_PAD;

}else{

//Thedeviceisatouchpadofunknownpurpose.

mParameters.deviceType=Parameters::DEVICE_TYPE_POINTER;

}

从驱动文件里面得到touch的类型

String8deviceTypeString;

if(getDevice()->getConfiguration().tryGetProperty(String8("touch.deviceType"),

deviceTypeString)){

if(deviceTypeString=="touchScreen"){

mParameters.deviceType=Parameters::DEVICE_TYPE_TOUCH_SCREEN;

}elseif(deviceTypeString=="touchPad"){

mParameters.deviceType=Parameters::DEVICE_TYPE_TOUCH_PAD;

}elseif(deviceTypeString=="pointer"){

mParameters.deviceType=Parameters::DEVICE_TYPE_POINTER;

}elseif(deviceTypeString!="default"){

LOGW("Invalidvaluefortouch.deviceType:'%s'",deviceTypeString.string());

}

}

如果idc文件有touch.deviceType存在,使用idc文件的配置,这里我们配置是touchScreen,即mParameters.deviceType=Parameters::DEVICE_TYPE_TOUCH_SCREEN

mParameters.orientationAware=mParameters.deviceType==Parameters::DEVICE_TYPE_TOUCH_SCREEN;

getDevice()->getConfiguration().tryGetProperty(String8("touch.orientationAware"),

mParameters.orientationAware);

从idc文件里面得到mParameters.orientationAware的值

mParameters.associatedDisplayId=-1;

mParameters.associatedDisplayIsExternal=false;

if(mParameters.orientationAware

||mParameters.deviceType==Parameters::DEVICE_TYPE_TOUCH_SCREEN

||mParameters.deviceType==Parameters::DEVICE_TYPE_POINTER){

mParameters.associatedDisplayIsExternal=

mParameters.deviceType==Parameters::DEVICE_TYPE_TOUCH_SCREEN

&&getDevice()->isExternal();

mParameters.associatedDisplayId=0;

}

根据mParameters.deviceTypegetDevice()->isExternal来判断是否使用外部显示配置。在eventhub里面我们的触摸屏是usbbus,被配置成外部设备,触摸屏配置成DEVICE_TYPE_TOUCH_SCREEN,因此mParameters.associatedDisplayIsExternal等于1,及使用外部的显示配置。

至此TouchInputMapper::configureParameters配置完成

//Configurecommonaccumulators.

mCursorScrollAccumulator.configure(getDevice());

mTouchButtonAccumulator.configure(getDevice());

配置光标和按键加速,都是根据驱动文件或者idc文件,这个都不需要。

//Configureabsoluteaxisinformation.

configureRawPointerAxes();

配置原始信息,它先调用TouchInputMapper::configureRawPointerAxes

mRawPointerAxes.clear()先将mRawPointerAxes清除干净

接着调用SingleTouchInputMapper::configureRawPointerAxes

getAbsoluteAxisInfo(ABS_X,&mRawPointerAxes.x);

getAbsoluteAxisInfo(ABS_Y,&mRawPointerAxes.y);

getAbsoluteAxisInfo(ABS_PRESSURE,&mRawPointerAxes.pressure);

getAbsoluteAxisInfo(ABS_TOOL_WIDTH,&mRawPointerAxes.toolMajor);

getAbsoluteAxisInfo(ABS_DISTANCE,&mRawPointerAxes.distance);

getAbsoluteAxisInfo(ABS_TILT_X,&mRawPointerAxes.tiltX);

getAbsoluteAxisInfo(ABS_TILT_Y,&mRawPointerAxes.tiltY);

InputMapper::getAbsoluteAxisInfo调用

getEventHub()->getAbsoluteAxisInfo从驱动文件里面得到需要参数

//Prepareinputdevicecalibration.

parseCalibration();

resolveCalibration();

根据idc文件配置校正参数。

if(!changes||(changes&InputReaderConfiguration::CHANGE_POINTER_SPEED)){

//Updatepointerspeed.

mPointerVelocityControl.setParameters(mConfig.pointerVelocityControlParameters);

mWheelXVelocityControl.setParameters(mConfig.wheelVelocityControlParameters);

mWheelYVelocityControl.setParameters(mConfig.wheelVelocityControlParameters);

}

如果是第一次配置或者是改变速度,需要updatepointerspeed

boolresetNeeded=false;

if(!changes||(changes&(InputReaderConfiguration::CHANGE_DISPLAY_INFO

|InputReaderConfiguration::CHANGE_POINTER_GESTURE_ENABLEMENT

|InputReaderConfiguration::CHANGE_SHOW_TOUCHES))){

//Configuredevicesources,surfacedimensions,orientationand

//scalingfactors.

configureSurface(when,&resetNeeded);

}

如果是第一次配置或者是显示等改变,需要调用configureSurface

TouchInputMapper::configureSurface

//Determinedevicemode.

if(mParameters.deviceType==Parameters::DEVICE_TYPE_POINTER

&&mConfig.pointerGesturesEnabled){

mSource=AINPUT_SOURCE_MOUSE;

mDeviceMode=DEVICE_MODE_POINTER;

}elseif(mParameters.deviceType==Parameters::DEVICE_TYPE_TOUCH_SCREEN

&&mParameters.associatedDisplayId>=0){

mSource=AINPUT_SOURCE_TOUCHSCREEN;

mDeviceMode=DEVICE_MODE_DIRECT;

}else{

mSource=AINPUT_SOURCE_TOUCHPAD;

mDeviceMode=DEVICE_MODE_UNSCALED;

}

根据mParameters.deviceType决定mSourcemDeviceMode

//EnsurewehavevalidXandYaxes.

if(!mRawPointerAxes.x.valid||!mRawPointerAxes.y.valid){

LOGW(INDENT"Touchdevice'%s'didnotreportsupportforXorYaxis!"

"Thedevicewillbeinoperable.",getDeviceName().string());

mDeviceMode=DEVICE_MODE_DISABLED;

return;

}

判断x和y参数是否有效,这里就是判断触摸屏x和y的坐标范围的,在eventhub里面,只要最大和最小不相等,就是有效的。如果无效,设备模式就是关闭的,不能使用。

//Getassociateddisplaydimensions.

if(mParameters.associatedDisplayId>=0){

if(!mConfig.getDisplayInfo(mParameters.associatedDisplayId,

mParameters.associatedDisplayIsExternal,

&mAssociatedDisplayWidth,&mAssociatedDisplayHeight,

&mAssociatedDisplayOrientation)){

LOGI(INDENT"Touchdevice'%s'couldnotquerythepropertiesofitsassociated"

"display%d.Thedevicewillbeinoperableuntilthedisplaysize"

"becomesavailable.",

getDeviceName().string(),mParameters.associatedDisplayId);

mDeviceMode=DEVICE_MODE_DISABLED;

return;

}

}

根据associatedDisplayIdassociatedDisplayIsExternal得到显示屏的分辨率,associatedDisplayIdconfigureParameters里面设为0,associatedDisplayIsExternal根据触摸屏类型和bus设为1.

调用InputReaderConfiguration::getDisplayInfo得到设置好的surfacesize

if(displayId==0){

constDisplayInfo&info=external?mExternalDisplay:mInternalDisplay;

if(info.width>0&&info.height>0){

if(width){

*width=info.width;

}

if(height){

*height=info.height;

}

if(orientation){

*orientation=info.orientation;

}

returntrue;

}

}

可以看到android4.0里面,分内部和外部分辨率两种。如果info里面都是0,这个函数返回false,就表示android设备还没有走到设置surfacesize这一步,就会打印提示信息,稍后android启动里面就会执行设置surfacesize的程序。

//Configuredimensions.

int32_twidth,height,orientation;

if(mDeviceMode==DEVICE_MODE_DIRECT||mDeviceMode==DEVICE_MODE_POINTER){

width=mAssociatedDisplayWidth;

height=mAssociatedDisplayHeight;

orientation=mParameters.orientationAware?

mAssociatedDisplayOrientation:DISPLAY_ORIENTATION_0;

}else{

width=mRawPointerAxes.x.maxValue-mRawPointerAxes.x.minValue+1;

height=mRawPointerAxes.y.maxValue-mRawPointerAxes.y.minValue+1;

orientation=DISPLAY_ORIENTATION_0;

}

根据mDeviceMode类型设置宽和高参数,根据上面的分析宽和高就是取自显示屏的分辨率。

boolsizeChanged=mSurfaceWidth!=width||mSurfaceHeight!=height;

if(sizeChanged||deviceModeChanged){

LOGI("Devicereconfigured:id=%d,name='%s',surfacesizeisnow%dx%d,modeis%d",

getDeviceId(),getDeviceName().string(),width,height,mDeviceMode);

mSurfaceWidth=width;

mSurfaceHeight=height;

//ConfigureXandYfactors.

mXScale=float(width)/(mRawPointerAxes.x.maxValue-mRawPointerAxes.x.minValue+1);

mYScale=float(height)/(mRawPointerAxes.y.maxValue-mRawPointerAxes.y.minValue+1);

mXPrecision=1.0f/mXScale;

mYPrecision=1.0f/mYScale;

如果显示屏分辨率不等于开始保存的,就需要重新设置一些参数。红色部分就是显示屏分辨率和触摸屏坐标范围得到的转换因子,使用这个转换因子就可以把触摸屏坐标转换成屏幕坐标。

接着下面是根据校准参数配置校准因子的。这里我们不使用这种方式,所以不执行

下面如果sizeChanged改变,重新配置一些参数,同时设置

*outResetNeeded=true;

至此configureSurface执行完成,与触摸屏坐标有关的配置也完成了。

if(changes&&resetNeeded){

//Sendreset,unlessthisisthefirsttimethedevicehasbeenconfigured,

//inwhichcasethereaderwillcallresetitselfafterallmappersareready.

getDevice()->notifyReset(when);

}

如果有改变而且需要reset,reader将reset自己

至此InputDevice的configure和mapperconfigure都已完成完成

配置完成就要进行初始化

device->reset(when);

调用InputDevice::reset

mapper->reset(when)

mapper初始化

至此整个InputReader::addDeviceLocked已经分析完成了,到了这一步,我们的整个input系统都已经准备好去接收真正的inputevent并处理。

分析设备删除,其代码如下:

voidInputReader::removeDeviceLocked(nsecs_twhen,int32_tdeviceId){

InputDevice*device=NULL;

ssize_tdeviceIndex=mDevices.indexOfKey(deviceId);

if(deviceIndex>=0){

device=mDevices.valueAt(deviceIndex);

mDevices.removeItemsAt(deviceIndex,1);

把设备从mDevices链表里面移除

}else{

LOGW("IgnoringspuriousdeviceremovedeventfordeviceId%d.",deviceId);

return;

}

if(device->isIgnored()){

LOGI("Deviceremoved:id=%d,name='%s'(ignorednon-inputdevice)",

device->getId(),device->getName().string());

}else{

LOGI("Deviceremoved:id=%d,name='%s',sources=0x%08x",

device->getId(),device->getName().string(),device->getSources());

}

device->reset(when);

清除device配置

deletedevice;

删除device

}

InputReader::processEventsLocked设备增加、删除处理总结:

它负责处理inputreader层Device增加、删除事件。增加事件的流程为:为一个新增的Device创建一个InputDevice,并增加到InputReader::mDevices中;根据新增加设备的class类别,创建对应的消息转换器(InputMapper),然后此消息转换器加入InputDevice::mMappers中。消息转换器负责把读取的RawEvent转换成特定的事件,以供应用程序使用。

EventHub与InputReader各自管理功能:

lEventHub管理一堆Device,每一个Device与Kernel中一个事件输入设备对应

lInputReader管理一堆InputDevice,每一个InputDevice与EventHub中的Device对应

lInputDevice管理一些与之相关的InputMapper,每个device类型不同,会有一个InputMapper或者多个InputMapper,如我们touch只有:SingleTouchInputMapper。

下面再来看看inputreader里面touch类的关系

2.7.2处理事件准备设置surfacesize

上一节讲到inputdevicetouchmapper配置时,会得到surfacesize,如果得到为0,就会把mDeviceMode配置为DEVICE_MODE_DISABLED,表示这个设备暂时无法使用,因此即使我们的input系统都准备好了,但是touch还是无法使用。下面讲如何配置surfacesize

在2.3节线程创建里面看到:

SystemServer.java(frameworks\base\services\java\com\android\server)里面

ServerThread::run调用

Slog.i(TAG,"WindowManager");

wm=WindowManagerService.main(context,power,

factoryTest!=SystemServer.FACTORY_TEST_LOW_LEVEL,

!firstBoot);

ServiceManager.addService(Context.WINDOW_SERVICE,wm);

下面接着就会调用

try{

wm.displayReady();wm就是上面创建的,为后面整个显示做准备

}catch(Throwablee){

reportWtf("makingdisplayready",e);

}

WindowManagerService.java(frameworks\base\services\java\com\android\server\wm)里面

displayReady里面调用

WindowManagerwm=(WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);

mDisplay=wm.getDefaultDisplay();

mInitialDisplayWidth=mDisplay.getRawWidth();

mInitialDisplayHeight=mDisplay.getRawHeight();

得到原始显示分辨率,这时就是整个屏幕的分辨率

mInputManager.setDisplaySize(Display.DEFAULT_DISPLAY,

mDisplay.getRawWidth(),mDisplay.getRawHeight(),

mDisplay.getRawExternalWidth(),mDisplay.getRawExternalHeight());

mInputManager是在WindowManagerService一开始就创建的,我们来看看setDisplaySize里面后四个参数:

publicintgetRawWidth(){

intw=getRawWidthNative();

if(DEBUG_DISPLAY_SIZE)Slog.v(

TAG,"Returningrawdisplaywidth:"+w);

returnw;

}

privatenativeintgetRawWidthNative();

publicintgetRawHeight(){

inth=getRawHeightNative();

if(DEBUG_DISPLAY_SIZE)Slog.v(

TAG,"Returningrawdisplayheight:"+h);

returnh;

}

privatenativeintgetRawHeightNative();

这两个函数要调用native空间函数

android_view_Display.cpp(frameworks\base\core\jni)

staticjintandroid_view_Display_getRawWidthNative(

JNIEnv*env,jobjectclazz)

{

DisplayIDdpy=env->GetIntField(clazz,offsets.display);

returnSurfaceComposerClient::getDisplayWidth(dpy);

}

staticjintandroid_view_Display_getRawHeightNative(

JNIEnv*env,jobjectclazz)

{

DisplayIDdpy=env->GetIntField(clazz,offsets.display);

returnSurfaceComposerClient::getDisplayHeight(dpy);

}

可以看到通过surface的client端得到屏幕的分辨率。

如何得到外部分辨率

publicintgetRawExternalWidth(){

return1280;

}

/**

*IfthedisplayismirroredtoanexternalHDMIdisplay,returnsthe

*heightofthatdisplay.

*@hide

*/

publicintgetRawExternalHeight(){

return720;

}

可以看到如果设备外界hdmi显示,就用1280*720分辨率,这里有个疑问:如果客户设备外的是1920*1080的hdmi,那么这两个值是否需要变化?而且看这两个函数用处,只有触摸坐标转换和HeightReceiver.java使用,如果使用外部分辨率,那么android显示系统是如何知道的?

继续看mInputManager.setDisplaySize

InputManager.java(frameworks\base\services\java\com\android\server\wm)里面

setDisplaySize调用

nativeSetDisplaySize(displayId,width,height,externalWidth,externalHeight);

从下面开始就进入native空间

com_android_server_InputManager.cpp(frameworks\base\services\jni)里面

nativeSetDisplaySize对应android_server_InputManager_nativeSetDisplaySize调用

gNativeInputManager->setDisplaySize(displayId,width,height,externalWidth,externalHeight);

setDisplaySize里面判断

if(mLocked.displayWidth!=width||mLocked.displayHeight!=height){

changed=true;

mLocked.displayWidth=width;

mLocked.displayHeight=height;

sp<PointerController>controller=mLocked.pointerController.promote();

if(controller!=NULL){

controller->setDisplaySize(width,height);

}

}

if(mLocked.displayExternalWidth!=externalWidth

||mLocked.displayExternalHeight!=externalHeight){

changed=true;

mLocked.displayExternalWidth=externalWidth;

mLocked.displayExternalHeight=externalHeight;

}

如果这次设置的值和旧值相等,就什么也不做退出。如果不相等,设置changed=true,同时保存新的值

if(changed){

mInputManager->getReader()->requestRefreshConfiguration(

InputReaderConfiguration::CHANGE_DISPLAY_INFO);

}

如果值有变化,就调用inputreader刷新配置,提示是displayinfo改变

首先是去InputManager.cpp(frameworks\base\services\input)文件里面

getReader()

returnmReader;

这个就是InputManager创建是创建的inputreader

InputReader.cpp(frameworks\base\services\input)里面

InputReader::requestRefreshConfiguration调用

if(changes){

boolneedWake=!mConfigurationChangesToRefresh;

mConfigurationChangesToRefresh|=changes;

if(needWake){

mEventHub->wake();

}

}

如果改变类型不为0,就mConfigurationChangesToRefresh取反送给needWakemConfigurationChangesToRefresh表示需要改变配置的类型集合,初始化为0,因此needWake就为1,同时把改变类型与给mConfigurationChangesToRefresh,接着判断needWake,如果为1,就进入mEventHub的唤醒程序。

这段话意思就是如果有正在改变配置需求,就表明整个input系统正在运行,所以不需要唤醒。只需要把新的改变类型放在mConfigurationChangesToRefresh就行了,如果没有,那么input系统有可能在睡眠,为了快速响应改变,需要唤醒整个input系统。

EventHub.cpp(frameworks\base\services\input)里面

EventHub::wake()调用

nWrite=write(mWakeWritePipeFd,"W",1);

直接向mWakeWritePipeFd管道里面写一个字符。前面2.5节讲到mWakeReadPipeFd已经被mEpollFd监控了,向mWakeWritePipeFd写就会引起mWakeReadPipeFd变化。在EventHub::getEvents里面就会执行if(eventItem.data.u32==EPOLL_ID_WAKE)这个分支,设置awokentrue,当mPendingEventItems事件处理完,就会判断awoken,如果为true就立即结束循环,返回给inputreader进行处理。

至此,配置surfacesize执行部分结束了,但是size并没有真正配置到mExternalDisplaymInternalDisplay里面,只是改变类型放在mConfigurationChangesToRefresh里面,真正的size还保存在NativeInputManagermLocked里面

这时并没有输入事件或者设备变化发生,因此InputReader::loopOnce很快结束,进入下一次循环

InputReader::loopOnce接着调用

uint32_tchanges=mConfigurationChangesToRefresh;

if(changes){

mConfigurationChangesToRefresh=0;

refreshConfigurationLocked(changes);

}

InputReader::refreshConfigurationLocked

mPolicy->getReaderConfiguration(&mConfig);

mPolicy就是NativeInputManager的对象,在创建inputreader传入的。

com_android_server_InputManager.cpp(frameworks\base\services\jni)里面

NativeInputManager::getReaderConfiguration调用

{//acquirelock

AutoMutex_l(mLock)

outConfig->setDisplayInfo(0,false/*external*/,

mLocked.displayWidth,mLocked.displayHeight,mLocked.displayOrientation);

outConfig->setDisplayInfo(0,true/*external*/,

mLocked.displayExternalWidth,mLocked.displayExternalHeight,

mLocked.displayOrientation);

}//releaselock

outConfig就是inputreader里面的mConfig,因此调用

InputReaderConfiguration::setDisplayInfo

if(displayId==0){

DisplayInfo&info=external?mExternalDisplay:mInternalDisplay;

info.width=width;

info.height=height;

info.orientation=orientation;

}

看到没有,饶了一个大圈,这里才把surfacesize真正放在mExternalDisplaymInternalDisplay里面,供后面调用InputReaderConfiguration::getDisplayInfo时使用。

refreshConfigurationLocked函数会在inputreader创建时执行一次,但那个时候inputdevice还没有创建,而且changes为0,因此不会执行下面的部分。

if(changes&InputReaderConfiguration::CHANGE_MUST_REOPEN){

mEventHub->requestReopenDevices();

如果改变类型是reopen,就调用eventhub的requestReopenDevices

EventHub::requestReopenDevices里面设置mNeedToReopenDevices=true,这个会在EventHub::getEvents里面进行判断,前面2.6节已经讲了

}else{

for(size_ti=0;i<mDevices.size();i++){

InputDevice*device=mDevices.valueAt(i);

device->configure(now,&mConfig,changes);

}

}

得到所有的device,循环调用每个device的configure去重新配置。

InputDevice::configure已经在InputReader::addDeviceLocked讲过了,刚开始第一次配置changes是0,表示需要全面的初始化。现在只需要配置改变的部分了。

至此,我们整个inputtouch的工作环境已经配置好了,就等有触摸事件发生然后处理了

2.7.3处理来自于事件驱动设备的事件

InputReader::processEventsForDeviceLocked它负责处理来自于同一个设备且在mEventBuffer中连续的多个事件,其函数原型如下:

ssize_tdeviceIndex=mDevices.indexOfKey(deviceId);

if(deviceIndex<0){

LOGW("DiscardingeventforunknowndeviceId%d.",deviceId);

return;

}

得到发生事件设备索引

InputDevice*device=mDevices.valueAt(deviceIndex);

根据索引得到发生事件的device

if(device->isIgnored()){

//LOGD("DiscardingeventforignoreddeviceId%d.",deviceId);

return;

}

如果device没有mapper,就返回不做任何处理。

device->process(rawEvents,count);

调用process处理

InputDevice::process

for(constRawEvent*rawEvent=rawEvents;count--;rawEvent++)

一次取出每一个事件

for(size_ti=0;i<numMappers;i++){

InputMapper*mapper=mMappers[i];

mapper->process(rawEvent);

}

对每一个事件都用这个device所有mapper进行处理

从上面的代码中可以看出,在InputDevice::process中,对于传入的每一个RawEvent,依次调用InputDevice中的每一个InputMapper来进行处理。前面提到过,InputDevice包含一组处理对应设备事件InputMapper,现在这些InputMapper开始干活了。

因为我们的touch只有一个SingleTouchInputMapper

这里先说说单点touch需要处理事件集合

代码:

input_report_abs(myInputDev,ABS_X,event->x);
input_report_abs(myInputDev,ABS_Y,event->y);

产生的事件:*type,code,value
EV_ABS,ABS_X,event->x
EV_ABS,ABS_Y,event->y

代码:

input_report_key(myInputDev,BTN_TOUCH,1);
产生的事件:*type,code,value
EV_KEY,BTN_TOUCH,1

代码:

input_sync(myInputDev);
它调用input_event(dev,EV_SYN,SYN_REPORT,0);
产生的事件:*type,code,value
EV_SYN,SYN_REPORT,0

SingleTouchInputMapper::process调用

TouchInputMapper::process(rawEvent);

TouchInputMapper::process

mCursorButtonAccumulator.process(rawEvent);

因为是touchrawEvent->typeEV_KEY,但是rawEvent->scanCode不匹配里面任何值,不起任何作用

mCursorScrollAccumulator.process(rawEvent);

因为是touchrawEvent->typeEV_KEY,不是EV_REL,不起任何作用

mTouchButtonAccumulator.process(rawEvent);

TouchButtonAccumulator::process

if(rawEvent->type==EV_KEY){

switch(rawEvent->scanCode){

caseBTN_TOUCH:

mBtnTouch=rawEvent->value;

break;

可以看到把BTN_TOUCH的值放在mBtnTouch里面

接着处理坐标信息:

mSingleTouchMotionAccumulator.process(rawEvent)

SingleTouchMotionAccumulator::process

caseABS_X:

mAbsX=rawEvent->value;

break;

caseABS_Y:

mAbsY=rawEvent->value;

break;

将坐标信息保存在mAbsXmAbsY里面

BTN_TOUCHABS_XABS_Y处理完,接下来就会处理EV_SYN事件

TouchInputMapper::process里面,

if(rawEvent->type==EV_SYN&&rawEvent->scanCode==SYN_REPORT){

sync(rawEvent->when);

}

TouchInputMapper::sync调用

syncTouch(when,&havePointerIds)

SingleTouchInputMapper::syncTouch

if(mTouchButtonAccumulator.isToolActive()){

判断BTN_TOUCH是否等于1,即是否有touchdown,如果有进入下面处理

mCurrentRawPointerData.pointerCount=1;设置触摸点数1

mCurrentRawPointerData.idToIndex[0]=0;触摸点索引为0

RawPointerData::Pointer&outPointer=mCurrentRawPointerData.pointers[0];

outPointer.id=0;

outPointer.x=mSingleTouchMotionAccumulator.getAbsoluteX();

outPointer.y=mSingleTouchMotionAccumulator.getAbsoluteY();

outPointer.pressure=mSingleTouchMotionAccumulator.getAbsolutePressure();

outPointer.touchMajor=0;

outPointer.touchMinor=0;

outPointer.toolMajor=mSingleTouchMotionAccumulator.getAbsoluteToolWidth();

outPointer.toolMinor=mSingleTouchMotionAccumulator.getAbsoluteToolWidth();

outPointer.orientation=0;

outPointer.distance=mSingleTouchMotionAccumulator.getAbsoluteDistance();

outPointer.tiltX=mSingleTouchMotionAccumulator.getAbsoluteTiltX();

outPointer.tiltY=mSingleTouchMotionAccumulator.getAbsoluteTiltY();

outPointer.toolType=mTouchButtonAccumulator.getToolType();

if(outPointer.toolType==AMOTION_EVENT_TOOL_TYPE_UNKNOWN){

outPointer.toolType=AMOTION_EVENT_TOOL_TYPE_FINGER;

}

outPointer.isHovering=isHovering;

把相关的信息放在mCurrentRawPointerData.pointers[0]里面,这里主要是xy坐标

TouchInputMapper::sync继续处理

//Resetstatethatwewillcomputebelow.

mCurrentFingerIdBits.clear();

mCurrentStylusIdBits.clear();

mCurrentMouseIdBits.clear();

mCurrentCookedPointerData.clear();

这几个清零,后面填入相应的值

if(mDeviceMode==DEVICE_MODE_DISABLED){

//Dropallinputifthedeviceisdisabled.

mCurrentRawPointerData.clear();

mCurrentButtonState=0;

}

如果设备状态是关闭的,就把mCurrentRawPointerData保存的数据清除,返回

uint32_tpolicyFlags=0;

boolinitialDown=mLastRawPointerData.pointerCount==0

&&mCurrentRawPointerData.pointerCount!=0;

boolbuttonsPressed=mCurrentButtonState&~mLastButtonState;

if(initialDown||buttonsPressed){

//Ifthisisatouchscreen,hidethepointeronaninitialdown.

if(mDeviceMode==DEVICE_MODE_DIRECT){

getContext()->fadePointer();

}

//Initialdownsonexternaltouchdevicesshouldwakethedevice.

//Wedon'tdothisforinternaltouchscreenstopreventthemfromwaking

//upinyourpocket.

//TODO:Usetheinputdeviceconfigurationtocontrolthisbehaviormorefinely.

if(getDevice()->isExternal()){

policyFlags|=POLICY_FLAG_WAKE_DROPPED;

}

}

判断是不是第一次按下,如果是,Ifthisisatouchscreen,hidethepointeronaninitialdown

如果是外部设备,就唤醒整个系统,如果是内部设备,就不用唤醒,注释写的很清楚,有可能放在口袋里面误触摸唤醒系统

//Consumerawoff-screentouchesbeforecookingpointerdata.

//Iftouchesareconsumed,subsequentcodewillnotreceiveanypointerdata.

if(consumeRawTouches(when,policyFlags)){

mCurrentRawPointerData.clear();

如果是唤醒设备的点击,就把mCurrentRawPointerData清零,只需要唤醒设备就行了。

cookPointerData();

进行触摸坐标到原始坐标的转换

TouchInputMapper::cookPointerData

首先进行一系列的坐标校准,接下来进行坐标转换

//XandY

//Adjustcoordsforsurfaceorientation.

floatx,y;

switch(mSurfaceOrientation){

caseDISPLAY_ORIENTATION_90:

x=float(in.y-mRawPointerAxes.y.minValue)*mYScale;

y=float(mRawPointerAxes.x.maxValue-in.x)*mXScale;

orientation-=M_PI_2;

if(orientation<-M_PI_2){

orientation+=M_PI;

}

break;

caseDISPLAY_ORIENTATION_180:

x=float(mRawPointerAxes.x.maxValue-in.x)*mXScale;

y=float(mRawPointerAxes.y.maxValue-in.y)*mYScale;

break;

caseDISPLAY_ORIENTATION_270:

x=float(mRawPointerAxes.y.maxValue-in.y)*mYScale;

y=float(in.x-mRawPointerAxes.x.minValue)*mXScale;

orientation+=M_PI_2;

if(orientation>M_PI_2){

orientation-=M_PI;

}

break;

default:

x=float(in.x-mRawPointerAxes.x.minValue)*mXScale;

y=float(in.y-mRawPointerAxes.y.minValue)*mYScale;

break;

}

TouchInputMapper::configureSurface里面

通过mConfig.getDisplayInfo(mParameters.associatedDisplayId,

mParameters.associatedDisplayIsExternal,

&mAssociatedDisplayWidth,&mAssociatedDisplayHeight,

&mAssociatedDisplayOrientation))

得到mAssociatedDisplayOrientation的值,这个值是通过setDisplayInfomLocked.displayOrientation得到的。在NativeInputManager创建是初始化这个值

mLocked.displayOrientation=DISPLAY_ORIENTATION_0

orientation=mParameters.orientationAware?

mAssociatedDisplayOrientation:DISPLAY_ORIENTATION_0;

mParameters.orientationAwareidc文件里面的值,我们这里是1,即

orientation=mAssociatedDisplayOrientation默认为DISPLAY_ORIENTATION_0

boolorientationChanged=mSurfaceOrientation!=orientation;

if(orientationChanged){

mSurfaceOrientation=orientation;

}

mSurfaceOrientation=orientation=mAssociatedDisplayOrientation=mLocked.displayOrientation

因此在cookPointerData会执行

x=float(in.x-mRawPointerAxes.x.minValue)*mXScale;

y=float(in.y-mRawPointerAxes.y.minValue)*mYScale;

这就是坐标转换的地方,in.xin.y是触摸屏坐标,mRawPointerAxes.x.minValuemRawPointerAxes.y.minValue是触摸屏坐标范围最小值,mXScalemYScale就是

mXScale=float(width)/(mRawPointerAxes.x.maxValue-mRawPointerAxes.x.minValue+1);

mYScale=float(height)/(mRawPointerAxes.y.maxValue-mRawPointerAxes.y.minValue+1);

把它们和在一起就是http://source.android.com/tech/input/touch-devices.html这个里面说的:

Foratouchscreen,thesystemautomaticallyinterpolatesthereportedtouchpositionsinsurfaceunitstoobtaintouchpositionsindisplaypixelsaccordingtothefollowingcalculation:

displayX=(x-minX)*displayWidth/(maxX-minX+1)

displayY=(y-minY)*displayHeight/(maxY-minY+1)

接着把转换后的坐标放在out里面

PointerCoords&out=mCurrentCookedPointerData.pointerCoords[i];

out.clear();

out.setAxisValue(AMOTION_EVENT_AXIS_X,x);

out.setAxisValue(AMOTION_EVENT_AXIS_Y,y);

dispatchTouches(when,policyFlags)调用

if(currentIdBits==lastIdBits)

如果当前点id和上一次id相同,表明这是个移动事件,不是的话就判断是downup或者move,然后调用dispatchMotion,它的第四个参数就是downupmove等类型

在dispatchMotion中,根据cooked数据创建NotifyMotionArg对象,它描述了一个移动事件,接着调用TouchInputMapper::getListener()->notifyMotion(&args)

TouchInputMapper::getListener()调用mContext->getListener(),此mContextInputReader::mContext所以其getListener()返回的则为InputReader::mQueuedListener,则最后调用QueuedInputListener::notifyMotion

QueuedInputListener::notifyMotion(constNotifyMotionArgs*args){

mArgsQueue.push(newNotifyMotionArgs(*args));

}

把传递过来的NotifyMotionArg参数复制一份,然后加入QueuedInputListener::mArgsQueue例表中

补充1)InputReader::mContext在构造时用自己的指针初始化了mContext,从而mContext::mReader则为此InputReader实例。
补充2)在InputReader::createDeviceLocked中创建InputDevice时,把自己的mContext作为参数传入,从而把它保存在InputDevice::mContext中;在创建InputMapper时,以InputDevice作为参数,且InputMapper把它保存在mDevice中,然后从把InputDevice中的mContext也保存在InputMapper的mContext中。

dispatchHoverEnterAndMove(when,policyFlags);

调用dispatchMotion

至此整个processEventsLocked处理流程结束,已经把来自于事件设备的事件处理之后放入到各种NotifyArgs(如NotifyMotionArgs)之中,然后把这些各种NotifyArgs加入InputReader::mQueuedListener::mArgsQueue链表中。接着InputReader::loopOnce调用

mQueuedListener->flush()

Flush函数就是要把mArgsQueue中的所有NotifyArgs进行处理。

voidQueuedInputListener::flush(){

size_tcount=mArgsQueue.size();

for(size_ti=0;i<count;i++){

NotifyArgs*args=mArgsQueue[i];

args->notify(mInnerListener);

deleteargs;

}

mArgsQueue.clear();

}

调用链表中每个NotifyArgs的notify函数,且有一个有意思的参数mInnerListener,这个参数在前面多次提到过,它是在创建mQueuedListener时提供的,它其实就是InputManager中的mDispatcher,前面一直在InputReader中打转转,现在终于看到InputDispatcher登场了,说明事件很快就可以谢幕了。

再向下看一下吧,这么多类NotifyArgs,为描述方便,下面以NotifyMotionArgs为例,其代码为:

voidNotifyMotionArgs::notify(constsp<InputListenerInterface>&listener)const{

listener->notifyMotion(this);

}

下面就看看InputDispatcher(mDispatcher)的notifyMotion函数做了些什么。这个InputDispatcher::notifyMotion(constNotifyMotionArgs*args)可就不简单了。

在InputDispatcher::notifyMotion中,
1)根据NotifyMotionArgs提供的信息,构造一个MotionEvent,再调用mPolicy->filterInputEvent看是否需要丢弃此事件,如果需要丢弃则马上返加。其中mPolicy为NativeInputManager实例,在构造InputDispatcher时提供的参数。

2)对于AMOTION_EVENT_ACTION_MOVE事件,则从mInboundQueue队列里面寻找到对应的entry,把args信息放在这个entry里面

3)对于AMOTION_EVENT_ACTION_UP或AMOTION_EVENT_ACTION_DOWN事件,则直接根据NotifyMotionArgs提供的信息,构造一个MotionEntry。

4)调用InputDispatcher::enqueueInboundEventLocked把新构造的MotionEntry添加到InputDispatcher::mInboundQueue中,并返回是否需要唤醒mLooper<向pipe中写入数据>的标识。

if(needWake){

mLooper->wake();

}

根据表示唤醒mLooper

以上操作都是在InputReader线程中完成的,现在应该InputDispatcher线程开始工作了。

至此InputReader::loopOnce一次循环结束,所有的input事件已经处理并放在了InputDispatcher::mInboundQueue里面

事件处理相关数据结构如下图所示:

至此的消息结构变化流程:

2.8分发事件

前面线程启动提到InputDispatcher::dispatchOnce调用

mLooper->pollOnce(timeoutMillis);

其功能为等待超时或被pipe唤醒(InputReader线程调用InputDispatcher::notifyMotion时,InputDispatcher::notifyMotion根据情况调用mLooper->wake)。

其调用流程如下:

mLooper->pollOnce(inttimeoutMillis)->

Looper.cpp(frameworks\base\libs\utils)

Looper::pollOnce(inttimeoutMillis,int*outFd,int*outEvents,void**outData)

如果没有事件输入,那么InputDispatcher::dispatchOnce就会被阻塞在pollOnce,调用mLooper->wake唤醒。就会重新执行dispatchOnce,就会调用dispatchOnceInnerLocked

InputDispatcher::dispatchOnceInnerLocked

1)从mInboundQueue从中依次取出EventEntry<MotionEntry的基类>

2)调用InputDispatcher::dispatchMotionLocked处理此MotionEntry

3)调用InputDispatcher::dispatchEventToCurrentInputTargetsLocked

对于InputDispatcher::mCurrentInputTargets中的每一个InputTarget,并获取对应的Connection,调用InputDispatcher::prepareDispatchCycleLocked,

InputDispatcher::dispatchEventToCurrentInputTargetsLocked

for(size_ti=0;i<mCurrentInputTargets.size();i++){

constInputTarget&inputTarget=mCurrentInputTargets.itemAt(i);

ssize_tconnectionIndex=getConnectionIndexLocked(inputTarget.inputChannel);

if(connectionIndex>=0){

sp<Connection>connection=mConnectionsByReceiveFd.valueAt(connectionIndex);

prepareDispatchCycleLocked(currentTime,connection,eventEntry,&inputTarget,

resumeWithAppendedMotionSample);

}else{

#ifDEBUG_FOCUS

LOGD("Droppingeventdeliverytotargetwithchannel'%s'becauseit"

"isnolongerregisteredwiththeinputdispatcher.",

inputTarget.inputChannel->getName().string());

#endif

}

}

InputDispatcher::prepareDispatchCycleLocked

1)调用enqueueDispatchEntryLocked创建DispatchEntry对象,并把它增加到Connection::outboundQueue队列中。

2)调用activateConnectionLocked把当前Connection增加到InputDispatcher::mActiveConnections链表中

3)调用InputDispatcher::startDispatchCycleLocked,接着它调用Connection::inputPublisher.publishMotionEvent来发布事件到ashmembuffer中,调用Connection::inputPublisher.sendDispatchSignal发送一个dispatch信号到InputConsumer通知它有一个新的消息到了,快来消费吧!

3内核层驱动

请参考网上的linux内核input子系统解析看下面的图:

这里只简单说一下驱动里面触摸屏事件注册

input_dev->evbit[0]=BIT_MASK(EV_KEY)|BIT_MASK(EV_ABS);

input_dev->keybit[BIT_WORD(BTN_TOUCH)]=BIT_MASK(BTN_TOUCH);

input_set_abs_params(input_dev,ABS_X,0,32767,0,0);

input_set_abs_params(input_dev,ABS_Y,0,32767,0,0);

input_register_device(input_dev);

事件发送

input_report_key(usbtouch->dev,BTN_TOUCH,touch);

input_report_abs(usbtouch->dev,ABS_X,x);

input_report_abs(usbtouch->dev,ABS_Y,y);

input_sync(usbtouch->dev);

更多相关文章

  1. android 开发使用 kotlin 进行点击事件监听和界面跳转,直接传也方
  2. android 主线程与分线程 做同步
  3. Android使用EventBus传递事件
  4. mono android 非UI线程操作UI线程
  5. android 按钮的四种点击事件
  6. 【Android】监听自定义通知栏消息事件
  7. 疯狂android讲义---事件处理2
  8. Android中如何使用基于监听的事件处理(上)

随机推荐

  1. Android绘制简单折线图的步骤
  2. 四.Android六种布局详细讲解
  3. android 百度地图3.0+常用操作
  4. 怎么去掉联系人、通话记录、拨号列表界面
  5. Android系统下如何在程序中对XML里面元素
  6. Android(安卓)读取资源文件实例详解
  7. APK_获取Android的APK包签名信息
  8. Android(安卓)屏幕设置
  9. Android开源图表库介绍
  10. android启动后根文件系统分析