Cải thiện hiệu suất của ứng dụng nhờ Cấu hình cơ sở

1. Trước khi bắt đầu

Lớp học lập trình này sẽ bạn thấy cách tạo Hồ sơ cơ sở để tối ưu hoá hiệu suất của ứng dụng cũng như cách xác minh các lợi ích về hiệu suất khi sử dụng Hồ sơ cơ sở.

Bạn cần

Bạn sẽ thực hiện

  • Thiết lập dự án để sử dụng trình tạo Hồ sơ cơ sở.
  • Tạo Hồ sơ cơ sở để tối ưu hoá hiệu suất khởi động và cuộn trong ứng dụng.
  • Xác minh mức tăng hiệu suất với thư viện Jetpack Macrobenchmark.

Kiến thức bạn sẽ học được

  • Hồ sơ cơ sở và cách dùng Hồ sơ cơ sở để cải thiện hiệu suất của ứng dụng.
  • Cách tạo Hồ sơ cơ sở.
  • Mức tăng hiệu suất của Hồ sơ cơ sở.

2. Thiết lập

Để bắt đầu, hãy sao chép kho lưu trữ GitHub từ dòng lệnh bằng cách sử dụng lệnh sau:

$ git clone https://github.com/android/codelab-android-performance.git

Ngoài ra, bạn có thể tải 2 tệp zip xuống:

Mở dự án trong Android Studio

  1. Trong cửa sổ Welcome to Android Studio (Chào mừng bạn đến với Android Studio), hãy chọn biểu tượng 61d0a4432ef6d396.png Open an Existing Project (Mở một dự án hiện có).
  2. Chọn thư mục [Download Location]/codelab-android-performance/baseline-profiles. Hãy nhớ chọn thư mục baseline-profiles.
  3. Khi Android Studio nhập dự án, hãy đảm bảo rằng bạn có thể chạy mô-đun app để tạo ứng dụng mẫu mà bạn sẽ sử dụng sau này.

Ứng dụng mẫu

Trong lớp học lập trình này, bạn sẽ làm việc với ứng dụng mẫu JetSnack. Đây là một ứng dụng đặt đồ ăn ảo sử dụng Jetpack Compose.

Để đo lường hiệu suất của ứng dụng, bạn cần hiểu cấu trúc của giao diện người dùng và cách hoạt động của ứng dụng. Nhờ đó, bạn có thể truy cập vào các thành phần giao diện người dùng qua các điểm chuẩn. Chạy ứng dụng và thực hiện gọi đồ ăn để làm quen với các màn hình cơ bản. Bạn không cần nắm chi tiết về kiến trúc ứng dụng.

23633b02ac7ce1bc.png

3. Hồ sơ cơ sở là gì?

Hồ sơ cơ sở cải thiện tốc độ thực thi mã khoảng 30% kể từ lần khởi chạy đầu tiên bằng cách tránh các bước diễn giải và các bước biên dịch trong khi thực thi (JIT) cho các đường dẫn mã đi kèm. Bằng cách truyền Hồ sơ cơ sở trong một ứng dụng hoặc thư viện, Android Runtime (ART) có thể tối ưu hoá các đường dẫn mã được đưa vào thông qua tính năng biên dịch trước khi thực thi (AOT), cung cấp các tính năng nâng cao hiệu suất cho mọi người dùng mới và trong mỗi bản cập nhật ứng dụng. Tính năng tối ưu hoá theo hồ sơ (PGO) này cho phép ứng dụng tối ưu hoá quá trình khởi động, giảm hiện tượng giật khi tương tác và cải thiện hiệu suất tổng thể khi chạy cho người dùng cuối từ lần khởi chạy đầu tiên.

Với Hồ sơ cơ sở, mọi hoạt động tương tác của người dùng (chẳng hạn như khởi động ứng dụng, di chuyển giữa các màn hình hoặc cuộn qua nội dung) đều mượt mà hơn ngay từ lần chạy đầu tiên. Việc tăng tốc độ và khả năng phản hồi của ứng dụng sẽ giúp gia tăng số người dùng hoạt động hằng ngày và tỷ lệ truy cập trung bình của người dùng cũ.

Hồ sơ cơ sở giúp định hướng hoạt động tối ưu hoá ngoài hoạt động khởi động ứng dụng bằng cách cung cấp các hoạt động tương tác phổ biến của người dùng nhằm cải thiện thời gian chạy ứng dụng ngay từ lần khởi chạy đầu tiên. Quá trình biên dịch AOT có hướng dẫn không dựa vào thiết bị của người dùng và có thể được thực hiện một lần trên mỗi bản phát hành trên máy phát triển thay vì trên thiết bị di động. Bằng cách sử dụng Hồ sơ cơ sở cho các bản phát hành, tính năng tối ưu hoá ứng dụng hoạt động nhanh hơn nhiều so với khi chỉ dựa vào Cấu hình đám mây.

