Thư viện JankStats

Stay organized with collections Save and categorize content based on your preferences.

Thư viện JankStats giúp bạn theo dõi và phân tích các vấn đề về hiệu suất của ứng dụng. Hiện tượng giật (jank) đề cập đến tình huống khi các khung hình mất nhiều thời gian để hiển thị. Còn thư viện JankStats được dùng để cung cấp các báo cáo thống kê về hiện tượng giật của ứng dụng.

Năng lực

JankStats xây dựng dựa trên các năng lực của nền tảng Android hiện có, bao gồmAPI FrameMetrics trên Android 7 (API cấp 24) trở lên hoặc OnPreDrawListener trên các phiên bản trước. Các cơ chế này có thể giúp ứng dụng theo dõi thời lượng hoàn tất một khung hình. Thư viện JanksStats cung cấp hai năng lực bổ sung, giúp tăng cường độ linh động và tính dễ sử dụng: suy đoán hiện tượng giật (jank heuristics) và trạng thái giao diện người dùng.

Suy đoán hiện tượng giật

Mặc dù FrameMetrics có thể dùng để theo dõi thời lượng khung hình, nhưng FrameMetrics không hỗ trợ việc xác định hiện tượng giật trên thực tế. Tuy nhiên, JankStats có các cơ chế nội bộ có khả năng định cấu hình để xác định thời điểm xảy ra hiện tượng giật, giúp báo cáo thông tin hữu ích tức thì.

Trạng thái giao diện người dùng

Thông thường, bạn cần phải biết ngữ cảnh xảy ra các vấn đề về hiệu suất trong ứng dụng. Ví dụ: nếu phát triển một ứng dụng có nhiều màn hình phức tạp, sử dụng FrameMetrics và bạn phát hiện khung hình của ứng dụng thường xuyên bị giật. Trong tình huống đó, bạn sẽ muốn biết đầy đủ thông tin về bối cảnh xảy ra sự cố chẳng hạn như vị trí xảy ra sự cố, người dùng lúc đó đang làm gì và làm cách nào để lặp lại sự cố này.

JankStats giúp giải quyết vấn đề này bằng cách giới thiệu API state, cho phép bạn giao tiếp với thư viện này để cung cấp thông tin về Hoạt động trong ứng dụng. Khi StartkStats ghi lại thông tin về một khung hình có hiện tượng giật, thông tin này sẽ bao gồm trạng thái hiện tại của ứng dụng trong báo cáo về hiện tượng giật.

Mức sử dụng

Để bắt đầu sử dụng thư viện JankStats, hãy tạo thực thể và bật thư viện này cho mỗi Window. Mỗi đối tượng JankStats chỉ theo dõi dữ liệu trong một Window. Để tạo bản sao của thư viện này, bạn cần có một bản sao Window cùng với trình nghe OnFrameListener – cả hai đều dùng để gửi các chỉ số đến máy khách. Trình nghe này được gọi cùng với FrameData trên mỗi khung và trình bày chi tiết các thông tin sau:

  • Thời gian bắt đầu khung hình
  • Giá trị thời lượng
  • Liệu có nên xem khung hình đó có hiện tượng giật không
  • Tập hợp các cặp chuỗi chứa thông tin về trạng thái ứng dụng trong khung hình

Để JankStats cung cấp nhiều thông tin hữu ích hơn, các ứng dụng nên đưa thông tin liên quan đến trạng thái giao diện người dùng vào thư viện để báo cáo trong FrameData. Bạn có thể thực hiện việc này thông qua API PerformanceMetricsState (không sử dụng trực tiếp JankStats), trong đó tất cả API và logic quản lý trạng thái đều hoạt động.

Khởi chạy

Để bắt đầu sử dụng thư viện JankStats, trước tiên, bạn hãy thêm phần phụ thuộc JankStats vào tệp Gradle:

implementation "androidx.metrics:metrics-performance:1.0.0-alpha03"

