Instrumentation框架分析及其使用

本文旨在從Android系統源碼出發,簡單梳理Instrumentation框架的行為及邏輯結構,供有興趣的同學一起學習

從am instrument談起

am instrument命令的執行

我們知道,命令行運行Android測試的命令是adb shell am instrument,這個命令是如何調起我們的測試代碼來進行測試的呢,讓我們從am命令的處理源碼來開始一步步的查看吧。

am.java是android系統處理am命令的類,其位于/frameworks/base/cmds/am/src/com/android/commands/am/下,有Android源碼的同學可以到相關目錄下自行查看

onRun方法是am處理各個不同命令的分發處,我們可以看到am命令有多種用法,其中am instrumentation命令會調用runInstrument()方法

public void onRun() throws Exception {

    mAm = ActivityManagerNative.getDefault();
    if (mAm == null) {
        System.err.println(NO_SYSTEM_ERROR_CODE);
        throw new AndroidException("Can't connect to activity manager; is the system running?");
    }

    String op = nextArgRequired();

    if (op.equals("start")) {
        runStart();
    } else if (op.equals("startservice")) {
        runStartService();
    } else if (op.equals("stopservice")) {
        runStopService();
    } else if (op.equals("force-stop")) {
        runForceStop();
    } else if (op.equals("kill")) {
        runKill();
    } else if (op.equals("kill-all")) {
        runKillAll();
    } else if (op.equals("instrument")) {
        runInstrument();
    } else if (op.equals("broadcast")) {
        sendBroadcast();
    } else if (op.equals("profile")) {
        runProfile();
    } else if (op.equals("dumpheap")) {
        runDumpHeap();
    } else if (op.equals("set-debug-app")) {
        runSetDebugApp();
    } else if (op.equals("clear-debug-app")) {
        runClearDebugApp();
    } else if (op.equals("bug-report")) {
        runBugReport();
    } else if (op.equals("monitor")) {
        runMonitor();
    } else if (op.equals("hang")) {
        runHang();
    } else if (op.equals("restart")) {
        runRestart();
    } else if (op.equals("idle-maintenance")) {
        runIdleMaintenance();
    } else if (op.equals("screen-compat")) {
        runScreenCompat();
    } else if (op.equals("to-uri")) {
        runToUri(0);
    } else if (op.equals("to-intent-uri")) {
        runToUri(Intent.URI_INTENT_SCHEME);
    } else if (op.equals("to-app-uri")) {
        runToUri(Intent.URI_ANDROID_APP_SCHEME);
    } else if (op.equals("switch-user")) {
        runSwitchUser();
    } else if (op.equals("start-user")) {
        runStartUserInBackground();
    } else if (op.equals("stop-user")) {
        runStopUser();
    } else if (op.equals("stack")) {
        runStack();
    } else if (op.equals("lock-task")) {
        runLockTask();
    } else if (op.equals("get-config")) {
        runGetConfig();
    } else {
        showError("Error: unknown command '" + op + "'");
    }
}

以下是runInsturmentation方法的源碼

private void runInstrument() throws Exception {
    String profileFile = null;
    boolean wait = false;
    boolean rawMode = false;
    boolean no_window_animation = false;
    int userId = UserHandle.USER_CURRENT;
    Bundle args = new Bundle();
    String argKey = null, argValue = null;
    IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
    String abi = null;

    String opt;
    while ((opt=nextOption()) != null) {
        if (opt.equals("-p")) {
            profileFile = nextArgRequired();
        } else if (opt.equals("-w")) {
            wait = true;
        } else if (opt.equals("-r")) {
            rawMode = true;
        } else if (opt.equals("-e")) {
            argKey = nextArgRequired();
            argValue = nextArgRequired();
            args.putString(argKey, argValue);
        } else if (opt.equals("--no_window_animation")
                || opt.equals("--no-window-animation")) {
            no_window_animation = true;
        } else if (opt.equals("--user")) {
            userId = parseUserArg(nextArgRequired());
        } else if (opt.equals("--abi")) {
            abi = nextArgRequired();
        } else {
            System.err.println("Error: Unknown option: " + opt);
            return;
        }
    }

    if (userId == UserHandle.USER_ALL) {
        System.err.println("Error: Can't start instrumentation with user 'all'");
        return;
    }

    String cnArg = nextArgRequired();
    ComponentName cn = ComponentName.unflattenFromString(cnArg);
    if (cn == null) throw new IllegalArgumentException("Bad component name: " + cnArg);

    InstrumentationWatcher watcher = null;
    UiAutomationConnection connection = null;
    if (wait) {
        watcher = new InstrumentationWatcher();
        watcher.setRawOutput(rawMode);
        connection = new UiAutomationConnection();
    }

    float[] oldAnims = null;
    if (no_window_animation) {
        oldAnims = wm.getAnimationScales();
        wm.setAnimationScale(0, 0.0f);
        wm.setAnimationScale(1, 0.0f);
    }

    if (abi != null) {
        final String[] supportedAbis = Build.SUPPORTED_ABIS;
        boolean matched = false;
        for (String supportedAbi : supportedAbis) {
            if (supportedAbi.equals(abi)) {
                matched = true;
                break;
            }
        }

        if (!matched) {
            throw new AndroidException(
                    "INSTRUMENTATION_FAILED: Unsupported instruction set " + abi);
        }
    }

    if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId, abi)) {
        throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
    }

    if (watcher != null) {
        if (!watcher.waitForFinish()) {
            System.out.println("INSTRUMENTATION_ABORTED: System has crashed.");
        }
    }

    if (oldAnims != null) {
        wm.setAnimationScales(oldAnims);
    }
}

該方法主要做了這么幾件事:

  1. 解析參數并處理異常,目前支持的參數為(-w,-p,-r,-e,--no_window_animation,--no-window-animation,--user,--abi)
  2. 獲取測試包名和TestRunner,格式為測試包名/TestRunner
  3. 進行一些參數的邏輯處理(通常沒有使用到,可以暫不關注)
  4. 啟動TestRunner進行測試(mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId, abi))
  5. 如果附帶了-w參數,會等待至執行完成,否則直接結束處理

各個指令含義解析:

  • -w, 等待執行完成后才返回,否則直接返回(Instrumentation的執行在不同線程,不管是否帶該參數都會正確執行)
  • -p, 帶1個參數,將一些配置寫入指定文件(具體用處還未研究,后續有需要再補充)
  • -r, 輸出原始的數據(具體用處還未研究,后續有需要再補充)
  • -e, 帶兩個參數,將這兩個參數作為鍵值對傳遞給TestRunner,由TestRunner處理(后面會提到)
  • --no_window_animation或--no-window-animation,執行Instrumentation過程中禁用動畫效果,執行完后會恢復
  • --user, 帶1個參數,使用指定的uid運行(具體用處還未研究,后續有需要再補充)
  • --abi, 帶1個參數,使用指定的abi運行(具體用處還未研究,后續有需要再補充)

mAm是一個IActivityManager的對象,調用其startInstrumentation方法開始處理Instrumentation,下面我們來看看ActivityManager相關的知識

ActivityManager相關知識

ActivityManager是android框架的一個重要部分,它負責一新ActivityThread進程創建,Activity生命周期的維護,下圖為這幾個類之間的層次關系:



在這張圖中,綠色的部分是在SDK中開放給應用程序開發人員的接口,藍色的部分是一個典型的Proxy模式,紅色的部分是底層的服務實現,是真正的動作執行者。這里的一個核心思想是Proxy模式,關于代理模式相關知識,請參考(暫卻,后續補上)。以上僅是簡單的介紹了下者幾個類的關系,隨著我們上文的步伐,我們會一點點分析出am命令是如何讓Android系統跑起來測試用例的。

獲取ActivityManager

還記得之前在am命令中啟動Instrumentation的命令么?對的就是這個
mAm.startInstrumentation(cn, profileFile, 0, args, watcher, connection, userId, abi)
其中的mAm為
mAm = ActivityManagerNative.getDefault();
接下來便是要研究ActivityManagerNative.getDefault()了:

static public IActivityManager getDefault() {
    return gDefault.get();
}

gDefault的定義是IActivityManager的一個單例對象

private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
    protected IActivityManager create() {
        IBinder b = ServiceManager.getService("activity");
        if (false) {
            Log.v("ActivityManager", "default service binder = " + b);
        }
        IActivityManager am = asInterface(b);
        if (false) {
            Log.v("ActivityManager", "default service = " + am);
        }
        return am;
    }
};

獲取到名為activity的服務后,調用asInterface方法:

static public IActivityManager asInterface(IBinder obj) {
    if (obj == null) {
        return null;
    }
    IActivityManager in =
        (IActivityManager)obj.queryLocalInterface(descriptor);
    if (in != null) {
        return in;
    }

    return new ActivityManagerProxy(obj);
}

返回的是一個ActivityManagerProxy對象,然后按照原來的流程應該執行的是startInstrumentation方法

public boolean startInstrumentation(ComponentName className, String profileFile,
        int flags, Bundle arguments, IInstrumentationWatcher watcher,
        IUiAutomationConnection connection, int userId, String instructionSet)
        throws RemoteException {
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    data.writeInterfaceToken(IActivityManager.descriptor);
    ComponentName.writeToParcel(className, data);
    data.writeString(profileFile);
    data.writeInt(flags);
    data.writeBundle(arguments);
    data.writeStrongBinder(watcher != null ? watcher.asBinder() : null);
    data.writeStrongBinder(connection != null ? connection.asBinder() : null);
    data.writeInt(userId);
    data.writeString(instructionSet);
    mRemote.transact(START_INSTRUMENTATION_TRANSACTION, data, reply, 0);
    reply.readException();
    boolean res = reply.readInt() != 0;
    reply.recycle();
    data.recycle();
    return res;
}

