Thời gian khởi động ứng dụng

Người dùng mong muốn ứng dụng vừa tải nhanh vừa phản hồi nhanh. Một ứng dụng có thời gian khởi động chậm không đáp ứng được kỳ vọng này và có thể khiến người dùng thất vọng. Tình trạng này có thể khiến người dùng đánh giá không tốt ứng dụng của bạn trên Cửa hàng Play, hoặc thậm chí là bỏ qua hoàn toàn ứng dụng của bạn.

Trang này cung cấp thông tin giúp tối ưu hoá thời gian khởi chạy ứng dụng, bao gồm nội dung tổng quan về các yếu tố bên trong trong quy trình khởi chạy, cách phân tích hiệu suất khởi động và một số vấn đề thường gặp về thời gian khởi động cùng các mẹo về cách giải quyết các vấn đề đó.

Hiểu rõ các trạng thái khởi động ứng dụng

Hoạt động khởi chạy ứng dụng có thể diễn ra ở một trong 3 trạng thái: khởi động nguội, khởi động ấm hoặc khởi động nóng. Mỗi trạng thái ảnh hưởng đến khoảng thời gian mà ứng dụng của bạn hiển thị cho người dùng. Khi khởi động nguội, ứng dụng của bạn sẽ khởi động lại từ đầu. Ở các trạng thái khác, hệ thống cần đưa ứng dụng đang chạy từ nền lên nền trước.

Bạn nên tối ưu hoá dựa trên giả định khởi động nguội. Việc này cũng có thể cải thiện hiệu suất của chế độ khởi động ấm và nóng.

Để tối ưu hoá ứng dụng của bạn vì mục đích khởi động nhanh, bạn nên nắm được những gì đang diễn ra ở cấp hệ thống và cấp ứng dụng, cũng như cách chúng tương tác ở mỗi trạng thái.

Hai chỉ số quan trọng để xác định thời gian khởi động ứng dụng là thời gian xuất hiện khung hình đầu tiên (TTID)thời gian xuất hiện đầy đủ (TTFD). TTID là thời gian cần thiết để hiển thị khung hình đầu tiên và TTFD là thời gian cần thiết để ứng dụng có khả năng tương tác hoàn toàn. Cả 2 chỉ số này đều quan trọng như nhau, vì TTID cho người dùng biết rằng ứng dụng đang tải và TTFD cho biết thời điểm ứng dụng thực sự có thể dùng được. Nếu một trong hai chỉ số này có giá trị quá lớn, thì người dùng có thể thoát khỏi ứng dụng của bạn trước khi ứng dụng tải hoàn toàn.

Khởi động nguội

Khởi động nguội là việc mở ứng dụng từ đầu. Điều này có nghĩa là quy trình của hệ thống sẽ chỉ tạo ra quy trình của ứng dụng cho khi quá trình khởi động nguội bắt đầu. Khởi động nguội xảy ra trong một số trường hợp, chẳng hạn như ứng dụng của bạn khởi chạy lần đầu kể từ khi thiết bị khởi động, hoặc kể từ khi hệ thống buộc dừng ứng dụng.

Hình thức khởi động này đưa ra thách thức lớn nhất về việc giảm thiểu thời gian khởi động, vì hệ thống và ứng dụng phải làm nhiều việc hơn so với các trạng thái chạy khác.

Khi bắt đầu khởi động nguội, hệ thống có 3 nhiệm vụ sau đây:

  1. Tải và khởi chạy ứng dụng.
  2. Hiển thị cửa sổ khởi động trống cho ứng dụng ngay sau khi khởi chạy.
  3. Tạo quy trình ứng dụng.

Ngay khi hệ thống tạo quy trình ứng dụng, quy trình này sẽ chịu trách nhiệm về các giai đoạn tiếp theo:

  1. Tạo đối tượng ứng dụng.
  2. Chạy luồng chính.
  3. Tạo hoạt động chính.
  4. Tăng cường khung hiển thị.
  5. Sắp xếp bố cục màn hình.
  6. Thực hiện thao tác vẽ ban đầu.

