בדף הזה מוסבר איך לתעד את עקבות המערכת באמצעות ProfilingManager API.
ProfilingManager יכול גם להקליט סוגים אחרים של פרופילים. התהליך הזה דומה להקלטה של עקבות המערכת, אבל כל סוג משתמש ב-builder אחר. הפרופילים הנתמכים והכלים ליצירת פרופילים הם:
עקבות המערכת: מתועדים באמצעות
SystemTraceRequestBuilder, ומועילים לניתוח זמן האחזור ולניפוי באגים כללי בביצועים.תמונות מצב של הזיכרון: מתועדות באמצעות
JavaHeapDumpRequestBuilder, ועוזרות לזהות דליפות זיכרון ולבצע אופטימיזציה.תמונות מצב של ערימה: מתועדות באמצעות
HeapProfileRequestBuilder, ומועילות לאופטימיזציה של הזיכרון.פרופילים של סטאק ביצוע: מתועדים באמצעות
StackSamplingRequestBuilder, ושימושיים להבנת הרצת הקוד ולניתוח זמן האחזור.
הוספת יחסי תלות
כדי ליהנות מחוויית השימוש הטובה ביותר ב-API של ProfilingManager, צריך להוסיף את ספריות Jetpack הבאות לקובץ build.gradle.kts.
Kotlin
dependencies { implementation("androidx.tracing:tracing-ktx:2.0.5") implementation("androidx.core:core:1.19.1") }
מגניב
dependencies { implementation 'androidx.tracing:tracing:2.0.5' implementation 'androidx.core:core:1.19.1' }
תיעוד עקבות המערכת
אחרי שמוסיפים את יחסי התלות הנדרשים, משתמשים בקוד הבא כדי לתעד את עקבות המערכת. בדוגמה הזו מוסבר איך להתחיל סשן פרופיילינג מ-פונקציה קומפוזבילית תוך ניהול בטוח של פעולות כבדות מחוץ ל-Thread הראשי.
Kotlin
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@Composable
fun ProfiledScreen(modifier: Modifier = Modifier) {
// Use the application context: requestProfiling resolves the ProfilingManager
// system service from it, so there's no reason to hand it a short-lived Activity.
val appContext = LocalContext.current.applicationContext
val scope = rememberCoroutineScope()
Button(
onClick = {
// Run the orchestration off the main thread. Profiling a heavy operation
// on the UI thread would freeze the UI (ANR) and distort the very metrics
// you're trying to capture.
//
// Note: this scope is tied to composition. If the user leaves this screen
// mid-session, the coroutine is cancelled and stopSignal.cancel() might not
// run, but setDurationMs() acts as a safety net and ends the trace.
scope.launch(Dispatchers.Default) {
val callbackExecutor = Dispatchers.IO.asExecutor()
val resultCallback = Consumer<ProfilingResult> { profilingResult ->
if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) {
Log.d("ProfileTest", "Result file: ${profilingResult.resultFilePath}")
} else {
// errorMessage explains the failure (e.g., rate limiting); keep it.
Log.e(
"ProfileTest",
"Profiling failed errorCode=${profilingResult.errorCode} " +
"errorMessage=${profilingResult.errorMessage}"
)
}
}
val stopSignal = CancellationSignal()
val requestBuilder = SystemTraceRequestBuilder().apply {
setCancellationSignal(stopSignal)
setTag("FOO") // Caller-supplied tag for identification.
setDurationMs(60000) // Hard cap: ends the session if cancel() never fires.
setBufferFillPolicy(BufferFillPolicy.RING_BUFFER)
setBufferSizeKb(32768)
}
// 1. Start the session. This is asynchronous system IPC. The tracing
// engine takes a moment to start and allocate buffers.
requestProfiling(appContext, requestBuilder.build(), callbackExecutor, resultCallback)
// 2. The API exposes no "profiling started" signal, so pad with a short,
// best-effort delay before running the code you care about. This is
// approximate. Increase it on slower or heavily loaded devices.
delay(STARTUP_PADDING_MS)
// 3. The session is already recording every thread in your app. This slice
// doesn't scope what's captured. It just labels this region of the
// timeline so heavyOperation() is easier to find. trace { } closes the
// section even if the block throws.
trace("MyApp:HeavyOperation") {
heavyOperation()
}
// 4. Stop recording. Until this fires or the setDurationMs() cap is
// reached (whichever comes first), the session keeps capturing app-wide
// activity.
stopSignal.cancel()
}
}
) {
Text("Run & Profile Heavy Operation")
}
}
// Best-effort wait for the system trace engine to initialize before profiling.
// There is no deterministic start callback; tune this for your target devices.
private const val STARTUP_PADDING_MS = 100L
fun heavyOperation() {
// Background computations to profile.
}
Java
void heavyOperation() {
// Computations you want to profile
}
void sampleRecordSystemTrace() {
Executor mainExecutor = Executors.newSingleThreadExecutor();
Consumer<ProfilingResult> resultCallback =
new Consumer<ProfilingResult>() {
@Override
public void accept(ProfilingResult profilingResult) {
if (profilingResult.getErrorCode() == ProfilingResult.ERROR_NONE) {
Log.d(
"ProfileTest",
"Received profiling result file=" + profilingResult.getResultFilePath());
setupProfileUploadWorker(profilingResult.getResultFilePath());
} else {
Log.e(
"ProfileTest",
"Profiling failed errorcode="
+ profilingResult.getErrorCode()
+ " errormsg="
+ profilingResult.getErrorMessage());
}
}
};
CancellationSignal stopSignal = new CancellationSignal();
SystemTraceRequestBuilder requestBuilder = new SystemTraceRequestBuilder();
requestBuilder.setCancellationSignal(stopSignal);
requestBuilder.setTag("FOO");
requestBuilder.setDurationMs(60000);
requestBuilder.setBufferFillPolicy(BufferFillPolicy.RING_BUFFER);
requestBuilder.setBufferSizeKb(32768);
Profiling.requestProfiling(getApplicationContext(), requestBuilder.build(), mainExecutor,
resultCallback);
// Wait some time for profiling to start.
Trace.beginSection("MyApp:HeavyOperation");
heavyOperation();
Trace.endSection();
// Once the interesting code section is profiled, stop profile
stopSignal.cancel();
}
הקוד לדוגמה מגדיר ומנהל את סשן יצירת הפרופיל באמצעות השלבים הבאים:
מגדירים את מנהל העיזבון. יוצרים
Executorכדי להגדיר את ה-thread שיקבל את תוצאות יצירת הפרופיל. הפרופיילינג מתבצע ברקע. שימוש ב-executor של שרשור שאינו UI עוזר למנוע שגיאות מסוג "האפליקציה לא מגיבה" (ANR) אם מוסיפים עוד עיבוד לקריאה החוזרת (callback) בשלב מאוחר יותר.טיפול בתוצאות של יצירת הפרופילים. יוצרים אובייקט
Consumer<ProfilingResult>. המערכת משתמשת באובייקט הזה כדי לשלוח את תוצאות יצירת הפרופיל מ-ProfilingManagerבחזרה לאפליקציה.יוצרים את בקשת הפרופיילינג. יוצרים
SystemTraceRequestBuilderכדי להגדיר את סשן יצירת הפרופיל. הכלי הזה מאפשר להתאים אישית את הגדרות המעקב שלProfilingManager. ההתאמה האישית של ה-builder היא אופציונלית. אם לא תבצעו התאמה אישית, המערכת תשתמש בהגדרות ברירת המחדל.- מגדירים תג. אפשר להשתמש ב-
setTag()כדי להוסיף תג לשם המעקב. התג הזה עוזר לזהות את המעקב. - אופציונלי: מגדירים את משך הזמן. משתמשים ב-
setDurationMs()כדי לציין את משך הזמן של יצירת הפרופיל באלפיות השנייה. לדוגמה,60000מגדיר מעקב של 60 שניות. המעקב מסתיים באופן אוטומטי אחרי משך הזמן שצוין, אם לא מפעילים אתCancellationSignalלפני כן. - בוחרים מדיניות מאגר נתונים זמני. משתמשים ב-
setBufferFillPolicy()כדי להגדיר איך נתוני המעקב נשמרים.BufferFillPolicy.RING_BUFFERפירושו שכאשר המאגר מלא, נתונים חדשים מחליפים את הנתונים הישנים ביותר, וכך נשמר תיעוד רציף של הפעילות האחרונה. - מגדירים את גודל המאגר. אפשר להשתמש ב-
setBufferSizeKb()כדי לציין את גודל המאגר למעקב, שבאמצעותו אפשר לשלוט בגודל של קובץ המעקב שנוצר.
- מגדירים תג. אפשר להשתמש ב-
אופציונלי: ניהול מחזור החיים של הסשן. יוצרים
CancellationSignal. האובייקט הזה מאפשר לכם לעצור את סשן הפרופיילינג מתי שתרצו, וכך לשלוט באופן מדויק במשך שלו.מתחילים ומקבלים תוצאות. כשקוראים לפונקציה
requestProfiling(),ProfilingManagerמתחיל סשן של יצירת פרופיל ברקע. בסיום יצירת הפרופיל, הוא שולח את ה-ProfilingResultל-methodresultCallback#accept. אם יצירת הפרופיל מסתיימת בהצלחה,ProfilingResultמספק את הנתיב שבו נשמר המעקב במכשיר באמצעותProfilingResult#getResultFilePath. אפשר לקבל את הקובץ הזה באופן פרוגרמטי, או על ידי הפעלתadb pull <trace_path>מהמחשב, כדי ליצור פרופיל מקומי.הוספת נקודות מעקב בהתאמה אישית אתם יכולים להוסיף נקודות מעקב בהתאמה אישית בקוד של האפליקציה. בדוגמת הקוד הקודמת, הבלוק
trace("MyApp:HeavyOperation") { ... }יוצר פרוסה בהתאמה אישית בפרופיל שנוצר.