Tiếp theo, hãy khởi chạy và bật JankStats cho mỗi Window. Bạn cũng nên tạm dừng theo dõi JankStats khi một Hoạt động (Activity) được thực hiện trong nền. Tạo và bật đối tượng JankStats trong phần ghi đè Hoạt động:

class JankLoggingActivity : AppCompatActivity() {

    private lateinit var jankStats: JankStats

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...
        // metrics state holder can be retrieved regardless of JankStats initialization
        val metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(binding.root)

        // initialize JankStats for current window
        jankStats = JankStats.createAndTrack(window, jankFrameListener)

        // add activity name as state
        metricsStateHolder.state?.putState("Activity", javaClass.simpleName)
        // ...
    }

Ví dụ ở trên sẽ chèn thông tin trạng thái về Hoạt động hiện tại sau khi tạo đối tượng JankStats. Bây giờ, tất cả báo cáo FrameData được tạo trong tương lai cho đối tượng JankStats này đều chứa thông tin về Hoạt động.

Phương thức JankStats.createAndTrack tham chiếu đến một đối tượng Window là proxy cho Hệ phân cấp chế độ xem bên trong Window cũng như với chính Window đó. jankFrameListener được gọi trên cùng một luồng dùng để phân phối thông tin đó từ nền tảng đến JankStats nội bộ.

Để bật tính năng theo dõi và báo cáo về mọi đối tượng JankStats, hãy thiết lập isTrackingEnabled = true. Mặc dù tính năng này được bật theo mặc định, nhưng khi tạm dừng một hoạt động thì tính năng theo dõi sẽ bị tắt. Trong trường hợp này, hãy nhớ bật lại tính năng theo dõi trước khi tiếp tục. Để ngừng theo dõi, hãy thiết lập isTrackingEnabled = false.

override fun onResume() {
    super.onResume()
    jankStats.isTrackingEnabled = true
}

override fun onPause() {
    super.onPause()
    jankStats.isTrackingEnabled = false
}

Báo cáo

Thư viện JankStats báo cáo tất cả hoạt động theo dõi dữ liệu cho mọi khung hình đến OnFrameListener cho các đối tượng được bật JankStats. Các ứng dụng có thể lưu trữ và tổng hợp dữ liệu này để tải lên sau này. Để biết thêm thông tin, hãy tham khảo các ví dụ được cung cấp trong mục Tổng hợp.

Bạn sẽ cần tạo và cung cấp OnFrameListener cho ứng dụng của mình để nhận được báo cáo theo khung. Trình xử lý được gọi trên mọi khung hình để cung cấp dữ liệu giật liên tục cho các ứng dụng.

private val jankFrameListener = JankStats.OnFrameListener { frameData ->
    // A real app could do something more interesting, like writing the info to local storage and later on report it.
    Log.v("JankStatsSample", frameData.toString())
}

Trình xử lý cung cấp thông tin theo khung hình về hiện tượng giật với đối tượng FrameData. Tệp này chứa các thông tin sau về khung hình được yêu cầu:

  • isjank: Một cờ boolean cho biết liệu hiện tượng giật có xảy ra trong khung hình hay không.
  • frameDurationUiNanos: Thời lượng khung hình (tính bằng nano giây).
  • frameStartNanos: Thời gian bắt đầu khung hình (tính bằng nano giây).
  • states: Trạng thái của ứng dụng trong khung.

Nếu đang sử dụng Android 12 (API cấp 31) trở lên, bạn có thể sử dụng những mục sau để hiển thị thêm dữ liệu về thời lượng khung hình:

Sử dụng StateInfo trong trình xử lý để lưu trữ thông tin về trạng thái của ứng dụng.

Lưu ý là OnFrameListener được gọi trên cùng một luồng dùng trong nội bộ để phân phối thông tin trên mỗi khung hình cho JankStats. Trên Android phiên bản 6 (API cấp 23) trở xuống, đó là luồng Chính (Giao diện người dùng). Trên Android phiên bản 7 (API cấp 24) trở lên, đây là luồng do FrameMetrics tạo và sử dụng. Trong cả hai trường hợp, quan trọng là bạn phải xử lý lệnh gọi lại và nhanh chóng trả về để ngăn các vấn đề về hiệu suất trên luồng đó.

