相信做机的小伙伴都少不了Monkey测试,产线反馈Monkey有偶先自动停止运行现象
刚开始怀疑是被系统kill掉,然后去追log 未发现Monkey进程被系统kill掉,自己尝试复现也未复现 ,老老实实去分析产线提供的log
1、check跑monkey输出的 monkeyerror.txt
args: [--ignore-crashes, --ignore-timeouts, --kill-process-after-error, --ignore-security-exceptions, -v, 5000000]
arg: "--ignore-crashes"
arg: "--ignore-timeouts"
arg: "--kill-process-after-error"
arg: "--ignore-security-exceptions"
arg: "-v"
arg: "5000000"
arg="--ignore-crashes" mCurArgData="null" mNextArg=1 argwas="--ignore-crashes" nextarg="--ignore-timeouts"
arg="--ignore-timeouts" mCurArgData="null" mNextArg=2 argwas="--ignore-timeouts" nextarg="--kill-process-after-error"
arg="--kill-process-after-error" mCurArgData="null" mNextArg=3 argwas="--kill-process-after-error" nextarg="--ignore-security-exceptions"
arg="--ignore-security-exceptions" mCurArgData="null" mNextArg=4 argwas="--ignore-security-exceptions" nextarg="-v"
** Error: A RuntimeException occurred:
java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission
at android.os.Parcel.readException(Parcel.java:2013)
at android.os.Parcel.readException(Parcel.java:1959)
at android.hardware.input.IInputManager$Stub$Proxy.injectInputEvent(IInputManager.java:636)
at android.hardware.input.InputManager.injectInputEvent(InputManager.java:925)
at com.android.commands.monkey.MonkeyMotionEvent.injectEvent(MonkeyMotionEvent.java:188)
at com.android.commands.monkey.Monkey.runMonkeyCycles(Monkey.java:1201)
at com.android.commands.monkey.Monkey.run(Monkey.java:697)
at com.android.commands.monkey.Monkey.main(Monkey.java:557)
at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:285)
** System appears to have crashed at event 801 of 5000000 using seed 1572873523464
同步check monkeyinfo.txt
// Injection Failed
// Injection Failed
:Sending Touch (ACTION_DOWN): 0:(318.0,1334.0)
:Sending Touch (ACTION_UP): 0:(317.81262,1324.7065)
:Sending Touch (ACTION_DOWN): 0:(601.0,993.0)
:Sending Touch (ACTION_UP): 0:(593.1743,1000.7338)
:Sending Trackball (ACTION_MOVE): 0:(-3.0,-1.0)
// Injection Failed
:Sending Touch (ACTION_DOWN): 0:(424.0,548.0)
//[calendar_time:2019-11-04 16:22:30.432 system_uptime:261352174]
// Sending event #800
Events injected: 801
:Sending rotation degree=0, persist=false
:Dropped: keys=18 pointers=2 trackballs=0 flips=2 rotations=0
## Network stats: elapsed time=22748ms (0ms mobile, 0ms wifi, 22748ms not connected)
通过异常信息科看出系统在5000000个操作的第801个操作里出现了异常,并停止
无奈去扒一扒sendPointerSync注入事件流程,分析 android.app.Instrumentation的sendpointerSync的实现原理
(1)android.app.Instrumentation
frameworks/base/core/java/android/app/Instrumentation.java
/**
* Dispatch a pointer event. Finished at some point after the recipient has
* returned from its event processing, though it may <em>not</em> have
* completely finished reacting from the event -- for example, if it needs
* to update its display as a result, it may still be in the process of
* doing that.
*
* @param event A motion event describing the pointer action. (As noted in
* {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use
* {@link SystemClock#uptimeMillis()} as the timebase.
*/
public void sendPointerSync(MotionEvent event) {
validateNotAppThread();
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
}
InputManager.getInstance().injectInputEvent(event,
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
}
(2)android.hardware.input.InputManager
frameworks/base/core/java/android/hardware/input/InputManager.java
/**
* Injects an input event into the event system on behalf of an application.
* The synchronization mode determines whether the method blocks while waiting for
* input injection to proceed.
* <p>
* Requires {@link android.Manifest.permission.INJECT_EVENTS} to inject into
* windows that are owned by other applications.
* </p><p>
* Make sure you correctly set the event time and input source of the event
* before calling this method.
* </p>
*
* @param event The event to inject.
* @param mode The synchronization mode. One of:
* {@link #INJECT_INPUT_EVENT_MODE_ASYNC},
* {@link #INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT}, or
* {@link #INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH}.
* @return True if input event injection succeeded.
*
* @hide
*/
public boolean injectInputEvent(InputEvent event, int mode) {
if (event == null) {
throw new IllegalArgumentException("event must not be null");
}
if (mode != INJECT_INPUT_EVENT_MODE_ASYNC
&& mode != INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
&& mode != INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT) {
throw new IllegalArgumentException("mode is invalid");
}
try {
return mIm.injectInputEvent(event, mode);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
(3)最后是调用Binder.transect进行跨进程调用。被调用者是系统服务,可追朔到InputManagerService的injectInputEvent
frameworks/base/services/java/com/android/server/input/InputManagerService.java
private boolean injectInputEventInternal(InputEvent event, int displayId, int mode) {
if (event == null) {
throw new IllegalArgumentException("event must not be null");
}
if (mode != InputManager.INJECT_INPUT_EVENT_MODE_ASYNC
&& mode != InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
&& mode != InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT) {
throw new IllegalArgumentException("mode is invalid");
}
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
final int result;
try {
result = nativeInjectInputEvent(mPtr, event, displayId, pid, uid, mode,
INJECTION_TIMEOUT_MILLIS, WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT);
} finally {
Binder.restoreCallingIdentity(ident);
}
switch (result) {
case INPUT_EVENT_INJECTION_PERMISSION_DENIED:
Slog.w(TAG, "Input event injection from pid " + pid + " permission denied.");
throw new SecurityException(
"Injecting to another application requires INJECT_EVENTS permission");
case INPUT_EVENT_INJECTION_SUCCEEDED:
return true;
case INPUT_EVENT_INJECTION_TIMED_OUT:
Slog.w(TAG, "Input event injection from pid " + pid + " timed out.");
return false;
case INPUT_EVENT_INJECTION_FAILED:
default:
Slog.w(TAG, "Input event injection from pid " + pid + " failed.");
return false;
}
}
在这个函数中,终于发现了我们要找的SecurityException INPUT_EVENT_INJECTION_PERMISSION_DENIED是jni层的nativeInjectInputEvent
(4)frameworks/base/services/jni/com_android_server_input_InputManagerService.cpp
static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */,
jlong ptr, jobject inputEventObj, jint displayId, jint injectorPid, jint injectorUid,
jint syncMode, jint timeoutMillis, jint policyFlags) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
if (env->IsInstanceOf(inputEventObj, gKeyEventClassInfo.clazz)) {
KeyEvent keyEvent;
status_t status = android_view_KeyEvent_toNative(env, inputEventObj, & keyEvent);
if (status) {
jniThrowRuntimeException(env, "Could not read contents of KeyEvent object.");
return INPUT_EVENT_INJECTION_FAILED;
}
return (jint) im->getInputManager()->getDispatcher()->injectInputEvent(
& keyEvent, displayId, injectorPid, injectorUid, syncMode, timeoutMillis,
uint32_t(policyFlags));
} else if (env->IsInstanceOf(inputEventObj, gMotionEventClassInfo.clazz)) {
const MotionEvent* motionEvent = android_view_MotionEvent_getNativePtr(env, inputEventObj);
if (!motionEvent) {
jniThrowRuntimeException(env, "Could not read contents of MotionEvent object.");
return INPUT_EVENT_INJECTION_FAILED;
}
return (jint) im->getInputManager()->getDispatcher()->injectInputEvent(
motionEvent, displayId, injectorPid, injectorUid, syncMode, timeoutMillis,
uint32_t(policyFlags));
} else {
jniThrowRuntimeException(env, "Invalid input event type.");
return INPUT_EVENT_INJECTION_FAILED;
}
}
(5)frameworks/base/services/input/InputDispatcher.cpp
// Check permission to inject into all touched foreground windows and ensure there
// is at least one touched foreground window.
{
bool haveForegroundWindow = false;
for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
haveForegroundWindow = true;
if (! checkInjectionPermission(touchedWindow.windowHandle,
entry->injectionState)) {
injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
injectionPermission = INJECTION_PERMISSION_DENIED;
goto Failed;
}
}
}
终于发现了INPUT_EVENT_INJECTION_PERMISSION_DENIED踪迹,继续查看checkInjectionPermission:
bool InputDispatcher::checkInjectionPermission(const sp<InputWindowHandle>& windowHandle,
const InjectionState* injectionState) {
if (injectionState
&& (windowHandle == NULL
|| windowHandle->getInfo()->ownerUid != injectionState->injectorUid)
&& !hasInjectionPermission(injectionState->injectorPid, injectionState->injectorUid)) {
if (windowHandle != NULL) {
ALOGW("Permission denied: injecting event from pid %d uid %d to window %s "
"owned by uid %d",
injectionState->injectorPid, injectionState->injectorUid,
windowHandle->getName().string(),
windowHandle->getInfo()->ownerUid);
} else {
ALOGW("Permission denied: injecting event from pid %d uid %d",
injectionState->injectorPid, injectionState->injectorUid);
}
return false;
}
return true;
}
查看系统日志,发现是造成Permission验证失败的原因是:当前windowHandle(被测app)->owneruid与注入者(instrument)->injectoruid不一致
android系统做了限制,不允许跨进程注入,这个方法只能在自己这个程序内用,home出去就不行了
好了 看到这里是因为跨进程注入事件 权限验证失败导致monkey自动退出的
了解到原理后 自己很容易就能复现该问题:
Monkey跑起来后 手动操作,乱点一通 很快就能复现Monkey crash退出
那么此题的解决思路是:
结合monkey crash的堆栈:
** Error: A RuntimeException occurred:
java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission
at android.os.Parcel.readException(Parcel.java:2013)
at android.os.Parcel.readException(Parcel.java:1959)
at android.hardware.input.IInputManager$Stub$Proxy.injectInputEvent(IInputManager.java:636)
at android.hardware.input.InputManager.injectInputEvent(InputManager.java:925)
at com.android.commands.monkey.MonkeyMotionEvent.injectEvent(MonkeyMotionEvent.java:188)
at com.android.commands.monkey.Monkey.runMonkeyCycles(Monkey.java:1201)
at com.android.commands.monkey.Monkey.run(Monkey.java:697)
at com.android.commands.monkey.Monkey.main(Monkey.java:557)
at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:285)
我们可以放心大胆的在injectInputEvent中捕获该异常
@Override
public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {
MotionEvent me = getEvent();
if ((verbose > 0 && !mIntermediateNote) || verbose > 1) {
StringBuilder msg = new StringBuilder(":Sending ");
msg.append(getTypeLabel()).append(" (");
switch (me.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
msg.append("ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
msg.append("ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
msg.append("ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
msg.append("ACTION_CANCEL");
break;
case MotionEvent.ACTION_POINTER_DOWN:
msg.append("ACTION_POINTER_DOWN ").append(me.getPointerId(me.getActionIndex()));
break;
case MotionEvent.ACTION_POINTER_UP:
msg.append("ACTION_POINTER_UP ").append(me.getPointerId(me.getActionIndex()));
break;
default:
msg.append(me.getAction());
break;
}
msg.append("):");
int pointerCount = me.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
msg.append(" ").append(me.getPointerId(i));
msg.append(":(").append(me.getX(i)).append(",").append(me.getY(i)).append(")");
}
Logger.out.println(msg.toString());
}
try {
if (!InputManager.getInstance().injectInputEvent(me,
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT)) {
return MonkeyEvent.INJECT_FAIL;
}
//*/Dev.Allen,20191121 modify for monkey crash
} catch (Exception ex) {
Logger.out.println(ex.toString());
//*/
} finally {
me.recycle();
}
return MonkeyEvent.INJECT_SUCCESS;
}
单编/development/cmds/monkey后验证
adb shell logcat | grep -i "INJECT_EVENTS permission"
10-01 00:01:20.452 6781 6781 I Monkey : java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission
10-01 00:01:20.453 6781 6781 I Monkey : java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission
10-01 00:01:45.115 6781 6781 I Monkey : java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission
10-01 00:01:45.120 6781 6781 I Monkey : java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission
10-01 00:01:45.123 6781 6781 I Monkey : java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission
10-01 00:01:45.130 6781 6781 I Monkey : java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission
10-01 00:01:45.134 6781 6781 I Monkey : java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission
10-01 00:01:52.799 6781 6781 I Monkey : java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission
10-01 00:01:52.801 6781 6781 I Monkey : java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission
10-01 00:01:52.803 6781 6781 I Monkey : java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission
10-01 00:01:52.806 6781 6781 I Monkey : java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission
异常被捕获后 monkey仍然健在