Android输入事件处理过程分析

Android输入事件处理过程分析 & 锁屏方法

  (2010-03-26 16:10:32)
标签: 

it

 

引子:WaveSecure是个安全软件,它拿到了ADC2的全球所有类别的第三名。猜测 其 锁屏 功能的实现原理,可能涉及到以下的内容。

参考词:远程锁屏 手机防盗

====================================================================

以下代码尝试 截获 HOME 键,但没有效果:
@Override
public void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     // 下一行 需要 android.permission.DISABLE_KEYGUARD 权限:
     getWindow_r().setType( WindowManager.LayoutParams.TYPE_KEYGUARD);   // NOT setFlags(int,int)
     setContentView(R.layout.main);
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
     if(keyCode == KeyEvent.KEYCODE_HOME) {
           return true;
     }
     return super.onKeyDown(keyCode, event);
}

但是以下代码是可以禁止HOME键对应用的影响(需API 5以上):
       @Override
       public void onAttachedToWindow() {
               this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD);
               super.onAttachedToWindow();
       }

注:HOME键会被android framework处理,除非程序的window是TYPE_KEYGUARD类型。
android.permission.DISABLE_KEYGUARD 是说你是否有权限禁止keyguard,比如你现在keyguard打开,就是说你需要输入密码才能进入系统,那么来电话了怎么办,等你输入完密码,人家都等不及都挂了,因此Phone程序可以在有电话的时候禁止keyguard,直接弹出来接电话的界面。等接完电话再恢复keyguard。应用程序Phone的InCallScreen.java文件的OnCreate()中有如下代码:
               // set this flag so this activity will stay in front of the keyguard
               int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
               if (app.getPhoneState() == Phone.State.OFFHOOK) {
                       flags |= WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
               }
               getWindow().addFlags(flags);
另外可参考WindowManagerService.java和PhoneWindowManager.java。

===============================================================

补充:

在写程序时,需要捕获KEYCODE_HOME、KEYCODE_ENDCALL、KEYCODE_POWER这几个按键,但是这几个按键系统做了特殊处理,在进行dispatch之前做了一些操作,HOME除了Keygaurd之外,不分发给任何其它App;ENDCALL 和POWER也类似,所以需要我们 ‘在系统处理之前’ 进行处理。

可选的做法是自己定义一个FLAG,在自己的程序中添加此FLAG,然后在 frameworks/base/services/java/com/android/server/WindowManagerService.java 中获取当前窗口的FLAG属性;如果是我们自己设置的那个FLAG,则不进行特殊处理,直接分发按键消息到我们的APP当中,由APP自己处理。

这部分代码最好添加在

    @Override
    boolean preprocessEvent(InputDevice device, RawInputEvent event)

方法中,这个方法是KeyInputQueue中的一个虚函数,在处理按键事件之前进行“预处理”。WindowManagerService.java中有KeyInputQueue的实现类:

         private class KeyQ extends KeyInputQueue  implements KeyInputQueue.FilterCallback {...}

Ref: 对HOME键的处理参考frameworks/policies/base/phone/com/android/internal/policy/impl/PhoneWindowManager.java中的interceptKeyTi()方法。


===============================================================