Khi không sử dụng Hồ sơ cơ sở, mọi đoạn mã ứng dụng đều được biên dịch khi thực thi trong bộ nhớ sau khi được diễn giải hoặc thành tệp odex owr chế độ nền khi thiết bị ở trạng thái rảnh. Người dùng có thể sẽ có trải nghiệm chưa tối ưu khi chạy ứng dụng sau khi cài đặt hoặc cập nhật ứng dụng lần đầu tiên, trước khi đường dẫn mới được tối ưu hoá.

4. Thiết lập mô-đun trình tạo Hồ sơ cơ sở

Bạn có thể tạo Hồ sơ cơ sở bằng một lớp kiểm thử đo lường yêu cầu thêm mô-đun Gradle mới vào dự án. Cách dễ dàng nhất để thêm Hồ sơ cơ sở vào dự án là sử dụng trình hướng dẫn mô-đun Android Studio đi kèm với Android Studio Hedgehog trở lên.

Mở cửa sổ trình hướng dẫn mô-đun mới bằng cách nhấp chuột phải vào dự án hoặc mô-đun trong bảng Project (Dự án) rồi chọn New > Module (Mới > Mô-đun).

232b04efef485e9c.png

Trong cửa sổ mở ra, hãy chọn Baseline Profile Generator (Trình tạo hồ sơ cơ sở) trong ngăn Templates (Mẫu).

b191fe07969e8c26.png

Bên cạnh các tham số thông thường như tên mô-đun, tên gói, ngôn ngữ hoặc ngôn ngữ cấu hình bản dựng, có hai thông tin đầu vào không thường dùng cho mô-đun mới: Target application (Ứng dụng mục tiêu) và Use Gradle Managed Device (Sử dụng thiết bị do Gradle quản lý).

Ứng dụng mục tiêu là mô-đun ứng dụng dùng để tạo Hồ sơ cơ sở. Nếu trong dự án của bạn có nhiều mô-đun ứng dụng, hãy chọn mô-đun mà bạn muốn chạy trình tạo.

Hộp đánh dấu Use Gradle Managed Device (Sử dụng thiết bị do Gradle quản lý) thiết lập mô-đun để chạy trình tạo Hồ sơ cơ sở trên trình mô phỏng Android được quản lý tự động. Bạn có thể đọc thêm về Thiết bị do Gradle quản lý trong phần Mở rộng quy mô kiểm thử bằng Thiết bị do Gradle quản lý. Nếu bỏ chọn hộp đánh dấu này, các trình tạo sẽ sử dụng bất cứ thiết bị nào được kết nối.

Sau khi xác định tất cả thông tin chi tiết về mô-đun mới, hãy nhấp vào Finish (Hoàn tất) để tiếp tục tạo mô-đun.

Các thay đổi do trình hướng dẫn mô-đun thực hiện

Trình hướng dẫn mô-đun thực hiện một số thay đổi đối với dự án của bạn.

Thao tác này sẽ thêm mô-đun Gradle có tên baselineprofile (hoặc tên mà bạn đã chọn trong trình hướng dẫn).

Mô-đun này sử dụng trình bổ trợ com.android.test (yêu cầu Gradle không đưa mô-đun này này vào ứng dụng của bạn), vì vậy, nó chỉ có thể chứa đoạn mã kiểm thử (hoặc phép đo điểm chuẩn). Mô-đun này cũng áp dụng trình bổ trợ androidx.baselineprofile, cho phép tự động tạo Hồ sơ cơ sở.

Trình hướng dẫn này cũng thực hiện các thay đổi đối với mô-đun ứng dụng mục tiêu mà bạn chọn. Cụ thể, công cụ này áp dụng trình bổ trợ androidx.baselineprofile, thêm phần phụ thuộc androidx.profileinstaller và thêm phần phụ thuộc baselineProfile vào mô-đun mới tạo build.gradle(.kts):

plugins {
  id("androidx.baselineprofile")
}

dependencies {
  // ...
  implementation("androidx.profileinstaller:profileinstaller:1.3.0")
  "baselineProfile"(project(mapOf("path" to ":baselineprofile")))
}

Việc thêm phần phụ thuộc androidx.profileinstaller cho phép bạn làm những việc sau:

  • Xác minh cục bộ mức tăng hiệu suất của Hồ sơ cơ sở đã tạo.
  • Sử dụng Hồ sơ cơ sở trên các thiết bị Android 7 (API cấp 24) và Android 8 (API cấp 26) không hỗ trợ cấu hình Đám mây.
  • Sử dụng Hồ sơ cơ sở trên các thiết bị không có Dịch vụ Google Play.