Khi quy trình ứng dụng hoàn tất lần vẽ đầu tiên, quy trình hệ thống sẽ thay đổi cửa sổ nền đang hiển thị và thay thế bằng hoạt động chính. Tại thời điểm này, người dùng có thể bắt đầu sử dụng ứng dụng.

Hình 1 cho thấy cách hệ thống và các quy trình ứng dụng chuyển đổi công việc luân phiên với nhau.

Hình 1. Hình ảnh minh hoạ các phần quan trọng của quá trình khởi chạy ứng dụng nguội.

Các vấn đề về hiệu suất có thể phát sinh trong quá trình tạo ứng dụng và tạo hoạt động.

Tạo ứng dụng

Khi ứng dụng của bạn chạy, cửa sổ khởi động trống vẫn sẽ hiển thị trên màn hình cho đến khi hệ thống hoàn tất thao tác vẽ ứng dụng lần đầu. Tại thời điểm này, quy trình hệ thống sẽ hoán đổi cửa sổ khởi động cho ứng dụng của bạn, để người dùng có thể tương tác với ứng dụng.

Nếu bạn đã ghi đè Application.onCreate() trong ứng dụng của riêng mình, thì hệ thống sẽ gọi phương thức onCreate() trên đối tượng ứng dụng của bạn. Sau đó, ứng dụng sẽ tạo luồng chính (còn gọi là luồng giao diện người dùng) và thực hiện thao tác bằng cách tạo hoạt động chính.

Từ đó, các quy trình ở cấp hệ thống và cấp ứng dụng sẽ tiếp tục theo các giai đoạn trong vòng đời của ứng dụng.

Tạo hoạt động

Sau khi ứng dụng tạo ra hoạt động, hoạt động đó sẽ thực hiện các hoạt động sau:

  1. Khởi chạy các giá trị.
  2. Gọi hàm khởi tạo.
  3. Gọi phương thức gọi lại, chẳng hạn như Activity.onCreate(), phù hợp với trạng thái vòng đời hiện tại của hoạt động.

Thường thì phương thức onCreate() có tác động lớn nhất đến thời gian tải, vì phương thức này thực hiện công việc với mức hao tổn cao nhất, bao gồm tải và tăng cường khung hiển thị cũng như khởi tạo các đối tượng cần thiết cho hoạt động để chạy.

Khởi động ấm

Một quy trình khởi động ấm bao gồm một tập con các thao tác diễn ra trong quá trình khởi động nguội. Đồng thời, điều đó cho thấy mức hao tổn cao hơn so với quy trình khởi động nóng. Có nhiều trạng thái tiềm năng có thể được coi là khởi động ấm, chẳng hạn như sau:

  • Người dùng quay lại ứng dụng của bạn, sau đó chạy lại ứng dụng. Quá trình này có thể tiếp tục chạy nhưng ứng dụng phải tạo lại hoạt động từ đầu thông qua lệnh gọi đến onCreate().

  • Hệ thống sẽ loại bỏ ứng dụng của bạn khỏi bộ nhớ, sau đó người dùng khởi chạy lại ứng dụng. Quy trình và hoạt động cần khởi động lại, nhưng tác vụ có thể hưởng lợi đôi chút từ gói trạng thái của thực thể đã lưu được truyền vào onCreate().

Khởi động nóng

Khởi động nóng ứng dụng có mức hao tổn thấp hơn so với khởi động nguội. Khi khởi động nóng, hệ thống sẽ đưa hoạt động của bạn lên nền trước. Nếu tất cả hoạt động của ứng dụng vẫn nằm trong bộ nhớ, thì ứng dụng có thể tránh lặp lại thao tác khởi tạo đối tượng, tăng cường bố cục và kết xuất.

Tuy nhiên, nếu một số bộ nhớ bị xoá hoàn toàn để đáp ứng các sự kiện cắt bỏ bộ nhớ, chẳng hạn như onTrimMemory(), thì các đối tượng đó cần được tạo lại để phản hồi sự kiện khởi động nóng.

Quá trình khởi động nóng sẽ hiển thị cùng hành vi trên màn hình như quá trình khởi động nguội. Quy trình hệ thống này sẽ hiển thị một màn hình trống cho đến khi ứng dụng hiển thị xong hoạt động.

