Phân tích và tối ưu hoá quá trình khởi động ứng dụng

Quá trình khởi động ứng dụng là quá trình mà ứng dụng của bạn sẽ tạo ấn tượng đầu tiên với người dùng. Quá trình khởi động ứng dụng phải nhanh chóng tải và hiện thông tin mà người dùng cần để dùng ứng dụng. Nếu quá trình này mất quá nhiều thời gian, thì người dùng có thể thoát khỏi ứng dụng vì họ chờ quá lâu.

Bạn nên dùng thư viện Macrobenchmark để đo lường quá trình khởi động. Thư viện này cung cấp thông tin tổng quan và dấu vết hệ thống chi tiết để biết chính xác những gì đang diễn ra trong quá trình khởi động.

Dấu vết hệ thống cung cấp thông tin hữu ích về những gì đang diễn ra trên thiết bị của bạn. Nhờ vậy, bạn có thể biết được hoạt động của ứng dụng trong quá trình khởi động và xác định các khu vực có thể tối ưu hoá được.

Để phân tích quá trình khởi động ứng dụng của bạn, hãy làm như sau:

Các bước phân tích và tối ưu hoá quá trình khởi động

Trong quá trình khởi động, các ứng dụng thường phải tải một số tài nguyên nhất định mà người dùng cuối rất cần. Các tài nguyên không cần thiết có thể sẽ tải sau khi quá trình khởi động hoàn tất.

Để đưa ra phương án đánh đổi về hiệu suất, hãy cân nhắc những điều sau:

  • Dùng thư viện Macrobenchmark để tính thời gian của mỗi thao tác và xác định các khối mã mất nhiều thời gian để hoàn thành.

  • Xác nhận rằng thao tác cần nhiều tài nguyên đóng vai trò quan trọng trong việc khởi động ứng dụng. Nếu có thể thực hiện thao tác sau khi ứng dụng hiện đầy đủ, thì thao tác này có thể giúp giảm thiểu các hạn chế về tài nguyên khi khởi động.

  • Đảm bảo rằng bạn muốn thực hiện thao tác này khi khởi động ứng dụng. Thông thường, các thao tác không bắt buộc có thể được gọi từ đoạn mã cũ hoặc thư viện của bên thứ ba.

  • Chuyển các thao tác diễn ra trong thời gian dài sang chế độ nền, nếu có thể. Các quy trình trong nền vẫn có thể ảnh hưởng đến mức sử dụng CPU trong quá trình khởi động.

Sau khi tìm hiểu đầy đủ thao tác, bạn có thể đưa ra quyết định về việc đánh đổi giữa thời gian tải và sự cần thiết của việc đưa thao tác đó vào quá trình khởi động ứng dụng. Hãy nhớ cân nhắc đến khả năng hồi quy hoặc các thay đổi có thể gây lỗi khi chỉnh sửa quy trình làm việc của ứng dụng.

Tối ưu hoá và đo lường lại cho đến khi bạn hài lòng với thời gian khởi động của ứng dụng. Để biết thêm thông tin, hãy xem phần Sử dụng các chỉ số để phát hiện và chẩn đoán vấn đề.

Đo lường và phân tích thời gian thực hiện các thao tác chính

Sau khi bạn có dấu vết khởi động ứng dụng hoàn chỉnh, hãy xem dấu vết đó và đo lường thời gian dành cho các thao tác chính như bindApplication hoặc activityStart. Bạn nên dùng Perfetto hoặc Trình phân tích tài nguyên trong Android Studio để phân tích các dấu vết này.