Phần phụ thuộc baselineProfile(project(":baselineprofile")) cho Gradle biết cần phải lấy mô-đun cơ sở đã tạo từ mô-đun nào.

Giờ đây khi bạn đã thiết lập dự án, hãy viết một lớp trình tạo Hồ sơ cơ sở.

5. Viết trình tạo Hồ sơ cơ sở

Thông thường, bạn sẽ tạo Hồ sơ cơ sở cho hành trình điển hình của người dùng trong ứng dụng.

Trình hướng dẫn mô-đun sẽ tạo một lớp kiểm thử BaselineProfileGenerator cơ bản có khả năng tạo Hồ sơ cơ sở để khởi động ứng dụng và có dạng như sau:

@RunWith(AndroidJUnit4::class)
@LargeTest
class BaselineProfileGenerator {

    @get:Rule
    val rule = BaselineProfileRule()

    @Test
    fun generate() {
        rule.collect("com.example.baselineprofiles_codelab") {
            // This block defines the app's critical user journey. This is where you
            // optimize for app startup. You can also navigate and scroll
            // through your most important UI.

            // Start default activity for your app.
            pressHome()
            startActivityAndWait()

            // TODO Write more interactions to optimize advanced journeys of your app.
            // For example:
            // 1. Wait until the content is asynchronously loaded.
            // 2. Scroll the feed content.
            // 3. Navigate to detail screen.

            // Check UiAutomator documentation for more information about how to interact with the app.
            // https://d.android.com/training/testing/other-components/ui-automator
        }
    }
}

Lớp này sử dụng quy tắc kiểm thử BaselineProfileRule và chứa một phương để thức kiểm thử việc tạo hồ sơ. Điểm nhập để tạo hồ sơ là hàm collect(). Hàm này chỉ yêu cầu 2 tham số:

  • packageName: gói ứng dụng của bạn.
  • profileBlock: tham số sau cùng của hàm lambda

Trong hàm lambda profileBlock, bạn sẽ chỉ định các hoạt động tương tác có bao gồm hành trình điển hình của người dùng trong ứng dụng. Thư viện này chạy profileBlock nhiều lần, thu thập các lớp và hàm được gọi, đồng thời tạo Hồ sơ cơ sở trên thiết bị bằng đoạn mã cần tối ưu hoá.

Theo mặc định, lớp trình tạo được tạo chứa các hoạt động tương tác để bắt đầu Activity mặc định và đợi cho đến khi khung hình đầu tiên của ứng dụng kết xuất bằng phương thức startActivityAndWait().

Mở rộng trình tạo bằng các hành trình tuỳ chỉnh

Bạn có thể thấy lớp được tạo cũng bao gồm một số TODO để ghi thêm nhiều lượt tương tác nhằm tối ưu hoá hành trình nâng cao của ứng dụng. Bạn nên làm điều này để có thể tối ưu hoá hiệu suất của các hoạt động khác ngoài phần khởi động ứng dụng.

Trong ứng dụng mẫu, bạn có thể xác định những hành trình này bằng cách làm như sau:

  1. Khởi động ứng dụng. Tính năng này hiện đã có trong lớp được tạo.
  2. Chờ cho đến khi nội dung được tải không đồng bộ.
  3. Cuộn trên danh sách món ăn vặt.
  4. Chuyển đến phần thông tin chi tiết về món ăn vặt.

Thay đổi trình tạo để chứa các hàm được nêu liên quan đến những hành trình điển hình trong đoạn mã sau:

// ...
rule.collect("com.example.baselineprofiles_codelab") {
    // This block defines the app's critical user journey. This is where you
    // optimize for app startup. You can also navigate and scroll
    // through your most important UI.

    // Start default activity for your app.
    pressHome()
    startActivityAndWait()

    // TODO Write more interactions to optimize advanced journeys of your app.
    // For example:
    // 1. Wait until the content is asynchronously loaded.
    waitForAsyncContent()
    // 2. Scroll the feed content.
    scrollSnackListJourney()
    // 3. Navigate to detail screen.
    goToSnackDetailJourney()

    // Check UiAutomator documentation for more information about how to interact with the app.
    // https://d.android.com/training/testing/other-components/ui-automator
}
// ...