Hình 2. Một sơ đồ cho thấy nhiều trạng thái khởi động và các quy trình tương ứng, trong đó mỗi trạng thái bắt đầu từ thời điểm khung hình đầu tiên được vẽ.

Cách xác định quá trình khởi động ứng dụng trong Perfetto

Để gỡ lỗi các vấn đề về việc khởi động ứng dụng, bạn nên xác định chính xác những gì được đưa vào giai đoạn khởi động ứng dụng. Để xác định toàn bộ giai đoạn khởi động ứng dụng trong Perfetto, hãy làm theo các bước sau:

  1. Trong Perfetto, hãy tìm hàng có chỉ số phát sinh từ Android App Startups (Khởi động ứng dụng Android). Nếu bạn không thấy hàng này, hãy thử ghi lại dấu vết bằng ứng dụng theo dõi hệ thống trên thiết bị.

    Hình 3. Lát cắt chỉ số phát sinh từ Android App Startups (Khởi động ứng dụng Android) trong Perfetto.
  2. Nhấp vào lát cắt được liên kết rồi nhấn m để chọn lát cắt đó. Các dấu ngoặc sẽ xuất hiện xung quanh lát cắt và biểu thị thời lượng cần thiết. Thời lượng đó cũng xuất hiện trong thẻ Current selection (Lựa chọn hiện tại).

  3. Ghim hàng Android App Startups (Khởi động ứng dụng Android) bằng cách nhấp vào biểu tượng ghim. Bạn có thể thấy biểu tượng này khi giữ con trỏ trên hàng đó.

  4. Di chuyển đến hàng có ứng dụng liên quan rồi nhấp vào ô đầu tiên để mở rộng hàng đó.

  5. Phóng to vào luồng chính (thường ở trên cùng), bằng cách nhấn w (nhấn s, a, d để lần lượt thu nhỏ, di chuyển sang trái và di chuyển sang phải).

    Hình 4. Lát chỉ số phát sinh từ Android App Startups (Khởi động ứng dụng Android) bên cạnh luồng chính của ứng dụng.
  6. Lát cắt chỉ số phát sinh giúp bạn dễ dàng biết được chính xác dữ liệu nào được đưa vào quá trình khởi động ứng dụng để có thể tiếp tục gỡ lỗi một cách cụ thể hơn.

Sử dụng các chỉ số để kiểm tra và cải thiện quy trình khởi động

Để chẩn đoán chính xác hiệu suất của thời gian khởi động, bạn có thể theo dõi các chỉ số cho biết thời gian cần để ứng dụng của bạn khởi động. Android sẽ cung cấp một số phương tiện để thông báo cho bạn biết ứng dụng của mình có vấn đề, đồng thời giúp bạn chẩn đoán vấn đề đó. Android vitals có thể cảnh báo cho bạn về một vấn đề đang xảy ra, và các công cụ chẩn đoán có thể giúp bạn chẩn đoán vấn đề đó.

Lợi ích của việc sử dụng các chỉ số khởi động

Android sử dụng các chỉ số thời gian xuất hiện khung hình đầu tiên (TTID)thời gian xuất hiện đầy đủ (TTFD) để tối ưu hoá quy trình khởi động ấm và khởi động nguội của ứng dụng. Android Runtime (ART) sử dụng dữ liệu từ các chỉ số này để biên dịch trước mã một cách hiệu quả nhằm tối ưu hoá các kiểu khởi động sau này.

Khởi động nhanh hơn giúp người dùng tương tác nhiều hơn với ứng dụng của bạn, giảm bớt các trường hợp thoát sớm, khởi động lại phiên bản hoặc chuyển sang một ứng dụng khác.

Android vitals

Android vitals có thể giúp cải thiện hiệu suất của ứng dụng bằng cách thông báo cho bạn trên Play Console, khi thời gian khởi động ứng dụng của bạn quá lâu.

Android vitals coi lượng thời gian khởi động sau đây là quá lâu đối với ứng dụng của bạn:

  • Khởi động nguội mất 5 giây trở lên.
  • Khởi động ấm mất từ 2 giây trở lên.
  • Khởi động nóng mất 1,5 giây trở lên.

Android vitals sử dụng chỉ số thời gian xuất hiện khung hình đầu tiên (TTID). Để biết thông tin về cách Google Play thu thập dữ liệu Android vitals, vui lòng xem tài liệu Play Console.