Ngoài ra, vui lòng lưu ý đối tượng FrameData được gửi trong lệnh gọi lại sẽ được tái sử dụng trên mọi khung hình để ngăn việc phân bổ các đối tượng mới cho hoạt động báo cáo dữ liệu. Do vậy, bạn phải sao chép và lưu dữ liệu nêu trên vào bộ nhớ đệm ở nơi khác, vì đối tượng đó phải được coi là có trạng thái và lỗi thời ngay khi lệnh gọi lại trở về.

Tổng hợp

Có thể bạn cần sử dụng mã ứng dụng để tổng hợp dữ liệu trên mỗi khung hình, cho phép lưu và tải thông tin lên theo quyết định riêng của mình. Mặc dù thông tin chi tiết về việc lưu và tải thông tin lên nằm ngoài phạm vi bản phát hành alpha của API JankStats, bạn có thể xem Hoạt động sơ bộ để tổng hợp dữ liệu trên mỗi khung hình thành một tập dữ liệu lớn hơn thông qua JankAggregatorActivity có sẵn trong Kho lưu trữ GitHub.

JankAggregatorActivity sử dụng lớp JankStatsAggregator để phân lớp cơ chế báo cáo của riêng mình ở trên cơ chế JankStats OnFrameListener. Điều này cho phép cung cấp bản tóm tắt ở mức tổng quan, chỉ báo cáo những thông tin trải rộng trên nhiều khung hình.

Thay vì tạo trực tiếp đối tượng JankStats, JankAggregatorActivity sẽ tạo đối tượng JankStatsaggregate. Đối tượng này sau đó sẽ được dùng để tạo đối tượng JankStats nội bộ riêng:

class JankAggregatorActivity : AppCompatActivity() {

    private lateinit var jankStatsAggregator: JankStatsAggregator

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...
        // Metrics state holder can be retrieved regardless of JankStats initialization.
        val metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(binding.root)

        // Initialize JankStats with an aggregator for the current window.
        jankStatsAggregator = JankStatsAggregator(window, jankReportListener)

        // Add the Activity name as state.
        metricsStateHolder.state?.putState("Activity", javaClass.simpleName)
    }

JankAggregatorActivity sử dụng cơ chế tương tự để tạm dừng và tiếp tục theo dõi, đồng thời thêm sự kiện pause() làm tín hiệu để xuất báo cáo thông qua lệnh gọi đến issueJankReport(), vì các thay đổi trong vòng đời thường là thời điểm thích hợp để nắm bắt thông tin về trạng thái giật của ứng dụng:

override fun onResume() {
    super.onResume()
    jankStatsAggregator.jankStats.isTrackingEnabled = true
}

override fun onPause() {
    super.onPause()
    // Before disabling tracking, issue the report with (optionally) specified reason.
    jankStatsAggregator.issueJankReport("Activity paused")
    jankStatsAggregator.jankStats.isTrackingEnabled = false
}

Mã ví dụ ở trên là những gì ứng dụng cần để bật JankStats và nhận dữ liệu từ khung hình.

Quản lý trạng thái

Có thể bạn muốn gọi các API khác để tùy chỉnh JankStats. Chẳng hạn, tính năng chèn thông tin trạng thái ứng dụng giúp dữ liệu khung hình trở nên hữu ích hơn nhờ cung cấp ngữ cảnh cho những khung hình có hiện tượng giật.

Phương thức tĩnh này truy xuất đối tượng MetricsStateHolder hiện tại cho hệ phân cấp chế độ xem hiện có.

PerformanceMetricsState.getHolderForHierarchy(view: View): MetricsStateHolder