Bây giờ, hãy viết các hành động tương tác cho từng hành trình được đề cập. Bạn có thể viết dưới dạng hàm mở rộng của MacrobenchmarkScope để truy cập vào các tham số cũng như hàm mà hành trình này cung cấp. Việc viết mã theo cách này cho phép bạn sử dụng lại các lượt tương tác với phép đo điểm chuẩn để xác minh mức tăng hiệu suất.

Chờ nội dung không đồng bộ

Nhiều ứng dụng có một số hình thức tải không đồng bộ khi khởi động ứng dụng (còn gọi là trạng thái hiển thị đầy đủ) cho hệ thống biết thời điểm tải và hiển thị nội dung, đồng thời người dùng có thể tương tác với ứng dụng. Chờ trạng thái trong trình tạo (waitForAsyncContent) với các tương tác này:

  1. Tìm danh sách đồ ăn trong nguồn cấp dữ liệu.
  2. Chờ cho đến khi một số mục trong danh sách xuất hiện trên màn hình.
fun MacrobenchmarkScope.waitForAsyncContent() {
   device.wait(Until.hasObject(By.res("snack_list")), 5_000)
   val contentList = device.findObject(By.res("snack_list"))
   // Wait until a snack collection item within the list is rendered.
   contentList.wait(Until.hasObject(By.res("snack_collection")), 5_000)
}

Hành trình cuộn của danh sách

Đối với hành trình cuộn danh sách món ăn vặt (scrollSnackListJourney), bạn có thể thực hiện theo các tương tác sau:

  1. Tìm thành phần trên giao diện người dùng của danh sách món ăn vặt
  2. Thiết lập lề cho cử chỉ để không kích hoạt thao tác trên hệ thống.
  3. Cuộn danh sách này rồi đợi đến khi thiết lập xong giao diện người dùng
fun MacrobenchmarkScope.scrollSnackListJourney() {
   val snackList = device.findObject(By.res("snack_list"))
   // Set gesture margin to avoid triggering gesture navigation.
   snackList.setGestureMargin(device.displayWidth / 5)
   snackList.fling(Direction.DOWN)
   device.waitForIdle()
}

Chuyển sang hành trình chi tiết

Hành trình gần đây nhất (goToSnackDetailJourney) sẽ triển khai các hoạt động tương tác này:

  1. Tìm danh sách các món ăn vặt và tất cả các món ăn vặt mà bạn muốn đưa vào.
  2. Chọn một mục trong danh sách.
  3. Nhấp vào mục đó và đợi cho đến khi màn hình chi tiết được tải. Bạn có thể tận dụng một lợi thế là danh sách món ăn vặt sẽ không xuất hiện trên màn hình nữa
fun MacrobenchmarkScope.goToSnackDetailJourney() {
    val snackList = device.findObject(By.res("snack_list"))
    val snacks = snackList.findObjects(By.res("snack_item"))
    // Select snack from the list based on running iteration.
    val index = (iteration ?: 0) % snacks.size
    snacks[index].click()
    // Wait until the screen is gone = the detail is shown.
    device.wait(Until.gone(By.res("snack_list")), 5_000)
}

Sau khi xác định tất cả các lượt tương tác cần thiết để trình tạo Cấu hình cơ sở sẵn sàng chạy, bạn cần xác định thiết bị sẽ chạy.

6. Chuẩn bị một thiết bị để chạy trình tạo

Để tạo Hồ sơ cơ sở, bạn nên sử dụng một trình mô phỏng như Thiết bị do Gradle quản lý hoặc một thiết bị chạy Android 13 (API 33) trở lên.

Bạn có thể sử dụng Thiết bị do Gradle quản lý để có thể tái sản xuất cho quy trình và tự động tạo Hồ sơ cơ sở. Thiết bị do Gradle quản lý cho phép bạn chạy thử nghiệm trên trình mô phỏng Android mà không cần khởi chạy và chia nhỏ theo cách thủ công. Bạn có thể tìm hiểu thêm về Thiết bị do Gradle quản lý trong phần Mở rộng quy mô kiểm thử bằng Thiết bị do Gradle quản lý.

Để xác định một Thiết bị do Gradle quản lý, hãy thêm định nghĩa của thiết bị đó vào mô-đun :baselineprofile của tệp build.gradle.kts như trong đoạn mã sau:

android {
  // ...

  testOptions.managedDevices.devices {
    create<ManagedVirtualDevice>("pixel6Api31") {
        device = "Pixel 6"
        apiLevel = 31
        systemImageSource = "aosp"
    }
  }
}

Trong trường hợp này, chúng ta sẽ sử dụng Android 11 (API cấp 31) và hình ảnh hệ thống aosp có khả năng truy cập can thiệp vào hệ thống.

