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仍然健在

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章