Hãy xem tổng thời gian dùng để khởi động ứng dụng nhằm xác định các thao tác có những hành vi sau:

  • Chiếm các khung thời gian lớn và có thể tối ưu hoá. Mỗi mili giây đều tính vào hiệu suất. Ví dụ: tìm thời gian hiện Choreographer, thời gian tăng cường bố cục, thời gian tải thư viện, giao dịch Binder hoặc thời gian tải tài nguyên. Đối với một quá trình khởi động chung, hãy xem tất cả các thao tác mất hơn 20 mili giây.
  • Chặn luồng chính. Để biết thêm thông tin, hãy xem bài viết Sử dụng một báo cáo Systrace.
  • Không cần chạy trong quá trình khởi động.
  • Có thể chờ cho đến khi hiện khung hình đầu tiên của bạn.

Hãy tìm hiểu thêm về từng dấu vết này để biết chênh lệch về hiệu suất.

Xác định các thao tác tiêu tốn nhiều tài nguyên trên luồng chính

Phương pháp hay nhất là đưa các thao tác tiêu tốn nhiều tài nguyên như thao tác I/O đối với tệp và thao tác truy cập mạng ra khỏi luồng chính. Điều này cũng quan trọng không kém trong quá trình khởi động ứng dụng vì các thao tác tiêu tốn nhiều tài nguyên trên luồng chính có thể khiến ứng dụng không phản hồi và trì hoãn các thao tác quan trọng khác. StrictMode.ThreadPolicy có thể giúp xác định các trường hợp trong đó thao tác tiêu tốn nhiều tài nguyên diễn ra trên luồng chính. Bạn nên bật StrictMode trên các bản gỡ lỗi để xác định vấn đề càng sớm càng tốt, như trong ví dụ sau đây:

Kotlin

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        ...
        if (BuildConfig.DEBUG)
            StrictMode.setThreadPolicy(
                StrictMode.ThreadPolicy.Builder()
                    .detectAll()
                    .penaltyDeath()
                    .build()
            )
        ...
    }
}

Java

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        ...
        if(BuildConfig.DEBUG) {
            StrictMode.setThreadPolicy(
                    new StrictMode.ThreadPolicy.Builder()
                            .detectAll()
                            .penaltyDeath()
                            .build()
            );
        }
        ...
    }
}

Việc sử dụng StrictMode.ThreadPolicy sẽ bật chính sách luồng trên tất cả các bản gỡ lỗi và khiến ứng dụng gặp sự cố mỗi khi phát hiện thấy các lỗi vi phạm chính sách về luồng, điều này khiến khó bỏ qua các lỗi vi phạm chính sách về luồng.

TTID và TTFD

Nếu bạn muốn biết thời gian để ứng dụng tạo khung hình đầu tiên, hãy đo lường thời gian hiển thị khung hình đầu tiên (TTID). Tuy nhiên, chỉ số này không nhất thiết thể hiện thời gian người dùng phải đợi trước khi có thể bắt đầu tương tác với ứng dụng. Chỉ số thời gian hiển thị đầy đủ (TTFD) hữu ích hơn trong việc đo lường và tối ưu hoá các đường dẫn mã cần thiết để ứng dụng chuyển sang trạng thái hoàn toàn có thể sử dụng được.

Để biết các chiến lược báo cáo khi giao diện người dùng ứng dụng hiện đầy đủ, hãy xem phần Cải thiện độ chính xác của thời gian khởi động.

Tối ưu hoá cho cả TTID và TTFD, vì cả hai chỉ số này đều quan trọng theo cách riêng của chúng. Chỉ số TTID thấp cho người dùng thấy ứng dụng đang thực sự chạy. Việc giữ cho TTFD thấp là rất quan trọng để đảm bảo rằng người dùng có thể bắt đầu tương tác ngay với ứng dụng.

Phân tích trạng thái luồng tổng thể

Chọn thời gian khởi động ứng dụng và xem các lát cắt của luồng tổng thể. Luồng chính phải luôn phản hồi.

Các công cụ như Trình phân tích tài nguyên trong Android StudioPerfetto cung cấp thông tin tổng quan chi tiết về luồng chính cũng như thời lượng dành cho mỗi giai đoạn. Để biết thêm thông tin về cách trực quan hoá dấu vết perfetto, hãy xem tài liệu về Giao diện người dùng Perfetto.