Tiếp theo, hãy định cấu hình Trình bổ trợ Hồ sơ cơ sở của Gradle để sử dụng Thiết bị do Gradle quản lý đã xác định. Để làm vậy, hãy thêm tên của thiết bị đó vào thuộc tính managedDevices và tắt useConnectedDevices như minh hoạ trong đoạn mã sau:

android {
  // ...
}

baselineProfile {
   managedDevices += "pixel6Api31"
   useConnectedDevices = false
}

dependencies {
  // ...
}

Tiếp theo, hãy tạo Hồ sơ cơ sở.

7. Tạo Hồ sơ cơ sở

Sau khi chuẩn bị thiết bị xong, bạn có thể tạo Hồ sơ cơ sở. Trình bổ trợ Hồ sơ cơ sở Gradle cơ sở tạo các tác vụ Gradle để tự động hoá toàn bộ quy trình chạy lớp kiểm thử trình tạo, đồng thời áp dụng các Hồ sơ cơ sở đã tạo vào ứng dụng của bạn.

Trình hướng dẫn mô-đun mới đã tạo cấu hình chạy để có thể chạy nhanh tác vụ Gradle với tất cả tham số cần thiết để chạy mà không cần chuyển đổi giữa thiết bị đầu cuối và Android Studio

Để chạy cấu hình đó, hãy tìm cấu hình chạy Generate Baseline Profile rồi nhấp vào nút Chạy 599be5a3531f863b.png.

6911ecf1307a213f.png

Tác vụ sẽ khởi động hình ảnh trình mô phỏng được xác định trước đó. Chạy các lượt tương tác từ lớp kiểm thử BaselineProfileGenerator nhiều lần, sau đó chia nhỏ trình mô phỏng và cung cấp kết quả cho Android Studio.

Sau khi trình tạo hoàn tất, trình bổ trợ Gradle sẽ tự động đặt baseline-prof.txt đã tạo vào ứng dụng mục tiêu (mô-đun :app) trong thư mục src/release/generated/baselineProfile/.

fa0f52de5d2ce5e8.png

(Không bắt buộc) Chạy trình tạo từ dòng lệnh

Ngoài ra, bạn có thể chạy trình tạo qua dòng lệnh. Bạn có thể tận dụng tác vụ do Thiết bị do Gradle quản lý tạo: :app:generateBaselineProfile. Lệnh này chạy tất cả chương trình kiểm thử trong dự án được xác định bởi phần phụ thuộc baselineProfile(project(:baselineProfile)). Vì mô-đun này cũng chứa các phép đo điểm chuẩn để xác minh mức tăng hiệu suất sau này, nên các chương trình kiểm thử đó sẽ không thành công kèm theo cảnh báo về việc chạy phép đo điểm chuẩn trên trình mô phỏng.

android
   .testInstrumentationRunnerArguments
   .androidx.benchmark.enabledRules=BaselineProfile

Do vậy, bạn có thể lọc tất cả trình tạo Hồ sơ cơ sở bằng đối số trình chạy đo lường sau đây và tất cả các phép đo điểm chuẩn bị bỏ qua:

Toàn bộ lệnh sẽ có dạng như sau:

./gradlew :app:generateBaselineProfile -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile

Phân phối ứng dụng bằng Hồ sơ cơ sở

Sau khi tạo và sao chép Hồ sơ cơ sở vào mã nguồn của ứng dụng, hãy tạo bản dựng cho phiên bản chính thức của ứng dụng như bình thường. Bạn không cần làm gì thêm để phân phối Hồ sơ cơ sở cho người dùng. Các hồ sơ này sẽ được Trình bổ trợ Android cho Gradle lựa chọn trong quá trình tạo bản dựng và được đưa vào tệp AAB hoặc APK. Tiếp theo, hãy tải bản dựng lên Google Play.

Khi người dùng cài đặt hoặc cập nhật ứng dụng từ phiên bản trước, Hồ sơ cơ sở cũng được cài đặt, mang lại hiệu suất tốt hơn ngay từ lần chạy đầu tiên của ứng dụng.

Bước tiếp theo sẽ cho bạn biết cách xác minh mức cải thiện hiệu suất của ứng dụng bằng Hồ sơ cơ sở.

8. (Không bắt buộc) Tuỳ chỉnh việc tạo Hồ sơ cơ sở

Trình bổ trợ Gradle cho Hồ sơ cơ sở có các lựa chọn để tuỳ chỉnh cách tạo hồ sơ nhằm đáp ứng nhu cầu cụ thể của bạn. Bạn có thể thay đổi hành vi này bằng khối cấu hình baselineProfile { } trong tập lệnh bản dựng.