(ref  http://www.linuxidc.com/Linux/2011-04/35045.htm )

框架总览(from  http://blog.csdn.net/windskier/article/details/6966264#comments)

Android输入事件处理过程分析 <wbr>& <wbr>锁屏方法




Android系统(2.2)输入事件处理过程

设备文件的打开

[文件frameworks/base/libs/ui/EventHub.cpp] 在系统启动后,android 会构建一个EvnetHub,并通过它进行所有输入事件的集中:

         static const char *device_path = "/dev/input";

        注: 文件/proc/bus/input/devices包含了所有输入设备的基本信息,如(内核定义的)设备名、支持的输入类型、总线接口位置、对应的/dev/input/下的文件名(handler)、各种flag等。

       可通过以下命令获得当前在用的键盘列表:

               adb shell getprop | grep "hw.keyboards"

       可能的输出如下:

          [hw.keyboards.65536.devname]: [LGTCN LGT FlyMouse]
          [hw.keyboards.65537.devname]: [LGTCN LGT FlyMouse]
          [hw.keyboards.65538.devname]: [adc_keypad]
          [hw.keyboards.65539.devname]: [aml_keypad]

       在文EventHub.cpp中,Android还通过变量mExcludedDevices禁用了一些输入设备

在函数EventHub::openPlatformInput()中,EventHub会通过Linux的系统调用inotify_add_watch()来监视此目录下的设备文件的变化,然后调用scan_dir(device_path)和open_device(const char *deviceName) 打开所有已存在的设备文件以准备接收外来的输入。这个监视设备变化的notify FD和这些已打开的设备FD都保存在mFDs[]数组中,在函数EventHub::getEvent()中通过poll轮询这些文件FD来获得新的输入事件。

通过下面的函数打开设备,并加载key code的映射:
int  EventHub::open_device( const   char   * deviceName)    // Android2.3.4中此函数或改 为openDevice(.)

    ... 
    fd 
=  open(deviceName, O_RDWR);
    if(ioctl(fd, EVIOCGVERSION, &version)) {...}
    if(ioctl(fd, EVIOCGID, &id)) {...}
    if(ioctl(fd, EVIOCGNAME(sizeof(name) - 1), &name) < 1) {...}    //得到设备文件/dev/input/eventX的驱动程序给出的描述性设备名(其中可能含有空格),
而不是/proc/bus/input/devices中的名字 ;目录/dev/input/by-id下会显示相应设备的总线和驱动名,只不过 描述性 驱动名中的 空格都替换成了'_',下同。
    ... 
    mFDs = new_mFDs;
    mDevices = new_devices;
    ...
    mFDs[mFDCount].fd 
=  fd; 
    mFDs[mFDCount].events 
=  POLLIN; 
    ... 
    ioctl(mFDs[mFDCount].fd, EVIOCGNAME(
sizeof (devname) - 1 ), devname); 
    ...
    if ((device->classes&CLASS_KEYBOARD) != 0) {
         // 依次尝试加载以下*.kl文件,
成功 加载某个文件后即停止尝试以后的文件
         //   1. 
  persist.sys.keylayout属性指定的.kl文件(可在源码的device/$MANUFACTUROR/$PRODUCT_NAME/init.rc或device/ $MANUFACTUROR/ $PRODUCT_NAME/system.prop中指定,最终会体现在运行系统的/system/build.prop文件中;也可在 运行 系统的 /data/property/$PROP_NAME 文件中指定 );注意:此属性指定的文件一定要安装到运行系统的system/usr/keylayout/目录下,可在文件 device/$MANUFACTUROR/$PRODUCT_NAME/$PRODUCT_NAME.mk文件的变量PRODUCT_COPY_FILES中增加对此文件的拷贝(必要时对应的.kcm文件也要拷贝)。
         //   2.   通过设备驱动中给出的描述性名字找到 对应的.kl文件,如对应设备"MY KBD"的文件名应该为MY_KBD.kl (注意对应文件的安装,见上一个文件的处理过程);
         //   3.  使用default的.kl文件,即
/system/usr/keylayout/qwerty.kl。

        // replace all the spaces with underscores:规范化设备的描述性名字
        strcpy(tmpfn, name);
        for (char *p = strchr(tmpfn, ' '); p && *p; p = strchr(tmpfn, ' '))
                                *p = '_';

       
  const   char *  root  =   getenv_r( " ANDROID_ROOT " );
        property_get("persist.sys.keylayout", keylayout, tmpfn);  //首先尝试persist property来指定.kl文件;如果此property不存在,则使用
规范化设备的描述性名字。
        snprintf(keylayoutFilename,  sizeof (keylayoutFilename), 
                      
  " %s/usr/keylayout/%s.kl " root, tmpfn);
        strcpy(devname, keylayout);        //保存所使用的layout
文件,实际上是所用的设备名
        bool  defaultKeymap = access(keylayoutFilename, R_OK);
        if (defaultKeymap) {
             //
  如果不能访问 刚才算出来的 layout文件,就使用默认的layout文件(/system/usr/keylayout/qwerty.kl):
            snprintf(keylayoutFilename, sizeof(keylayoutFilename),
                     "%s/usr/keylayout/%s", root, "qwerty.kl");
            strcpy(devname, "qwerty");    
  //保存所使用的layout文件,实际上是所用的设备名
                       defaultKeymap = true;
         }  
         device
-> layoutMap -> load(keylayoutFilename);   // 加载scan code表,并构建它 与 key code的映射表
         ...
        char propName[100];
        sprintf(propName, "hw.keyboards.%u.devname", device->id);
        property_set(propName, name);       //设定Android属性,以通知系统所使用的键盘设备列表。

         // See if this has a DPAD:
         ...
    }

 

打开设备的时候,如果 device->classes & CLASS_KEYBOARD 不等于 0, 则表明是它是个键盘设备。

输入设备的类型定义为(见frameworks/base/include/ui/EventHub.h):
enum { 
        CLASS_KEYBOARD             = 0x00000001,  //键盘
        CLASS_ALPHAKEY              = 0x00000002,  // ?
        CLASS_TOUCHSCREEN    = 0x00000004,  //触摸屏
        CLASS_TRACKBALL           = 0x00000008  //轨迹球
};

对于生产厂商自定义的键盘,可通过上面的 ioctl 获得其设备名称,命令字 EVIOCGNAME 的定义在文件kernel/include/linux/input.h 中:

              #define EVIOCGNAME(len)   _IOC(_IOC_READ, 'E', 0x06, len)

在内核键盘驱动文件 drivers/input/keyboard/pxa27x_keypad.c 中定义了设备名称:pxa27x-keypad

static struct platform_driver pxa27x_keypad_driver = {
    .probe           = pxa27x_keypad_probe,
    .remove        = __devexit_p(pxa27x_keypad_remove),
    .suspend      = pxa27x_keypad_suspend,
    .resume        = pxa27x_keypad_resume,
    .driver           = {
           .name    = "pxa27x-keypad",
           .owner   = THIS_MODULE,
     },
};

前面函数open_device()中的ANDROID_ROOT 为环境变量,在android的命令模式下通过 printenv 可以知道它为: 'system'。所以 keylayoutFilename 为:/system/usr/keylayout/pxa27x-keypad.kl

===============================================================

键码转换: 扫描码向Android KeyCode的转换
Android通过键码标签完成扫描码向Android Key Code的转换,即:
                 -----------+------+--------------+----------+----------------------------------
                   扫描码     --->       键码标签             --->               Android Key Code
                                                   ^
                                                   |____*.kl文件
                                                                                                                     ^
                                                                                                                     |_____ KeycodeLabels.h
                 -----------+------+--------------+----------+----------------------------------

键盘布局定义文件(*.kl),如pxa27x-keypad.kl,定义了扫描码到键码标签的映射。(如果没有定义键盘布局定义文件,那么默认使用系统的 /system/usr/keylayout/qwerty.kl 文件。)键盘布局定义文件的格式如下:

NUMERIC  KEYS  3x4 
key   2     1  
key 
  3     2  
key 
  4     3  
key 
  5     4  
key 
  6     5  
key 
  7     6  
key 
  8     7  
key 
  9     8  
key 
10    9  
key 
11    0  
key 
83   POUND 
key 
55   STAR 

FUNCTIONAL KEYS 
key 
231   MENU                   WAKE_DROPPED 
key 
192   BACK                    WAKE_DROPPED 
key 
193   HOME                   WAKE 
key 
107   DEL                       WAKE 
key 
102   CALL                     WAKE_DROPPED 
key 
158   ENDCALL             WAKE_DROPPED 
key 
28     DPAD_CENTER   WAKE 
key 
115   VOLUME_UP 
key 
114   VOLUME_DOWN

格式说明:

         'key’为一个键码定义的开始,一般在一行的开始;
         ‘158’为扫描码;
         ’BACK‘为键码标签;
         'WAKE_DROPPED‘为此键码的可选标志。


前面函数open_device()中,device->layoutMap->load(keylayoutFilename)实际是调用文件 frameworks/base/libs/ui/KeyLayoutMap.cpp中定义的以下函数来加载键盘布局的定义,并构建扫描码与Android KeyCode的映射表:

             status_t   KeyLayoutMap::load(const char* filename)

键码标签到Android KeyCode的映射定义在文件frameworks/base/include/ui/KeycodeLabels.h中,由KeyLayoutMap.cpp中的函数token_to_value()完成实际的映射:
         static int32_t  token_to_value(const char *literal, const KeycodeLabel *list);

函数KeyLayoutMap::load()是这样调用token_to_value()的:
          int   keycode  =  token_to_value(token.string(), KEYCODES);     // String8  token;

Android所支持的所有Key Code定义在文件frameworks/base/include/ui/KeycodeLabels.h中,同时文件frameworks/base/core/java/android/view/KeyEvent.java也给出了Java语言中的定义:

          static     const  KeycodeLabel   KEYCODES[] = {...};
          typedef    enum  KeyCode {...};
          static     const  KeycodeLabel   FLAGS[] = {...};

所以,要扩充Android支持的Key Code,需要修改以下文件:
  1)文件frameworks/base/include/ui/KeycodeLabels.h定义的KEYCODES数组(和enum KeyCode定义);
  2)文件frameworks/base/core/java/android/view/KeyEvent.java开头的常量定义;
  3)(for API Document) 文件frameworks/base/core/res/res/values/attrs.xml的<attr name="keycode">部分;
  4)文件development/cmds/monkey/src/com/android/commands/monkey/Monkey.java中的函数checkInternalConfiguration()。