Thời gian xuất hiện khung hình đầu tiên

Thời gian xuất hiện khung hình đầu tiên (TTID) là thời gian cần thiết để hiển thị khung hình đầu tiên trên giao diện người dùng của ứng dụng. Chỉ số này cho biết thời lượng cần thiết để một ứng dụng tạo ra khung hình đầu tiên, bao gồm cả khởi chạy quy trình khi khởi động nguội, tạo hoạt động khi khởi động nguội hoặc khởi động ấm và hiển thị khung hình đầu tiên. Việc duy trì chỉ số TTID của ứng dụng ở mức thấp giúp cải thiện trải nghiệm người dùng bằng cách để người dùng thấy ứng dụng của bạn khởi động nhanh chóng. TTID được Khung Android báo cáo tự động cho mỗi ứng dụng. Khi tối ưu hoá cho quá trình khởi động ứng dụng, bạn nên triển khai reportFullyDrawn để nhận thông tin đến TTFD.

TTID được đo dưới dạng một giá trị thời gian, đại diện cho tổng thời gian đã trôi qua, bao gồm cả chuỗi sự kiện sau đây:

  • Khởi chạy quy trình.
  • Khởi tạo đối tượng.
  • Tạo và khởi tạo hoạt động.
  • Tăng cường bố cục.
  • Hiển thị ứng dụng lần đầu.

Truy xuất TTID

Để tìm TTID, hãy tìm một dòng đầu ra có chứa giá trị có tên là Displayed trong công cụ dòng lệnh Logcat. Giá trị này là TTID và tương tự như ví dụ sau, trong đó TTID là 3 giây 534 mili giây:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms

Để tìm TTID trong Android Studio, hãy tắt các bộ lọc trong khung hiển thị Logcat trong trình đơn bộ lọc thả xuống, sau đó tìm thời gian Displayed, như minh hoạ trong hình 5. Bạn cần tắt bộ lọc cho máy chủ hệ thống chứ không phải chính ứng dụng phân phát nhật ký này.

Hình 5. Các bộ lọc bị tắt và giá trị Displayed trong logcat.

Chỉ số Displayed trong dữ liệu đầu ra Logcat không nhất thiết ghi lại khoảng thời gian cho đến khi tất cả tài nguyên được tải và hiển thị. Chỉ số này sẽ loại bỏ các tài nguyên không được tham chiếu trong tệp bố cục hoặc ứng dụng tạo ra trong quá trình khởi tạo đối tượng. Chỉ số đó cũng loại trừ các tài nguyên này vì việc tải chúng là một quy trình cùng dòng, đồng thời không chặn khung hình đầu tiên của ứng dụng.

Đôi khi, dòng Displayed trong đầu ra Logcat chứa một trường bổ sung dành cho tổng thời gian. Ví dụ:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)

Trong trường hợp này, phép đo lần đầu chỉ dành cho hoạt động được vẽ lần đầu. Hoạt động đo lường thời gian total bắt đầu khi quá trình ứng dụng bắt đầu, và có thể bao gồm một hoạt động khác được bắt đầu trước tiên nhưng không hiển thị bất kỳ thông tin nào đối với màn hình. Chế độ đo lường thời gian total chỉ hiển thị khi có sự khác biệt giữa một hoạt động và tổng thời gian khởi động.

Bạn nên sử dụng Logcat trong Android Studio, nhưng nếu không sử dụng Android Studio, bạn cũng có thể đo lường TTID bằng cách chạy ứng dụng thông qua lệnh của trình quản lý hoạt động shell adb của chúng tôi. Ví dụ:

adb [-d|-e|-s <serialNumber>] shell am start -S -W
com.example.app/.MainActivity
-c android.intent.category.LAUNCHER
-a android.intent.action.MAIN

Chỉ số Displayed xuất hiện trong dữ liệu đầu ra Logcat như trước đây. Cửa sổ dòng lệnh hiển thị như sau:

Starting: Intent
Activity: com.example.app/.MainActivity
ThisTime: 2044
TotalTime: 2044
WaitTime: 2054
Complete