將相關參數寫入打包后調用mRemote.transact方法,這個mRemote即初始化ActivityManagerProxy時傳入的IBinder對象,即ServiceManager.getService("activity")

public static IBinder getService(String name) {
    try {
        IBinder service = sCache.get(name);
        if (service != null) {
            return service;
        } else {
            return getIServiceManager().getService(name);
        }
    } catch (RemoteException e) {
        Log.e(TAG, "error in getService", e);
    }
    return null;
}

可見ServiceManager會先從sCache緩存中查看是否有對應的Binder對象,有則返回,沒有則調用getIServiceManager().getService(name),那么要獲取這個以activity命名的Service,它是在哪里創建的呢?通過全局搜索,我們找到這個調用關系,由于中間的方法實在是太太太太太長了,大家有興趣的自己去看源碼吧,其調用過程如下:
zygote->main->new SystemServer().run()->[SystemServer]startBootstrapServices()->[SystemServer]mActivityManagerService.setSystemProcess()->[ActivityManagerService]ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true)

由此可見,這個名為mRemote的Binder對應的是ActivityManagerService,ActivityManagerService的transact方法繼承了Binder的實現:

public final boolean transact(int code, Parcel data, Parcel reply,
        int flags) throws RemoteException {
    if (false) Log.v("Binder", "Transact: " + code + " to " + this);
    if (data != null) {
        data.setDataPosition(0);
    }
    boolean r = onTransact(code, data, reply, flags);
    if (reply != null) {
        reply.setDataPosition(0);
    }
    return r;
}

會調用onTransact方法:

public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
        throws RemoteException {
    if (code == SYSPROPS_TRANSACTION) {
        // We need to tell all apps about the system property change.
        ArrayList<IBinder> procs = new ArrayList<IBinder>();
        synchronized(this) {
            final int NP = mProcessNames.getMap().size();
            for (int ip=0; ip<NP; ip++) {
                SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip);
                final int NA = apps.size();
                for (int ia=0; ia<NA; ia++) {
                    ProcessRecord app = apps.valueAt(ia);
                    if (app.thread != null) {
                        procs.add(app.thread.asBinder());
                    }
                }
            }
        }

        int N = procs.size();
        for (int i=0; i<N; i++) {
            Parcel data2 = Parcel.obtain();
            try {
                procs.get(i).transact(IBinder.SYSPROPS_TRANSACTION, data2, null, 0);
            } catch (RemoteException e) {
            }
            data2.recycle();
        }
    }
    try {
        return super.onTransact(code, data, reply, flags);
    } catch (RuntimeException e) {
        // The activity manager only throws security exceptions, so let's
        // log all others.
        if (!(e instanceof SecurityException)) {
            Slog.wtf(TAG, "Activity Manager Crash", e);
        }
        throw e;
    }
}

由于statusCode不為SYSPROPS_TRANSACTION會調用父類ActivityManagerNative的onTransact方法,方法由于statusCode很多,我們只挑選了符合我們要求的部分的源碼:

case START_INSTRUMENTATION_TRANSACTION: {
    data.enforceInterface(IActivityManager.descriptor);
    ComponentName className = ComponentName.readFromParcel(data);
    String profileFile = data.readString();
    int fl = data.readInt();
    Bundle arguments = data.readBundle();
    IBinder b = data.readStrongBinder();
    IInstrumentationWatcher w = IInstrumentationWatcher.Stub.asInterface(b);
    b = data.readStrongBinder();
    IUiAutomationConnection c = IUiAutomationConnection.Stub.asInterface(b);
    int userId = data.readInt();
    String abiOverride = data.readString();
    boolean res = startInstrumentation(className, profileFile, fl, arguments, w, c, userId,
            abiOverride);
    reply.writeNoException();
    reply.writeInt(res ? 1 : 0);
    return true;
}

在讀取出相應數據后調用startInstrumentation方法,開始執行Instrumentation

啟動Instrumentation

所以回到之前am命令的處理,實際調用的是ActivityManagerService的startInstrumentation方法。所以Instrumentation的啟動是由ActivityManagerService.startInstrumentation()方法完成的

public boolean startInstrumentation(ComponentName className,
        String profileFile, int flags, Bundle arguments,
        IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection,
        int userId, String abiOverride) {
    enforceNotIsolatedCaller("startInstrumentation");
    userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
            userId, false, ALLOW_FULL_ONLY, "startInstrumentation", null);
    // Refuse possible leaked file descriptors
    if (arguments != null && arguments.hasFileDescriptors()) {
        throw new IllegalArgumentException("File descriptors passed in Bundle");
    }

    synchronized(this) {
        InstrumentationInfo ii = null;
        ApplicationInfo ai = null;
        try {
            ii = mContext.getPackageManager().getInstrumentationInfo(
                className, STOCK_PM_FLAGS);
            ai = AppGlobals.getPackageManager().getApplicationInfo(
                    ii.targetPackage, STOCK_PM_FLAGS, userId);
        } catch (PackageManager.NameNotFoundException e) {
        } catch (RemoteException e) {
        }
        if (ii == null) {
            reportStartInstrumentationFailure(watcher, className,
                    "Unable to find instrumentation info for: " + className);
            return false;
        }
        if (ai == null) {
            reportStartInstrumentationFailure(watcher, className,
                    "Unable to find instrumentation target package: " + ii.targetPackage);
            return false;
        }

        int match = mContext.getPackageManager().checkSignatures(
                ii.targetPackage, ii.packageName);
        if (match < 0 && match != PackageManager.SIGNATURE_FIRST_NOT_SIGNED) {
            String msg = "Permission Denial: starting instrumentation "
                    + className + " from pid="
                    + Binder.getCallingPid()
                    + ", uid=" + Binder.getCallingPid()
                    + " not allowed because package " + ii.packageName
                    + " does not have a signature matching the target "
                    + ii.targetPackage;
            reportStartInstrumentationFailure(watcher, className, msg);
            throw new SecurityException(msg);
        }

        final long origId = Binder.clearCallingIdentity();
        // Instrumentation can kill and relaunch even persistent processes
        forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, false, userId,
                "start instr");
        ProcessRecord app = addAppLocked(ai, false, abiOverride);
        app.instrumentationClass = className;
        app.instrumentationInfo = ai;
        app.instrumentationProfileFile = profileFile;
        app.instrumentationArguments = arguments;
        app.instrumentationWatcher = watcher;
        app.instrumentationUiAutomationConnection = uiAutomationConnection;
        app.instrumentationResultClass = className;
        Binder.restoreCallingIdentity(origId);
    }

    return true;
}

該方法做了如下的事情:

  • 檢查TestRunner是否存在
  • 檢查TargetPackage是否存在
  • 檢測簽名是否一致
  • 強制關閉被測包
  • 通過addAppLocked方法創建ProcessRecord

addAppLocked方法的源碼如下:

final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated,
        String abiOverride) {
    ProcessRecord app;
    if (!isolated) {
        app = getProcessRecordLocked(info.processName, info.uid, true);
    } else {
        app = null;
    }

    if (app == null) {
        app = newProcessRecordLocked(info, null, isolated, 0);
        mProcessNames.put(info.processName, app.uid, app);
        if (isolated) {
            mIsolatedProcesses.put(app.uid, app);
        }
        updateLruProcessLocked(app, false, null);
        updateOomAdjLocked();
    }

    // This package really, really can not be stopped.
    try {
        AppGlobals.getPackageManager().setPackageStoppedState(
                info.packageName, false, UserHandle.getUserId(app.uid));
    } catch (RemoteException e) {
    } catch (IllegalArgumentException e) {
        Slog.w(TAG, "Failed trying to unstop package "
                + info.packageName + ": " + e);
    }

    if ((info.flags&(ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT))
            == (ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT)) {
        app.persistent = true;
        app.maxAdj = ProcessList.PERSISTENT_PROC_ADJ;
    }
    if (app.thread == null && mPersistentStartingProcesses.indexOf(app) < 0) {
        mPersistentStartingProcesses.add(app);
        startProcessLocked(app, "added application", app.processName, abiOverride,
                null /* entryPoint */, null /* entryPointArgs */);
    }

    return app;
}

之后會調用startProcessLocked方法啟動進程,啟動進程的過程就比較復雜了,暫時不去分析了,具體調用流程如下:startProcessLocked->Process.start->startViaZygote->zygoteSendArgsAndGetResult,zygoteSendArgsAndGetResult函數最終實現的,是向socket服務端寫書據,把創建進程的請求通過socket通訊方式讓framework的進程孵化類zygote創建新進程。而數據就是argsForZygote(一個以字符串List形式的把Process.start()所有調用參數都包含在里面的變量),具體的啟動過程可以參考:android進程創建分析

socket服務端收到創建新進程的請求,ZygoteConnection.runOnce()接收到新進程的參數,然后調用Zygote.forkAndSpecialize()來fork一個子進程,在子進程中會接著關閉socket,調用ZygoteInit.invokeStaticMain(cloader, className, mainArgs),即調用ActivityThread.main()。 新的應用進程會從ActivityThread 的 main()函數處開始執行。