Android通过解析键盘布局定义文件,把所支持的键盘的{scan_code,   {key_code, flags} }映射关系保存在以下向量中(文件frameworks/base/libs/ui/KeyLayoutMap.h):
                   KeyedVector<int32_t,Key>       m_keys;
在获得按键事件以后调用:
                     status_t       KeyLayoutMap::map(int32_t scancode, int32_t *keycode, uint32_t *flags)


这个map()函数会根据 键盘映射关系表 KeyedVector<int32_t,Key> m_keys 把扫描码转换成andorid framework及上层应用使用的统一键码,即Android Key Code。Android应用接收到的都是Android Key Code

====================================================================

输入事件的集中获取:EventHub

文件frameworks/base/libs/ui/EventHub.cpp的函数getEvent()通过对所有输入设备的轮询来获取输入事件

      bool EventHub::getEvent(           int32_t* outDeviceId,  int32_t* outType,
                      int32_t* outScancode, int32_t* outKeycode,  uint32_t *outFlags,
                      int32_t* outValue,         nsecs_t* outWhen)

这是一个c++函数,而Android中负责收集和分发输入事件的WindowManagerService是一个java进程。文件frameworks/base/services/jni/com_android_server_KeyInputQueue.cpp向 JAVA 提供了JNI函数android_server_KeyInputQueue_readEvent(),用于java代码 读取 输入事件。

