แอปหลายแอปใช้ Hilt เพื่อแทรกลักษณะการทำงานที่แตกต่างกันลงในตัวแปรต่างๆ ของบิลด์ ซึ่งจะเป็นประโยชน์อย่างยิ่งเมื่อทำการทดสอบประสิทธิภาพระดับไมโครแอป เนื่องจากช่วยให้คุณเปลี่ยนคอมโพเนนต์ที่อาจทำให้ผลการทดสอบบิดเบือนได้ ตัวอย่างเช่น ข้อมูลโค้ดต่อไปนี้แสดงที่เก็บข้อมูลที่ดึงข้อมูลและจัดเรียงรายการชื่อ
Kotlin
class PeopleRepository @Inject constructor( @Kotlin private val dataSource: NetworkDataSource, @Dispatcher(DispatcherEnum.IO) private val dispatcher: CoroutineDispatcher ) { private val _peopleLiveData = MutableLiveData<List<Person>>() val peopleLiveData: LiveData<List<Person>> get() = _peopleLiveData suspend fun update() { withContext(dispatcher) { _peopleLiveData.postValue( dataSource.getPeople() .sortedWith(compareBy({ it.lastName }, { it.firstName })) ) } } }}
Java
public class PeopleRepository { private final MutableLiveData<List<Person>> peopleLiveData = new MutableLiveData<>(); private final NetworkDataSource dataSource; public LiveData<List<Person>> getPeopleLiveData() { return peopleLiveData; } @Inject public PeopleRepository(NetworkDataSource dataSource) { this.dataSource = dataSource; } private final Comparator<Person> comparator = Comparator.comparing(Person::getLastName) .thenComparing(Person::getFirstName); public void update() { Runnable task = new Runnable() { @Override public void run() { peopleLiveData.postValue( dataSource.getPeople() .stream() .sorted(comparator) .collect(Collectors.toList()) ); } }; new Thread(task).start(); } }
หากคุณรวมการเรียกใช้เครือข่ายเมื่อทำการเปรียบเทียบ ให้ใช้การเรียกใช้เครือข่ายจำลองเพื่อให้ได้ผลลัพธ์ที่แม่นยำยิ่งขึ้น
การรวมการเรียกใช้เครือข่ายจริงเมื่อทำการเปรียบเทียบทำให้ตีความผลลัพธ์การเปรียบเทียบได้ยากขึ้น การเรียกใช้เครือข่ายอาจได้รับผลกระทบจากปัจจัยภายนอกหลายประการ และระยะเวลาอาจแตกต่างกันไปในแต่ละรอบของการเรียกใช้การเปรียบเทียบ ระยะเวลาการเรียกใช้เครือข่ายอาจนานกว่าการจัดเรียง
ใช้การเรียกใช้เครือข่ายจำลองโดยใช้ Hilt
การเรียกใช้ dataSource.getPeople()
ดังที่แสดงในตัวอย่างก่อนหน้านี้มีคำเรียกใช้เครือข่าย อย่างไรก็ตาม Hilt จะแทรกอินสแตนซ์ NetworkDataSource
และคุณสามารถแทนที่ด้วยการติดตั้งใช้งานจำลองต่อไปนี้เพื่อทำการเปรียบเทียบ
Kotlin
class FakeNetworkDataSource @Inject constructor( private val people: List<Person> ) : NetworkDataSource { override fun getPeople(): List<Person> = people }
Java
public class FakeNetworkDataSource implements NetworkDataSource{ private List<Person> people; @Inject public FakeNetworkDataSource(List<Person> people) { this.people = people; } @Override public List<Person> getPeople() { return people; } }
การเรียกใช้เครือข่ายจำลองนี้ออกแบบมาเพื่อให้ทำงานได้เร็วที่สุดเมื่อคุณเรียกใช้เมธอด getPeople()
หากต้องการให้ Hilt แทรกข้อมูลนี้ ระบบจะใช้ผู้ให้บริการต่อไปนี้
Kotlin
@Module @InstallIn(SingletonComponent::class) object FakekNetworkModule { @Provides @Kotlin fun provideNetworkDataSource(@ApplicationContext context: Context): NetworkDataSource { val data = context.assets.open("fakedata.json").use { inputStream -> val bytes = ByteArray(inputStream.available()) inputStream.read(bytes) val gson = Gson() val type: Type = object : TypeToken<List<Person>>() {}.type gson.fromJson<List<Person>>(String(bytes), type) } return FakeNetworkDataSource(data) } }
Java
@Module @InstallIn(SingletonComponent.class) public class FakeNetworkModule { @Provides @Java NetworkDataSource provideNetworkDataSource( @ApplicationContext Context context ) { List<Person> data = new ArrayList<>(); try (InputStream inputStream = context.getAssets().open("fakedata.json")) { int size = inputStream.available(); byte[] bytes = new byte[size]; if (inputStream.read(bytes) == size) { Gson gson = new Gson(); Type type = new TypeToken<ArrayList<Person>>() { }.getType(); data = gson.fromJson(new String(bytes), type); } } catch (IOException e) { // Do something } return new FakeNetworkDataSource(data); } }
ระบบจะโหลดข้อมูลจากชิ้นงานโดยใช้การเรียก I/O ที่มีความยาวผันแปรได้
อย่างไรก็ตาม การดำเนินการนี้จะดำเนินการระหว่างการเริ่มต้นและจะไม่ทำให้เกิดความผิดปกติเมื่อเรียก getPeople()
ในระหว่างการเปรียบเทียบประสิทธิภาพ
แอปบางแอปใช้ข้อมูลจำลองในบิลด์แก้ไขข้อบกพร่องอยู่แล้วเพื่อนำการพึ่งพาแบ็กเอนด์ออก อย่างไรก็ตาม คุณต้องทำการเปรียบเทียบกับบิลด์ที่ใกล้เคียงกับบิลด์รุ่นมากที่สุด ส่วนที่เหลือของเอกสารนี้ใช้โครงสร้างแบบหลายโมดูลและหลายตัวแปรตามที่อธิบายไว้ในการตั้งค่าโปรเจ็กต์แบบสมบูรณ์
โดยแบ่งออกเป็น 3 โมดูล ดังนี้
benchmarkable
: มีโค้ดเพื่อเปรียบเทียบbenchmark
: มีรหัสการเปรียบเทียบapp
: มีโค้ดแอปที่เหลืออยู่
โมดูลก่อนหน้าแต่ละรายการมีตัวแปรบิลด์ชื่อ benchmark
พร้อมกับตัวแปร debug
และ release
ปกติ
กำหนดค่าข้อบังคับ
โค้ดสําหรับการเรียกใช้เครือข่ายจำลองอยู่ในชุดซอร์สdebug
ของโมดูลbenchmarkable
และการใช้งานเครือข่ายแบบเต็มอยู่ในชุดซอร์สrelease
ของโมดูลเดียวกัน ไฟล์ชิ้นงานที่มีข้อมูลที่แสดงผลโดยการติดตั้งใช้งานจำลองอยู่ในชุดแหล่งที่มา debug
เพื่อหลีกเลี่ยงการทำให้ APK มีขนาดเพิ่มขึ้นในบิลด์ release
ตัวแปร benchmark
ต้องอิงตาม release
และใช้ชุดแหล่งที่มา debug
การกําหนดค่าบิลด์สําหรับตัวแปร benchmark
ของโมดูล benchmarkable
ที่มีการใช้งานจำลองมีดังนี้
Kotlin
android { ... buildTypes { release { isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } create("benchmark") { initWith(getByName("release")) } } ... sourceSets { getByName("benchmark") { java.setSrcDirs(listOf("src/debug/java")) assets.setSrcDirs(listOf("src/debug/assets")) } } }
Groovy
android { ... buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' ) } benchmark { initWith release } } ... sourceSets { benchmark { java.setSrcDirs ['src/debug/java'] assets.setSrcDirs(listOf ['src/debug/assets'] } } }
ในโมดูล benchmark
ให้เพิ่มโปรแกรมรันทดสอบที่กำหนดเองซึ่งสร้าง Application
เพื่อให้การทดสอบทำงานในสภาพแวดล้อมที่รองรับ Hilt ดังนี้
Kotlin
class HiltBenchmarkRunner : AndroidBenchmarkRunner() { override fun newApplication( cl: ClassLoader?, className: String?, context: Context? ): Application { return super.newApplication(cl, HiltTestApplication::class.java.name, context) } }
Java
public class JavaHiltBenchmarkRunner extends AndroidBenchmarkRunner { @Override public Application newApplication( ClassLoader cl, String className, Context context ) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return super.newApplication(cl, HiltTestApplication.class.getName(), context); } }
ซึ่งทําให้ออบเจ็กต์ Application
ที่เรียกใช้การทดสอบขยายคลาส HiltTestApplication
ทำการเปลี่ยนแปลงต่อไปนี้ในการกําหนดค่าการสร้าง
Kotlin
plugins { alias(libs.plugins.android.library) alias(libs.plugins.benchmark) alias(libs.plugins.jetbrains.kotlin.android) alias(libs.plugins.kapt) alias(libs.plugins.hilt) } android { namespace = "com.example.hiltmicrobenchmark.benchmark" compileSdk = 34 defaultConfig { minSdk = 24 testInstrumentationRunner = "com.example.hiltbenchmark.HiltBenchmarkRunner" } testBuildType = "benchmark" buildTypes { debug { // Since isDebuggable can't be modified by Gradle for library modules, // it must be done in a manifest. See src/androidTest/AndroidManifest.xml. isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "benchmark-proguard-rules.pro" ) } create("benchmark") { initWith(getByName("debug")) } } } dependencies { androidTestImplementation(libs.bundles.hilt) androidTestImplementation(project(":benchmarkable")) implementation(libs.androidx.runner) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.junit) implementation(libs.androidx.benchmark) implementation(libs.google.dagger.hiltTesting) kaptAndroidTest(libs.google.dagger.hiltCompiler) androidTestAnnotationProcessor(libs.google.dagger.hiltCompiler) }
Groovy
plugins { alias libs.plugins.android.library alias libs.plugins.benchmark alias libs.plugins.jetbrains.kotlin.android alias libs.plugins.kapt alias libs.plugins.hilt } android { namespace = 'com.example.hiltmicrobenchmark.benchmark' compileSdk = 34 defaultConfig { minSdk = 24 testInstrumentationRunner 'com.example.hiltbenchmark.HiltBenchmarkRunner' } testBuildType "benchmark" buildTypes { debug { // Since isDebuggable can't be modified by Gradle for library modules, // it must be done in a manifest. See src/androidTest/AndroidManifest.xml. minifyEnabled true proguardFiles( getDefaultProguardFile('proguard-android-optimize.txt'), 'benchmark-proguard-rules.pro' ) } benchmark { initWith debug" } } } dependencies { androidTestImplementation libs.bundles.hilt androidTestImplementation project(':benchmarkable') implementation libs.androidx.runner androidTestImplementation libs.androidx.junit androidTestImplementation libs.junit implementation libs.androidx.benchmark implementation libs.google.dagger.hiltTesting kaptAndroidTest libs.google.dagger.hiltCompiler androidTestAnnotationProcessor libs.google.dagger.hiltCompiler }
ตัวอย่างก่อนหน้านี้ทําสิ่งต่อไปนี้
- ใช้ปลั๊กอิน Gradle ที่จำเป็นกับบิลด์
- ระบุว่าใช้โปรแกรมรันทดสอบที่กําหนดเองเพื่อเรียกใช้การทดสอบ
- ระบุว่าตัวแปร
benchmark
เป็นประเภทการทดสอบสําหรับข้อบังคับนี้ - เพิ่มตัวแปร
benchmark
- เพิ่มการพึ่งพาที่จำเป็น
คุณต้องเปลี่ยน testBuildType
เพื่อให้ Gradle สร้างงาน connectedBenchmarkAndroidTest
ซึ่งจะทำการเปรียบเทียบ
สร้างการทดสอบประสิทธิภาพระดับไมโคร
การติดตั้งใช้งานการเปรียบเทียบมีดังนี้
Kotlin
@RunWith(AndroidJUnit4::class) @HiltAndroidTest class PeopleRepositoryBenchmark { @get:Rule val benchmarkRule = BenchmarkRule() @get:Rule val hiltRule = HiltAndroidRule(this) private val latch = CountdownLatch(1) @Inject lateinit var peopleRepository: PeopleRepository @Before fun setup() { hiltRule.inject() } @Test fun benchmarkSort() { benchmarkRule.measureRepeated { runBlocking { benchmarkRule.getStart().pauseTiming() withContext(Dispatchers.Main.immediate) { peopleRepository.peopleLiveData.observeForever(observer) } benchmarkRule.getStart().resumeTiming() peopleRepository.update() latch.await() assert(peopleRepository.peopleLiveData.value?.isNotEmpty() ?: false) } } } private val observer: Observer<List<Person>> = object : Observer<List<Person>> { override fun onChanged(people: List<Person>?) { peopleRepository.peopleLiveData.removeObserver(this) latch.countDown() } } }
Java
@RunWith(AndroidJUnit4.class) @HiltAndroidTest public class PeopleRepositoryBenchmark { @Rule public BenchmarkRule benchmarkRule = new BenchmarkRule(); @Rule public HiltAndroidRule hiltRule = new HiltAndroidRule(this); private CountdownLatch latch = new CountdownLatch(1); @Inject JavaPeopleRepository peopleRepository; @Before public void setup() { hiltRule.inject(); } @Test public void benchmarkSort() { BenchmarkRuleKt.measureRepeated(benchmarkRule, (Function1<BenchmarkRule.Scope, Unit>) scope -> { benchmarkRule.getState().pauseTiming(); new Handler(Looper.getMainLooper()).post(() -> { awaitValue(peopleRepository.getPeopleLiveData()); }); benchmarkRule.getState().resumeTiming(); peopleRepository.update(); try { latch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } assert (!peopleRepository.getPeopleLiveData().getValue().isEmpty()); return Unit.INSTANCE; }); } private <T> void awaitValue(LiveData<T> liveData) { Observer<T> observer = new Observer<T>() { @Override public void onChanged(T t) { liveData.removeObserver(this); latch.countDown(); } }; liveData.observeForever(observer); return; } }
ตัวอย่างก่อนหน้านี้สร้างกฎสําหรับทั้งการเปรียบเทียบและ Hilt
benchmarkRule
ดำเนินการกำหนดเวลาของการเปรียบเทียบ hiltRule
ทำการฉีดข้อมูลพึ่งพาในคลาสการทดสอบการเปรียบเทียบ คุณต้องเรียกใช้เมธอด inject()
ของกฎ Hilt ในฟังก์ชัน @Before
เพื่อทำการแทรกก่อนทำการทดสอบแต่ละรายการ
ข้อมูลเปรียบเทียบจะหยุดการจับเวลาชั่วคราวขณะที่ลงทะเบียนผู้สังเกตการณ์ LiveData
จากนั้นจะใช้แถบสลักเพื่อรอจนกว่า LiveData
จะอัปเดตก่อนที่จะดำเนินการต่อ เนื่องจากการจัดเรียงจะทํางานในช่วงเวลาระหว่างที่เรียกใช้ peopleRepository.update()
กับเวลาที่ LiveData
ได้รับการอัปเดต ระยะเวลาการจัดเรียงจึงรวมอยู่ในการกำหนดเวลาการเปรียบเทียบ
เรียกใช้การทดสอบประสิทธิภาพระดับไมโคร
เรียกใช้การเปรียบเทียบกับ ./gradlew :benchmark:connectedBenchmarkAndroidTest
เพื่อทำการเปรียบเทียบซ้ำหลายครั้งและพิมพ์ข้อมูลเวลาไปยัง Logcat
PeopleRepositoryBenchmark.log[Metric (timeNs) results: median 613408.3952380952, min 451949.30476190476, max 1412143.5142857144, standardDeviation: 273221.2328680522...
ตัวอย่างก่อนหน้านี้แสดงผลการทดสอบประสิทธิภาพระหว่าง 0.6-1.4 มิลลิวินาทีในการเรียกใช้อัลกอริทึมการจัดเรียงในรายการ 1,000 รายการ อย่างไรก็ตาม หากคุณรวมการเรียกใช้เครือข่ายไว้ในการเปรียบเทียบ ผลต่างระหว่างการทำซ้ำจะนานกว่าเวลาที่การเรียงลำดับใช้ในการทำงาน จึงจําเป็นต้องแยกการเรียงลําดับออกจากการเรียกใช้เครือข่าย
คุณรีแฟกทอริกโค้ดได้เสมอเพื่อให้การแยกการเรียงลำดับได้ง่ายขึ้น แต่หากใช้ Hilt อยู่แล้ว คุณก็ใช้เพื่อแทรกข้อมูลจำลองสำหรับการเปรียบเทียบประสิทธิภาพแทนได้