Android创建窗口机制,请看如下转载:
http://blog.csdn.net/sfdev/article/details/9130527
一、Android4.2系统服务侧——与View关系
1.服务端channel注册过程
frameworks/base/core/java/android/view/ViewRootImpl.java
[cpp] view plaincopy
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
mInputChannel = new InputChannel(); //创建InputChannel
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mInputChannel); //创建与上述InputChannel对应的通道至服务端
/*
mWindowSession = WindowManagerGlobal.getWindowSession(context.getMainLooper());
frameworks/base/core/java/android/view/WindowManagerGlobal.java
public static IWindowSession getWindowSession(Looper mainLooper) {
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
imm.getClient(), imm.getInputContext());
return sWindowSession;
}
frameworks/base/services/java/com/android/server/wm/WindowManagerService.java
public IWindowSession openSession(IInputMethodClient client,
IInputContext inputContext) {
if (client == null) throw new IllegalArgumentException("null client");
if (inputContext == null) throw new IllegalArgumentException("null inputContext");
Session session = new Session(this, client, inputContext);
return session;
}
*/
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper()); //将本通道注册进InputEventReceiver
}
frameworks/base/services/java/com/android/server/wm/Session.java
[cpp] view plaincopy
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets,
InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outInputChannel);
}
frameworks/base/services/java/com/android/server/wm/WindowManagerService.java
[cpp] view plaincopy
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, InputChannel outInputChannel) {
//以下包括了管道的创建(用于WMS与应用程序View通信)等
String name = win.makeInputChannelName();
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
win.setInputChannel(inputChannels[0]);
inputChannels[1].transferTo(outInputChannel);
//以下便是注册至server端过程
//final InputManagerService mInputManager;
mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
}
frameworks/base/service/java/com/android/server/input/InputManagerService.java
[cpp] view plaincopy
public void registerInputChannel(InputChannel inputChannel,
InputWindowHandle inputWindowHandle) {
nativeRegisterInputChannel(mPtr, inputChannel, inputWindowHandle, false);
}
private static native void nativeRegisterInputChannel(int ptr, InputChannel inputChannel,
InputWindowHandle inputWindowHandle, boolean monitor);
frameworks/base/service/jni/com_android_server_input_InputManagerService.cpp
[cpp] view plaincopy
static void nativeRegisterInputChannel(JNIEnv* env, jclass clazz,
jint ptr, jobject inputChannelObj, jobject inputWindowHandleObj, jboolean monitor) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
status_t status = im->registerInputChannel(
env, inputChannel, inputWindowHandle, monitor);
}
status_t NativeInputManager::registerInputChannel(JNIEnv* env,
const sp<InputChannel>& inputChannel,
const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
return mInputManager->getDispatcher()->registerInputChannel(
inputChannel, inputWindowHandle, monitor);
//mInputManager = new InputManager(eventHub, this, this);
/*
frameworks/base/services/input/InputManager.cpp
sp<InputDispatcherInterface> InputManager::getDispatcher() {
return mDispatcher;
}
mDispatcher = new InputDispatcher(dispatcherPolicy);
*/
}
frameworks/base/services/input/InputDispatcher.cpp
[cpp] view plaincopy
status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
int fd = inputChannel->getFd();
mConnectionsByFd.add(fd, connection);
//该fd监听对应的处理函数为handleReceiveCallback
mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
}
2.服务端上报过程
2.1.InputReaderThread线程从驱动读取数据并处理,如实现鼠标右键上报back键即在此处完成、以下代码将会看到
frameworks/base/services/input/InputReader.cpp
[cpp] view plaincopy
bool InputReaderThread::threadLoop() {
mReader->loopOnce();
return true;
}
void InputReader::loopOnce() {
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
/*
frameworks/base/services/input/EventHub.cpp
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
int32_t readSize = read(device->fd, readBuffer,
sizeof(struct input_event) * capacity);//从驱动读取事件
}
*/
processEventsLocked(mEventBuffer, count);
}
void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
}
void InputReader::processEventsForDeviceLocked(int32_t deviceId,
const RawEvent* rawEvents, size_t count) {
device->process(rawEvents, count);
}
void InputDevice::process(const RawEvent* rawEvents, size_t count) {
//该设备的所有mapper进行处理;注意:这里使用了多态
for (size_t i = 0; i < numMappers; i++) {
InputMapper* mapper = mMappers[i];
mapper->process(rawEvent);
}
}
//以下就是各个mapper
//CursorInput鼠标设备
void CursorInputMapper::process(const RawEvent* rawEvent) {
mCursorButtonAccumulator.process(rawEvent);
mCursorMotionAccumulator.process(rawEvent);
mCursorScrollAccumulator.process(rawEvent);
if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
sync(rawEvent->when);
}
}
//CursorButtonAccumulator::process(const RawEvent* rawEvent)
//CursorMotionAccumulator::process(const RawEvent* rawEvent)
//CursorScrollAccumulator::process(const RawEvent* rawEvent)
void CursorInputMapper::sync(nsecs_t when) {
int32_t currentButtonState = mCursorButtonAccumulator.getButtonState();
/*
uint32_t CursorButtonAccumulator::getButtonState() const {
if (mBtnRight) {
//Changed by tank for mouse left button to back
result |= AMOTION_EVENT_BUTTON_BACK;
// result |= AMOTION_EVENT_BUTTON_SECONDARY;
}
if (mBtnMiddle) {
//change by tank@tcl.com for mouse middle button to menu
result |= AMOTION_EVENT_BUTTON_MENU;
//result |= AMOTION_EVENT_BUTTON_TERTIARY;
}
}
*/
getListener()->notifyMotion(&args);
synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, getDeviceId(), mSource,
policyFlags, lastButtonState, currentButtonState);
/*
static void synthesizeButtonKeys(InputReaderContext* context, int32_t action,
nsecs_t when, int32_t deviceId, uint32_t source,
uint32_t policyFlags, int32_t lastButtonState, int32_t currentButtonState) {
synthesizeButtonKey(context, action, when, deviceId, source, policyFlags,
lastButtonState, currentButtonState,
AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK);
synthesizeButtonKey(context, action, when, deviceId, source, policyFlags,
lastButtonState, currentButtonState,
AMOTION_EVENT_BUTTON_FORWARD, AKEYCODE_FORWARD);
//add by tank mouse key event middle->menu.
synthesizeButtonKey(context, action, when, deviceId, source, policyFlags,
lastButtonState, currentButtonState,
AMOTION_EVENT_BUTTON_MENU, AKEYCODE_MENU);
//end tank
}
static void synthesizeButtonKey(InputReaderContext* context, int32_t action,
nsecs_t when, int32_t deviceId, uint32_t source,
uint32_t policyFlags, int32_t lastButtonState, int32_t currentButtonState,
int32_t buttonState, int32_t keyCode) {
if ((action == AKEY_EVENT_ACTION_DOWN && !(lastButtonState & buttonState)
&& (currentButtonState & buttonState))
|| (action == AKEY_EVENT_ACTION_UP
&& (lastButtonState & buttonState)
&& !(currentButtonState & buttonState))) {
context->getListener()->notifyKey(&args);
}
}
*/
}
//TouchInput触摸板设备
void SingleTouchInputMapper::process(const RawEvent* rawEvent)
TouchInputMapper::process(rawEvent);
mSingleTouchMotionAccumulator.process(rawEvent);
}
//SingleTouchMotionAccumulator::process(const RawEvent* rawEvent)
void MultiTouchInputMapper::process(const RawEvent* rawEvent) {
TouchInputMapper::process(rawEvent);
mMultiTouchMotionAccumulator.process(rawEvent);
}
//MultiTouchMotionAccumulator::process(const RawEvent* rawEvent)
void TouchInputMapper::process(const RawEvent* rawEvent) {
mCursorButtonAccumulator.process(rawEvent);
mCursorScrollAccumulator.process(rawEvent);
mTouchButtonAccumulator.process(rawEvent);
if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
sync(rawEvent->when);
}
}
//TouchButtonAccumulator::process(const RawEvent* rawEvent)
void TouchInputMapper::sync(nsecs_t when) {
dispatchTouches(when, policyFlags);
}
void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
dispatchMotion(when, policyFlags, mSource,
AMOTION_EVENT_ACTION_MOVE, 0, metaState, buttonState,
AMOTION_EVENT_EDGE_FLAG_NONE,
mCurrentCookedPointerData.pointerProperties,
mCurrentCookedPointerData.pointerCoords,
mCurrentCookedPointerData.idToIndex,
currentIdBits, -1,
mOrientedXPrecision, mOrientedYPrecision, mDownTime);
}
void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source,
int32_t action, int32_t flags, int32_t metaState, int32_t buttonState, int32_t edgeFlags,
const PointerProperties* properties, const PointerCoords* coords,
const uint32_t* idToIndex, BitSet32 idBits,
int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime) {
getListener()->notifyMotion(&args);
}
//SwitchInput设备
void SwitchInputMapper::process(const RawEvent* rawEvent) {
sync(rawEvent->when);
}
void SwitchInputMapper::sync(nsecs_t when) {
getListener()->notifySwitch(&args);
}
//JoystickInput游戏手柄设备
void JoystickInputMapper::process(const RawEvent* rawEvent) {
sync(rawEvent->when, false /*force*/);
}
void JoystickInputMapper::sync(nsecs_t when, bool force) {
getListener()->notifyMotion(&args);
}
//KeyboardInput按键设备
void KeyboardInputMapper::process(const RawEvent* rawEvent) {
processKey(rawEvent->when, rawEvent->value != 0, keyCode, scanCode, flags);
}
void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode,
int32_t scanCode, uint32_t policyFlags) {
getListener()->notifyKey(&args);
}
2.2.InputReaderThread线程对系统层按键做处理(比较重要的是POWER键,最终在PhoneWindowManager中的 interceptKeyBeforeQueueing和interceptMotionBeforeQueueingWhenScreenOff)后分 发给InputDispatcherThread线程,以下分析将看到之前一个鼠标操作过程中无法待机的问题解决
以下几种情况都会唤醒InputDispatcherThread线程,即调用mLooper->wake()唤醒正在awoken()中的InputReaderThread线程:
frameworks/base/services/input/InputDispatcher.cpp
[cpp] view plaincopy
//有新输入设备注册等
void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) {
ConfigurationChangedEntry* newEntry = new ConfigurationChangedEntry(args->eventTime);
needWake = enqueueInboundEventLocked(newEntry);
if (needWake) {
mLooper->wake();
}
}
//分发按键事件
void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
//说明:PhoneWindowManager.java中policyFlags位决定系统按键(如HOME等是否需要由系统处理)
mPolicy->interceptKeyBeforeQueueing(&event, policyFlags);
//以下分析将看到,该调用实际是在PhoneWindowManager.java中实现
/*
frameworks/base/services/input/InputManager.cpp
InputManager::InputManager(
const sp<EventHubInterface>& eventHub,
const sp<InputReaderPolicyInterface>& readerPolicy,
const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
mDispatcher = new InputDispatcher(dispatcherPolicy);
mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
}
frameworks/base/services/jni/com_android_server_input_InputManagerService.cpp
NativeInputManager::NativeInputManager(jobject contextObj,
jobject serviceObj, const sp<Looper>& looper) :
mLooper(looper) {
mInputManager = new InputManager(eventHub, this, this);
}
void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent,
uint32_t& policyFlags) {
wmActions = env->CallIntMethod(mServiceObj,
gServiceClassInfo.interceptKeyBeforeQueueing,
keyEventObj, policyFlags, isScreenOn);
//如下函数中将有待机和开机的处理
handleInterceptActions(wmActions, when, policyFlags);
}
frameworks/base/service/java/com/android/server/input/InputManagerService.java
private int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) {
return mWindowManagerCallbacks.interceptKeyBeforeQueueing(
event, policyFlags, isScreenOn);
}
frameworks/base/service/java/com/android/server/SystemServer.java
inputManager = new InputManagerService(context, wmHandler);
wm = WindowManagerService.main(context, power, display, inputManager,
uiHandler, wmHandler,
factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL,
!firstBoot, onlyCore);
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
frameworks/base/service/java/com/android/server/wm/WindowManagerService.java
public InputMonitor getInputMonitor() {
return mInputMonitor;
}
frameworks/base/service/java/com/android/server/wm/InputMonitor.java
public int interceptKeyBeforeQueueing(
KeyEvent event, int policyFlags, boolean isScreenOn) {
return mService.mPolicy.interceptKeyBeforeQueueing(event, policyFlags, isScreenOn);
}
public InputMonitor(WindowManagerService service) {
mService = service;
}
frameworks/base/service/java/com/android/server/wm/WindowManagerService.java
final WindowManagerPolicy mPolicy = PolicyManager.makeNewWindowManager();
frameworks/base/core/java/com/android/internal/policy/PolicyManager.java
public static WindowManagerPolicy makeNewWindowManager() {
return sPolicy.makeNewWindowManager();
}
private static final String POLICY_IMPL_CLASS_NAME =
"com.android.internal.policy.impl.Policy";
Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
sPolicy = (IPolicy)policyClass.newInstance();
frameworks/base/core/java/com/android/internal/policy/Policy.java
package com.android.internal.policy.impl;
public class Policy implements IPolicy {
public WindowManagerPolicy makeNewWindowManager() {
return new PhoneWindowManager();
}
}
frameworks/base/core/java/com/android/internal/policy/PhoneWindowManager.java
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) {
case KeyEvent.KEYCODE_POWER: {
result = (result & ~ACTION_WAKE_UP) | ACTION_GO_TO_SLEEP;
}
}
*/
KeyEntry* newEntry = new KeyEntry(args->eventTime,
args->deviceId, args->source, policyFlags,
args->action, flags, args->keyCode, args->scanCode,
metaState, repeatCount, args->downTime);
needWake = enqueueInboundEventLocked(newEntry);
if (needWake) {
mLooper->wake();
}
}
//分发Motion事件
void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
mPolicy->interceptMotionBeforeQueueing(args->eventTime, /*byref*/ policyFlags);
/*
如上分析,不再累赘;该接口是:
frameworks/base/services/jni/com_android_server_input_InputManagerService.cpp
void NativeInputManager::interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags) {
jint wmActions = env->CallIntMethod(mServiceObj,
gServiceClassInfo.interceptMotionBeforeQueueingWhenScreenOff,
policyFlags);
handleInterceptActions(wmActions, when, policyFlags);
}
如上interceptMotionBeforeQueueingWhenScreenOff在PhoneWindowManager中实现;分析同上,不再累赘:
frameworks/base/core/java/com/android/internal/policy/PhoneWindowManager.java
public int interceptMotionBeforeQueueingWhenScreenOff(int policyFlags) {
//result |= ACTION_WAKE_UP;
//add by tank
result = result & (~ACTION_WAKE_UP);
//end tank
return result;
}
看看handleInterceptActions函数:
void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when,
uint32_t& policyFlags) {
//接上边PhoneWindowManager中interceptKeyBeforeQueueing对于power键的返回值可知,系统将待机
if (wmActions & WM_ACTION_GO_TO_SLEEP) {
#if DEBUG_INPUT_DISPATCHER_POLICY
ALOGD("handleInterceptActions: Going to sleep.");
#endif
android_server_PowerManagerService_goToSleep(when);
}
//以下说明PhoneWindowManager中interceptMotionBeforeQueueingWhenScreenOff返回值WM_ACTION_WAKE_UP将会导致唤醒
//当然,是可是收到motion事件的前提下
if (wmActions & WM_ACTION_WAKE_UP) {
#if DEBUG_INPUT_DISPATCHER_POLICY
ALOGD("handleInterceptActions: Waking up.");
#endif
android_server_PowerManagerService_wakeUp(when);
}
//以下是可以上报给系统的
if (wmActions & WM_ACTION_PASS_TO_USER) {
policyFlags |= POLICY_FLAG_PASS_TO_USER;
}
}
*/
MotionEntry* newEntry = new MotionEntry(args->eventTime,
args->deviceId, args->source, policyFlags,
args->action, args->flags, args->metaState, args->buttonState,
args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime,
args->displayId,
args->pointerCount, args->pointerProperties, args->pointerCoords);
needWake = enqueueInboundEventLocked(newEntry);
if (needWake) {
mLooper->wake();
}
}
//设备重置
void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
DeviceResetEntry* newEntry = new DeviceResetEntry(args->eventTime, args->deviceId);
needWake = enqueueInboundEventLocked(newEntry);
if (needWake) {
mLooper->wake();
}
}
//C层的按键注入接口
int32_t InputDispatcher::injectInputEvent(const InputEvent* event,
int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis,
uint32_t policyFlags) {
needWake |= enqueueInboundEventLocked(entry);
if (needWake) {
mLooper->wake();
}
}
//setInputWindows
//setFocusedApplication
//setInputDispatchMode
//setInputFilterEnabled
//transferTouchFocus
//registerInputChannel
//unregisterInputChannel
//monitor
2.3.InputDispatcherThread线程处理,根据PhoneWindowManager中的interceptKeyBeforeDispatching决定是否丢弃按键
InputDispatcherThread线程被唤醒
[cpp] view plaincopy
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}
void InputDispatcher::dispatchOnce() {
dispatchOnceInnerLocked(&nextWakeupTime);
mLooper->pollOnce(timeoutMillis);
}
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
if (!mPolicy->isKeyRepeatEnabled()) {
resetKeyRepeatLocked();
}
switch (mPendingEvent->type) {
case EventEntry::TYPE_CONFIGURATION_CHANGED: {
done = dispatchConfigurationChangedLocked(currentTime, typedEntry);
}
case EventEntry::TYPE_DEVICE_RESET: {
done = dispatchDeviceResetLocked(currentTime, typedEntry);
}
case EventEntry::TYPE_KEY: {
done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
}
case EventEntry::TYPE_MOTION: {
done = dispatchMotionLocked(currentTime, typedEntry,
&dropReason, nextWakeupTime);
}
}
dropInboundEventLocked(mPendingEvent, dropReason); //丢弃的事件!!!!
}
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
CommandEntry* commandEntry = postCommandLocked(
& InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
/*
void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
CommandEntry* commandEntry) {
//说明:PhoneWindowManager.java中可以截断事件而不上报,即返回-1、将被丢弃
nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle,
&event, entry->policyFlags);
if (delay < 0) {
entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP;
} else if (!delay) {
entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
} else {
entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER;
entry->interceptKeyWakeupTime = now() + delay;
}
}
*/
else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) {
if (*dropReason == DROP_REASON_NOT_DROPPED) {
*dropReason = DROP_REASON_POLICY; //dropReason是因为策略丢弃
}
}
if (*dropReason != DROP_REASON_NOT_DROPPED) {
setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY
? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);
return true;
}
dispatchEventLocked(currentTime, entry, inputTargets);
}
bool InputDispatcher::dispatchMotionLocked(
nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
dispatchEventLocked(currentTime, entry, inputTargets);
}
2.4.InputDispatcherThread线程分发给应用程序进程
在这里解决了up事件上报两次的问题!!!!!!
frameworks/base/services/input/InputDispatcher.cpp
[cpp] view plaincopy
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {
pokeUserActivityLocked(eventEntry); //和Activity相关,后边三中有设备删除的分析;基本同下
ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
}
void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
}
void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT); //将按键注入队列
/*
void InputDispatcher::enqueueDispatchEntryLocked(
const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget,
int32_t dispatchMode) {
DispatchEntry* dispatchEntry = new DispatchEntry(eventEntry, // increments ref
inputTargetFlags, inputTarget->xOffset, inputTarget->yOffset,
inputTarget->scaleFactor);
if (!connection->inputState.trackKey(keyEntry,
dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags) || (dispatchEntry->resolvedFlags == 0x28)){
//add by tankai 0x28
delete dispatchEntry;
return;
}
}
*/
//dropInboundEventLocked
//synthesizeCancelationEventsForAllConnectionsLocked->
//synthesizeCancelationEventsForConnectionLocked->
/*
void InputDispatcher::synthesizeCancelationEventsForConnectionLocked(
const sp<Connection>& connection, const CancelationOptions& options) {
Vector<EventEntry*> cancelationEvents;
connection->inputState.synthesizeCancelationEvents(currentTime,
cancelationEvents, options);
//关键在这里,mKeyMementos;在enqueueDispatchEntryLocked时调用trackKey由addKeyMemento注入!!!!!!
if (!cancelationEvents.isEmpty()) {
enqueueDispatchEntryLocked(connection, cancelationEventEntry, // increments ref
&target, InputTarget::FLAG_DISPATCH_AS_IS);
}
}
*/
//enqueueDispatchEntriesLocked,注入了0x28标志的按键
startDispatchCycleLocked(currentTime, connection);
}
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
const sp<Connection>& connection) {
switch (eventEntry->type) {
case EventEntry::TYPE_KEY: {
status = connection->inputPublisher.publishKeyEvent(dispatchEntry->seq,
keyEntry->deviceId, keyEntry->source,
dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
keyEntry->keyCode, keyEntry->scanCode,
keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,
keyEntry->eventTime);
}
case EventEntry::TYPE_MOTION: {
status = connection->inputPublisher.publishMotionEvent(dispatchEntry->seq,
motionEntry->deviceId, motionEntry->source,
dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
motionEntry->edgeFlags, motionEntry->metaState, motionEntry->buttonState,
xOffset, yOffset,
motionEntry->xPrecision, motionEntry->yPrecision,
motionEntry->downTime, motionEntry->eventTime,
motionEntry->pointerCount, motionEntry->pointerProperties,
usingCoords);
}
}
}
frameworks/base/libs/androidfw/InputTransport.cpp
[cpp] view plaincopy
status_t InputPublisher::publishKeyEvent(
uint32_t seq,
int32_t deviceId,
int32_t source,
int32_t action,
int32_t flags,
int32_t keyCode,
int32_t scanCode,
int32_t metaState,
int32_t repeatCount,
nsecs_t downTime,
nsecs_t eventTime) {
return mChannel->sendMessage(&msg);
}
status_t InputChannel::sendMessage(const InputMessage* msg) {
do {
nWrite = ::send(mFd, msg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
} while (nWrite == -1 && errno == EINTR);
}
二、Android4.2系统应用程序侧——与View关系
InputManagerService也就是InputDispatcher与应用程序通信是靠looper。
说明:
InputReader从设备文件中读取的是RawEvent,在交给InputDispatcher进行分发之前,它需要先把RawEvent进行转化分类,拆分成KeyEvent、MotionEvent、TrackEvent各种类型等。
InputDispatcher获得按键事件后,根据当前设备的状况来优先消化事件(该过程交由PhoneWindowManager.java来处理);最后,剩余事件分发给ViewRoot;ViewRoot再分发给IME输入法或View、Activity。
1.应用程序View中channel注册过程
frameworks/base/core/java/android/view/ViewRootImpl.java
[cpp] view plaincopy
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
mInputChannel = new InputChannel(); //创建InputChannel
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mInputChannel); //创建与上述InputChannel对应的通道至服务端
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper()); //将本通道注册进InputEventReceiver
}
final class WindowInputEventReceiver extends InputEventReceiver {
public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}
@Override
public void onInputEvent(InputEvent event) {
enqueueInputEvent(event, this, 0, true);
}
}
frameworks/base/core/java/android/view/InputEventReceiver.java
[cpp] view plaincopy
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
mReceiverPtr = nativeInit(this, inputChannel, mMessageQueue);
}
private static native int nativeInit(InputEventReceiver receiver,
InputChannel inputChannel, MessageQueue messageQueue);
frameworks/base/core/jni/android_view_InputEventReceiver.cpp
[cpp] view plaincopy
static jint nativeInit(JNIEnv* env, jclass clazz, jobject receiverObj,
jobject inputChannelObj, jobject messageQueueObj) {
sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,
receiverObj, inputChannel, messageQueue);
status_t status = receiver->initialize();
}
status_t NativeInputEventReceiver::initialize() {
int receiveFd = mInputConsumer.getChannel()->getFd();
mMessageQueue->getLooper()->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, this, NULL);
return OK;
}
frameworks/native/libs/utils/Looper.cpp
[cpp] view plaincopy
int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {
request.callback = callback;
}
2.应用程序View响应过程
frameworks/native/libs/utils/Looper.cpp
[cpp] view plaincopy
int Looper::pollInner(int timeoutMillis) {
awoken(); //阻塞,等待
int callbackResult = response.request.callback->handleEvent(fd, events, data);
}
frameworks/base/core/jni/android_view_InputEventReceiver.cpp
[cpp] view plaincopy
int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
status_t status = consumeEvents(env, false /*consumeBatches*/, -1);
}
status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
bool consumeBatches, nsecs_t frameTime) {
env->CallVoidMethod(mReceiverObjGlobal,
gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
}
frameworks/base/core/java/android/view/InputEventReceiver.java
[cpp] view plaincopy
private void dispatchInputEvent(int seq, InputEvent event) {
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);
}
frameworks/base/core/java/android/view/ViewRootImpl.java
[cpp] view plaincopy
final class WindowInputEventReceiver extends InputEventReceiver {
public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}
@Override
public void onInputEvent(InputEvent event) {
enqueueInputEvent(event, this, 0, true);
}
}
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
scheduleProcessInputEvents();
}
/
有关handler机制请看下文:
http://blog.csdn.net/itachi85/article/details/8035333
[cpp] view plaincopy
final ViewRootHandler mHandler = new ViewRootHandler();
private void scheduleProcessInputEvents() {
Message msg = mHandler.obtainMessage(MSG_PROCESS_INPUT_EVENTS);
mHandler.sendMessage(msg);
}
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_PROCESS_INPUT_EVENTS:
doProcessInputEvents();
}
}
///
[cpp] view plaincopy
void doProcessInputEvents() {
deliverInputEvent(q);
}
private void deliverInputEvent(QueuedInputEvent q) {
deliverKeyEvent(q);
deliverPointerEvent(q);
deliverTrackballEvent(q);
deliverGenericMotionEvent(q);
}
private void deliverKeyEvent(QueuedInputEvent q) {
imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback); //分发给输入法
deliverKeyEventPostIme(q);//分发给View
/*
private void deliverKeyEventPostIme(QueuedInputEvent q) {
mView.dispatchKeyEvent(event)
}
*/
}
private void deliverPointerEvent(QueuedInputEvent q) {
boolean handled = mView.dispatchPointerEvent(event); //分发给View
}
private void deliverTrackballEvent(QueuedInputEvent q) {
imm.dispatchTrackballEvent(mView.getContext(), seq, event,
mInputMethodCallback); //分发给输入法
deliverTrackballEventPostIme(q); //分发给View
/*
private void deliverTrackballEventPostIme(QueuedInputEvent q) {
mView.dispatchTrackballEvent(event)
}
*/
}
private void deliverGenericMotionEvent(QueuedInputEvent q) {
imm.dispatchGenericMotionEvent(mView.getContext(), seq, event,
mInputMethodCallback); //分发给输入法
deliverGenericMotionEventPostIme(q); //分发给View
/*
private void deliverGenericMotionEventPostIme(QueuedInputEvent q) {
updateJoystickDirection(event, false); //游戏手柄的摇杆就是在这处理
mView.dispatchGenericMotionEvent(event)
}
*/
}
分发给应用程序Activity:
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java
[java] view plaincopy
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
public boolean dispatchKeyEvent(KeyEvent event) {
final Callback cb = getCallback();
//cb为应用程序MainActivity
final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event) : super.dispatchKeyEvent(event);
//给应用程序Activity的dispatchKeyEvent处理或交给View的dispatchKeyEvent
}
}
而上述应用程序中的dispatchKeyEvent一般会调用其父类的该方法,例如:
packages/apps/Launcher2/src/com/android/launcher2/Launcher.java
[java] view plaincopy
public boolean dispatchKeyEvent(KeyEvent event) {
return super.dispatchKeyEvent(event);
}
应用程序Activity在分发给与之关联的某个View,如果这个View没有处理、最终交给该Activity自己处理。
应用程序有关View的设置:
[java] view plaincopy
private Dialog mMenuWin;
mMenuWin = new Dialog(aActivity, R.style.CameraDialog);
mMenuWin.setContentView(mMenuLayout);
mMenuWin.setOnClickListener(); //鼠标单击
mMenuWin.setOnLongClickListener(); //
mMenuWin.setOnTouchListener(); //触摸板
mMenuWin.setOnKeyListener(new OnKeyListener() {
public boolean onKey(); //按键
public void onClick(View v); //鼠标单击
}
frameworks/base/core/java/android/app/Activity.java
[java] view plaincopy
public boolean dispatchKeyEvent(KeyEvent event) {
onUserInteraction();
Window win = getWindow();
if (win.superDispatchKeyEvent(event)) { //首先由Window消化,即如果View消化了、则Activity将不在回调onKeyDown
return true;
}
View decor = mDecor; //如果没被消化,会调用Activity的onKeyDown
if (decor == null) decor = win.getDecorView();
return event.dispatch(this, decor != null ? decor.getKeyDispatcherState() : null, this);
}
}
我们重点分析win.superDispatchKeyEvent,也就是View的处理流程:
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java
[java] view plaincopy
public class PhoneWindow extends Window implements MenuBuilder.Callback {
public boolean superDispatchKeyEvent(KeyEvent event) {
return mDecor.superDispatchKeyEvent(event);
}
}
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
public boolean superDispatchKeyEvent(KeyEvent event) {
super.dispatchKeyEvent(event)
}
}
frameworks/base/core/java/android/view/ViewGroup.java //分发给View的关键部分!!!
[java] view plaincopy
public boolean dispatchKeyEvent(KeyEvent event) {
mInputEventConsistencyVerifier.onKeyEvent(event, 1);
super.dispatchKeyEvent(event)
}
frameworks/base/core/java/android/view/View.java
[java] view plaincopy
public boolean dispatchKeyEvent(KeyEvent event) {
li.mOnKeyListener.onKey(this, event.getKeyCode(), event); //回调应用程序View相应方法
event.dispatch(this, mAttachInfo != null ? mAttachInfo.mKeyDispatchState : null, this)
/*
frameworks/base/core/java/android/view/KeyEvent.java
public final boolean dispatch(Callback receiver, DispatcherState state,
Object target) {
//按键响应
boolean res = receiver.onKeyDown(mKeyCode, this); //应用程序回调函数
}
*/
}
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
public boolean dispatchTouchEvent(MotionEvent event) {
//触摸板响应
li.mOnTouchListener.onTouch(this, event) //应用程序继承OnTouchListener,实现的回调接口
//鼠标左键响应
onTouchEvent(event)
/*
public boolean onTouchEvent(MotionEvent event) {
performClick();
//该接口调用li.mOnClickListener.onClick(this);为应用程序继承OnClickListener的回调函数
}
*/
}
以下不再做分析
dispatchGenericMotionEvent
dispatchTrackballEvent
dispatchConfigurationChanged //添加或删除键盘设备Activity重启,见http://blog.csdn.net/tankai19880619/article/details/16805401
三、Input设备与Activity关系
1.InputReaderThread线程检测到设备插入删除
frameworks/base/service/input/InputReader.cpp
[cpp] view plaincopy
void InputReader::loopOnce() {
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
/*
frameworks/base/services/input/EventHub.cpp
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
int32_t readSize = read(device->fd, readBuffer,
sizeof(struct input_event) * capacity);//从驱动读取事件
}
*/
processEventsLocked(mEventBuffer, count);
}
void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
case EventHubInterface::FINISHED_DEVICE_SCAN:
handleConfigurationChangedLocked(rawEvent->when);
}
void InputReader::handleConfigurationChangedLocked(nsecs_t when) {
updateGlobalMetaStateLocked();
// Enqueue configuration changed.
NotifyConfigurationChangedArgs args(when);
mQueuedListener->notifyConfigurationChanged(&args);
}
说明:有的平台需要在接入硬件键盘时Activity不需要刷新;可以在上处做屏蔽:
[cpp] view plaincopy
// add by tank
// do not send configuration change
//NotifyConfigurationChangedArgs args(when);
//mQueuedListener->notifyConfigurationChanged(&args);
// end tank
2.InputReaderThread线程分发给InputDispatcherThread线程
frameworks/base/service/input/InputDispatcher.cpp
[cpp] view plaincopy
void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) {
needWake = enqueueInboundEventLocked(newEntry);
if (needWake) {
mLooper->wake();
}
}
3.InputReaderThread线程收到消息并处理
frameworks/base/service/input/InputDispatcher.cpp
[cpp] view plaincopy
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}
void InputDispatcher::dispatchOnce() {
dispatchOnceInnerLocked(&nextWakeupTime);
}
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
case EventEntry::TYPE_CONFIGURATION_CHANGED: {
ConfigurationChangedEntry* typedEntry =
static_cast<ConfigurationChangedEntry*>(mPendingEvent);
done = dispatchConfigurationChangedLocked(currentTime, typedEntry);
}
}
bool InputDispatcher::dispatchConfigurationChangedLocked(
nsecs_t currentTime, ConfigurationChangedEntry* entry) {
CommandEntry* commandEntry = postCommandLocked(
& InputDispatcher::doNotifyConfigurationChangedInterruptible);
}
void InputDispatcher::doNotifyConfigurationChangedInterruptible(
CommandEntry* commandEntry) {
mPolicy->notifyConfigurationChanged(commandEntry->eventTime);
}
如上,不再做分析:
frameworks/base/services/jni/com_android_server_input_InputManagerService.cpp
[cpp] view plaincopy
void NativeInputManager::notifyConfigurationChanged(nsecs_t when) {
env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyConfigurationChanged, when);
}
frameworks/base/services/java/com/android/server/input/InputManagerService.cpp
[cpp] view plaincopy
private void notifyConfigurationChanged(long whenNanos) {
mWindowManagerCallbacks.notifyConfigurationChanged();
}
如上,不再做分析:
frameworks/base/service/java/com/android/server/wm/InputMonitor.java
[cpp] view plaincopy
public void notifyConfigurationChanged() {
mService.sendNewConfiguration();
}
frameworks/base/service/java/com/android/server/wm/WindowManagerService.java
[cpp] view plaincopy
void sendNewConfiguration() {
mActivityManager.updateConfiguration(null);
/*
mActivityManager = ActivityManagerNative.getDefault();
frameworks/base/core/java/android/app/ActivityManagerNative.java
static public IActivityManager getDefault() {
return gDefault.get();
}
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
IBinder b = ServiceManager.getService("activity");
IActivityManager am = asInterface(b);
return am;
}
frameworks/base/services/java/com/android/server/am/ActivityManagerService.java
public static void setSystemProcess() {
ActivityManagerService m = mSelf;
ServiceManager.addService("activity", m, true);
}
*/
}
4.交由ActivityManagerService进程处理
frameworks/base/services/java/com/android/server/am/ActivityManagerService.java
[cpp] view plaincopy
public void updateConfiguration(Configuration values) {
updateConfigurationLocked(values, null, false, false);
}
boolean updateConfigurationLocked(Configuration values,
ActivityRecord starting, boolean persistent, boolean initLocale) {
kept = mMainStack.ensureActivityConfigurationLocked(starting, changes);
public void setWindowManager(WindowManagerService wm) {
mWindowManager = wm;
}
}
frameworks/base/services/java/com/android/server/am/ActivityStack.java
[cpp] view plaincopy
final boolean ensureActivityConfigurationLocked(ActivityRecord r,
int globalChanges) {
//一般会重启Activity
if ((changes&(~r.info.getRealConfigChanged())) != 0 || r.forceNewConfig) {
relaunchActivityLocked(r, r.configChangeFlags, false);
return false;
}
//应用程序AndroidMenifest中写标记将不会重启
r.app.thread.scheduleActivityConfigurationChanged(r.appToken);
}
frameworks/base/core/java/android/app/ActivityThread.java
[cpp] view plaincopy
public void scheduleActivityConfigurationChanged(IBinder token) {
queueOrSendMessage(H.ACTIVITY_CONFIGURATION_CHANGED, token);
}
//消息循环同上,不再分析
public void handleMessage(Message msg) {
case ACTIVITY_CONFIGURATION_CHANGED:
handleActivityConfigurationChanged((IBinder)msg.obj);
}
final void handleActivityConfigurationChanged(IBinder token) {
performConfigurationChanged(r.activity, mCompatConfiguration);
}
private static void performConfigurationChanged(ComponentCallbacks2 cb, Configuration config) {
cb.onConfigurationChanged(config); //回调Activity类的onConfigurationChanged方法
}
四、项目问题
resumeTopActivity时的Activity重启。http://blog.csdn.net/jivin_shen/article/details/6839175
操作逻辑:打开Launcher界面下的一个应用(比如播放器),完后接入USB键盘;之后退出该应用,也就是resumeTopActivity到Launcher时也引发了config配置更新导致的Activity重启。
原理以及解决部分:
frameworks/base/services/java/com/android/server/am/ActivityStack.java
[cpp] view plaincopy
final boolean resumeTopActivityLocked(ActivityRecord prev) {
return resumeTopActivityLocked(prev, null);
}
final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) {
Configuration config = mService.mWindowManager.updateOrientationFromAppTokens(
mService.mConfiguration,
next.mayFreezeScreenLocked(next.app) ? next.appToken : null);
}
frameworks/base/services/java/com/android/server/wm/WindowManagerService.java
[cpp] view plaincopy
public Configuration updateOrientationFromAppTokens(
Configuration currentConfig, IBinder freezeThisOneIfNeeded) {
config = updateOrientationFromAppTokensLocked(currentConfig,
freezeThisOneIfNeeded);
}
private Configuration updateOrientationFromAppTokensLocked(
Configuration currentConfig, IBinder freezeThisOneIfNeeded) {
computeScreenConfigurationLocked(mTempConfiguration)
}
boolean computeScreenConfigurationLocked(Configuration config) {
if ((sources & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN) {
//change by tank
config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
//config.touchscreen = Configuration.TOUCHSCREEN_FINGER;
//end tank
}
else if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD
&& config.navigation == Configuration.NAVIGATION_NONAV) {
//change by tank
//config.navigation = Configuration.NAVIGATION_DPAD;
//navigationPresence |= presenceFlag;
//end tank
}
if (device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
//change by tank
//config.keyboard = Configuration.KEYBOARD_QWERTY;
//keyboardPresence |= presenceFlag;
//end tank
}
}
面板设备与虚拟驱动导致的up上报两次:
1.drop类按键
down或up:
dispatchOnceInnerLocked>
dropInboundEventLocked>synthesizeCancelationEventsForAllConnectionsLocked- synthesizeCancelationEventsForConnectionLocked>inputState.synthesizeCancelationEvents->mKeyMementos.itemAt(i), 最后上报系统(synthesizeCancelationEventsForConnectionLocked调用 enqueueDispatchEntryLocked)
2.非drop类按键
down:
dispatchOnceInnerLocked->
dispatchKeyLocked->dispatchEventLocked->prepareDispatchCycleLocked->enqueueDispatchEntriesLocked->enqueueDispatchEntryLocked->InputState::trackKey->addKeyMemento //只在down时保存对up的处理
问题:
面板down->drop
虚拟down->非drop,保存up
面板down->drop,将虚拟保存的up送上去
虚拟up->非drop,直接上报
结果——两个虚拟的up
修改方法:
frameworks/base/service/input/InputDispatcher.cpp
[cpp] view plaincopy
void InputDispatcher::enqueueDispatchEntryLocked(
const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget,
int32_t dispatchMode)
{
if (!connection->inputState.trackKey(keyEntry,
dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags)/*add by tank@tcl.com end */ || (dispatchEntry->resolvedFlags == 0x28))
{
#if DEBUG_DISPATCH_CYCLE
ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent key event",
connection->getInputChannelName());
#endif
delete dispatchEntry;
return; // skip the inconsistent event
}
/*
//add by tankai
if(dispatchEntry->resolvedFlags == 0x28 && keyEntry->deviceId == 3){
ALOGD("TK--------->>>delete sim KeyMementos up\n");
delete dispatchEntry;
return; // skip the inconsistent event
}
//end tankai
*/
}
转载于:https://blog.51cto.com/8840150/1577418