static   sp<EventHub>     gHub;       // 系统级的全局变量!
static  jboolean android_server_KeyInputQueue_readEvent(JNIEnv *  env, jobject clazz, 
                                           jobject 
event )

    gLock.
lock (); 
    sp<EventHub> hub 
=  gHub; 
    
if  (hub  ==  NULL) 
        hub 
=   new  EventHub; 
        gHub 
=  hub; 
    
    gLock.unlock(); 
    int32_t     deviceId; 
    int32_t     type; 
    int32_t     scancode, keycode; 
    uint32_t   flags; 
    int32_t     value; 
    nsecs_t   when; 
    
bool  res  =  hub ->  getEvent( & deviceId,  & type,  & scancode,  & keycode, 
            
& flags,  & value,  & when); 
    env
-> SetIntField( event gInputOffsets.mDeviceId, (jint)deviceId); 
    env
-> SetIntField( event gInputOffsets.mType, (jint)type); 
    env
-> SetIntField( event gInputOffsets.mScancode, (jint)scancode); 
    env
-> SetIntField( event gInputOffsets.mKeycode, (jint)keycode); 
    env
-> SetIntField( event gInputOffsets.mFlags, (jint)flags); 
    env
-> SetIntField( event gInputOffsets.mValue, value); 
    env
-> SetLongField( event gInputOffsets.mWhen, 
                       (jlong)(nanoseconds_to_milliseconds(when))); 
    
return  res; 
}

 以上函数调用hub->getEvent()读取输入事件,然后构造并返回一个JAVA结构'event'。

====================================================================
输入事件 排队 线程
在frameworks/base/services/java/com/android/server/KeyInputQueue.java 里创建了一个线程,它循环地读取输入事件,然后把事件放入事件队列里(WindowManagerService当然会使用这个类)。

Java代码:

Thread  mThread = new Thread("InputDeviceReader") {
        public void run() {
            android.os.Process.setThreadPriority(
                    android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);

             try {
                RawInputEvent ev = new RawInputEvent();
                while (true) {
                    InputDevice di;

                    // block, doesn't release the monitor
                    readEvent(ev);                     // 实际上就是前面的android_server_KeyInputQueue_readEvent()

                    boolean send = false;
                    boolean configChanged = false;
                   
                    if (false) {
                        Log.i(TAG, "Input event: dev=0x"
                                + Integer.toHexString(ev.deviceId)
                                + " type=0x" + Integer.toHexString(ev.type)
                                + " scancode=" + ev.scancode
                                + " keycode=" + ev.keycode
                                + " value=" + ev.value);
                    }
                   
                    if (ev.type == RawInputEvent.EV_DEVICE_ADDED) {
                        synchronized (mFirst) {
                            di = newInputDevice(ev.deviceId);
                            mDevices.put(ev.deviceId, di);
                            configChanged = true;
                        }
                    } else if (ev.type == RawInputEvent.EV_DEVICE_REMOVED) {
                        ...
                   } else {
                        ...  // 验证输入设备有效否
                        send = preprocessEvent(di, ev);   // 此函数对输入的键做预处理
                   }

     ......
                   if(!send)  continue;
                   synchronized (mFirst) {
                               int keycode = rotateKeyCodeLocked(ev.keycode);
                              addLocked(di, curTimeNano, ev.flags, ...... );   // 将事件加入到QueuedEvent中排队!
                   }
    }
   }
  }
};
此线程在
KeyInputQueue类的构造函数中被启动。其中函数 preprocessEvent()的功能为:
       (1) 根据PowerManager获取的Screen on,Screen off状态来判定用户输入的是否WakeUPScreen。
       (2) 如果按键是应用程序切换按键,则切换应用程序。
       (3) 根据WindowManagerPolicy觉得该用户输入是否投递。
文件frameworks/base/services/java/com/android /server/WindowManagerService.java定义并实现一个KeyInputQueue的派生类KeyQ:
              private class KeyQ extends KeyInputQueue implements KeyInputQueue.FilterCallback
该类在构造时生成一个KeyQ的实例mQueue,实际上就是创建了一个线程 InputDeviceReader (见以上参考代码),专门用来从输入设备读取按键事件。

====================================================================

输入事件分发线程(Android2.2): 由WindowManagerService中的InputDispatcherThread分发