Khối cấu hình trong mô-đun :baselineprofile sẽ ảnh hưởng đến cách chạy trình tạo với khả năng thêm managedDevices, đồng thời quyết định xem nên dùng useConnectedDevices hay thiết bị do Gradle quản lý.

Khối cấu hình trong mô-đun đích :app sẽ quyết định vị trí lưu cấu hình hoặc cách tạo cấu hình. Bạn có thể thay đổi các tham số sau:

  • automaticGenerationDuringBuild: nếu được bật, bạn có thể tạo Hồ sơ cơ sở khi tạo bản dựng bản phát hành chính thức. Điều này rất hữu ích khi tạo bản dư trên CI trước khi bạn chuyển ứng dụng sang các nền tảng khác.
  • saveInSrc: chỉ định xem Cấu hình cơ sở đã tạo có được lưu trữ trong thư mục src/ hay không. Ngoài ra, bạn có thể truy cập tệp này từ thư mục bản dựng :baselineprofile.
  • baselineProfileOutputDir: xác định nơi lưu trữ Cấu hình cơ sở đã tạo.
  • mergeIntoMain: theo mặc định, Cấu hình cơ sở sẽ được tạo theo từng biến thể bản dựng (phiên bản sản phẩm và loại bản dựng). Nếu bạn muốn hợp nhất tất cả hồ sơ vào src/main, bạn có thể thực hiện bằng cách bật cờ này.
  • filter: bạn có thể lọc các lớp hoặc phương thức để đưa vào hoặc loại trừ khỏi Cấu hình cơ sở đã tạo. Điều này có thể hữu ích cho các nhà phát triển thư viện chỉ muốn thêm mã từ thư viện.

9. Xác minh mức độ cải thiện hiệu suất khi khởi động

Sau khi bạn tạo Hồ sơ cơ sở và thêm hồ sơ đó vào ứng dụng của mình, hãy xác minh rằng hồ sơ đó có tác động đúng như mình mong muốn đối với hiệu suất của ứng dụng.

Trình hướng dẫn mô-đun mới sẽ tạo một lớp điểm chuẩn có tên là StartupBenchmarks. Lớp này có chứa một phép đo điểm chuẩn để đo lường thời gian khởi động ứng dụng và so sánh với thời điểm ứng dụng sử dụng Hồ sơ cơ sở.

Lớp này có dạng như sau:

@RunWith(AndroidJUnit4::class)
@LargeTest
class StartupBenchmarks {

    @get:Rule
    val rule = MacrobenchmarkRule()

    @Test
    fun startupCompilationNone() =
        benchmark(CompilationMode.None())

    @Test
    fun startupCompilationBaselineProfiles() =
        benchmark(CompilationMode.Partial(BaselineProfileMode.Require))

    private fun benchmark(compilationMode: CompilationMode) {
        rule.measureRepeated(
            packageName = "com.example.baselineprofiles_codelab",
            metrics = listOf(StartupTimingMetric()),
            compilationMode = compilationMode,
            startupMode = StartupMode.COLD,
            iterations = 10,
            setupBlock = {
                pressHome()
            },
            measureBlock = {
                startActivityAndWait()

                // TODO Add interactions to wait for when your app is fully drawn.
                // The app is fully drawn when Activity.reportFullyDrawn is called.
                // For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and ReportDrawnAfter
                // from the AndroidX Activity library.

                // Check the UiAutomator documentation for more information on how to
                // interact with the app.
                // https://d.android.com/training/testing/other-components/ui-automator
            }
        )
    }
}

Công cụ này sử dụng MacrobenchmarkRule có khả năng chạy các phép đo điểm chuẩn cho ứng dụng của bạn và thu thập các chỉ số hiệu suất. Điểm truy cập để viết phép đo điểm chuẩn là hàm measureRepeated qua quy tắc.

Hàm này yêu cầu một vài tham số:

  • packageName:: ứng dụng nào cần đo lường.
  • metrics: loại thông tin bạn muốn đo lường trong lúc đo điểm chuẩn.
  • iterations: số lần phép đo điểm chuẩn lặp lại.
  • startupMode: cách bạn muốn ứng dụng bắt đầu khi bắt đầu đo điểm chuẩn.
  • setupBlock: những lượt tương tác với ứng dụng phải diễn ra trước khi đo lường.
  • measureBlock: số lượt tương tác với ứng dụng mà bạn muốn đo lường trong khi đo điểm chuẩn.

Lớp kiểm thử này cũng chứa 2 chương trình kiểm thử: startupCompilationeNone()startupCompilationBaselineProfiles() (gọi hàm benchmark() với compilationMode khác).

CompilationMode