Các đối số -c-a là không bắt buộc và cho phép bạn chỉ định <category> lẫn <action>.

Thời gian xuất hiện đầy đủ

Thời gian xuất hiện đầy đủ (TTFD) là thời gian mà một ứng dụng cần để tương tác với người dùng. Thời gian này được báo cáo là thời gian cần thiết để hiển thị khung hình đầu tiên trên giao diện người dùng của ứng dụng, cũng như nội dung tải không đồng bộ sau khi xuất hiện khung hình đầu tiên. Thông thường, đây là nội dung chính được tải từ mạng hoặc ổ đĩa (theo báo cáo của ứng dụng). Nói cách khác, TTFD bao gồm cả TTID cũng như thời gian cần thiết để có thể dùng được ứng dụng. Việc giữ TTFD của ứng dụng ở mức thấp sẽ giúp cải thiện trải nghiệm người dùng bằng cách cho phép người dùng nhanh chóng tương tác với ứng dụng của bạn.

Hệ thống sẽ xác định TTID khi Choreographer gọi phương thức onDraw() của hoạt động, và khi biết hệ thống gọi phương thức này lần đầu tiên. Tuy nhiên, hệ thống sẽ không biết thời điểm nào cần xác định TTFD vì mỗi ứng dụng sẽ hoạt động theo cách khác nhau. Để xác định TTFD, ứng dụng cần báo hiệu cho hệ thống khi đạt đến trạng thái đã hiển thị đầy đủ.

Truy xuất TTFD

Để tìm TTFD, hãy báo hiệu trạng thái đã hiển thị đầy đủ bằng cách gọi phương thức reportFullyDrawn() của ComponentActivity. Phương thức reportFullyDrawn báo cáo khi ứng dụng hiển thị đầy đủ và ở trạng thái sử dụng được. TTFD là thời gian trôi qua kể từ khi hệ thống nhận được ý định khởi chạy ứng dụng cho đến khi reportFullyDrawn() được gọi. Nếu bạn không gọi reportFullyDrawn(), thì sẽ không có giá trị TTFD nào được báo cáo.

Để đo lường TTFD, hãy gọi reportFullyDrawn() sau khi bạn hiển thị xong giao diện người dùng và tất cả dữ liệu. Đừng gọi reportFullyDrawn() trước khi cửa sổ của hoạt động đầu tiên được hiển thị lần đầu và hiển thị khi được hệ thống đo lường, vì sau đó hệ thống sẽ báo cáo thời gian hệ thống đo lường được. Nói cách khác, nếu bạn gọi reportFullyDrawn() trước khi hệ thống phát hiện TTID, thì hệ thống sẽ báo cáo cả TTID và TTFD là cùng một giá trị và giá trị này là giá trị TTID.

Khi bạn sử dụng reportFullyDrawn(), Logcat sẽ hiển thị một kết quả như ví dụ sau, trong đó TTFD là 1 giây 54 mili giây:

system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms

Kết quả Logcat đôi khi bao gồm thời gian total, như thảo luận trong mục Thời gian xuất hiện khung hình đầu tiên.

Nếu thời gian hiển thị của bạn chậm hơn mong muốn, bạn có thể tìm cách xác định điểm tắc nghẽn trong quá trình khởi động.

Trong các trường hợp cơ bản, bạn có thể sử dụng reportFullyDrawn() để báo hiệu trạng thái đã hiển thị đầy đủ khi biết rằng đã đạt được trạng thái đó. Tuy nhiên, trong trường hợp các luồng ở chế độ nền phải hoàn tất công việc ở chế độ nền trước khi đạt được trạng thái đã hiển thị đầy đủ, bạn cần trì hoãn reportFullyDrawn() để đo lường TTFD chính xác hơn. Để tìm hiểu cách trì hoãn reportFullyDrawn(), hãy xem phần sau.

Cải thiện độ chính xác về thời gian khởi động

Nếu ứng dụng của bạn đang tải từng phần và màn hình ban đầu chưa bao gồm tất cả tài nguyên, chẳng hạn như khi ứng dụng của bạn đang tìm nạp hình ảnh từ mạng, thì bạn có thể muốn trì hoãn việc gọi reportFullyDrawn cho đến khi ứng dụng không sử dụng được để bạn có thể đưa hoạt động điền danh sách vào thời gian đo điểm chuẩn.

