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

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

Người dùng mong muốn ứng dụng sẽ phản hồi nhanh và tả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ể gây thất vọng cho người dù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.

Tài liệu này cung cấp thông tin giúp bạn tối ưu hóa thời gian phát hành ứng dụng. Nền tảng này bắt đầu bằng việc giải thích nội bộ về quy trình ra mắt. Tiếp theo là nội dung thảo luận về cách lập hồ sơ hiệu suất khởi động. Cuối cùng, tài liệu sẽ mô tả một số vấn đề thường gặp về thời gian bắt đầu, đồng thời đưa ra một số gợi ý 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 ra mắt ứng dụng có thể diễn ra ở một trong ba trạng thái, 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: khởi động nguội, khởi động ấm hoặc khởi động nó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 hóa 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 hóa ứng dụng của bạn cho việc khởi động nhanh, bạn nên hiểu 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 này.

Khởi động nguội

Khởi động nguội đề cập đến việc một ứng dụng bắt đầu lại từ đầu: quy trình của hệ thống sẽ không tạo ra quy trình của ứng dụng cho đến khi khởi động nguội bắt đầu. Khởi động nguội xảy ra trong 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 vô hiệu hóa ứ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ụ. Những nhiệm vụ này bao gồm:

  1. Tải và 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. Khởi chạy chuỗi chính.
  3. Tạo hoạt động chính.
  4. Chế độ xem tăng cường.
  5. Bố trí màn hình.
  6. Thực hiện lần vẽ đầu tiên.

Sau 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 hiệ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 họa các phần quan trọng của việc 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. Khi đó, hệ thống sẽ hoán đổi cửa sổ khởi động cho ứng dụng của bạn, cho phép người dùng bắt đầu 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, 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 chuỗi 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ường, phương thức onCreate() này 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 chi phí cao nhất, bao gồm tải và tăng cường chế độ xem 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 khởi động ấm bao gồm một số hoạt động được thực hiện trong thời gian khởi động nguội; đồng thời, nó cho thấy mức chi phí cao hơn so với một khởi động nóng. Có nhiều trạng thái tiềm năng có thể được coi là khởi đầu ấm. Ví dụ:

  • 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 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 chạy lại ứng dụng. Quy trình và hoạt động cần được 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 đơn giản hơn và chi phí thấp hơn nhiều so với khởi động nguội. Trong một khởi động nóng, tất cả các 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 phải lặp lại thao tác khởi tạo đối tượng, tăng cường bố cục và hiển thị.

Tuy nhiên, nếu một số bộ nhớ đã bị xóa 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 đó sẽ cần được tạo lại để phản hồi sự kiện khởi động nóng.

Một khởi động nóng sẽ hiển thị cùng hành vi trên màn hình như một tình huống khởi động nguội:

Quá trình 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: Sơ đồ này 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ừ khung đầu tiên được vẽ.

Sử dụng các chỉ số để phát hiện và chẩn đoán vấn đề

Để chẩn đoán chính xác hiệu suất của thời gian bắt đầu, 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ề vấn đề này đ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 hiển thị khung hình đầu tiênThời gian hiển thị đầy đủ để tối ưu hóa việc khởi động ứng dụng ấm và lạnh. 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 hóa các kiểu khởi động trong tương lai.

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 thông qua Play Console, khi thời gian khởi động ứng dụng của bạn quá lớn. Android vitals coi số lần khởi động của ứng dụng là quá mức khi:

  • 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 hiển thị khung hình đầu tiên và không báo cáo dữ liệu cho các khởi động nóng. Để 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 để hiển thị khung hình đầu tiên

Chỉ số thời gian để xuất hiện khung hình đầu tiên (TTID) đo thời gian cần để một ứng dụng tạo ra khung đầu tiên, bao gồm khởi chạy quy trình (nếu khởi động nguội), tạo hoạt động (nếu khởi động nguội/ấm) và hiển thị khung hình đầu tiên.

Cách truy xuất TTID

Trong Android 4.4 (API cấp 19) trở lên, logcat bao gồm một dòng đầu ra chứa giá trị có tên là Displayed. Giá trị này biểu thị khoảng thời gian đã trôi qua giữa thời điểm chạy quy trình và hoàn tất việc vẽ hoạt động tương ứng trên màn hình. Thời gian đã trôi qua bao gồm chuỗi sự kiện sau đây:

  • Chạy quy trình này.
  • Khởi tạo đối tượng.
  • Tạo và khởi tạo hoạt động.
  • Tăng cường bố cục.
  • Vẽ ứng dụng của bạn lần đầu.

Dòng nhật ký được báo cáo sẽ giống như ví dụ sau:

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

Nếu bạn đang theo dõi đầu ra logcat từ dòng lệnh hoặc trong thiết bị đầu cuối, thì việc tìm thời gian đã trôi qua rất đơn giản. Để tìm thời gian đã trôi qua trong Android Studio, bạn phải tắt các bộ lọc trong chế độ xem logcat. 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.

