בדף הזה מוסבר איך לתעד מעקב מערכת באמצעות ProfilingManager API.
ProfilingManager אפשר גם להקליט סוגים אחרים של פרופילים. התהליך הזה דומה לתהליך של הקלטת מעקב מערכת, אבל כל סוג משתמש בבונה אחר. פרופילים נתמכים והכלים ליצירתם:
עקבות מערכת: נרשמים באמצעות
SystemTraceRequestBuilder, והם שימושיים לניתוח זמן האחזור ולניפוי באגים כלליים בביצועים.Heap dumps: נתונים שמוקלטים באמצעות
JavaHeapDumpRequestBuilder, שימושיים לזיהוי דליפות זיכרון ולאופטימיזציה.פרופילים של ערימה (heap): נרשמים באמצעות
HeapProfileRequestBuilder, והם שימושיים לאופטימיזציה של הזיכרון.פרופילים של סטאק ביצוע: נרשמים באמצעות
StackSamplingRequestBuilder, ומועילים להבנת ביצוע הקוד ולניתוח זמן האחזור.
הוספת יחסי תלות
כדי ליהנות מחוויית השימוש הטובה ביותר ב-ProfilingManager API, מוסיפים את ספריות Jetpack הבאות לקובץ build.gradle.kts.
Kotlin
dependencies { implementation("androidx.tracing:tracing-ktx:2.0.1") implementation("androidx.core:core:1.19.0") }
מגניב
dependencies { implementation 'androidx.tracing:tracing:2.0.1' implementation 'androidx.core:core:1.19.0' }
תיעוד עקבות המערכת
אחרי שמוסיפים את יחסי התלות הנדרשים, משתמשים בקוד הבא כדי לתעד מעקב מערכת. בדוגמה הזו מוסבר איך להתחיל סשן פרופילים מרכיב שאפשר להרכיב, תוך ניהול בטוח של פעולות כבדות מחוץ לשרשור הראשי.
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כדי להגדיר את השרשור שיקבל את תוצאות הפרופיל. יצירת הפרופילים מתבצעת ברקע. שימוש ב-executor של thread שאינו UI עוזר למנוע שגיאות מסוג 'האפליקציה לא מגיבה (ANR)' אם מוסיפים עיבוד נוסף לקריאה החוזרת מאוחר יותר.טיפול בתוצאות של יצירת פרופילים. יוצרים אובייקט
Consumer<ProfilingResult>. המערכת משתמשת באובייקט הזה כדי לשלוח את תוצאות הפרופיל מ-ProfilingManagerבחזרה לאפליקציה שלכם.יוצרים את בקשת הפרופיל. יוצרים
SystemTraceRequestBuilderכדי להגדיר את סשן הפרופיל. הכלי הזה מאפשר לכם להתאים אישית את הגדרות המעקב שלProfilingManager. התאמה אישית של הכלי היא אופציונלית. אם לא מבצעים התאמה אישית, המערכת משתמשת בהגדרות ברירת המחדל.- הגדרת תג משתמשים ב-
setTag()כדי להוסיף תג לשם של ה-trace. התג הזה עוזר לכם לזהות את העקבות. - אופציונלי: הגדרת משך הזמן. משתמשים ב-
setDurationMs()כדי לציין את משך הפרופיל באלפיות השנייה. לדוגמה,60000מגדיר מעקב למשך 60 שניות. המעקב מסתיים אוטומטית אחרי משך הזמן שצוין אם לא מופעלת לפני כן הפקודהCancellationSignal. - בוחרים מדיניות של מאגר זמני. משתמשים ב-
setBufferFillPolicy()כדי להגדיר איך נתוני המעקב מאוחסנים. BufferFillPolicy.RING_BUFFERפירושו שכשהמאגר מלא, נתונים חדשים מחליפים את הנתונים הכי ישנים, וכך נשמרת רשומה רציפה של הפעילות האחרונה. - מגדירים את גודל המאגר. משתמשים במאפיין
setBufferSizeKb()כדי לציין את שטח האחסון הזמני למעקב. כך אפשר לשלוט בגודל של קובץ הפלט של המעקב.
- הגדרת תג משתמשים ב-
אופציונלי: ניהול מחזור החיים של הסשן. יצירת
CancellationSignal. האובייקט הזה מאפשר לכם להפסיק את סשן יצירת הפרופיל מתי שתרצו, וכך לשלוט בדיוק באורך שלו.איך מתחילים ומקבלים תוצאות כשמתקשרים אל
requestProfiling(),ProfilingManagerמתחיל סשן פרופילים ברקע. אחרי שהפרופיל מוכן, הוא נשלח לשיטתProfilingResultשלכם ב-resultCallback#accept. אם יצירת הפרופיל מסתיימת בהצלחה,ProfilingResultמספק את הנתיב שבו השמירה של נתוני המעקב במכשיר בוצעה דרךProfilingResult#getResultFilePath. אפשר לקבל את הקובץ הזה באופן פרוגרמטי או, ליצירת פרופיל מקומי, על ידי הפעלת הפקודהadb pull <trace_path>מהמחשב.הוספת נקודות מעקב בהתאמה אישית אפשר להוסיף נקודות מעקב מותאמות אישית לקוד של האפליקציה. בדוגמה הקודמת, הבלוק
trace("MyApp:HeavyOperation") { ... }יוצר פרופיל בהתאמה אישית בפרופיל שנוצר.