Tham số CompilationMode xác định cách biên dịch trước ứng dụng thành mã máy. Tham số này có các tuỳ chọn sau:

  • DEFAULT: biên dịch trước một phần ứng dụng bằng Hồ sơ cơ sở nếu có (tuỳ chọn này được sử dụng nếu không áp dụng tham số compilationMode)
  • None(): đặt lại trạng thái biên dịch ứng dụng và không biên dịch trước ứng dụng. Tính năng biên dịch đúng thời điểm (JIT) vẫn được bật trong quá trình thực thi ứng dụng.
  • Partial(): biên dịch trước ứng dụng bằng Hồ sơ cơ sở hoặc khởi động, hoặc cả hai.
  • Full(): biên dịch trước toàn bộ mã ứng dụng. Đây là tuỳ chọn duy nhất trên thiết bị Android 6 (API cấp 23) trở xuống.

Nếu muốn bắt đầu tối ưu hoá hiệu suất của ứng dụng, bạn có thể chọn chế độ biên dịch DEFAULT, vì hiệu suất cũng tương tự như khi ứng dụng được cài đặt từ Google Play. Nếu muốn so sánh các lợi ích về hiệu suất mà Hồ sơ cơ sở cung cấp, bạn có thể so sánh kết quả giữa chế độ biên dịch NonePartial.

Sửa đổi phép đo điểm chuẩn để chờ nội dung

Phép đo điểm chuẩn được viết tương tự như trình tạo Hồ sơ cơ sở bằng cách viết các hoạt động tương tác với ứng dụng. Theo mặc định, các phép đo điểm chuẩn được tạo chỉ đợi khung hình đầu tiên hiển thị (tương tự như cách BaselineProfileGenerator đã làm), vì vậy bạn nên cải thiện phép đo điểm chuẩn để chờ nội dung không đồng bộ.

Bạn có thể thực hiện việc này bằng cách sử dụng lại các hàm mở rộng mà bạn đã viết cho trình tạo. Vì phép đo điểm chuẩn này nắm bắt thời gian khởi động (bằng cách sử dụng StartupTimingMetric()), bạn chỉ nên chờ cho nội dung không đồng bộ ở đây rồi sau đó viết một phép đo điểm chuẩn riêng cho hành trình của người dùng khác được xác định trong trình tạo.

// ...
measureBlock = {
   startActivityAndWait()

   // The app is fully drawn when Activity.reportFullyDrawn is called.
   // For Jetpack Compose, you can use ReportDrawn, ReportDrawnWhen and ReportDrawnAfter
   // from the AndroidX Activity library.
   waitForAsyncContent() // <------- Added to wait for async content.

   // Check the UiAutomator documentation for more information on how to
   // interact with the app.
   // https://d.android.com/training/testing/other-components/ui-automator
}

Chạy phép đo điểm chuẩn

Bạn có thể chạy phép đo điểm chuẩn giống như cách chạy kiểm thử đo lường. Bạn có thể chạy hàm kiểm thử, hoặc toàn bộ lớp bằng biểu tượng tương ứng với lớp đó ở cạnh bên.

587b04d1a76d1e9d.png

Hãy đảm bảo bạn đã chọn thiết bị thực, vì việc chạy phép đo điểm chuẩn trên trình mô phỏng Android sẽ không thành công trong thời gian chạy nếu có cảnh báo về việc phép đo điểm chuẩn có thể cho kết quả không chính xác. Mặc dù về mặt kỹ thuật, bạn có thể chạy trên trình mô phỏng, nhưng bạn đang đo lường hiệu suất của máy chủ lưu trữ. Nếu quá tải, phép đo điểm chuẩn của bạn sẽ hoạt động chậm hơn và ngược lại.

94e0da86b6f399d5.png

Sau khi chạy phép đo phép đo điểm chuẩn, ứng dụng sẽ được tạo lại, sau đó sẽ chạy phép đo điểm chuẩn. Phép đo điểm chuẩn bắt đầu, dừng và thậm chí cài đặt lại ứng dụng của bạn nhiều lần dựa trên iterations mà bạn xác định.

Sau khi hoàn tất việc đo điểm chuẩn, bạn có thể thấy dấu thời gian trong kết quả của Android Studio như ở ảnh chụp màn hình dưới đây:

282f90d5f6ff5196.png

Trên ảnh chụp màn hình, bạn có thể thấy thời gian khởi động ứng dụng của mỗi CompilationMode là khác nhau. Giá trị trung bình như minh hoạ trong bảng sau đây:

timeToInitialDisplay [ms]

timeToFullDisplay [ms]

Không có

202.2

818.8

Hồ sơ cơ sở

193.7

637.9

Cải thiện

4%

28%

