แอปจำนวนมากใช้ Hilt เพื่อแทรกลักษณะการทำงานที่แตกต่างกันลงในตัวแปรของบิลด์ที่แตกต่างกัน ซึ่งจะเป็นประโยชน์อย่างยิ่งเมื่อทำการเปรียบเทียบแบบไมโครกับแอปของคุณ เนื่องจากจะทำให้ คุณจะสลับส่วนประกอบที่สามารถบิดเบือนผลลัพธ์ได้ ตัวอย่างเช่น URL ต่อไปนี้ ข้อมูลโค้ดจะแสดงที่เก็บซึ่งดึงข้อมูลและจัดเรียงรายชื่อดังนี้
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()
ดังที่แสดงในตัวอย่างก่อนหน้านี้
มีการเรียกใช้เครือข่าย แต่จะมีการแทรกอินสแตนซ์ NetworkDataSource
โดย Hilt ได้ และคุณสามารถแทนที่ด้วยการติดตั้งปลอมต่อไปนี้สำหรับ
การเปรียบเทียบ:
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()
ระหว่างการเปรียบเทียบ
แอปบางแอปใช้ของปลอมอยู่แล้วในเวอร์ชันการแก้ไขข้อบกพร่องเพื่อนำทรัพยากร Dependency ของแบ็กเอนด์ออก อย่างไรก็ตาม คุณจำเป็นต้องเปรียบเทียบบิลด์ที่ใกล้เคียงกับบิลด์ เท่าที่จะเป็นไปได้ ส่วนที่เหลือของเอกสารนี้ใช้โครงสร้างหลายโมดูลและหลายตัวแปร ตามที่อธิบายไว้ในการตั้งค่าโปรเจ็กต์แบบเต็ม
มี 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")) } } }
ดึงดูด
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) }
ดึงดูด
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
- เพิ่มทรัพยากร Dependency ที่จำเป็น
คุณต้องเปลี่ยน 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
ดำเนินการ
Dependency Injection ในคลาสทดสอบการเปรียบเทียบ คุณจะต้องเรียกใช้
inject()
ของกฎ Hilt ในฟังก์ชัน @Before
เพื่อดำเนินการ
ก่อนดำเนินการทดสอบแต่ละครั้ง
ตัวเปรียบเทียบจะหยุดช่วงเวลาชั่วคราวในขณะที่ผู้สังเกตการณ์ LiveData
ที่ลงทะเบียนแล้ว จากนั้นจะใช้สลักเพื่อรอจนกว่าจะอัปเดต LiveData
แล้ว
จนเสร็จสิ้น เมื่อการจัดเรียงทํางานในช่วงเวลาต่อไปนี้
ระบบจะเรียกใช้ peopleRepository.update()
และเมื่อ LiveData
ได้รับการอัปเดต
ระยะเวลาของการจัดเรียงจะรวมอยู่ในเวลาเปรียบเทียบ
เรียกใช้ Microbenchmark
ทำการเปรียบเทียบกับ ./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 อยู่แล้ว คุณก็สามารถใช้ Hilt เพื่อแทรกข้อมูลปลอมสำหรับ การเปรียบเทียบแทน