ActivityThread,新的進程

首先來看入口,main函數:

public static void main(String[] args) {
    SamplingProfilerIntegration.start();

    // CloseGuard defaults to true and can be quite spammy.  We
    // disable it here, but selectively enable it later (via
    // StrictMode) on debug builds, but using DropBox, not logs.
    CloseGuard.setEnabled(false);

    Environment.initForCurrentUser();

    // Set the reporter for event logging in libcore
    EventLogger.setReporter(new EventLoggingReporter());

    Security.addProvider(new AndroidKeyStoreProvider());

    // Make sure TrustedCertificateStore looks in the right place for CA certificates
    final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
    TrustedCertificateStore.setDefaultUserDirectory(configDir);

    Process.setArgV0("<pre-initialized>");

    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

我們看到main方法初始化了主線程的消息隊列,實例化了一個ActivityThread對象,然后調用了它的attach方法:

private void attach(boolean system) {
    sCurrentActivityThread = this;
    mSystemThread = system;
    if (!system) {
        ViewRootImpl.addFirstDrawHandler(new Runnable() {
            @Override
            public void run() {
                ensureJitEnabled();
            }
        });
        android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
                                                UserHandle.myUserId());
        RuntimeInit.setApplicationObject(mAppThread.asBinder());
        final IActivityManager mgr = ActivityManagerNative.getDefault();
        try {
            mgr.attachApplication(mAppThread);
        } catch (RemoteException ex) {
            // Ignore
        }
        // Watch for getting close to heap limit.
        BinderInternal.addGcWatcher(new Runnable() {
            @Override public void run() {
                if (!mSomeActivitiesChanged) {
                    return;
                }
                Runtime runtime = Runtime.getRuntime();
                long dalvikMax = runtime.maxMemory();
                long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
                if (dalvikUsed > ((3*dalvikMax)/4)) {
                    if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
                            + " total=" + (runtime.totalMemory()/1024)
                            + " used=" + (dalvikUsed/1024));
                    mSomeActivitiesChanged = false;
                    try {
                        mgr.releaseSomeActivities(mAppThread);
                    } catch (RemoteException e) {
                    }
                }
            }
        });
    } else {
        // Don't set application object here -- if the system crashes,
        // we can't display an alert, we just want to die die die.
        android.ddm.DdmHandleAppName.setAppName("system_process",
                UserHandle.myUserId());
        try {
            mInstrumentation = new Instrumentation();
            ContextImpl context = ContextImpl.createAppContext(
                    this, getSystemContext().mPackageInfo);
            mInitialApplication = context.mPackageInfo.makeApplication(true, null);
            mInitialApplication.onCreate();
        } catch (Exception e) {
            throw new RuntimeException(
                    "Unable to instantiate Application():" + e.toString(), e);
        }
    }

    // add dropbox logging to libcore
    DropBox.setReporter(new DropBoxReporter());

    ViewRootImpl.addConfigCallback(new ComponentCallbacks2() {
        @Override
        public void onConfigurationChanged(Configuration newConfig) {
            synchronized (mResourcesManager) {
                // We need to apply this change to the resources
                // immediately, because upon returning the view
                // hierarchy will be informed about it.
                if (mResourcesManager.applyConfigurationToResourcesLocked(newConfig, null)) {
                    // This actually changed the resources!  Tell
                    // everyone about it.
                    if (mPendingConfiguration == null ||
                            mPendingConfiguration.isOtherSeqNewer(newConfig)) {
                        mPendingConfiguration = newConfig;
                        
                        sendMessage(H.CONFIGURATION_CHANGED, newConfig);
                    }
                }
            }
        }
        @Override
        public void onLowMemory() {
        }
        @Override
        public void onTrimMemory(int level) {
        }
    });
}

我們看到由于是非系統初始化(不是系統啟動時啟動的進程),傳入的參數為false,我們重點關注前面一半的邏輯。這里又出現了

final IActivityManager mgr = ActivityManagerNative.getDefault();

有了之前的經驗我們已經知道這是指向的ActivityManagerService,然后調用了

mgr.attachApplication(mAppThread);

調用ActivityManagerService.attachApplication

public final void attachApplication(IApplicationThread thread) {
    synchronized (this) {
        int callingPid = Binder.getCallingPid();
        final long origId = Binder.clearCallingIdentity();
        attachApplicationLocked(thread, callingPid);
        Binder.restoreCallingIdentity(origId);
    }
}

接著走到attachApplicationLocked,這個方法比較長,為了節約篇幅,不貼源碼了,會調用ActivityThread的bindApplication方法

thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
        profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
        app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace,
        isRestrictedBackupMode || !normalMode, app.persistent,
        new Configuration(mConfiguration), app.compat,
        getCommonServicesLocked(app.isolated),
        mCoreSettingsObserver.getCoreSettingsLocked());

而bindApplication做的事情是數據綁定并發送消息(源碼部分節選)

AppBindData data = new AppBindData();
data.processName = processName;
data.appInfo = appInfo;
data.providers = providers;
data.instrumentationName = instrumentationName;
data.instrumentationArgs = instrumentationArgs;
data.instrumentationWatcher = instrumentationWatcher;
data.instrumentationUiAutomationConnection = instrumentationUiConnection;
data.debugMode = debugMode;
data.enableOpenGlTrace = enableOpenGlTrace;
data.restrictedBackupMode = isRestrictedBackupMode;
data.persistent = persistent;
data.config = config;
data.compatInfo = compatInfo;
data.initProfilerInfo = profilerInfo;
sendMessage(H.BIND_APPLICATION, data);

在handleMessage方法中可以看到接到Message后的處理

case BIND_APPLICATION:
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
    AppBindData data = (AppBindData)msg.obj;
    handleBindApplication(data);
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    break;

handleBindApplication方法實在是太長了,我們就截取和Instrumentation相關的部分吧

if (data.instrumentationName != null) {
    InstrumentationInfo ii = null;
    try {
        ii = appContext.getPackageManager().
            getInstrumentationInfo(data.instrumentationName, 0);
    } catch (PackageManager.NameNotFoundException e) {
    }
    if (ii == null) {
        throw new RuntimeException(
            "Unable to find instrumentation info for: "
            + data.instrumentationName);
    }

    mInstrumentationPackageName = ii.packageName;
    mInstrumentationAppDir = ii.sourceDir;
    mInstrumentationSplitAppDirs = ii.splitSourceDirs;
    mInstrumentationLibDir = ii.nativeLibraryDir;
    mInstrumentedAppDir = data.info.getAppDir();
    mInstrumentedSplitAppDirs = data.info.getSplitAppDirs();
    mInstrumentedLibDir = data.info.getLibDir();

    ApplicationInfo instrApp = new ApplicationInfo();
    instrApp.packageName = ii.packageName;
    instrApp.sourceDir = ii.sourceDir;
    instrApp.publicSourceDir = ii.publicSourceDir;
    instrApp.splitSourceDirs = ii.splitSourceDirs;
    instrApp.splitPublicSourceDirs = ii.splitPublicSourceDirs;
    instrApp.dataDir = ii.dataDir;
    instrApp.nativeLibraryDir = ii.nativeLibraryDir;
    LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
            appContext.getClassLoader(), false, true, false);
    ContextImpl instrContext = ContextImpl.createAppContext(this, pi);

    try {
        java.lang.ClassLoader cl = instrContext.getClassLoader();
        mInstrumentation = (Instrumentation)
            cl.loadClass(data.instrumentationName.getClassName()).newInstance();
    } catch (Exception e) {
        throw new RuntimeException(
            "Unable to instantiate instrumentation "
            + data.instrumentationName + ": " + e.toString(), e);
    }

    mInstrumentation.init(this, instrContext, appContext,
           new ComponentName(ii.packageName, ii.name), data.instrumentationWatcher,
           data.instrumentationUiAutomationConnection);

    if (mProfiler.profileFile != null && !ii.handleProfiling
            && mProfiler.profileFd == null) {
        mProfiler.handlingProfiling = true;
        File file = new File(mProfiler.profileFile);
        file.getParentFile().mkdirs();
        Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
    }

} else {
    mInstrumentation = new Instrumentation();
}

if ((data.appInfo.flags&ApplicationInfo.FLAG_LARGE_HEAP) != 0) {
    dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();
}

// Allow disk access during application and provider setup. This could
// block processing ordered broadcasts, but later processing would
// probably end up doing the same disk access.
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
try {
    // If the app is being launched for full backup or restore, bring it up in
    // a restricted environment with the base application class.
    Application app = data.info.makeApplication(data.restrictedBackupMode, null);
    mInitialApplication = app;

    // don't bring up providers in restricted mode; they may depend on the
    // app's custom Application class
    if (!data.restrictedBackupMode) {
        List<ProviderInfo> providers = data.providers;
        if (providers != null) {
            installContentProviders(app, providers);
            // For process that contains content providers, we want to
            // ensure that the JIT is enabled "at some point".
            mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
        }
    }

    // Do this after providers, since instrumentation tests generally start their
    // test thread at this point, and we don't want that racing.
    try {
        mInstrumentation.onCreate(data.instrumentationArgs);
    }
    catch (Exception e) {
        throw new RuntimeException(
            "Exception thrown in onCreate() of "
            + data.instrumentationName + ": " + e.toString(), e);
    }

    try {
        mInstrumentation.callApplicationOnCreate(app);
    } catch (Exception e) {
        if (!mInstrumentation.onException(app, e)) {
            throw new RuntimeException(
                "Unable to create application " + app.getClass().getName()
                + ": " + e.toString(), e);
        }
    }
} finally {
    StrictMode.setThreadPolicy(savedPolicy);
}