在frameworks/base/services/java/com/android /server/WindowManagerService.java(运行于system_server中)里创建了一个输入事件分发线程InputDispatcherThread,它从KeyQ(即mQueue)中读取Events,找到Window Manager中的Focus Window,通过Focus Window记录的mClient接口,将Events传递到Client端:

private final class InputDispatcherThread extends Thread {
   // Time to wait when there is nothing to do: 9999 seconds.
   static final int LONG_WAIT=9999*1000;
   public InputDispatcherThread() {
        super("InputDispatcher");
   }
   @Override
   public void run() {
       while (true) process();
   }
   private void process() {
      android.os.Process.setThreadPriority(
                    android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);
      ... ...
      while (true) {
        long curTime = SystemClock.uptimeMillis();
        // Retrieve next event, waiting only as long as the next
        // repeat timeout. If the configuration has changed, then
        // don't wait at all -- we'll report the change as soon as
        // we have processed all events.
        QueuedEvent ev = mQueue.getEvent(
                       (int)( (!configChanged && curTime < nextKeyTime)
                              ? (nextKeyTime-curTime) 
                              : 0));
        ... ...
        try {
          if (ev != null) {
            switch (ev.classType) {
               case RawInputEvent.CLASS_KEYBOARD:
                    ...
                    dispatchKey((KeyEvent)ev.event, 0, 0);
                    mQueue.recycleEvent(ev);
                    break;
               case RawInputEvent.CLASS_TOUCHSCREEN:
                    //Slog.i(TAG, "Read next event " + ev);
                    dispatchPointer(ev, (MotionEvent)ev.event, 0, 0);
                    break;
               case RawInputEvent.CLASS_TRACKBALL:
                    dispatchTrackball(ev, (MotionEvent)ev.event, 0, 0);
                    break;
               case RawInputEvent.CLASS_CONFIGURATION_CHANGED:
                    configChanged = true;
                    break;
               default:
                    mQueue.recycleEvent(ev);
                    break;
           }
        } else if (configChanged) {
           configChanged = false;
           sendNewConfiguration();
        } else if (lastKey != null) {
           curTime = SystemClock.uptimeMillis();
           ... ...
           dispatchKey(newEvent, 0, 0);
        } else {
           curTime = SystemClock.uptimeMillis();
           lastKeyTime = curTime;
           nextKeyTime = curTime + LONG_WAIT;
        }
     } catch (Exception e) {
       ... ...
     }
   } 
  }  
}    

1) 寻找焦点
输入事件分发线程InputDispatcherThread会调用以下方法寻找焦点并调用焦点窗口的dispatchKey()函数完成事件的分发:

        private int dispatchKey(KeyEvent event, int pid, int uid) {
           Object  focusObj = mKeyWaiter.waitForNextEventTarget(event, null, null, false, false, pid, uid);
           if (event.getRepeatCount() > 0 && mQueue.hasKeyUpEvent(event)) {
                return INJECT_SUCCEEDED;
           }
           WindowState focus = (WindowState)focusObj;    // 得到当前含焦点的窗口
           // 权限验证:mContext.checkPermission(android.Manifest.permission.INJECT_EVENTS, pid, uid)
           try {
                 focus.mClient.dispatchKey(event);   //通过mClinet接口向焦点窗口分发事件
                 return INJECT_SUCCEEDED;
           } catch(...) { }
        }
其中waitForNextEventTarget()会调用以下函数获取当前窗口:此函数有约100行!

    Object   findTargetWindow(KeyEvent nextKey, QueuedEvent qev,
                      MotionEvent nextMotion, boolean isPointerEvent,
                      int callingPid, int callingUid);

实际工作是getFocusedWindowLocked()做的:它直接返回WindowManagerService维护的一个私有变量mCurrentFocus。

2) 向焦点窗口分发事件
事件的分发是根据事件类型分别处理的,对于键盘事件就是函数dispatchKey()。那么键盘事件是怎样传递到当前窗口的呢?这要涉及到以下几个类:

   ViewRoot                      --- frameworks/base/core/java/android/view/ViewRoot.java
   WindowManager         --- frameworks/base/core/java/android/view/WindowManager.java
   WindowManagerImpl    --- frameworks/base/core/java/android/view/WindowManagerImpl.java
   IWindowSession          --- frameworks/base/core/java/android/view/IWindowSession.aidl
   IWindow                --- frameworks/base/core/java/android/view/IWindow.aidl

ViewRoot是逻辑上的东西,用来负责View的事件处理和逻辑处理,并和WindowsManagerService建立联系。ViewRootHandler的子类,而Handler的基本功能就是处理回调、发送消息等。源码对ViewRoot的说明:

        The top of a view hierarchy, implementing the needed protocol between View
        and the WindowManager.  This is for the most part an internal implementation
        detail of {@link WindowManagerImpl}

WindowManager是个接口类,其实际的实现就是frameworks/base/core/java/android/view/WindowManagerImpl.java,后者是Android应用与系统全局的WindowManager之间底层通讯的媒介,它维护viewRoot的数组,并实现了addView(),removeView(),closeAll()等维护用函数。