Bạn có thể sử dụng bất cứ thành phần hiển thị nào trong hệ thống phân cấp đang hoạt động. Về phía nội bộ, thành phần hiển thị này sẽ kiểm tra xem có tồn tại đối tượng Holder nào được liên kết với hệ phân cấp thành phần hiển thị đó hay không. Thông tin này được lưu vào bộ nhớ đệm trong một thành phần hiển thị ở trên đỉnh của hệ phân cấp đó. Nếu không tìm thấy đối tượng nào như vậy, getHolderForHierarchy() sẽ tạo đối tượng đó.

Phương thức getHolderForHierarchy() tĩnh cho phép bạn tránh phải lưu bản sao lưu giữ vào bộ nhớ đệm ở nơi nào đó cho mục đích truy xuất sau này, đồng thời tạo điều kiện dễ dàng hơn trong việc truy xuất đối tượng trạng thái hiện có từ vị trí bất kỳ trong mã (hoặc thậm chí cả mã thư viện, nếu không thì sẽ không có quyền truy cập vào bản sao gốc).

Lưu ý rằng giá trị trả về là một đối tượng lưu giữ trạng thái (holder object), không phải là đối tượng trạng thái. Chỉ JankStats mới có thể thiết lập giá trị của đối tượng trạng thái bên trong đối tượng lưu giữ này. Nghĩa là nếu ứng dụng tạo một đối tượng JankStats cho cửa sổ chứa hệ phân cấp chế độ xem đó, thì đối tượng trạng thái sẽ được tạo và thiết lập. Ngược lại, nếu không tạo đối tượng JankStats theo dõi thông tin thì bạn sẽ không cần đối tượng trạng thái và cũng không cần phải tạo mã ứng dụng hay mã thư viện để chèn trạng thái.

Cách tiếp cận này sẽ cho phép bạn truy xuất vào đối tượng lưu giữ mà JankStats có thể đưa vào sau đó. Mã bên ngoài có thể yêu cầu đối tượng lưu giữ này bất cứ lúc nào. Các phương thức gọi có thể lưu đối tượng Holder ít quan trọng (lightweight) này vào bộ nhớ đệm, cũng như sử dụng đối tượng đó bất cứ lúc nào để thiết lập trạng thái, tuỳ thuộc vào giá trị thuộc tính state nội bộ của đối tượng. Trong mã ví dụ bên dưới, trạng thái chỉ được thiết lập khi thuộc tính trạng thái nội bộ của đối tượng lưu giữ có giá trị khác rỗng:

val metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(binding.root)
// ...
metricsStateHolder.state?.putState("Activity", javaClass.simpleName)

Để kiểm soát trạng thái giao diện người dùng/ứng dụng, ứng dụng có thể chèn (hoặc xoá) trạng thái bằng các phương thức putStateremoveState. JankStats ghi lại dấu thời gian cho các lệnh gọi này. Nếu một khung hình trùng với thời điểm bắt đầu và kết thúc trạng thái thì JankStats sẽ báo cáo thông tin đó cùng với dữ liệu thời gian của khung hình.

Với bất kỳ trạng thái nào, hãy thêm hai thông tin: key(danh mục trạng thái, chẳng hạn như “RecyclerView”) và value (thông tin về những gì đang xảy ra vào thời điểm đó, chẳng hạn như “đang cuộn").

Hãy xóa trạng thái bằng phương thức removeState() nếu trạng thái đó không còn hợp lệ. Điều này giúp loại ra những thông tin không chính xác hoặc gây hiểu lầm khi báo cáo cùng dữ liệu của khung hình.

Việc gọi putState() bằng key đã được thêm trước đó sẽ thay thế value hiện có của trạng thái đó bằng một trạng thái mới.

Phiên bản putSingleFrameState() của API trạng thái này sẽ thêm một trạng thái chỉ cho phép ghi lại thông tin một lần trên khung hình được báo cáo tiếp theo. Sau đó, hệ thống sẽ tự động xoá trạng thái đó, đảm bảo mã của bạn không vô tình chứa bất cứ trạng thái đã lỗi thời nào. Lưu ý là không có singleFrame nào tương đương với removeState(), vì JankStats tự động xoá các trạng thái khung hình đơn.

private val scrollListener = object : RecyclerView.OnScrollListener() {
    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        // check if JankStats is initialized and skip adding state if not
        val metricsState = metricsStateHolder?.state ?: return

        when (newState) {
            RecyclerView.SCROLL_STATE_DRAGGING -> {
                metricsState.putState("RecyclerView", "Dragging")
            }
            RecyclerView.SCROLL_STATE_SETTLING -> {
                metricsState.putState("RecyclerView", "Settling")
            }
            else -> {
                metricsState.removeState("RecyclerView")
            }
        }
    }
}

