Monkey自动停止运行

相信做机的小伙伴都少不了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仍然健在

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UTF-8XD

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值