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 hoá 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 hoá việc khởi động nhanh ứng dụng, 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.
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 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ụ. Những nhiệm vụ này bao gồm:
- Tải và chạy ứng dụng.
- Hiển thị cửa sổ khởi động trống cho ứng dụng ngay sau khi khởi chạy.
- 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:
- Tạo đối tượng ứng dụng.
- Khởi chạy chuỗi chính.
- Tạo hoạt động chính.
- Chế độ xem tăng cường.
- Bố trí màn hình.
- 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.

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. 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:
- Khởi chạy các giá trị.
- Gọi hàm khởi tạo.
- 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 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 quy trình khởi động ấm bao gồm một số tập con của các tác vụ đượ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 khởi 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 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 đó 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:
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.

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ên và Thời gian hiển thị đầy đủ để tối ưu hoá việc khởi động ứng dụng ấm và nguội. 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 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á lâu 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. Để 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 của 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 áp dụng chế độ 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
.

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 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 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 đã 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 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</pre>
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
và -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ị đầy đủ
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 truy xuất TTFD
Bạn có thể sử dụng phương thức reportFullyDrawn()
để đo lường thời gian giữa thời điểm chạy ứng dụng và thời điểm hiển thị toàn bộ tài nguyên cũng như hệ phân cấp khung hiển thị. Đ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 màn hình ban đầu của ứng dụng không bao gồm tất cả tài nguyên vì đang trong quá trình tải từng phần, bạn có thể coi việc tải và hiển thị hoàn chỉnh tất cả tài nguyên và thành phần hiển thị 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 Trace
và thô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 đặc biệt quan tâm 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 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 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 hoá 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 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 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 khung hiển thị của bạn càng lớn, ứng dụng càng mất nhiều thời gian để tăng cường.
Bạn có thể giải quyết vấn đề này qua hai bước:
- 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ị 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ờ tuỳ chỉnh
Bạn có thể nhanaj thấy thời gian dôi ra 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 vẽ ra trong khi khởi 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 SplashScreen
API.
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:
- Đặt giao diện để thay đổi giao diện của màn hình chờ
- Kiểm soát thời gian hiển thị màn hình chờ
- Xác định thời gian cần thiết để ảnh động trên màn hình chờ và xử lý nhanh ảnh động để đóng màn hình chờ
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.