Ví dụ: nếu giao diện người dùng chứa một danh sách động, chẳng hạn như danh sách RecyclerView hoặc danh sách từng phần, thì có thể danh sách này sẽ được điền bằng một tác vụ ở chế độ nền (hoàn tất sau khi danh sách này hiển thị lần đầu và do đó, chính là thời điểm sau khi giao diện người dùng được đánh dấu là đã hiển thị đầy đủ). Trong những trường hợp như vậy, hoạt động điền danh sách không được đưa vào quy trình đo điểm chuẩn.

Để đưa hoạt động điền danh sách vào khoảng thời gian đo điểm chuẩn, hãy lấy FullyDrawnReporter bằng cách sử dụng getFullyDrawnReporter() và thêm một trình báo cáo vào đó trong mã ứng dụng của bạn. Giải phóng trình báo cáo khi tác vụ trong nền điền xong danh sách.

FullyDrawnReporter sẽ không gọi phương thức reportFullyDrawn() cho đến khi mọi trình báo cáo đã thêm được loại bỏ. Nếu bạn thêm một trình báo cáo cho đến khi tiến trình nền hoàn tất, thời gian này cũng sẽ bao gồm cả khoảng thời gian cần thiết để điền danh sách trong dữ liệu về thời gian khởi động. Điều này không làm thay đổi hành vi của ứng dụng đối với người dùng nhưng cho phép dữ liệu về thời gian khởi động bao gồm cả thời gian cần thiết để điền danh sách. reportFullyDrawn() không được gọi cho đến khi hoàn tất tất cả các tác vụ, bất kể thứ tự.

Ví dụ sau cho thấy cách bạn có thể chạy đồng thời nhiều tác vụ trong nền, trong đó mỗi tác vụ đăng ký một công cụ báo cáo riêng:

Kotlin

class MainActivity : ComponentActivity() {

    sealed interface ActivityState {
        data object LOADING : ActivityState
        data object LOADED : ActivityState
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            var activityState by remember {
                mutableStateOf(ActivityState.LOADING as ActivityState)
            }
            fullyDrawnReporter.addOnReportDrawnListener {
                activityState = ActivityState.LOADED
            }
            ReportFullyDrawnTheme {
                when(activityState) {
                    is ActivityState.LOADING -> {
                        // Display the loading UI.
                    }
                    is ActivityState.LOADED -> {
                        // Display the full UI.
                    }
                }
            }
            SideEffect {
                lifecycleScope.launch(Dispatchers.IO) {
                    fullyDrawnReporter.addReporter()

                    // Perform the background operation.

                    fullyDrawnReporter.removeReporter()
                }
                lifecycleScope.launch(Dispatchers.IO) {
                    fullyDrawnReporter.addReporter()

                    // Perform the background operation.

                    fullyDrawnReporter.removeReporter()
                }
            }
        }
    }
}

Java

public class MainActivity extends ComponentActivity {
    private FullyDrawnReporter fullyDrawnReporter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        fullyDrawnReporter = getFullyDrawnReporter();
        fullyDrawnReporter.addOnReportDrawnListener(() -> {
            // Trigger the UI update.
            return Unit.INSTANCE;
        });

        new Thread(new Runnable() {
            @Override
            public void run() {
                fullyDrawnReporter.addReporter();

                // Do the background work.

               fullyDrawnReporter.removeReporter();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                fullyDrawnReporter.addReporter();

                // Do the background work.

                fullyDrawnReporter.removeReporter();
            }
        }).start();
    }
}

Nếu ứng dụng của bạn dùng Jetpack Compose, thì bạn có thể dùng các API sau để biểu thị trạng thái đã hiển thị đầy đủ:

  • ReportDrawn: cho biết thành phần kết hợp của bạn đã sẵn sàng tương tác ngay lập tức.
  • ReportDrawnWhen: sử dụng một thuộc tính, chẳng hạn như list.count > 0, để cho biết thời điểm thành phần kết hợp của bạn đã sẵn sàng tương tác.
  • ReportDrawnAfter: sử dụng phương thức tạm ngưng sau khi hoàn tất để cho biết thành phần kết hợp đã sẵn sàng tương tác.