ViewRoot代表含有Looper的窗口(或View)与WindowManager进行通讯,是ViewWindowsManger通讯的桥梁,而通讯的核心是IWindowSessionIWindowViewRoot通过IWindowSession添加窗口到WindowManager,而IWindowWindowManager分发消息给Client ViewRoot的渠道。

引用几个图片说明它们之间的关系(引自 http://blog.csdn.net/maxleng/article/details/5561401):

Android输入事件处理过程分析 <wbr>& <wbr>锁屏方法

Android输入事件处理过程分析 <wbr>& <wbr>锁屏方法

Activity使用getSystemService("window")获取对WindowManagerImpl的引用WindowManagerService的代理: 

    wm = (WindowManagerImpl)context.getSystemService(Context.WINDOW_SERVICE);

然后可以调用wm.addView()添加新的窗口(View)到WindowManagerImpl类的私有数组mViews[]中。在addView()的最后调用ViewRoot的setView()函数,以便把此新View和WindowManagerService联系起来:

  public void setView(View view, WindowManager.LayoutParams attrs,
            View panelParentView)

 {
                     ......
                    try {
                           res = sWindowSession.add(mWindow, mWindowAttributes,
                                          getHostVisibility(), mAttachInfo.mContentInsets);
                    } catch (RemoteException e)
                     ......
  }

这里sWindowSession的类型是 IWindowSession,其add()函数即把ViewRoot的私有成员mWindow(类型为 IWindow.Stub的子类W)注册到WindowManagerService中(见上面的两个图)。mWindow中的dispatchKey()函数实际上就是ViewRoot自身的dispatchKey()函数,而ViewRoot的dispatchKey()函数就是把键盘输入事件以消息的形式发送到Looper线程的MessageQueue:
                 // ViewRoot::dispatchKey():
               Message msg = obtainMessage(DISPATCH_KEY);
               msg.obj = event;
               sendMessageAtTime(msg, event.getEventTime());

WindwoManagerService就是调用已注册过的mWindow的dispatchKey()函数把键盘事件分发到当前窗口的。

引用另一张图显示事件分发涉及的Java类的关系 (引自 http://blog.csdn.net/maxleng/article/details/5561401)
Android输入事件处理过程分析 <wbr>& <wbr>锁屏方法

焦点窗口对于输入事件的处理
在ViewRoot (文件frameworks/base/core/java/android/view/ViewRoot.java)处理消息(handleMessage())时,会调用deliverKeyEvent((KeyEvent)msg.obj, true)。
如果存在输入法,deliverKeyEvent()会分发事件到输入法服务,否则会直接分发到View窗口层次结构中:
             if (mLastWasImTarget) {
                         InputMethodManager imm = InputMethodManager.peekInstance();
                        imm.dispatchKeyEvent(mView.getContext(), seq, event,   mInputMethodCallback);
                         return;
             }
             deliverKeyEventToViewHie rarchy(event, sendDone);
1)   (文件frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java)
       InputMethodManager类的dispatchKeyEvent()方法会弹出Picker,并把事件发送给当前的输入法:
                       mCurMethod.dispatchKeyEvent(seq, key, callback);
2)deliverKeyEventToViewHie rarchy()会调用ViewRoot所在的View的dispatchKeyEvent()来向该View发送事件(随后还要根据按键来调整新的焦点窗口)。后者先调用View的mOnKeyListener.onKey()回调函数(这就到达了普通Android应用内部),如果回调函数需要继续处理(即返回false),那么把事件沿着焦点路径继续向下传递(event.dispatch())。

不管事件发送到何处,最后都要调用*.finishedEvent()表明一次输入事件处理的结束。