Lưu ý rằng khoá dùng cho các trạng thái phải đủ ý nghĩa để sau này có thể phân tích. Cụ thể, vì trạng thái có cùng key với khoá từng được thêm vào sẽ thay thế giá trị trước đó, nên bạn cần cố gắng sử dụng tên key duy nhất cho các đối tượng có thể có nhiều bản sao khác nhau trong ứng dụng hoặc thư viện của mình. Chẳng hạn, một ứng dụng có 5 RecyclerView khác nhau muốn cung cấp các khoá có thể nhận dạng từng RecyclerView thay vì chỉ sử dụng RecyclerView cho mỗi khoá và sau đó thật khó để biết được dữ liệu khung hình đề cập đến bản sao nào trong dữ liệu kết quả.

Suy đoán hiện tượng giật

Để điều chỉnh thuật toán nội bộ nhằm xác định khung hình nào có hiện tượng giật, hãy sử dụng thuộc tính jankHeuristicMultiplier.

Theo mặc định, hệ thống định nghĩa một khung hình có hiện tượng giật là khi tốc độ hiển thị khung hình gấp đôi tốc độ làm mới khung hình đó ở thời điểm hiện tại. Không phải bất cứ những gì vượt quá tốc độ làm mới đều được xem là hiện tượng giật vì thông tin về thời gian hiển thị ứng dụng không hoàn toàn rõ ràng. Do đó, bạn nên thêm một khoảng đệm cần thiết và chỉ báo cáo những sự cố gây ra các vấn đề đáng chú ý về mặt hiệu suất.

Cả hai giá trị này đều có thể thay đổi thông qua các phương thức để phù hợp hơn với tình huống của ứng dụng. Ngoài ra, trong quá trình kiểm thử, các giá trị này có thể được thay đổi để kiểm tra lúc nào hiện tượng giật xảy ra và không xảy ra nếu cần thiết.

Cách sử dụng trong Jetpack Compose

Hiện tại, bạn không cần phải thiết lập gì nhiều để sử dụng JankStats trong tính năng Soạn thư. Để giữ lại PerformanceMetricsState trên các thay đổi về cấu hình, vui lòng lưu ý:

/**
 * Retrieve MetricsStateHolder from compose and remember until the current view changes.
 */
@Composable
fun rememberMetricsStateHolder(): PerformanceMetricsState.Holder {
    val view = LocalView.current
    return remember(view) { PerformanceMetricsState.getHolderForHierarchy(view) }
}

Và để sử dụng JankStats, hãy thêm trạng thái hiện tại vào stateHolder như minh họa bên dưới:

val metricsStateHolder = rememberMetricsStateHolder()

// Reporting scrolling state from compose should be done from side effect to prevent recomposition.
LaunchedEffect(metricsStateHolder, listState) {
    snapshotFlow { listState.isScrollInProgress }.collect { isScrolling ->
        if (isScrolling) {
            metricsStateHolder.state?.putState("LazyList", "Scrolling")
        } else {
            metricsStateHolder.state?.removeState("LazyList")
        }
    }
}

Để biết toàn bộ thông tin chi tiết về cách sử dụng JankStats trong ứng dụng Jetpack Compose, vui lòng tham khảo ứng dụng mẫu hiệu suất của chúng tôi.

Gửi phản hồi

Hãy chia sẻ phản hồi và ý kiến của bạn với chúng tôi thông qua các tài nguyên sau:

Công cụ theo dõi lỗi
Báo cáo sự cố để chúng tôi có thể khắc phục.