方法中首先初始化了mInstrumentation,此處Load的Class即am instrument命令傳入的TestRunner

java.lang.ClassLoader cl = instrContext.getClassLoader();
mInstrumentation = (Instrumentation)
    cl.loadClass(data.instrumentationName.getClassName()).newInstance();

然后對mInstrumentation進行了初始化

mInstrumentation.init(this, instrContext, appContext,
       new ComponentName(ii.packageName, ii.name), data.instrumentationWatcher,
       data.instrumentationUiAutomationConnection);

調用mInstrumentation的onCreate方法進行執行(參考下文的InstrumentationTestRunner介紹)

mInstrumentation.onCreate(data.instrumentationArgs);

最后調用mInstrumentation的callApplicationOnCreate方法啟動測試包的Application

mInstrumentation.callApplicationOnCreate(app);

至此從命令到啟動測試的流程就分析完了,下面我們就將從Instrumentation類出發,看看我們常見的Instrumentation,InstrumentationTestRunner,ActivityInstrumentationTestCase2類都分別做了哪些事

Instrumentation類源碼分析

弄清楚了Instrumentation的啟動之后,我們來分析下Instrumentation這個類及其功能吧

Instrumentation 流程控制函數分析

在Instrumentation類中有幾個關鍵的流程控制函數,我們先來分析下這些方法:

onCreate方法

在Instrumentation中是個空方法,之前我們有提到ActivityThread的handleBindApplication方法會調用InstrumentationTestRunner的onCreate方法來啟動測試代碼,InstrumentationTestRunner重寫了這個類(下文分析InstrumentationTestRunner時會分析)

start方法

start方法會創建一個新的線程來執行instrumentation,通常由繼承Instrumentation的類來調用該方法,InstrumentationTestRunner在onCreate方法的最后面調用了這個方法執行instrumentation,下面是Instrumentation中start方法的源碼

public void start() {
    if (mRunner != null) {
        throw new RuntimeException("Instrumentation already started");
    }
    mRunner = new InstrumentationThread("Instr: " + getClass().getName());
    mRunner.start();
}

其中mRunner是一個InstrumentationThread對象,是負責運行Instrumentation的線程對象,運行該方法會觸發Instrumentation的onStart方法

private final class InstrumentationThread extends Thread {
    public InstrumentationThread(String name) {
        super(name);
    }
    public void run() {
        try {
            Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
        } catch (RuntimeException e) {
            Log.w(TAG, "Exception setting priority of instrumentation thread "                                            
                    + Process.myTid(), e);                                                                             
        }
        if (mAutomaticPerformanceSnapshots) {
            startPerformanceSnapshot();
        }
        onStart();
    }
}

onStart方法

同onCreate方法一樣,在Instrumentation中是個空方法,通過重寫該方法能夠在測試使執行時產生一些阻塞性動作,Google的原文是這樣的

/**
 * Method where the instrumentation thread enters execution.  This allows
 * you to run your instrumentation code in a separate thread than the
 * application, so that it can perform blocking operation such as
 * {@link #sendKeySync} or {@link #startActivitySync}.
 * 
 * <p>You will typically want to call finish() when this function is done,
 * to end your instrumentation.
 */

onException方法

onException方法會在系統catch到Exception時由ActivityThread調用,在Instrumentation中它僅僅返回false,通過重寫該方法可以在需要時返回true,來自定義異常的處理,此時發生異常的被測工程會繼續執行下去而忽略該異常的發生,轉交給自定義實現處理。

sendStatus方法

sendStatus方法是在測試執行過程中狀態改變時會調用的方法,Instrumentation中已定義了如下四種狀態,用戶也可以定義自己需要的狀態并在合適的地方調用sendStatus方法發送自定義的狀態

/**
 * The test is starting.
 */
public static final int REPORT_VALUE_RESULT_START = 1;
/**
 * The test completed successfully.
 */
public static final int REPORT_VALUE_RESULT_OK = 0;
/**
 * The test completed with an error.
 */
public static final int REPORT_VALUE_RESULT_ERROR = -1;
/**
 * The test completed with a failure.
 */
public static final int REPORT_VALUE_RESULT_FAILURE = -2;

finish方法

finish方法的調用會終止Instrumentation的執行,使被測應用退出,其源碼如下:

public void finish(int resultCode, Bundle results) {
    if (mAutomaticPerformanceSnapshots) {
        endPerformanceSnapshot();
    }
    if (mPerfMetrics != null) {
        if (results == null) {
            results = new Bundle();
        }
        results.putAll(mPerfMetrics);
    }
    if (mUiAutomation != null) {
        mUiAutomation.disconnect();
        mUiAutomation = null;
    }
    mThread.finishInstrumentation(resultCode, results);
}

Instrumentation中的幾個內部類

Instrumentation定義了幾個內部類,為了能夠更好的閱讀后文,我們先來學習以下這些內部類及其作用

ActivityResult

定義了Activity向源Activity傳遞的執行結果,有兩個成員變量,一個是

int mResultCode;
Intent mResultData;

ActivityMonitor

ActivityMonitor是用來監視應用中單個活動的,它可以用來監視一些指定的Intent。創建好ActivityMonitor的實例后,通過調用Instrumentation.addMonitor函數來添加這個實例。當Activity啟動后,系統會匹配Instrumentation中的ActivityMonitory實例列表,如果匹配,就會累加計數器。

ActivityMonitor同樣可以被用于獲取新創建的Activity,通過waitForActivity方法,可以返回一個符合IntentFilter的Activity對象

ActivityMonitor有6個成員變量
private final IntentFilter mWhich;    //IntentFilter,被監視的Acitivity的條件
private final String mClass;    //被監視的Acitivity類名
private final ActivityResult mResult;    //如果monitor符合條件,返回的ActivityResult
private final boolean mBlock;    //決定monitor是否會阻止activity的啟動(使用mResult啟動,true時阻止),或者繼續activity的啟動
int mHits = 0;    //記錄被監視的Activity的啟動次數
Activity mLastActivity = null;    //最后匹配成功的Activity
ActivityMonitor的兩種構造函數

使用IntentFilter做篩選條件

public ActivityMonitor(
    IntentFilter which, ActivityResult result, boolean block) {
    mWhich = which;
    mClass = null;
    mResult = result;
    mBlock = block;
}

使用Activity類名做篩選條件

public ActivityMonitor(
    String cls, ActivityResult result, boolean block) {
    mWhich = null;
    mClass = cls;
    mResult = result;
    mBlock = block;
}
其他關鍵方法

match方法在Instrumentation啟動Activity時會被調用,根據初始化ActivityMonitor時使用的是IntentFilter還是Activity類名有不同的處理,但都是用于檢查被添加的ActivityMonitor對象是否和新啟動的Activity匹配,如果不一致則返回false,比較一致就把新的Activity寫入ActivityMonitor對象的最后匹配成功Activity屬性中

final boolean match(Context who, Activity activity, Intent intent) {
    synchronized (this) {
        if (mWhich != null
            && mWhich.match(who.getContentResolver(), intent,
                            true, "Instrumentation") < 0) {
            return false;
        }
        if (mClass != null) {
            String cls = null;
            if (activity != null) {
                cls = activity.getClass().getName();
            } else if (intent.getComponent() != null) {
                cls = intent.getComponent().getClassName();
            }
            if (cls == null || !mClass.equals(cls)) {
                return false;
            }
        }
        if (activity != null) {
            mLastActivity = activity;
            notifyAll();
        }
        return true;
    }
}

waitForActivity方法是一個阻塞方法,會一直等待直至有啟動的Acitivity成功匹配,否則會一直阻塞

/**
 * Block until an Activity is created that matches this monitor,
 * returning the resulting activity.
 *
 * @return Activity
 */
public final Activity waitForActivity() {
    synchronized (this) {
        while (mLastActivity == null) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
        Activity res = mLastActivity;
        mLastActivity = null;
        return res;
    }
}

waitForActivityWithTimeout方法作用同waitForActivity一致,但是有一個timeout,超時后就不會繼續阻塞Instrumentation的執行了。

/**
 * Block until an Activity is created that matches this monitor, 
 * returning the resulting activity or till the timeOut period expires.
 * If the timeOut expires before the activity is started, return null. 
 * 
 * @param timeOut Time to wait before the activity is created.
 * 
 * @return Activity
 */
public final Activity waitForActivityWithTimeout(long timeOut) {
    synchronized (this) {
        if (mLastActivity == null) {
            try {
                wait(timeOut);
            } catch (InterruptedException e) {
            }
        }
        if (mLastActivity == null) {
            return null;
        } else {
            Activity res = mLastActivity;
            mLastActivity = null;
            return res;
        }
    }
}

InstrumentationThread

前文已介紹,負責運行Instrumentation的線程對象

EmptyRunnable

一個run方法為空的Runnable

SyncRunnable

同步任務類,提供一個方法waitForComplete,使任務完成前會一直阻塞,在Instrumentation.runOnMainSync方法中會被使用到,在主線程做操作時會阻塞Instrumentation的執行

private static final class SyncRunnable implements Runnable {
    private final Runnable mTarget;
    private boolean mComplete;