Xác định điểm tắc nghẽn

Để tìm điểm tắc nghẽn, bạn có thể sử dụng Trình phân tích CPU của Android Studio. Để biết thêm thông tin, hãy xem bài viết Kiểm tra hoạt động của CPU bằng Trình phân tích CPU.

Bạn cũng có thể nắm được thông tin chi tiết về điểm tắc nghẽn tiềm ẩn qua các phương thức onCreate() của ứng dụng và hoạt động của bạn. Để tìm hiểu về tính năng theo dõi cùng dòng, vui lòng xem tài liệu về hàm Tracethông tin tổng quan về tính năng theo dõi hệ thống.

Giải quyết các vấn đề thường gặp

Phần này thảo luận một số vấn đề thường gặp ảnh hưởng đến hiệu suất khởi động của ứng dụng. Các vấn đề này chủ yếu ảnh hưởng đến việc khởi chạy các đối tượng ứng dụng và hoạt động, cũng như hoạt động tải màn hình.

Khởi động ứng dụng nặng

Hiệu suất khởi chạy có thể bị ảnh hưởng khi mã của bạn ghi đè đối tượng Application, và thực thi logic phức tạp hoặc phức tạp khi khởi tạo đối tượng đó. Ứng dụng của bạn có thể lãng phí thời gian trong quá trình khởi động nếu các lớp con của Application thực hiện việc khởi chạy chưa cần thiết.

Một số hoạt động khởi tạo có thể hoàn toàn không cần thiết, chẳng hạn như khi khởi tạo thông tin trạng thái cho hoạt động chính khi thực ra ứng dụng khởi động để phản hồi một ý định. Với ý định, ứng dụng chỉ sử dụng một số dữ liệu trạng thái đã khởi tạo trước đó.

Những thách thức khác trong quá trình khởi chạy ứng dụng bao gồm các sự kiện thu thập rác có tác động lớn hoặc nhiều sự kiện, hoặc hoạt động I/O ổ đĩa diễn ra đồng thời với việc khởi chạy, chặn thêm quá trình khởi chạy. Việc thu gom rác được đặc biệt quan tâm trong thời gian chạy Dalvik; Android Runtime (ART) thực hiện đồng thời thu thập rác, giảm thiểu tác động của hoạt động đó.

Chẩn đoán sự cố

Bạn có thể sử dụng tính năng theo dõi phương thức hoặc theo dõi cùng dòng để chẩn đoán vấn đề.

Tìm dấu vết phương thức

Việc chạy Trình phân tích CPU cho biết phương thức callApplicationOnCreate() sẽ gọi phương thức com.example.customApplication.onCreate của bạn. Nếu công cụ cho thấy các phương thức này mất nhiều thời gian để hoàn thành việc thực thi, bạn nên khám phá thêm để xem phương thức nào đang diễn ra ở đó.

Theo dõi cùng dòng

Sử dụng tính năng theo dõi cùng dòng điều tra các nguyên nhân có khả năng xảy ra, bao gồm:

  • Hàm onCreate() ban đầu cho ứng dụng của bạn.
  • Mọi đối tượng singleton toàn cầu mà ứng dụng của bạn khởi tạo.
  • Bất kỳ ổ đĩa I/O, quá trình tái thiết hoặc lặp lại nào có thể xảy ra trong nút thắt cổ chai.

Giải pháp cho vấn đề

Cho dù vấn đề nằm ở việc khởi tạo không cần thiết hay với I/O ổ đĩa, giải pháp là khởi tạo lazy. Nói cách khác, chỉ khởi tạo các đối tượng cần thiết ngay lập tức. Thay vì tạo đối tượng tĩnh toàn cầu, hãy chuyển sang mẫu singleton mà ứng dụng chỉ khởi tạo các đối tượng vào lần đầu tiên bạn cần các đối tượng đó.

Ngoài ra, hãy cân nhắc sử dụng một khung chèn phụ thuộc như Hilt để tạo đối tượng và phần phụ thuộc khi chúng được chèn lần đầu tiên.

Nếu ứng dụng của bạn sử dụng nhà cung cấp nội dung để khởi chạy các thành phần ứng dụng khi khởi động, hãy cân nhắc sử dụng thư viện Khởi động ứng dụng.