Xác định phần chính trong trạng thái ngủ của luồng chính

Nếu bạn dành nhiều thời gian cho trạng thái ngủ, thì đó có thể là do luồng chính của ứng dụng đang chờ công việc hoàn tất. Nếu bạn có một ứng dụng đa luồng, hãy xác định luồng mà luồng chính đang chờ xử lý và cân nhắc việc tối ưu hoá các thao tác đó. Đây cũng có thể là cách hữu ích để đảm bảo không có tranh chấp khoá (lock contention) không cần thiết gây ra sự chậm trễ trong đường dẫn quan trọng của bạn.

Giảm tình trạng chặn luồng chính và trạng thái ngủ không gián đoạn

Tìm kiếm mọi phiên bản của luồng chính chuyển sang trạng thái bị chặn. Perfetto và Trình phân tích tài nguyên trong Studio hiển thị điều này với một chỉ báo màu cam trên tiến trình trạng thái chuỗi. Xác định các hoạt động, khám phá xem những thao tác này có được dự kiến hoặc có thể tránh được hay không và tối ưu hoá khi cần.

Trạng thái ngủ có thể gián đoạn liên quan đến IO có thể là cơ hội tốt để cải thiện. Các quy trình khác thực hiện IO, ngay cả khi chúng là ứng dụng không liên quan, vẫn có thể cạnh tranh với IO mà ứng dụng hàng đầu đang thực hiện.

Cải thiện thời gian khởi động

Sau khi xác định cơ hội tối ưu hoá, hãy tìm hiểu một số giải pháp có thể giúp cải thiện thời gian khởi động:

  • Tải nội dung từng phần và không đồng bộ để tăng tốc TTID.
  • Hạn chế tối đa việc gọi các hàm tạo các lệnh gọi liên kết. Nếu buộc phải thực hiện những lệnh gọi này, hãy đảm bảo rằng bạn đang tối ưu hoá các lệnh gọi đó bằng cách lưu các giá trị vào bộ nhớ đệm thay vì lặp lại các lệnh gọi hoặc chuyển công việc không chặn sang luồng trong nền.
  • Để quá trình khởi động ứng dụng có vẻ nhanh hơn, bạn có thể nhanh chóng hiện cho người dùng những nội dung cần ít kết xuất hình ảnh nhất trong lúc đợi đến khi phần còn lại của màn hình tải xong.
  • Tạo và thêm hồ sơ khởi động vào ứng dụng của bạn.
  • Sử dụng thư viện Khởi động ứng dụng Jetpack để đơn giản hoá quá trình khởi chạy các thành phần trong quá trình khởi động ứng dụng.

Phân tích hiệu suất của giao diện người dùng

Quá trình khởi động ứng dụng bao gồm màn hình chờ và thời gian tải trang chủ của bạn. Để tối ưu hoá quá trình khởi động ứng dụng, hãy kiểm tra các dấu vết để biết được thời gian cần thiết cho việc hiện giao diện người dùng.

Giới hạn công việc khi khởi chạy

Một số khung hình có thể mất nhiều thời gian tải hơn so với các khung hình khác. Đây được coi là những bản vẽ tốn tài nguyên của ứng dụng.

Để tối ưu hoá quy trình khởi chạy, hãy làm như sau:

  • Ưu tiên chuyển bố cục chậm và chọn các bản vẽ này để cải thiện.
  • Kiểm tra từng cảnh báo từ Perfetto và cảnh báo từ Systrace bằng cách thêm các sự kiện theo dõi tuỳ chỉnh để giảm số bản vẽ và độ trễ tốn tài nguyên.

Đo lường dữ liệu khung