    public SyncRunnable(Runnable target) {
        mTarget = target;
    }

    public void run() {
        mTarget.run();
        synchronized (this) {
            mComplete = true;
            notifyAll();
        }
    }

    public void waitForComplete() {
        synchronized (this) {
            while (!mComplete) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
    }
}

ActivityWaiter

一個數據結構,包含了一個Intent和一個Activity,主要作用是用于判斷Intent是否能被Activity處理

ActivityGoing

實現了MessageQueue.IdleHandler接口,帶有一個ActivityWaiter的成員,queueIdle方法的實現是將這個ActivityWaiter的從mWaitingActivities列表中移除

private final class ActivityGoing implements MessageQueue.IdleHandler {
    private final ActivityWaiter mWaiter;

    public ActivityGoing(ActivityWaiter waiter) {
        mWaiter = waiter;
    }

    public final boolean queueIdle() {
        synchronized (mSync) {
            mWaitingActivities.remove(mWaiter);
            mSync.notifyAll();
        }
        return false;
    }
}

Idler

實現了MessageQueue.IdleHandler接口,檢測線程是否處于idle狀態并做相應操作

Instrumentation中一些公有方法的介紹

性能相關

Profiling是一個Android系統自帶的用于性能分析的系統,這里暫不介紹,僅記錄功能入口,如果后期有性能同學對此做了深入研究會鏈接到相關頁面

  • public boolean isProfiling():判斷是否開啟了Profiling模式
  • public void startProfiling():生成Profiling相關文件,并開始記錄每個方法執行
  • public void stopProfiling():停止Profiling模式
    PerformanceSnapshots沒有找到相關的文檔信息,從其關鍵類PerformanceCollector的注釋來看是記錄一些性能數據用的
  • public void setAutomaticPerformanceSnapshots():
  • public void startPerformanceSnapshot():
  • public void endPerformanceSnapshot():

線程相關

以下三個方法貼了原文注釋,主要是不知道如何合適的翻譯,大體上前兩個方法用于等待被測Activity處于idle狀態(一個同步,一個異步),然后做相應操作,第三個方法是在被測Activity主線程做指定的操作

  • public void waitForIdle(Runnable recipient):Schedule a callback for when the application's main thread goes idle(has no more events to process)
  • public void waitForIdleSync():Synchronously wait for the application to be idle. Can not be called from the main application thread -- use start() to execute instrumentation in its own thread.
  • public void runOnMainSync(Runnable runner):Execute a call on the application's main thread, blocking until it is complete. Useful for doing things that are not thread-safe, such as looking at or modifying the view hierarchy.

啟動Activity

  • public Activity startActivitySync(Intent intent)
    這是是通過Instrumentation啟動Activity的主要方法,我們先來看看源碼及注釋是怎樣的,再來對著源碼分析下Activity是如何啟動的
/**
 * Start a new activity and wait for it to begin running before returning.
 * In addition to being synchronous, this method as some semantic
 * differences from the standard {@link Context#startActivity} call: the
 * activity component is resolved before talking with the activity manager
 * (its class name is specified in the Intent that this method ultimately
 * starts), and it does not allow you to start activities that run in a
 * different process.  In addition, if the given Intent resolves to
 * multiple activities, instead of displaying a dialog for the user to
 * select an activity, an exception will be thrown.
 * 
 * <p>The function returns as soon as the activity goes idle following the
 * call to its {@link Activity#onCreate}.  Generally this means it has gone
 * through the full initialization including {@link Activity#onResume} and
 * drawn and displayed its initial window.
 * 
 * @param intent Description of the activity to start.
 * 
 * @see Context#startActivity
 */
public Activity startActivitySync(Intent intent) {
    validateNotAppThread();

    synchronized (mSync) {
        intent = new Intent(intent);

        ActivityInfo ai = intent.resolveActivityInfo(
            getTargetContext().getPackageManager(), 0);
        if (ai == null) {
            throw new RuntimeException("Unable to resolve activity for: " + intent);
        }
        String myProc = mThread.getProcessName();
        if (!ai.processName.equals(myProc)) {
            // todo: if this intent is ambiguous, look here to see if
            // there is a single match that is in our package.
            throw new RuntimeException("Intent in process "
                    + myProc + " resolved to different process "
                    + ai.processName + ": " + intent);
        }

        intent.setComponent(new ComponentName(
                ai.applicationInfo.packageName, ai.name));
        final ActivityWaiter aw = new ActivityWaiter(intent);

        if (mWaitingActivities == null) {
            mWaitingActivities = new ArrayList();
        }
        mWaitingActivities.add(aw);

        getTargetContext().startActivity(intent);

        do {
            try {
                mSync.wait();
            } catch (InterruptedException e) {
            }
        } while (mWaitingActivities.contains(aw));
     
        return aw.activity;
    }
}

從官方注釋我們可以看到,startActivitySync方法的功能也是啟動Activity,同Context.startActivity方法不同的是,startActivitySync是無法啟動進程外的Activity的。另外,如果用于啟動的Intent可以被多個Activity接受,該方法會直接拋出異常而不是彈出選擇對話框讓用戶選擇需要使用的程序。本方法作為一個同步方法,在Activity啟動后處于idle狀態時返回,意味著Activity會走完onCreate,onStart,onResume的方法,并完成UI初始化。

下面我們看看這個方法的具體實現

  • 首先會獲取到被測APK的ActivityInfo(可以通過其獲取到相關的Activity)
  • 然后判斷是否是運行在同一進程(具體實現不是很明白,有興趣的調研下補充上來?)
  • 再將從Activity中提取的Component信息設置給Intent(決定誰來處理Intent)
  • 之后使用該Intent初始化一個ActivityWaiter對象,并加入到mWaitingActivities列表中
  • 獲取目標的Context并通過intent啟動Acticity
  • 在之前生成的ActivityWaiter對象從mWaitingActivities中移除之前一直會處于阻塞狀態
    那么問題來了,這個ActivityWaiter對象是何時被移除的呢?Instrumentation提供了操作Activity生命周期的方法(下面也會提到),使用Instrumentation啟動Activity時會調用callActivityOnCreate方法,處理Acticity的onCreate,而callActivityOnCreate分成了3個步驟
prePerformCreate(activity);
activity.performCreate(icicle);
postPerformCreate(activity);

在prePerformCreate方法中

private void prePerformCreate(Activity activity) {
    if (mWaitingActivities != null) {
        synchronized (mSync) {
            final int N = mWaitingActivities.size();
            for (int i=0; i<N; i++) {
                final ActivityWaiter aw = mWaitingActivities.get(i);
                final Intent intent = aw.intent;
                if (intent.filterEquals(activity.getIntent())) {
                    aw.activity = activity;
                    mMessageQueue.addIdleHandler(new ActivityGoing(aw));
                }
            }
        }
    }
}

針對mWaitingActivities列表中已加入的每一個ActivityWaiter,判斷intent是否符合目標Activity,如果符合則將目標Activity綁定到ActivityWaiter對象。然后發送消息,在空閑時調用這個以aw為參數的ActivityGoing對象,上文中我們介紹過ActivityGoing類,其功能是在目標Activity空閑后將指定的ActivityWaiter從mWaitingActivities列表中移除。

至此就實現了啟動Activity并阻塞到Activity啟動的全過程

下面的一系列方法是通過Instrumentation啟動Activity的,具體使用場景暫未分析

  • public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options)
  • public void execStartActivities(Context who, IBinder contextThread, IBinder token, Activity target, Intent[] intents, Bundle options)
  • public void execStartActivitiesAsUser(Context who, IBinder contextThread, IBinder token, Activity target, Intent[] intents, Bundle options, int userId)
  • public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Fragment target, Intent intent, int requestCode, Bundle options)
  • public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options, UserHandle user)
  • public ActivityResult execStartActivityAsCaller(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options, int userId)
  • public void execStartActivityFromAppTask(Context who, IBinder contextThread, IAppTask appTask,Intent intent, Bundle options)

ActivityMonitor相關

addMonitor共有三種重載,作用都是增加向當前Instrumentation添加一個ActivityMonitor,其中后兩種會同時返回構造出的ActivityMonitor對象

  • public void addMonitor(ActivityMonitor monitor)
  • public ActivityMonitor addMonitor(IntentFilter filter, ActivityResult result, boolean block)
  • public ActivityMonitor addMonitor(String cls, ActivityResult result, boolean block)

其他相關的還有如下的方法,其中waitForMonitorWithTimeout(waitForMonitor,可能會造成程序阻塞,不推薦使用)可以用于檢測是否啟動了符合指定ActivityMonitor的Activity,來驗證某些操作是否啟動了正確的Activity:

  • public boolean checkMonitorHit(ActivityMonitor monitor, int minHits):檢查指定的ActivityMonitor的匹配次數是否達到minHits,minHits會在各種execStartActivity(上一節)中intent和ActivityMonitor匹配時+1。
  • public Activity waitForMonitor(ActivityMonitor monitor):等待指定的ActivityMonitor被命中(符合ActivityMonitor條件的Activity被啟動),并返回啟動的Activity對象,同時將該ActivityMonitor從mActivityMonitors列表中移除,如果沒有等到會一直阻塞,代碼如下:
public Activity waitForMonitor(ActivityMonitor monitor) {
    Activity activity = monitor.waitForActivity();
    synchronized (mSync) {
        mActivityMonitors.remove(monitor);
    }
    return activity;
}
  • public Activity waitForMonitorWithTimeout(ActivityMonitor monitor, long timeOut):等待指定的ActivityMonitor被命中(符合ActivityMonitor條件的Activity被啟動),并返回啟動的Activity對象,同時將該ActivityMonitor從mActivityMonitors列表中移除,在計時器結束前沒有等到會一直阻塞,代碼類似,不貼了。
  • public void removeMonitor(ActivityMonitor monitor):從mActivityMonitors列表中移除指定的ActivityMonitor

操作按鍵

以下這個方法提供了在主線程中的一些操作按鍵的方法,方法名都很直白,不用一一解釋了吧

  • public boolean invokeMenuActionSync(Activity targetActivity, int id, int flag)
  • public boolean invokeContextMenuAction(Activity targetActivity, int id, int flag)
  • public void sendStringSync(String text)
  • public void sendKeySync(KeyEvent event)
  • public void sendKeyDownUpSync(int key)
  • public void sendCharacterSync(int keyCode)
  • public void sendPointerSync(MotionEvent event)
  • public void sendTrackballEventSync(MotionEvent event)

處理Activity生命周期相關

以下的方法提供了各種操縱Activity生命周期的方法,通過重寫這些方法可以修改Activity在每個生命周期方法被調用時的行為。

  • public void callActivityOnCreate(Activity activity, Bundle icicle)
  • public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState)
  • public void callActivityOnDestroy(Activity activity)
  • public void callActivityOnRestoreInstanceState(Activity activity, Bundle savedInstanceState)
  • public void callActivityOnRestoreInstanceState(Activity activity, Bundle savedInstanceState, PersistableBundle persistentState)
  • public void callActivityOnPostCreate(Activity activity, Bundle icicle)
  • public void callActivityOnPostCreate(Activity activity, Bundle icicle, PersistableBundle persistentState)
  • public void callActivityOnNewIntent(Activity activity, Intent intent)
  • public void callActivityOnNewIntent(Activity activity, ReferrerIntent intent)
  • public void callActivityOnStart(Activity activity)
  • public void callActivityOnRestart(Activity activity)
  • public void callActivityOnResume(Activity activity)
  • public void callActivityOnStop(Activity activity)
  • public void callActivityOnSaveInstanceState(Activity activity, Bundle outState)
  • public void callActivityOnSaveInstanceState(Activity activity, Bundle outState, PersistableBundle outPersistentState)
  • public void callActivityOnPause(Activity activity)
  • public void callActivityOnUserLeaving(Activity activity)

其他

  • public void setInTouchMode(boolean inTouch):Set to true to be in touch mode, false to be in focus mode.
  • public void startAllocCounting(): Starts allocation counting. This triggers a gc and resets the counts.
  • public void stopAllocCounting(): Stops allocation counting.
  • public Bundle getAllocCounts(): Returns a bundle with the current results from the allocation counting.
  • public Bundle getBinderCounts(): Returns a bundle with the counts for various binder counts for this process. Currently the only two that are reported are the number of send and the number of received transactions.
  • public UiAutomation getUiAutomation():獲取UiAutomation對象,UI自動化測試相關
  • public Application newApplication(ClassLoader cl, String className, Context context):新建Application,測試Application時可用,我們通常不會使用到
  • static public Application newApplication(Class<?> clazz, Context context):靜態類,新建Application,在ApplicationTestCase中被使用
  • public void callApplicationOnCreate(Application app):啟動指定的Application

InstrumentationTestRunner源碼分析

InstrumentationTestRunner實際上是繼承自Instrumentation,所以上面所有對Instrumentation類的分析都適用于它,我們在這里主要要看的是它多了些什么新玩意,按照之前的邏輯我們先來看流程控制函數

InstrumentationTestRunner流程控制函數分析

onCreate方法

InstrumentationTestRunner重寫了onCreate,在之前的啟動流程分析中我們也知道,Instrumentation啟動的入口即是onCreate方法:

public void onCreate(Bundle arguments) {
    super.onCreate(arguments);
    mArguments = arguments;

    // Apk paths used to search for test classes when using TestSuiteBuilders.
    String[] apkPaths =
            {getTargetContext().getPackageCodePath(), getContext().getPackageCodePath()};
    ClassPathPackageInfoSource.setApkPaths(apkPaths);

    Predicate<TestMethod> testSizePredicate = null;
    Predicate<TestMethod> testAnnotationPredicate = null;
    Predicate<TestMethod> testNotAnnotationPredicate = null;
    String testClassesArg = null;
    boolean logOnly = false;

    if (arguments != null) {
        // Test class name passed as an argument should override any meta-data declaration.
        testClassesArg = arguments.getString(ARGUMENT_TEST_CLASS);
        mDebug = getBooleanArgument(arguments, "debug");
        mJustCount = getBooleanArgument(arguments, "count");
        mSuiteAssignmentMode = getBooleanArgument(arguments, "suiteAssignment");
        mPackageOfTests = arguments.getString(ARGUMENT_TEST_PACKAGE);
        testSizePredicate = getSizePredicateFromArg(
                arguments.getString(ARGUMENT_TEST_SIZE_PREDICATE));
        testAnnotationPredicate = getAnnotationPredicate(
                arguments.getString(ARGUMENT_ANNOTATION));
        testNotAnnotationPredicate = getNotAnnotationPredicate(
                arguments.getString(ARGUMENT_NOT_ANNOTATION));

        logOnly = getBooleanArgument(arguments, ARGUMENT_LOG_ONLY);
        mCoverage = getBooleanArgument(arguments, "coverage");
        mCoverageFilePath = arguments.getString("coverageFile");

        try {
            Object delay = arguments.get(ARGUMENT_DELAY_MSEC);  // Accept either string or int
            if (delay != null) mDelayMsec = Integer.parseInt(delay.toString());
        } catch (NumberFormatException e) {
            Log.e(LOG_TAG, "Invalid delay_msec parameter", e);
        }
    }

    TestSuiteBuilder testSuiteBuilder = new TestSuiteBuilder(getClass().getName(),
            getTargetContext().getClassLoader());

    if (testSizePredicate != null) {
        testSuiteBuilder.addRequirements(testSizePredicate);
    }
    if (testAnnotationPredicate != null) {
        testSuiteBuilder.addRequirements(testAnnotationPredicate);
    }
    if (testNotAnnotationPredicate != null) {
        testSuiteBuilder.addRequirements(testNotAnnotationPredicate);
    }

    //判斷是否傳入了參數指定測試類,并做相應處理
    if (testClassesArg == null) {
        if (mPackageOfTests != null) {
            testSuiteBuilder.includePackages(mPackageOfTests);
        } else {
            TestSuite testSuite = getTestSuite();
            if (testSuite != null) {
                testSuiteBuilder.addTestSuite(testSuite);
            } else {
                // no package or class bundle arguments were supplied, and no test suite
                // provided so add all tests in application
                testSuiteBuilder.includePackages("");
            }
        }
    } else {
        parseTestClasses(testClassesArg, testSuiteBuilder);
    }

    testSuiteBuilder.addRequirements(getBuilderRequirements());

    mTestRunner = getAndroidTestRunner();
    mTestRunner.setContext(getTargetContext());
    mTestRunner.setInstrumentation(this);
    mTestRunner.setSkipExecution(logOnly);
    mTestRunner.setTest(testSuiteBuilder.build());
    mTestCount = mTestRunner.getTestCases().size();
    if (mSuiteAssignmentMode) {
        mTestRunner.addTestListener(new SuiteAssignmentPrinter());
    } else {
        WatcherResultPrinter resultPrinter = new WatcherResultPrinter(mTestCount);
        mTestRunner.addTestListener(new TestPrinter("TestRunner", false));
        mTestRunner.addTestListener(resultPrinter);
        mTestRunner.setPerformanceResultsWriter(resultPrinter);
    }
    start();
}

可以看到方法根據的參數arguments做了一系列的處理,這些arguments即是在am instrument命令中傳入的各種-e后的鍵值對參數,初始化mTestRunner,然后調用start函數

mTestRunner是一個AndroidTestRunner對象,主要用于記錄TestCase和TestResult,同時維護了一個TestListener類型的List,然后用mTestRunner.setTest(testSuiteBuilder.build())獲取測試用例集并傳遞給mTestRunner。關于AndroidTestRunner和TestSuite相關的內容我們后續分析。

onStart方法

InstrumentationTestRunner并沒有重寫start方法,所以start方法會調用父類Instrumentation的start,啟動一個InstrumentationThread,調用onStart方法,onStart方法是被重寫過了的:

public void onStart() {
    prepareLooper();

    if (mJustCount) {
        mResults.putString(Instrumentation.REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID);
        mResults.putInt(REPORT_KEY_NUM_TOTAL, mTestCount);
        finish(Activity.RESULT_OK, mResults);
    } else {
        if (mDebug) {
            Debug.waitForDebugger();
        }

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        PrintStream writer = new PrintStream(byteArrayOutputStream);
        try {
            StringResultPrinter resultPrinter = new StringResultPrinter(writer);

            mTestRunner.addTestListener(resultPrinter);

            long startTime = System.currentTimeMillis();
            mTestRunner.runTest();
            long runTime = System.currentTimeMillis() - startTime;

            resultPrinter.printResult(mTestRunner.getTestResult(), runTime);
        } catch (Throwable t) {
            // catch all exceptions so a more verbose error message can be outputted
            writer.println(String.format("Test run aborted due to unexpected exception: %s",
                            t.getMessage()));
            t.printStackTrace(writer);
        } finally {
            mResults.putString(Instrumentation.REPORT_KEY_STREAMRESULT,
                    String.format("\nTest results for %s=%s",
                    mTestRunner.getTestClassName(),
                    byteArrayOutputStream.toString()));

            if (mCoverage) {
                generateCoverageReport();
            }
            writer.close();

            finish(Activity.RESULT_OK, mResults);
        }
    }
}

