التحكّم في تطبيقك من Macrobenchmark (طرق العرض)

المفاهيم والتنفيذ في Jetpack Compose

على عكس معظم اختبارات واجهة المستخدم على Android، يتم تشغيل اختبارات Macrobenchmark في عملية منفصلة عن التطبيق نفسه. وهذا ضروري لتفعيل إجراءات مثل إيقاف عملية التطبيق والترجمة من رمز بايت DEX إلى رمز آلة.

يمكنك التحكّم في حالة تطبيقك باستخدام مكتبة UIAutomator أو آليات أخرى يمكنها التحكّم في التطبيق المستهدَف من عملية الاختبار. لا يمكنك استخدام Espresso أو ActivityScenario مع Macrobenchmark لأنّهما يتطلّبان التشغيل في عملية مشتركة مع التطبيق.

يعثر المثال التالي على RecyclerView باستخدام معرّف المورد الخاص به، ثم ينتقل للأسفل عدة مرات:

Kotlin

@Test
fun scrollList() {
    benchmarkRule.measureRepeated(
        // ...
        setupBlock = {
            uiAutomator {
                // Before starting to measure, navigate to the UI to be measured
                startIntent(Intent("$packageName.RECYCLER_VIEW_ACTIVITY"))
            }
        }
    ) {
        uiAutomator {
            val recycler = onElement { className == "androidx.recyclerview.widget.RecyclerView" }
            // Scroll down several times
            repeat(3) { recycler.fling(Direction.DOWN) }
        }

    }
}

Java

@Test
public void scrollList() {
    benchmarkRule.measureRepeated(
        // ...
        /* setupBlock */ scope -> {
            // Before measuring, navigate to the UI to be measured.
            val intent = Intent("$packageName.RECYCLER_VIEW_ACTIVITY")
            scope.startActivityAndWait();
            return Unit.INSTANCE;
        },
        /* measureBlock */ scope -> {
            UiDevice device = scope.getDevice();
            UiObject2 recycler = device.findObject(By.res(scope.getPackageName(), "recycler"));

            // Set gesture margin to avoid triggering gesture navigation
            // with input events from automation.
            recycler.setGestureMargin(device.getDisplayWidth() / 5);

            // Fling the recycler several times.
            for (int i = 0; i < 3; i++) {
                recycler.fling(Direction.DOWN);
            }

            return Unit.INSTANCE;
        }
    );
}

ليس من الضروري أن يتم تمرير واجهة المستخدم في اختبار الأداء. بدلاً من ذلك، يمكنه تشغيل رسم متحرك، على سبيل المثال. كما أنّه لا يحتاج إلى استخدام UI Automator تحديدًا. ويجمع مقاييس الأداء طالما يتم إنشاء اللقطات.

في بعض الأحيان، تريد قياس أداء أجزاء من تطبيقك لا يمكن الوصول إليها مباشرةً من الخارج. وقد يشمل ذلك، على سبيل المثال، الوصول إلى الأنشطة الداخلية التي تم وضع علامة exported=false عليها، أو الانتقال إلى Fragment، أو إزالة جزء من واجهة المستخدم عن طريق التمرير السريع. يجب أن تنتقل مقاييس الأداء يدويًا إلى هذه الأجزاء من التطبيق كما يفعل المستخدم.

للتنقّل يدويًا، غيِّر الرمز داخل setupBlock{} ليحتوي على التأثير الذي تريده، مثل النقر على زر أو التمرير سريعًا. يحتوي measureBlock{} على عمليات معالجة واجهة المستخدم التي تريد قياس أدائها فعليًا:

Kotlin

@Test
fun nonExportedActivityScrollList() {
    benchmarkRule.measureRepeated(
        // ...
        setupBlock = setupBenchmark()
    ) {
        // ...
    }
}

private fun setupBenchmark(): MacrobenchmarkScope.() -> Unit = {
    uiAutomator {
        // Before starting to measure, navigate to the UI to be measured
        startApp(TARGET_PACKAGE)
        // click a button to launch the target activity.
        onElement { textAsString() == "RecyclerView" }.click()
        // wait until the activity is shown
        waitForStableInActiveWindow()
    }
}

Java

@Test
public void scrollList() {
    benchmarkRule.measureRepeated(
        // ...
        /* setupBlock */ scope -> {
            // Before measuring, navigate to the default activity.
            scope.startActivityAndWait();

            // Click a button to launch the target activity.
            // While you use resourceId here to find the button, you can also
            // use accessibility info or button text content.
            UiObject2 launchRecyclerActivity = scope.getDevice().findObject(
                By.res(packageName, "launchRecyclerActivity")
            )
            launchRecyclerActivity.click();

            // Wait until activity is shown.
            scope.getDevice().wait(
                Until.hasObject(By.clazz("$packageName.NonExportedRecyclerActivity")),
                10000L
            )

            return Unit.INSTANCE;
        },
        /* measureBlock */ scope -> {
            // ...
        }
    );
}