Có nhiều cách để đo lường dữ liệu khung. 5 phương thức thu thập chính là:

  • Thu thập cục bộ bằng dumpsys gfxinfo: Không phải khung hình nào bạn thấy trong dữ liệu dumpsys đều chịu trách nhiệm về việc ứng dụng kết xuất chậm hoặc có tác động đến người dùng cuối. Tuy nhiên, đây là biện pháp đo lường thích hợp để xem xét nhiều chu kỳ phát hành giúp nắm được xu hướng chung về hiệu suất. Nếu bạn muốn tìm hiểu thêm về cách sử dụng gfxinfoframestats để tích hợp hoạt động đo lường hiệu suất giao diện người dùng vào các phương pháp kiểm thử, hãy xem Nguyên tắc cơ bản về việc kiểm thử ứng dụng Android.
  • Thu thập trường bằng JankStats: Thu thập thời gian kết xuất khung hình từ các phần cụ thể trong ứng dụng bằng thư viện JankStats, đồng thời ghi lại và phân tích dữ liệu.
  • Trong các phép kiểm thử sử dụng Macrobenchmark (Perfetto chạy ngầm)
  • Perfetto Khung dòng thời gian: Trên Android 12 (API cấp 31), bạn có thể thu thập Dòng thời gian Khung các chỉ số từ dấu vết Perfetto chỉ đến tác vụ gây hiện tượng rớt khung hình. Đây có thể là bước đầu tiên để chẩn đoán lý do dẫn đến hiện tượng rớt khung hình.
  • Trình phân tích tài nguyên trong Android Studio giúp phát hiện tình trạng giật

Kiểm tra thời gian tải hoạt động chính

Hoạt động chính của ứng dụng có thể chứa một lượng lớn thông tin được tải từ nhiều nguồn. Kiểm tra bố cục Activity của trang chủ và xem cụ thể phương thức Choreographer.onDraw của hoạt động trên trang chủ.

  • Dùng reportFullyDrawn để báo cáo cho hệ thống rằng ứng dụng của bạn giờ đã hiện đầy đủ cho mục đích tối ưu hoá.
  • Đo lường hoạt động và các đợt khởi chạy ứng dụng bằng StartupTimingMetric với thư viện Macrobenchmark.
  • Xem số lần giảm khung hình.
  • Xác định các bố cục mất nhiều thời gian để hiển thị hoặc đo lường.
  • Xác định những nội dung mất nhiều thời gian tải.
  • Xác định những bố cục không cần thiết sẽ được tăng cường trong quá trình khởi động.

Hãy cân nhắc các giải pháp khả thi sau để tối ưu hoá thời gian tải hoạt động chính:

  • Tạo bố cục ban đầu càng cơ bản càng tốt. Để biết thêm thông tin, hãy xem phần Tối ưu hoá hệ phân cấp bố cục.
  • Thêm điểm theo dõi tuỳ chỉnh để cung cấp thêm thông tin về các khung hình bị rớt và bố cục phức tạp.
  • Giảm thiểu số lượng và kích thước của tài nguyên bitmap được tải trong quá trình khởi động.
  • Sử dụng ViewStub trong đó bố cục không VISIBLE ngay lập tức. ViewStub là một Khung hiển thị vô hình, có kích thước bằng 0, có thể dùng để tăng cường từng phần tài nguyên bố cục trong thời gian chạy. Để biết thêm thông tin, hãy xem ViewStub.

    Nếu đang dùng Jetpack Compose, bạn có thể nhận được hành vi tương tự đối với ViewStub khi sử dụng trạng thái để trì hoãn việc tải một số thành phần:

    var shouldLoad by remember {mutableStateOf(false)}
    
    if (shouldLoad) {
     MyComposable()
    }
    

    Tải các thành phần kết hợp bên trong khối có điều kiện bằng cách sửa đổi shouldLoad:

    LaunchedEffect(Unit) {
     shouldLoad = true
    }
    

    Lệnh này kích hoạt quá trình kết hợp lại, bao gồm mã bên trong khối có điều kiện ở đoạn mã đầu tiên.