可以看到方法中先是增加了一個StringResultPrinter類型的TestListener,然后調用了mTestRunner的runTest方法執行測試用例,如果設置生成覆蓋率報告則調用generateCoverageReport方法生成覆蓋率報告,最后調用finish方法返回測試結果并停止被測app的運行。

測試集與測試用例(TestSuite相關)

在前文中我們看到了Instrumentation執行測試用例的過程,那么這些測試用例是如何被框架識別的呢?

我們記得在InstrumentationTestRunner的onCreate方法中有這樣一句:

mTestRunner.setTest(testSuiteBuilder.build());

是用來設定測試用例集的,我們就從這里出發看看如何從測試腳本中找到測試用例集的:

TestGrouping類

TestGrouping類的成員變量

private static final String LOG_TAG = "TestGrouping";
SortedSet<Class<? extends TestCase>> testCaseClasses;
protected String firstIncludedPackage = null;
private ClassLoader classLoader;

TestGrouping類的構造函數

InstrumentationTestRunner中調用的構造函數,其中SORT_BY_c_QUALIFIED_NAME是一個實現了Comparotor接口的SortByFullyQualifiedName對象,提供根據Class類名排序的功能:

private final TestGrouping testGrouping = new TestGrouping(SORT_BY_c_QUALIFIED_NAME);

對應的構造函數,使用指定的comparator初始化一個TreeSet用于存儲TestCase

public TestGrouping(Comparator<Class<? extends TestCase>> comparator) {
    testCaseClasses = new TreeSet<Class<? extends TestCase>>(comparator);
}

TestGrouping類的共有函數

getTests方法

將所有的testcase以List的形式返回

public List<TestMethod> getTests() {
    List<TestMethod> testMethods = new ArrayList<TestMethod>();
    for (Class<? extends TestCase> testCase : testCaseClasses) {
        for (Method testMethod : getTestMethods(testCase)) {
            testMethods.add(new TestMethod(testMethod, testCase));
        }
    }
    return testMethods;
}

遍歷所有的測試類,然后將每個測試方法加入到testMethods列表中,getTestMethods用于篩選測試方法

protected List<Method> getTestMethods(Class<? extends TestCase> testCaseClass) {
    List<Method> methods = Arrays.asList(testCaseClass.getMethods());
    return select(methods, new TestMethodPredicate());
}

通過反射獲得測試類的所有方法后使用selcet方法進行過濾,過濾器為TestMethodPredicate,我們先看selcet方法:

private <T> List<T> select(Collection<T> items, Predicate<T> predicate) {
    ArrayList<T> selectedItems = new ArrayList<T>();
    for (T item : items) {
        if (predicate.apply(item)) {
            selectedItems.add(item);
        }
    }
    return selectedItems;
}

可以看到實際上是針對所有的方法使用過濾器的aplly方法做檢驗,通過的就可以加入到返回的列表中,那么TestMethodPredicate的apply方法是怎么過濾的呢?

public boolean apply(Method method) {
    return ((method.getParameterTypes().length == 0) &&
            (method.getName().startsWith("test")) &&
            (method.getReturnType().getSimpleName().equals("void")));
}

可以看到設定了3個條件:

  • 無參數
  • 方法名以test開頭
  • 返回值類型為void
    至此為什么在寫測試用例時有這些要求就找到根源了,通過一頓掃描會把所有符合要求的測試方法形成一個TestMethod類型的List
addPackagesRecursive方法

將指定的package(包含其子package)中的所有測試類加入到當前TestGrouping中,每個給出的package至少要包含一個測試類,同時將第一個處理的package包名賦給firstIncludedPackage

public TestGrouping addPackagesRecursive(String... packageNames) {
    for (String packageName : packageNames) {
        List<Class<? extends TestCase>> addedClasses = testCaseClassesInPackage(packageName);
        if (addedClasses.isEmpty()) {
            Log.w(LOG_TAG, "Invalid Package: '" + packageName
                    + "' could not be found or has no tests");
        }
        testCaseClasses.addAll(addedClasses);
        if (firstIncludedPackage == null) {
            firstIncludedPackage = packageName;
        }
    }
    return this;
}

下面是其中的私有方法testCaseClassesInPackage,其中ClassPathPackageInfo一個用于列舉給定package內的類和子package的工具類,ClassPathPackageInfoSource類負責通過掃描APK路徑,獲取ClassPathPackageInfo。

private List<Class<? extends TestCase>> testCaseClassesInPackage(String packageName) {
    ClassPathPackageInfoSource source = PackageInfoSources.forClassPath(classLoader);
    ClassPathPackageInfo packageInfo = source.getPackageInfo(packageName);

    return selectTestClasses(packageInfo.getTopLevelClassesRecursive());
}

其中的私有方法selectTestClasses,從給定的測試類集合中挑選出符合要求的測試類列表:

private List<Class<? extends TestCase>> selectTestClasses(Set<Class<?>> allClasses) {
    List<Class<? extends TestCase>> testClasses = new ArrayList<Class<? extends TestCase>>();
    for (Class<?> testClass : select(allClasses,
            new TestCasePredicate())) {
        testClasses.add((Class<? extends TestCase>) testClass);
    }
    return testClasses;
}

其中的select方法,按照指定的要求(predicate)挑選item并返回列表:

private <T> List<T> select(Collection<T> items, Predicate<T> predicate) {
    ArrayList<T> selectedItems = new ArrayList<T>();
    for (T item : items) {
        if (predicate.apply(item)) {
            selectedItems.add(item);
        }
    }
    return selectedItems;
}

Predicate是一個只定義了apply一個方法的接口,實現該接口能夠使對象能夠用于判斷目標是否滿足指定的要求,我們看下TestCasePredicate的具體實現:

private static class TestCasePredicate implements Predicate<Class<?>> {

    public boolean apply(Class aClass) {
        int modifiers = ((Class<?>) aClass).getModifiers();
        return TestCase.class.isAssignableFrom((Class<?>) aClass)
                && Modifier.isPublic(modifiers)
                && !Modifier.isAbstract(modifiers)
                && hasValidConstructor((Class<?>) aClass);
    }

    @SuppressWarnings("unchecked")
    private boolean hasValidConstructor(java.lang.Class<?> aClass) {
        // The cast below is not necessary with the Java 5 compiler, but necessary with the Java 6 compiler,
        // where the return type of Class.getDeclaredConstructors() was changed
        // from Constructor<T>[] to Constructor<?>[]
        Constructor<? extends TestCase>[] constructors
                = (Constructor<? extends TestCase>[]) aClass.getConstructors();
        for (Constructor<? extends TestCase> constructor : constructors) {
            if (Modifier.isPublic(constructor.getModifiers())) {
                java.lang.Class[] parameterTypes = constructor.getParameterTypes();
                if (parameterTypes.length == 0 ||
                        (parameterTypes.length == 1 && parameterTypes[0] == String.class)) {
                    return true;
                }
            }
        }
        Log.i(LOG_TAG, String.format(
                "TestCase class %s is missing a public constructor with no parameters " +
                "or a single String parameter - skipping",
                aClass.getName()));
        return false;
    }
}

我們看到在這個條件限制有有4個:

  • 目標class可以被轉換為TestCase
  • 是一個public類
  • 不是abstrarct類
  • 有符合要求的構造函數(存在至少一個public構造方法,沒有參數或者有一個參數也參數類型為String)
removePackagesRecursive方法

從當前TestGrouping中移除指定package及其子package內的測試類

public TestGrouping removePackagesRecursive(String... packageNames) {
    for (String packageName : packageNames) {
        testCaseClasses.removeAll(testCaseClassesInPackage(packageName));
    }
    return this;
}

調用的私有方法testCaseClassesInPackage,同selectTestClasses只不過這次的源是package包名:

private List<Class<? extends TestCase>> testCaseClassesInPackage(String packageName) {
    ClassPathPackageInfoSource source = PackageInfoSources.forClassPath(classLoader);
    ClassPathPackageInfo packageInfo = source.getPackageInfo(packageName);

    return selectTestClasses(packageInfo.getTopLevelClassesRecursive());
}

TestSuiteBuilder類

TestSuiteBuilder類的成員變量

private Context context;    //
private final TestGrouping testGrouping = new TestGrouping(SORT_BY_FULLY_QUALIFIED_NAME);    //
private final Set<Predicate<TestMethod>> predicates = new HashSet<Predicate<TestMethod>>();    //篩選條件集合
private List<TestCase> testCases;    //測試用例集
private TestSuite rootSuite;    //
private TestSuite suiteForCurrentClass;    //
private String currentClassname;    //
private String suiteName;    //Suite名稱

onCreate方法中調用的構造函數

TestSuiteBuilder testSuiteBuilder = new TestSuiteBuilder(getClass().getName(),
        getTargetContext().getClassLoader());

對應的構造函數,將suiteName設置為TestRunner的類名,初始化testCases列表:

public TestSuiteBuilder(String name, ClassLoader classLoader) {
    this.suiteName = name;
    this.testGrouping.setClassLoader(classLoader);
    this.testCases = Lists.newArrayList();
    addRequirements(REJECT_SUPPRESSED);
}

其他公有方法

build方法

TestCase分成了兩部分,一部分是獲取TestGrouping后,根據每一個TestMethod是否能夠滿足所有的Predicates篩選出后加入列表,另一部分是通過addTestClassByName和addTestSuite直接加入的case(存儲于testCases中),同樣會判斷是否滿足所有的Predicates

public final TestSuite build() {
    rootSuite = new TestSuite(getSuiteName());

    // Keep track of current class so we know when to create a new sub-suite.
    currentClassname = null;
    try {
        for (TestMethod test : testGrouping.getTests()) {
            if (satisfiesAllPredicates(test)) {
                addTest(test);
            }
        }
        if (testCases.size() > 0) {
            for (TestCase testCase : testCases) {
                if (satisfiesAllPredicates(new TestMethod(testCase))) {
                    addTest(testCase);
                }
            }
        }
    } catch (Exception exception) {
        Log.i("TestSuiteBuilder", "Failed to create test.", exception);
        TestSuite suite = new TestSuite(getSuiteName());
        suite.addTest(new FailedToCreateTests(exception));
        return suite;
    }
    return rootSuite;
}
addTestClassByName方法

通過指定的類名和方法名添加測試類

public TestSuiteBuilder addTestClassByName(String testClassName, String testMethodName,
        Context context) {

    AndroidTestRunner atr = new AndroidTestRunner();
    atr.setContext(context);
    atr.setTestClassName(testClassName, testMethodName);

    this.testCases.addAll(atr.getTestCases());
    return this;
}
addTestSuite方法

將testSuite中的測試類加入進來

public TestSuiteBuilder addTestSuite(TestSuite testSuite) {
    for (TestCase testCase : (List<TestCase>) TestCaseUtil.getTests(testSuite, true)) {
        this.testCases.add(testCase);
    }
    return this;
}

AndroidTestRunner

AndroidTestRunner類

成員變量

private TestResult mTestResult;    //存儲測試結果
private String mTestClassName;    //當前測試的名字
private List<TestCase> mTestCases;    //TestCase集合
private Context mContext;    //測試目標APK的Context
private boolean mSkipExecution = false;    //當出現異常時是否終止執行

private List<TestListener> mTestListeners = Lists.newArrayList();    //TestListener列表
private Instrumentation mInstrumentation;    //Instrumentation對象
private PerformanceResultsWriter mPerfWriter;    //PerformanceResultsWriter對象,性能相關

幾個常用的公有方法

setTest方法
public void setTest(Test test) {
    setTest(test, test.getClass());
}

調用私有方法

private void setTest(Test test, Class<? extends Test> testClass) {
    mTestCases = (List<TestCase>) TestCaseUtil.getTests(test, true);
    if (TestSuite.class.isAssignableFrom(testClass)) {
        mTestClassName = TestCaseUtil.getTestName(test);
    } else {
        mTestClassName = testClass.getSimpleName();
    }
}

從給定的Test中獲取Cases,分為兩種情況:單獨的Test和TestSuite

runTest方法

runTest方法是調用執行測試用例的入口

public void runTest() {
    runTest(createTestResult());
}

調用私有方法

public void runTest(TestResult testResult) {
    mTestResult = testResult;

    for (TestListener testListener : mTestListeners) {
        mTestResult.addListener(testListener);
    }

    Context testContext = mInstrumentation == null ? mContext : mInstrumentation.getContext();
    for (TestCase testCase : mTestCases) {
        setContextIfAndroidTestCase(testCase, mContext, testContext);
        setInstrumentationIfInstrumentationTestCase(testCase, mInstrumentation);
        setPerformanceWriterIfPerformanceCollectorTestCase(testCase, mPerfWriter);
        testCase.run(mTestResult);
    }
}

設定了mTestResult,并將所有已注冊的TestListener傳遞給mTestResult(關于TestListener如何被使用我們下面再聊),然后又是三個私有方法:

private void setContextIfAndroidTestCase(Test test, Context context, Context testContext) {
    if (AndroidTestCase.class.isAssignableFrom(test.getClass())) {
        ((AndroidTestCase) test).setContext(context);
        ((AndroidTestCase) test).setTestContext(testContext);
    }
}

setContextIfAndroidTestCase方法用于對每一個可以轉化為AndroidTestCase的Case設定context

private void setInstrumentationIfInstrumentationTestCase(
        Test test, Instrumentation instrumentation) {
    if (InstrumentationTestCase.class.isAssignableFrom(test.getClass())) {
        ((InstrumentationTestCase) test).injectInstrumentation(instrumentation);
    }
}

setInstrumentationIfInstrumentationTestCase方法用于對每一個可以轉化為InstrumentationTestCase的Case注入instrumentation

private void setPerformanceWriterIfPerformanceCollectorTestCase(
        Test test, PerformanceResultsWriter writer) {
    if (PerformanceCollectorTestCase.class.isAssignableFrom(test.getClass())) {
        ((PerformanceCollectorTestCase) test).setPerformanceResultsWriter(writer);
    }
}

setPerformanceWriterIfPerformanceCollectorTestCase方法用于對每一個可以轉化為PerformanceCollectorTestCase的Case注入PerformanceResultsWriter

最后執行testCase的run方法,執行測試并將結果存儲在mTestResult中返回,查看TestCase源碼找到run方法,發現是實際調用的傳入的TestResult的run方法:

public void run(TestResult result) {
    result.run(this);
}

轉回頭看看我們傳入的TestResult:

protected TestResult createTestResult() {
    if (mSkipExecution) {
        return new NoExecTestResult();
    }
    return new TestResult();
}

即如果設定了忽略異常會使用NoExecTestResult,否則使用TestResult,我們分別看一下這兩個類

TestResult類

執行測試會調用TestResult的run方法,我們先來看下這個方法:

protected void run(final TestCase test) {
    startTest(test);
    Protectable p= new Protectable() {
        public void protect() throws Throwable {
            test.runBare();
        }
    };
    runProtected(test, p);

    endTest(test);
}

分別調用了startTest,runProtected,endTest完成預處理,執行和收尾工作,依次看下做了什么

startTest方法

public void startTest(Test test) {
    final int count= test.countTestCases();
    synchronized(this) {
        fRunTests+= count;
    }
    for (TestListener each : cloneListeners())
        each.startTest(test);
}

可以看到實現中將測試類中的測試用例數,并針對每一個已注冊的TestListener執行startTest方法

runProtected方法

public void runProtected(final Test test, Protectable p) {
    try {
        p.protect();
    } 
    catch (AssertionFailedError e) {
        addFailure(test, e);
    }
    catch (ThreadDeath e) { // don't catch ThreadDeath by accident
        throw e;
    }
    catch (Throwable e) {
        addError(test, e);
    }
}

這里通過runProtected方法執行test.runBare(),并處理異常分發給addFailure和addError方法,最后看test.runBare()的執行,先來看下這兩個異常處理做了什么

addFailure方法
public synchronized void addFailure(Test test, AssertionFailedError t) {
    fFailures.add(new TestFailure(test, t));
    for (TestListener each : cloneListeners())
        each.addFailure(test, t);
}

實際上同startTest,針對每一個已注冊的TestListener執行addFailure方法

addError方法
public synchronized void addError(Test test, Throwable t) {
    fErrors.add(new TestFailure(test, t));
    for (TestListener each : cloneListeners())
        each.addError(test, t);
}
test.runBare()

實際上這是JUINT的TestCase的實現了,理論上和Android無關了,我們也可以看看:

public void runBare() throws Throwable {
    Throwable exception= null;
    setUp();
    try {
        runTest();
    } catch (Throwable running) {
        exception= running;
    }
    finally {
        try {
            tearDown();
        } catch (Throwable tearingDown) {
            if (exception == null) exception= tearingDown;
        }
    }
    if (exception != null) throw exception;
}

會分別調用setUp,runTest,tearDown方法,是不是很熟悉啊,那runTest做了什么呢,只是通過反射運行testMethod啦:

protected void runTest() throws Throwable {
    assertNotNull("TestCase.fName cannot be null", fName); // Some VMs crash when calling getMethod(null,null);
    Method runMethod= null;
    try {
        // use getMethod to get all public inherited
        // methods. getDeclaredMethods returns all
        // methods of this class but excludes the
        // inherited ones.
        runMethod= getClass().getMethod(fName, (Class[])null);
    } catch (NoSuchMethodException e) {
        fail("Method \""+fName+"\" not found");
    }
    if (!Modifier.isPublic(runMethod.getModifiers())) {
        fail("Method \""+fName+"\" should be public");
    }

    try {
        runMethod.invoke(this);
    }
    catch (InvocationTargetException e) {
        e.fillInStackTrace();
        throw e.getTargetException();
    }
    catch (IllegalAccessException e) {
        e.fillInStackTrace();
        throw e;
    }
}

endTest方法

實際也上同startTest,針對每一個已注冊的TestListener執行endTest方法

public void endTest(Test test) {
    for (TestListener each : cloneListeners())
        each.endTest(test);
}

NoExecTestResult類

NoExecTestResult繼承自TestResult類,區別是重寫了run方法,并不會執行Test,僅僅檢查startTest和endTest能否正常通過

protected void run(final TestCase test) {
    startTest(test);
    endTest(test);
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容