Khởi chạy hoạt động nặng

Việc tạo hoạt động thường đòi hỏi nhiều công việc với mức hao tổn cao. Thông thường, có nhiều cơ hội để tối ưu hoá công việc này nhằm cải thiện hiệu suất. Sau đây là các vấn đề phổ biến:

  • Chèn bố cục lớn hoặc phức tạp.
  • Chặn vẽ màn hình trên đĩa hoặc I/O mạng.
  • Tải và giải mã bitmap.
  • Đang tạo điểm ảnh cho đối tượng VectorDrawable.
  • Khởi chạy các hệ thống con khác của hoạt động.

Chẩn đoán sự cố

Trong trường hợp này, cả theo dõi phương pháp lẫn theo dõi cùng dòng đều có thể hữu ích.

Tìm dấu vết phương thức

Khi sử dụng Trình phân tích CPU, hãy chú ý đến hàm khởi tạo lớp con Application và phương thức com.example.customApplication.onCreate() của ứng dụng.

Nếu công cụ cho thấy các phương thức này mất nhiều thời gian để hoàn thành việc thực thi, bạn nên khám phá thêm để xem phương thức nào đang diễn ra ở đó.

Theo dõi cùng dòng

Sử dụng tính năng theo dõi cùng dòng điều tra các nguyên nhân có khả năng xảy ra, bao gồm:

  • Hàm onCreate() ban đầu của ứng dụng.
  • Mọi đối tượng singleton toàn cầu mà hàm khởi tạo.
  • Bất kỳ ổ đĩa I/O, quá trình tái thiết hoặc lặp lại nào có thể xảy ra trong nút thắt cổ chai.

Giải pháp cho vấn đề

Có thể có nhiều điểm tắc nghẽn, nhưng vấn đề và các phương án khắc phục thông thường sẽ như sau:

  • Hệ phân cấp khung hiển thị của bạn càng lớn thì ứng dụng càng mất nhiều thời gian để tăng cường nó. Bạn có thể giải quyết vấn đề này qua 2 bước sau:
    • Làm phẳng hệ phân cấp khung hiển thị bằng cách giảm bố cục thừa hoặc lồng nhau.
    • Không tăng cường các phần của giao diện người dùng không cần hiển thị trong quá trình khởi chạy. Thay vào đó, hãy sử dụng đối tượng ViewStub làm phần giữ chỗ cho các hệ phân cấp phụ mà ứng dụng có thể tăng kích thước vào thời điểm thích hợp hơn.
  • Tất cả hoạt động khởi tạo tài nguyên của bạn trên luồng chính cũng có thể làm chậm quá trình khởi động. Bạn có thể giải quyết vấn đề này như sau:
    • Di chuyển tất cả hoạt động khởi tạo tài nguyên để ứng dụng có thể thực hiện từng phần trên một luồng khác.
    • Cho phép ứng dụng tải và hiển thị khung hiển thị của bạn, sau đó cập nhật các thuộc tính hình ảnh phụ thuộc vào bitmap và các tài nguyên khác.

Màn hình chờ tuỳ chỉnh

Bạn có thể thấy thêm thời gian khởi động trong quá trình khởi động, nếu trước đó bạn đã sử dụng một trong các phương thức sau để triển khai màn hình chờ tuỳ chỉnh trong Android 11 (API cấp 30) trở xuống:

  • Sử dụng thuộc tính giao diện windowDisablePreview để tắt màn hình trống ban đầu do hệ thống hiển thị trong khi chạy.
  • Sử dụng Activity chuyên biệt.

Bắt đầu từ Android 12, bạn phải chuyển sang API SplashScreen. API này giúp thời gian khởi động nhanh hơn và bạn có thể điều chỉnh màn hình chờ theo các cách sau:

Hơn nữa, thư viện khả năng tương thích điều chỉnh SplashScreen API cho phiên bản cũ để mang đến khả năng tương thích ngược và tạo giao diện nhất quán để hiện màn hình chờ trên tất cả các phiên bản Android.

Hãy xem Hướng dẫn di chuyển màn hình chờ để biết thông tin chi tiết.