输入事件分发线程(Android2.3.4)
(ref: http://blog.csdn.net/andyhuabing/article/details/7006688 )

A、WindowManagerService(在system_server进程中,简称WMS)与ViewRoot之间建立双向管道:

事件分发系统中的管道的主要作用是在有事件被存储到共享内存中时,system_server端通知ViewRoot去读取事件的通信机制。既然是 ViewRoot和system_server之间建立管道通信,那么ViewRoot和WindowManagerService(负责事件传递,运行 在system_server进程中)各需维护管道的一个文件描述符,其实ViewRoot和WindowManagerService不是各自维护一个 管道的文件描述符,而是两个,当然,这两个描述符不属于同一管道,实际上也就是ViewRoot和WindowManagerService之间实现了全 双工的管道通信。

        WindowManagerService--->ViewRoot方向的管道通信:
               表示WMS通知ViewRoot有新事件被写入到共享内存(对应于IWindow接口);

    ViewRoot-->WindowManagerService方向的管道通信:
               表示ViewRoot已经消化完共享内存中的新事件,特此通知WMS
(对应于IWindowSession接口)

ViewRoot和WindowManagerService之间的管道的文件描述符都是被存储在一个名为InputChannel的类android_view_InputChannel.cpp中,这个InputChannel类是管道通信的载体,而这二者之间通过ashmem_create_region()创建的匿名共享内存进行数据传递。

ViewRoot.java 端建立管道(setView()@ViewRoot.java): 
   requestLayout();
   mInputChannel = new InputChannel();
   try {
       res = sWindowSession.add(mWindow, mWindowAttributes, getHostVisibility(),
                                               mAttachInfo.mContentInsets, mInputChannel);
   } catch (RemoteException e) {
       ...
   }

在ViewRoot和WMS(WindowManagerService)建立起连接之前首先会创建一个InputChannel对象。同样的WMS端也会创建一个InputChannel对象,不过WMS的创建过程是在ViewRoot调用add()方法时调用的。InputChannel的构造不做任何操作,所以在ViewRoot中创建InputChannel时尚未初始化,它的初始化过程是在调用WMS方法add()时进行的,看到上面代码中将mInputChannel作为参数传递给WMS,目的就是为了初始化。

WindowManagerService.java建立管道:
  if(outInputChannel != null) {
    String name = win.makeInputChannelName();
    InputChannel[] inputChannels = InputCHannel.openInputChannel(name);
    win.mInputChannel = inputChannels[0];
    inputChannels[1].transferToBinderOutParameter(outInputChannel);

    mInputManager.registerInputChannel(win.mInputChannel);
  }

outInputChannel就是ViewRoot传递来的InputChannel对象 上述代码主要的工作其实就是创建一对InputChannel,这一对InputChannel中实现了一组全双工管道。 在创建InputChannel对的同时,会申请共享内存,并向2个InputChannel对象中各自保存一个共享内存的文件描述符( openInputChannelPair()@InputTransport.cpp )。 InputChannel创建完成后,会将其中一个的native InputChannel 赋值给outInputChannel,也就是对ViewRoot端InputChannel对象的初始化,这样随着ViewRoot和WMS两端的 InputChannel对象的创建,事件传输系统的管道通信也就建立了起来。

B、InputChannel的注册过程:

需要清楚的一点是,一个管道通信只是对应一个Activity的事件处理,也就是当前系统中有多少个Activity就会有多少个全双工管道,那么系统需要一个管理者来管理以及调度每一个管道通信,因此在创建完InputChannel对象后,需要将其注册到某个管理者中。那么ViewRoot和WMS端的InputChannel对象各自需要注册到哪里?

因为所处的位置不同,这InputChannel对象肯定需要被两个不同的管理者来管理:

  1) ViewRoot端的InputChannel一般会注册到一个NativeInputQueue对象中(这是一个Native的对象,而JAVA端的InputQueue类仅仅是提供了一些static方法与NativeInputQueue通信);而当用到NativeActivity时,会是另外一种处理机制,这里暂不管它,NativeActivity毕竟很少用到;

ViewRoot端的InputChannel对象是这样向NativeInputQueue注册的:
    InputQueue.registerInputChannel(mInputChannel, mInputHandler,
                   Looper.myQueue());
其中的3个参数是:
  a) mInputChannel: ViewRoot端创建的InputChannel对象,注册时会把它传递给NativeInputQueue
  b) mInputHandler:  ViewRoot的成员变量InputHandler, 它实际上就是事件处理函数;
  c) Looper.myQueue(): 就是当前Application的主进程的MessageQueue,主线程会在此队列上轮询(poll)。

需要注意的是,整个系统中只有一个NativeInputQueue对象,为了负责管理众多的Application的事件传递,android在NativeInputQueue类中定义了一个私有子类Connection,每个InputChannel对象在注册时都会创建一个自己的 Connection对象 (见frameworks/base/core/jni/android_view_InputQueue.cpp)。见下图:

Android输入事件处理过程分析 <wbr>& <wbr>锁屏方法

Android输入事件处理过程分析 <wbr>& <wbr>锁屏方法



   2) WMS端注册在 InputManager对象中。

WMS端并不采用Looper机制,而是启动了2个线程来轮询事件的发生与及时地传递事件:InputReaderThread和InputDispatcherThread。InputReaderThread进程负责轮询事件的发生,InputDispatcherThread负责dispatch事件。

WMS在初始化时会创建一个InputManager实例,当然,它也是系统唯一的一个实例。JAVA层的InputManager实例并没有实现太多的业 务,真正实现Input Manager业务的是Native的NativeInputManager实例,它在被创建时,建立起了整个WMS端事件传递系统的静态逻辑。NativeInputManager的核心其实就是InputReader和InputDispatcher两个线程。事件分发是在InputDispatcherThread中进行的,因此最终WMS端InputChannel对象会注册到InputDispatcher中。同样地,由于整个系统中InputDispatcher实例只有一个,而WMS端InputChannel对象是和ViewRoot一一对应的,因此InputDispatcher类中也定义了一个内部类 Connect来管理各自的InputChannel对象。不同于NativeInputQueue类中的内部Connect 类,InputDispatcher中的内部Connect类的主要工作是由InputPublisher对象来实现的,该对象负责将发生的事件信息写入到共享内存。

 InputDispatcherThread的主要工作有两部分:

1)  对InputReader传递过来的事件进行dispatch前处理(比如确定focus window)、特殊按键处理(如HOME/ENDCALL等)。在预处理完成后,InputDispatcher会将事件存储到对应的focus window的outBoundQueue,这个outBoundQueue队列是InputDispatcher::Connection的成员函数, 因此它是和ViewRoot相关的。

2) 对looper进行轮询,这个轮询过程是检查NativeInputQueue是否处理完成上一个事件。如果NativeInputQueue处理完一个事件,它就会通过管道向InputDispatcher发送消息指示consume完成。只有NativeInputQueue consume完一个事件,InputDispatcher才会向共享内存写入另一个事件。


另外,并不是所有的InputReader发送来的事件都需要传递给应用,比如翻盖/滑盖事件,除此之外的按键、触屏、轨迹球(后两者统一按motion事件处理)等事件,也会有部分被丢弃,InputDispatcher总会根据一些规则来丢弃掉一部分事件。  InputDispatcher.h中定义了一个包含有丢弃原因的枚举:

   enum DropReason {
        DROP_REASON_NOT_DROPPED = 0,
        DROP_REASON_POLICY = 1,
        DROP_REASON_APP_SWITCH = 2,
        DROP_REASON_DISABLED = 3,
    };

  a) DROP_REASON_NOT_DROPPED
      不需要丢弃
  b) DROP_REASON_POLICY
     设置为DROP_REASON_POLICY主要有两种情形:
      1) 在InputReader notify InputDispatcher之前,Policy会判断不需要传递给应用的事件。
      2) 在InputDispatcher dispatch事件前,PhoneWindowManager使用方法interceptKeyBeforeDispatching()提前consume掉一些按键事件。interceptKeyBeforeDispatching()主要对HOME/MENU/SEARCH按键的特殊处理,如果此时能被consume掉,那么在InputDispatcher 中将被丢弃。
  c) DROP_REASON_APP_SWITCH
     当有App switch 按键如HOME/ENDCALL按键发生时,当InputReader向InputDispatcher传递app switch按键时,会设置一个APP_SWITCH_TIMEOUT 0.5S的超时时间,当0.5s超时时,InputDispatcher尚未dispatch到这个app switch按键时,InputDispatcher将会丢弃掉mInboundQueue中所有处在app switch按键前的按键事件。这么做的目的是保证app switch按键能够确保被处理。此时被丢弃掉的按键会被置为DROP_REASON_APP_SWITCH。
  d) DROP_REASON_DISABLED
    这个标志表示当前的InputDispatcher被disable掉了,不能dispatch任何事件,比如当系统休眠时或者正在关机时会用到。



另附上一个老外给出的输入事件处理过程图,以帮助理解(http://cjix.info/blog/misc/internal-input-event-handling-in-the-linux-kernel-and-the-android-userspace/):
Android输入事件处理过程分析 <wbr>& <wbr>锁屏方法


<end>


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
大学生参加学科竞赛有着诸多好处,不仅有助于个人综合素质的提升,还能为未来职业发展奠定良好基础。以下是一些分析: 首先,学科竞赛是提高专业知识和技能水平的有效途径。通过参与竞赛,学生不仅能够深入学习相关专业知识,还能够接触到最新的科研成果和技术发展趋势。这有助于拓展学生的学科视野,使其对专业领域有更深刻的理解。在竞赛过程中,学生通常需要解决实际问题,这锻炼了他们独立思考和解决问题的能力。 其次,学科竞赛培养了学生的团队合作精神。许多竞赛项目需要团队协作来完成,这促使学生学会有效地与他人合作、协调分工。在团队合作中,学生们能够学到如何有效沟通、共同制定目标和分工合作,这对于日后进入职场具有重要意义。 此外,学科竞赛是提高学生综合能力的一种途径。竞赛项目通常会涉及到理论知识、实际操作和创新思维等多个方面,要求参赛者具备全面的素质。在竞赛过程中,学生不仅需要展现自己的专业知识,还需要具备创新意识和解决问题的能力。这种全面的综合能力培养对于未来从事各类职业都具有积极作用。 此外,学科竞赛可以为学生提供展示自我、树立信心的机会。通过比赛的舞台,学生有机会展现自己在专业领域的优势,得到他人的认可和赞誉。这对于培养学生的自信心和自我价值感非常重要,有助于他们更加积极主动地投入学习和未来的职业生涯。 最后,学科竞赛对于个人职业发展具有积极的助推作用。在竞赛中脱颖而出的学生通常能够引起企业、研究机构等用人单位的关注。获得竞赛奖项不仅可以作为个人履历的亮点,还可以为进入理想的工作岗位提供有力的支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值