Sự khác biệt giữa các chế độ biên dịch cho timeToFullDisplay180 mili giây, cải thiện được khoảng 28% chỉ bằng cách dùng Hồ sơ cơ sở. CompilationNone hoạt động kém hiệu quả hơn vì thiết bị phải biên dịch JIT nhiều nhất trong quá trình khởi động ứng dụng. CompilationBaselineProfiles hoạt động hiệu quả hơn vì chức năng biên dịch một phần với Hồ sơ cơ sở AOT sẽ biên dịch mã mà người dùng có nhiều khả năng sử dụng nhất, và mã không quan trọng sẽ không được biên dịch trước nên không cần phải tải ngay.

10. (Không bắt buộc) Xác minh mức độ cải thiện hiệu suất của tính năng cuộn

Tương tự như bước trước, bạn có thể đo lường và xác minh hiệu suất cuộn. Trước tiên, hãy tạo một lớp kiểm thử ScrollBenchmarks có quy tắc đo điểm chuẩn và hai phương thức kiểm thử sử dụng nhiều chế độ biên dịch:

@LargeTest
@RunWith(AndroidJUnit4::class)
class ScrollBenchmarks {

   @get:Rule
   val rule = MacrobenchmarkRule()

   @Test
   fun scrollCompilationNone() = scroll(CompilationMode.None())

   @Test
   fun scrollCompilationBaselineProfiles() = scroll(CompilationMode.Partial())

   private fun scroll(compilationMode: CompilationMode) {
       // TODO implement
   }
}

Bên trong phương thức scroll, hãy sử dụng hàm measureRepeated với các tham số bắt buộc. Đối với tham số metrics, hãy sử dụng FrameTimingMetric để đo lường thời gian cần thiết để tạo khung giao diện người dùng:

private fun scroll(compilationMode: CompilationMode) {
   rule.measureRepeated(
       packageName = "com.example.baselineprofiles_codelab",
       metrics = listOf(FrameTimingMetric()),
       compilationMode = compilationMode,
       startupMode = StartupMode.WARM,
       iterations = 10,
       setupBlock = {
           // TODO implement
       },
       measureBlock = {
           // TODO implement
       }
   )
}

Lần này, bạn cần phân tách các lượt tương tác giữa setupBlockmeasureBlock nhiều hơn để chỉ đo thời lượng khung hình trong bố cục đầu tiên và cuộn nội dung. Do đó, hãy đặt các hàm khởi động màn hình mặc định vào setupBlock, cũng như các hàm mở rộng đã tạo waitForAsyncContent()scrollSnackListJourney() vào measureBlock:

private fun scroll(compilationMode: CompilationMode) {
   rule.measureRepeated(
       packageName = "com.example.baselineprofiles_codelab",
       metrics = listOf(FrameTimingMetric()),
       compilationMode = compilationMode,
       startupMode = StartupMode.WARM,
       iterations = 10,
       setupBlock = {
           pressHome()
           startActivityAndWait()
       },
       measureBlock = {
           waitForAsyncContent()
           scrollSnackListJourney()
       }
   )
}

Khi điểm chuẩn đã sẵn sàng, bạn có thể chạy như trước để có kết quả như trong ảnh chụp màn hình sau:

84aa99247226fc3a.png

FrameTimingMetric sẽ xuất ra thời lượng khung hình tính bằng mili giây (frameDurationCpuMs) ở phân vị thứ 50, 90, 95 và 99. Trên Android 12 (API cấp 31) trở lên, thiết bị này cũng sẽ trả về thời lượng khung hình vượt quá giới hạn (frameOverrunMs). Giá trị này có thể là số âm, nghĩa là bạn vẫn còn thời gian để tạo khung hình.

Từ kết quả, bạn có thể thấy CompilationBaselineProfiles có thời lượng khung hình trung bình ngắn hơn 2 mili giây. Người dùng có thể không nhận thấy điều này. Tuy nhiên, đối với các phân vị khác, kết quả sẽ rõ ràng hơn. Đối với phân vị thứ 99, mức chênh lệch là 43,5 mili giây, tức là có hơn 3 khung hình bị bỏ qua trên thiết bị hoạt động ở tốc độ 90 khung hình/giây. Ví dụ: đối với Pixel 6, thời gian kết xuất khung hình là 1000 mili giây/90 khung hình/giây = ~11 mili giây.

11. Xin chúc mừng

Xin chúc mừng, bạn đã hoàn thành lớp học lập trình này và cải thiện hiệu suất của ứng dụng bằng Hồ sơ cơ sở!

Tài nguyên khác

Hãy xem các tài nguyên khác sau đây:

Tài liệu tham khảo