Sau khi đã thực hiện các tùy chọn cài đặt thích hợp, bạn có thể dễ dàng tìm kiếm đúng cụm từ để xem thời gian. Hình 2 cho thấy cách tắt các bộ lọc, và ở dòng đầu ra thứ hai từ dưới cùng, ví dụ về đầu ra logcat của thời gian Displayed.

Hình 2: Tắt các bộ lọc và tìm giá trị `Được hiển thị` 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ị: nó 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 dưới dạng một phần của quá trình khởi tạo đối tượng. Nó 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 màn hình ban đầu 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 đã bắt đầu đầu tiên nhưng không hiển thị bất kỳ thông tin nào cho 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 cũng có thể đo thời gian để hiển thị khung hình đầu tiên bằng cách chạy ứng dụng với lệnh ADB Shell Activity Manager. Dưới đây là một 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 đầu ra logcat như trước đây. Cửa sổ dòng lệnh của bạn cũng phải hiển thị các nội dung 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 hiển thị toàn màn hình

Chỉ số Thời gian để hiển thị đầy đủ (TTFD) đo thời gian mà ứng dụng thực hiện để tạo ra khung hình đầu tiên với nội dung đầy đủ, bao gồm cả nội dung được tải không đồng bộ sau khung hình đầu tiên. Thông thường, đây là nội dung danh sách chính được tải từ mạng theo báo cáo của ứng dụng.

Cách thu hồi TTFD

Bạn có thể sử dụng phương thức reportFullyDrawn() để đo lường thời gian đã trôi qua giữa thời điểm chạy ứng dụng và hiển thị toàn bộ tài nguyên cũng như hệ phân cấp chế độ xem. Điều này có thể hữu ích trong trường hợp ứng dụng thực hiện tải từng phần. Trong khi tải từng phần, một ứng dụng không chặn quá trình vẽ cửa sổ ban đầu mà sẽ tải không đồng bộ các tài nguyên và cập nhật hệ phân cấp chế độ xem.

Nếu do tải từng phần, màn hình ban đầu của ứng dụng không bao gồm tất cả tài nguyên, bạn có thể coi việc tải và hiển thị hoàn chỉnh của tất cả tài nguyên và chế độ xem là một chỉ số riêng: chẳng hạn như giao diện người dùng của bạn có thể đã tải đầy đủ bằng một số văn bản được vẽ, nhưng chưa hiển thị hình ảnh mà ứng dụng phải tìm nạp từ mạng.

Để giải quyết vấn đề này, bạn có thể gọi reportFullyDrawn() theo cách thủ công để hệ thống biết hoạt động của bạn đã kết thúc khi tải từng phần. Khi bạn sử dụng phương thức này, giá trị mà logcat hiển thị là thời gian trôi qua từ lúc tạo đối tượng ứng dụng đến thời điểm reportFullyDrawn() được gọi. Dưới đây là ví dụ về kết quả logcat:

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

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

Nếu bạn biết thời gian hiển thị chậm hơn mong muốn, bạn có thể tiếp tục cố gắng xác định nút thắt cổ chai trong quá trình khởi động.

Xác định nút thắt cổ chai

Một cách hay để tìm nút thắt cổ chai là sử dụng Trình phân tích CPU của Android Studio. Để biết thông tin, vui lòng xem phần Kiểm tra hoạt động của CPU bằng Trình phân tích tài nguyên CPU.

Bạn cũng có thể nắm được thông tin chi tiết về nút thắt cổ chai 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.

Chú ý đến 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 chạy ứ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 ứng dụng thực hiện việc khởi chạy chưa cần thực hiện. 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ư khởi tạo thông tin trạng thái cho hoạt động chính, khi ứng dụng thực sự khởi động để đáp ứng ý đị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 biệt được xem xét trong thời gian chạy Dalvik; thời gian chạy 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 vấn đề

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 để cố gắ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 của ứng dụng.
  • 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, bạn chỉ nên 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 hóa công việc này nhằm cải thiện hiệu suất. Những vấn đề phổ biến như vậy bao gồm:

  • 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 vấn đề

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 dựng 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 nút thắt cổ chai, 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 chế độ xem của bạn càng lớn, ứng dụng càng mất nhiều thời gian để tăng cường cấp. Bạn có thể thực hiện hai bước để giải quyết vấn đề này:

    • Làm phẳng hệ phân cấp chế độ xem 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 U. không cần xuất hiện trong quá trình 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.

  • Việc khởi tạo tất cả tài nguyên trên chuỗi chính cũng có thể làm chậm quá trình khởi động lại. 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ị chế độ xem 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ờ tùy 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ờ tùy 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 vẽ trong khi chạy.

  • Sử dụng một hoạt động chuyên dụng

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

Hơn nữa, thư viện compat hỗ trợ API SplashScreen để cho phép khả năng tương thích ngược và tạo giao diện nhất quán để hiển thị màn hình chờ trên tất cả các phiên bản Android.

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