手机总弹com.andriod.token已一点公益停止运行行怎么办

你的位置: >
> 【android技术demo】仿iphone的一个从底部弹出的操作菜单
很多交互设计师都是拿iphone手机的,会给android提出很多iphone的交互风格,比如:一个从底部弹出的操作菜单,还要和黑色半透明蒙层的渐变一起出来。
看过很多应用,比如招行的掌上生活,甚至是连手机qq做的都不是很好。
这里贡献一个demo,可以很好的实现该功能。
效果如下:
源码地址:
实现原理:
在弹出自定义的PopupWindow时,增加一个半透明蒙层view到窗口,并置于PopupWindow下方。
代码如下:
public void showAtLocation(View parent, int gravity, int x, int y) {
addMaskView(parent.getWindowToken());
super.showAtLocation(parent, gravity, x, y);
private void addMaskView(IBinder token) {
WindowManager.LayoutParams p = new WindowManager.LayoutParams();
p.width = WindowManager.LayoutParams.MATCH_PARENT;
p.height = WindowManager.LayoutParams.MATCH_PARENT;
p.format = PixelFormat.TRANSLUCENT;
p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
p.windowAnimations = android.R.style.Animation_T
maskView = new View(context);
maskView.setBackgroundColor(0x7f000000);
maskView.setFitsSystemWindows(false);
maskView.setOnKeyListener(new OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
removeMaskView();
wm.addView(maskView, p);
具体使用情况,请参考/yzeaho/BottomPushPopWindow
转载请注明: &
与本文相关的文章Android输入事件处理过程分析&&&锁屏方法
引子:WaveSecure是个安全软件,它拿到了ADC2的全球所有类别的第三名。猜测 其 锁屏
功能的实现原理,可能涉及到以下的内容。
参考词:远程锁屏 手机防盗
====================================================================
以下代码尝试 截获 HOME 键,但没有效果:
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);
public boolean onKeyDown(int keyCode, KeyEvent event) {
&& if(keyCode ==
KeyEvent.KEYCODE_HOME) {
super.onKeyDown(keyCode, event);
但是以下代码是可以禁止HOME键对应用的影响(需API 5以上):
&&& 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
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自己处理。
这部分代码最好添加在
&&& boolean
preprocessEvent(InputDevice device, RawInputEvent event)
方法中,这个方法是KeyInputQueue中的一个虚函数,在处理按键事件之前进行“预处理”。WindowManagerService.java中有KeyInputQueue的实现类:
private class KeyQ extends KeyInputQueue&
implements KeyInputQueue.FilterCallback {...}
对HOME键的处理参考frameworks/policies/base/phone/com/android/internal/policy/impl/PhoneWindowManager.java中的interceptKeyTi()方法。
===============================================================
/Linux/45.htm )
框架总览(from&
http://blog.csdn.net/windskier/article/details/6966264#comments)
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)
//得到设备文件/dev/input/eventX的驱动程序给出的描述性设备名(其中可能含有空格),而不是/proc/bus/input/devices中的名字;目录/dev/input/by-id下会显示相应设备的总线和驱动名,只不过描述性驱动名中的空格都替换成了'_',下同。
&&& mFDs =
&&& mDevices =
&&&&mFDs[mFDCount].fd&=&&
&&&&mFDs[mFDCount].events&=&POLLIN;&
&&&&ioctl(mFDs[mFDCount].fd,&EVIOCGNAME(sizeof(devname)-1),&devname);&
((device-&classes&CLASS_KEYBOARD) !=
// 依次尝试加载以下*.kl文件,成功加载某个文件后即停止尝试以后的文件:
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文件也要拷贝)。
通过设备驱动中给出的描述性名字找到对应的.kl文件,如对应设备"MY KBD"的文件名应该为MY_KBD.kl
(注意对应文件的安装,见上一个文件的处理过程);
使用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, '
&&&&&&&&&&&
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,
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 =
&& 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):
&&&&&&&&CLASS_KEYBOARD
0x,& //键盘
CLASS_ALPHAKEY&
= 0x,& // ?
CLASS_TOUCHSCREEN& & =
0x,& //触摸屏
CLASS_TRACKBALL&
= 0x& //轨迹球
对于生产厂商自定义的键盘,可通过上面的 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&
& = "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&
#&FUNCTIONAL&KEYS&
MENU&&&&&&
&&&&&&&&&&&
WAKE_DROPPED&
BACK&&&&&&
&&&&&&&&&&&&
WAKE_DROPPED&
&&&&&&&&&&
&&&&&&&&&&&&&
CALL&&&&&&
&&&&&&&&&&&&&
WAKE_DROPPED&
ENDCALL&&&&&&&&&&&&
WAKE_DROPPED&
DPAD_CENTER&&
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&
Android所支持的所有Key
Code定义在文件frameworks/base/include/ui/KeycodeLabels.h中,同时文件frameworks/base/core/java/android/view/KeyEvent.java也给出了Java语言中的定义:
static&&&&
KeycodeLabel&& KEYCODES[] =
typedef && enum&
KeyCode {...};
static&&&&
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&&&&
在获得按键事件以后调用:
&&&&&&&&&&
status_t&&&
KeyLayoutMap::map(int32_t scancode, int32_t *keycode, uint32_t
这个map()函数会根据 键盘映射关系表
KeyedVector&int32_t,Key&
m_keys 把扫描码转换成andorid framework及上层应用使用的统一键码,即Android Key
Code。Android应用接收到的都是Android Key
====================================================================
输入事件的集中获取:EventHub
文件frameworks/base/libs/ui/EventHub.cpp的函数getEvent()通过对所有输入设备的轮询来获取输入事件:
EventHub::getEvent(&&&&&&&&&&
int32_t* outDeviceId,& int32_t*
&&& int32_t*
outScancode, int32_t* outKeycode,& uint32_t
*outFlags,
outValue,&&&&&&&&
nsecs_t* outWhen)
这是一个c++函数,而Android中负责收集和分发输入事件的WindowManagerService是一个java进程。文件frameworks/base/services/jni/com_android_server_KeyInputQueue.cpp向
JAVA 提供了JNI函数android_server_KeyInputQueue_readEvent(),用于java代码 读取
输入事件。
sp&EventHub&&&
系统级的全局变量!
<img src="/blog7style/images/common/sg_trans.gif" real_src ="/OutliningIndicators/ContractedBlock.gif" STYLE="display:" NAME="code_img_closed_e1c2dd92-a008-46f9-b1d2-d4f44baa7157"
ALT="Android输入事件处理过程分析&&&锁屏方法"
TITLE="Android输入事件处理过程分析&&&锁屏方法" />
static&jboolean&android_server_KeyInputQueue_readEvent(JNIEnv*&env,&jobject&clazz,&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&jobject&event)
&&&&gLock.lock();&
&&&&sp&EventHub&
&&&&if&(hub&==&NULL)&{&
&&&&&&&&hub&=&new&EventH&
&&&&&&&&gHub&=&&
&&&&gLock.unlock();&
&&&&int32_t&&
& deviceId;&
&&&&int32_t&&
&&&&int32_t
scancode,&&
&&&&uint32_t
&&&&int32_t
&&&&nsecs_t
&&&&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&&
&以上函数调用hub-&getEvent()读取输入事件,然后构造并返回一个JAVA结构'event'。
====================================================================
输入事件 排队 线程
在frameworks/base/services/java/com/android/server/KeyInputQueue.java
里创建了一个线程,它循环地读取输入事件,然后把事件放入事件队列里(WindowManagerService当然会使用这个类)。
mThread = new Thread("InputDeviceReader") {
public void run() {
&&&&&&&&&&&
android.os.Process.setThreadPriority(
&&&&&&&&&&&&&&&&&&&
android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);
&&&&&&&&&&&&
&&&&&&&&&&&&&&&
RawInputEvent ev = new RawInputEvent();
&&&&&&&&&&&&&&&
while (true) {
&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&
// block, doesn't release the monitor
&&&&&&&&&&&&&&&&&&&
readEvent(ev);&&&&&&&&&&&&&&&&&&&&
// 实际上就是前面的android_server_KeyInputQueue_readEvent()
&&&&&&&&&&&&&&&&&&&
boolean send =
&&&&&&&&&&&&&&&&&&&
boolean configChanged =
&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&
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 =
&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&
} else if (ev.type == RawInputEvent.EV_DEVICE_REMOVED) {
&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&
...& // 验证输入设备有效否
&&&&&&&&&&&&&&&&&&&&&&&
send = preprocessEvent(di,
ev);&& // 此函数对输入的键做预处理
&&&&&&&&&&&&&&&&&&
     ......
&&&&&&&&&&&&&&&&&&
if(!send)&
&&&&&&&&&&&&&&&&&&
synchronized (mFirst) {
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
int keycode = rotateKeyCodeLocked(ev.keycode);
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
addLocked(di,
curTimeNano, ev.flags, ......
将事件加入到QueuedEvent中排队!
&&&&&&&&&&&&&&&&&&
此线程在KeyInputQueue类的构造函数中被启动。其中函数preprocessEvent()的功能为:
根据PowerManager获取的Screen on,Screen
off状态来判定用户输入的是否WakeUPScreen。
如果按键是应用程序切换按键,则切换应用程序。
根据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=;
&& 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);
&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&& case RawInputEvent.CLASS_TOUCHSCREEN:
&&&&&&&&&&&&&&&&&&& //Slog.i(TAG, "Read next event " + ev);
&&&&&&&&&&&&&&&&&&&
dispatchPointer(ev, (MotionEvent)ev.event, 0, 0);
&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&& case RawInputEvent.CLASS_TRACKBALL:
&&&&&&&&&&&&&&&&&&&
dispatchTrackball(ev, (MotionEvent)ev.event, 0, 0);
&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&& case RawInputEvent.CLASS_CONFIGURATION_CHANGED:
&&&&&&&&&&&&&&&&&&& configChanged =
&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&& default:
&&&&&&&&&&&&&&&&&&& mQueue.recycleEvent(ev);
&&&&&&&&&&&&&&&&&&&
&&&&&&&&&& }
&&&&&&& } else if (configChanged) {
&&&&&&&&&& configChanged =
&&&&&&&&&& sendNewConfiguration();
&&&&&&& } else if (lastKey != null) {
&&&&&& & & curTime = SystemClock.uptimeMillis();
&&&&&& & & ... ...
&&&&&&&&&& dispatchKey(newEvent, 0, 0);
&&&&&&& } else {
&&&&&&&&&& curTime = SystemClock.uptimeMillis();
&&& & & && lastKeyTime = curT
&&&&&& & & 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)focusO&&&
// 得到当前含焦点的窗口
&&&&&&&&&&
权限验证:mContext.checkPermission(android.Manifest.permission.INJECT_EVENTS,
&&&&&&&&&&
&&&&&&&&&&&&&&&&
focus.mClient.dispatchKey(event);&&
//通过mClinet接口向焦点窗口分发事件
&&&&&&&&&&&
return INJECT_SUCCEEDED;
&&&&&&&&&&
} catch(...) { }
其中waitForNextEventTarget()会调用以下函数获取当前窗口:此函数有约100行!
& & Object
& findTargetWindow(KeyEvent nextKey, QueuedEvent
&&&&&&&&&&&&&&&&&&&&&
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建立联系。ViewRoot是Handler的子类,而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进行通讯,是主View与WindowsManger通讯的桥梁,而通讯的核心是IWindowSession和IWindow。ViewRoot通过IWindowSession添加窗口到WindowManager,而IWindow是WindowManager分发消息给Client
ViewRoot的渠道。
引用几个图片说明它们之间的关系(引自
http://blog.csdn.net/maxleng/article/details/5561401):
Activity使用getSystemService("window")获取对WindowManagerImpl的引用,即WindowManagerService的代理:&
(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)
&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&
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);
sendMessageAtTime(msg, event.getEventTime());
WindwoManagerService就是调用已注册过的mWindow的dispatchKey()函数把键盘事件分发到当前窗口的。
引用另一张图显示事件分发涉及的Java类的关系(引自
http://blog.csdn.net/maxleng/article/details/5561401):
焦点窗口对于输入事件的处理
在ViewRoot
(文件frameworks/base/core/java/android/view/ViewRoot.java)处理消息(handleMessage())时,会调用deliverKeyEvent((KeyEvent)msg.obj,
如果存在输入法,deliverKeyEvent()会分发事件到输入法服务,否则会直接分发到View窗口层次结构中:
if (mLastWasImTarget) {
&&&&&&&&&&&&
InputMethodManager imm = InputMethodManager.peekInstance();
&&&&&&&&&&
& imm.dispatchKeyEvent(mView.getContext(), seq,
event,& mInputMethodCallback);
&&&&&&&&&&&&
deliverKeyEventToViewHierarchy(event, sendDone);
(文件frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java)
InputMethodManager类的dispatchKeyEvent()方法会弹出Picker,并把事件发送给当前的输入法:
&&&&&&&&&&&
mCurMethod.dispatchKeyEvent(seq, key, callback);
2)deliverKeyEventToViewHierarchy()会调用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();
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对象肯定需要被两个不同的管理者来管理:
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)。见下图:
&& 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相关的。
对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主要有两种情形:
在InputReader notify InputDispatcher之前,Policy会判断不需要传递给应用的事件。
在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任何事件,比如当系统休眠时或者正在关机时会用到。
另附上一个老外给出的输入事件处理过程图,以帮助理解(/blog/misc/internal-input-event-handling-in-the-linux-kernel-and-the-android-userspace/):
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

我要回帖

更多关于 是否停止运行此脚本 的文章

 

随机推荐