Debug dei profili di riferimento

Questo documento fornisce best practice e passaggi per la risoluzione dei problemi per diagnosticare i problemi e assicurarsi che i profili di base funzionino correttamente per fornire il massimo vantaggio.

Problemi di compilazione

Se hai copiato l'esempio di profili di base nell'app di esempio Now in Android, potresti riscontrare errori di test durante l'attività Profilo di base che indica che i test non possono essere eseguiti su un emulatore:

./gradlew assembleDemoRelease
Starting a Gradle Daemon (subsequent builds will be faster)
Calculating task graph as no configuration cache is available for tasks: assembleDemoRelease
Type-safe project accessors is an incubating feature.

> Task :benchmarks:pixel6Api33DemoNonMinifiedReleaseAndroidTest
Starting 14 tests on pixel6Api33

com.google.samples.apps.nowinandroid.foryou.ScrollForYouFeedBenchmark > scrollFeedCompilationNone[pixel6Api33] FAILED
        java.lang.AssertionError: ERRORS (not suppressed): EMULATOR
        WARNINGS (suppressed):
        ...

Gli errori si verificano perché Now in Android utilizza un dispositivo gestito da Gradle per la generazione del profilo di base. Gli errori sono previsti perché in genere non devi eseguire benchmark delle prestazioni su un emulatore. Tuttavia, poiché non raccogli le metriche delle prestazioni quando generi i profili di base, puoi eseguire la raccolta dei profili di base sugli emulatori per comodità. Per utilizzare i profili di base con un emulatore, esegui la build e l'installazione dalla riga di comando e imposta un argomento per attivare le regole dei profili di base:

installDemoRelease -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile

In alternativa, puoi creare una configurazione di esecuzione personalizzata in Android Studio per attivare i profili di base sugli emulatori selezionando Esegui > Modifica configurazioni:

Aggiungere una configurazione di esecuzione personalizzata per creare profili di base in Now in Android
Figura 1. Aggiungi una configurazione di esecuzione personalizzata per creare profili di base in Now in Android.

Verificare l'installazione e l'applicazione del profilo

Per verificare che l'APK o l'Android App Bundle (AAB) che stai esaminando provenga da una variante di build che include i profili di base:

  1. In Android Studio, seleziona Build > Analyze APK.
  2. Apri l'AAB o l'APK.
  3. Verifica che il file baseline.prof esista:

    • Se stai esaminando un AAB, il profilo si trova in /BUNDLE-METADATA/com.android.tools.build.profiles/baseline.prof.
    • Se stai esaminando un APK, il profilo si trova in /assets/dexopt/baseline.prof.

      La presenza di questo file è il primo segno di una configurazione di build corretta. Se manca, significa che Android Runtime non riceverà istruzioni di precompilazione al momento dell'installazione.

      Controllare un profilo di base utilizzando APK Analyzer in Android Studio
      Figura 2. Controlla la presenza di un profilo di base utilizzando lo strumento di analisi APK in Android Studio.

I profili di base devono essere compilati sul dispositivo su cui è in esecuzione l'app. Quando installi build non eseguibili in modalità di debug utilizzando Android Studio o lo strumento a riga di comando Gradle Wrapper, la compilazione sul dispositivo avviene automaticamente. Se installi l'app dal Google Play Store, i profili di base vengono compilati durante gli aggiornamenti in background del dispositivo anziché al momento dell'installazione. Quando l'app viene installata utilizzando altri strumenti, la libreria Jetpack ProfileInstaller è responsabile dell'accodamento dei profili per la compilazione durante il successivo processo di ottimizzazione DEX in background.

In questi casi, se vuoi assicurarti che i profili di base vengano utilizzati, potresti dover forzare la compilazione dei profili di base. ProfileVerifier consente di eseguire query sullo stato dell'installazione e della compilazione del profilo, come mostrato nell'esempio seguente:

Kotlin

private const val TAG = "MainActivity"

class MainActivity : ComponentActivity() {
  ...
  override fun onResume() {
    super.onResume()
    lifecycleScope.launch {
      logCompilationStatus()
    }
  }

  private suspend fun logCompilationStatus() {
     withContext(Dispatchers.IO) {
        val status = ProfileVerifier.getCompilationStatusAsync().await()
        when (status.profileInstallResultCode) {
            RESULT_CODE_NO_PROFILE ->
                Log.d(TAG, "ProfileInstaller: Baseline Profile not found")
            RESULT_CODE_COMPILED_WITH_PROFILE ->
                Log.d(TAG, "ProfileInstaller: Compiled with profile")
            RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION ->
                Log.d(TAG, "ProfileInstaller: Enqueued for compilation")
            RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING ->
                Log.d(TAG, "ProfileInstaller: App was installed through Play store")
            RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST ->
                Log.d(TAG, "ProfileInstaller: PackageName not found")
            RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ ->
                Log.d(TAG, "ProfileInstaller: Cache file exists but cannot be read")
            RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE ->
                Log.d(TAG, "ProfileInstaller: Can't write cache file")
            RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION ->
                Log.d(TAG, "ProfileInstaller: Enqueued for compilation")
            else ->
                Log.d(TAG, "ProfileInstaller: Profile not compiled or enqueued")
        }
    }
}

Java

public class MainActivity extends ComponentActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onResume() {
        super.onResume();

        logCompilationStatus();
    }

    private void logCompilationStatus() {
         ListeningExecutorService service = MoreExecutors.listeningDecorator(
                Executors.newSingleThreadExecutor());
        ListenableFuture<ProfileVerifier.CompilationStatus> future =
                ProfileVerifier.getCompilationStatusAsync();
        Futures.addCallback(future, new FutureCallback<>() {
            @Override
            public void onSuccess(CompilationStatus result) {
                int resultCode = result.getProfileInstallResultCode();
                if (resultCode == RESULT_CODE_NO_PROFILE) {
                    Log.d(TAG, "ProfileInstaller: Baseline Profile not found");
                } else if (resultCode == RESULT_CODE_COMPILED_WITH_PROFILE) {
                    Log.d(TAG, "ProfileInstaller: Compiled with profile");
                } else if (resultCode == RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION) {
                    Log.d(TAG, "ProfileInstaller: Enqueued for compilation");
                } else if (resultCode == RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING) {
                    Log.d(TAG, "ProfileInstaller: App was installed through Play store");
                } else if (resultCode == RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST) {
                    Log.d(TAG, "ProfileInstaller: PackageName not found");
                } else if (resultCode == RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ) {
                    Log.d(TAG, "ProfileInstaller: Cache file exists but cannot be read");
                } else if (resultCode
                        == RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE) {
                    Log.d(TAG, "ProfileInstaller: Can't write cache file");
                } else if (resultCode == RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION) {
                    Log.d(TAG, "ProfileInstaller: Enqueued for compilation");
                } else {
                    Log.d(TAG, "ProfileInstaller: Profile not compiled or enqueued");
                }
            }

            @Override
            public void onFailure(Throwable t) {
                Log.d(TAG,
                        "ProfileInstaller: Error getting installation status: " + t.getMessage());
            }
        }, service);
    }
}

I seguenti codici di risultato forniscono suggerimenti sulla causa di alcuni problemi:

RESULT_CODE_COMPILED_WITH_PROFILE
Il profilo viene installato, compilato e utilizzato ogni volta che viene eseguita l'app. Questo è il risultato che vuoi visualizzare.
RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED
Nessun profilo trovato nell'APK in esecuzione. Se visualizzi questo errore, assicurati di utilizzare una variante di build che includa i profili di base e che l'APK contenga un profilo.
RESULT_CODE_NO_PROFILE
Nessun profilo è stato installato per questa app durante l'installazione tramite l'app store o il gestore dei pacchetti. Il motivo principale di questo codice di errore è che il programma di installazione del profilo non è stato eseguito perché ProfileInstallerInitializer è stato disattivato. Tieni presente che quando viene segnalato questo errore, è stato comunque trovato un profilo incorporato nell'APK dell'applicazione. Quando non viene trovato un profilo incorporato, il codice di errore restituito è RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED.
RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION
Un profilo viene trovato nell'APK o nell'AAB e viene messo in coda per la compilazione. Quando un profilo viene installato da ProfileInstaller, viene messo in coda per la compilazione la volta successiva che il sistema esegue l'ottimizzazione DEX in background. Il profilo non è attivo finché la compilazione non viene completata. Non tentare di eseguire il benchmark dei profili di base finché la compilazione non è completata. Potrebbe essere necessario forzare la compilazione dei profili di base. Questo errore non si verifica quando l'app viene installata da Play Store o dal gestore di pacchetti sui dispositivi con Android 9 (API 28) e versioni successive, perché la compilazione viene eseguita durante l'installazione.
RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING
È installato un profilo non corrispondente e l'app è stata compilata con questo profilo. Questo è il risultato dell'installazione tramite Google Play Store o package manager. Tieni presente che questo risultato è diverso da RESULT_CODE_COMPILED_WITH_PROFILE perché il profilo non corrispondente compilerà solo i metodi ancora condivisi tra il profilo e l'app. Il profilo è effettivamente più piccolo del previsto e verranno compilati meno metodi rispetto a quelli inclusi nel profilo di base.
RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE
ProfileVerifier non può scrivere il file della cache dei risultati della verifica. Questo può accadere perché si è verificato un problema con le autorizzazioni della cartella dell'app o se non c'è spazio libero su disco sufficiente sul dispositivo.
RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION
ProfileVerifieris running on an unsupported API version of Android. ProfileVerifier supporta solo Android 9 (livello API 28) e versioni successive.
RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST
Viene generata un'PackageManager.NameNotFoundException quando viene eseguita una query su PackageManager per il pacchetto dell'app. Questo dovrebbe accadere raramente. Prova a disinstallare l'app e reinstallare tutto.
RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ
Esiste un file di cache dei risultati di verifica precedenti, ma non può essere letto. Questo dovrebbe accadere raramente. Prova a disinstallare l'app e a reinstallare tutto.

Utilizzare ProfileVerifier in produzione

In produzione, puoi utilizzare ProfileVerifier insieme alle librerie di generazione di report di analisi, come Google Analytics for Firebase, per generare eventi di analisi che indicano lo stato del profilo. Ad esempio, questo ti avvisa rapidamente se viene rilasciata una nuova versione dell'app che non contiene profili di base.

Forza la compilazione dei profili di base

Se lo stato di compilazione dei profili di base è RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION, puoi forzare la compilazione immediata utilizzando adb:

adb shell cmd package compile -r bg-dexopt PACKAGE_NAME

Controlla lo stato di compilazione del profilo di base senza ProfileVerifier

Se non utilizzi ProfileVerifier, puoi controllare lo stato della compilazione utilizzando adb, anche se non fornisce informazioni approfondite come ProfileVerifier:

adb shell dumpsys package dexopt | grep -A 2 PACKAGE_NAME

L'utilizzo di adb produce un risultato simile al seguente:

  [com.google.samples.apps.nowinandroid.demo]
    path: /data/app/~~dzJiGMKvp22vi2SsvfjkrQ==/com.google.samples.apps.nowinandroid.demo-7FR1sdJ8ZTy7eCLwAnn0Vg==/base.apk
      arm64: [status=speed-profile] [reason=bg-dexopt] [primary-abi]
        [location is /data/app/~~dzJiGMKvp22vi2SsvfjkrQ==/com.google.samples.apps.nowinandroid.demo-7FR1sdJ8ZTy7eCLwAnn0Vg==/oat/arm64/base.odex]

Il valore dello stato indica lo stato di compilazione del profilo ed è uno dei seguenti valori:

Stato della compilazione Significato
speed‑profile Esiste un profilo compilato e viene utilizzato.
verify Non esiste alcun profilo compilato.

Lo stato verify non significa che l'APK o l'AAB non contenga un profilo, perché può essere messo in coda per la compilazione dalla successiva attività di ottimizzazione DEX in background.

Il valore del motivo indica cosa attiva la compilazione del profilo ed è uno dei seguenti valori:

Motivo Significato
install‑dm Un profilo di base è stato compilato manualmente o da Google Play durante l'installazione dell'app.
bg‑dexopt È stato compilato un profilo mentre il dispositivo era inattivo. Potrebbe trattarsi di un profilo di base o di un profilo raccolto durante l'utilizzo dell'app.
cmdline La compilazione è stata attivata utilizzando adb. Potrebbe trattarsi di un profilo di base o di un profilo raccolto durante l'utilizzo dell'app.

Verifica dell'applicazione del profilo di avvio a DEX e r8.json

Le regole del profilo di avvio vengono utilizzate al momento della creazione da R8 per ottimizzare il layout delle classi nei file DEX. Questa ottimizzazione in fase di compilazione è diversa da come vengono utilizzati i profili di base (baseline.prof), in quanto sono inclusi nell'APK o nell'AAB per consentire ad ART di eseguire la compilazione sul dispositivo. Poiché le regole del profilo di avvio vengono applicate durante la procedura di build, non esiste un file startup.prof separato all'interno dell'APK o dell'AAB da esaminare. L'effetto dei profili di avvio è visibile nel layout del file DEX.

Ispeziona la disposizione DEX con r8.json (consigliato per AGP 8.8 o versioni successive)

Per i progetti che utilizzano il plug-in Android per Gradle (AGP) 8.8 o versioni successive, puoi verificare se il profilo di avvio è stato applicato esaminando il file r8.json generato. Questo file è incluso nel tuo AAB.

  1. Apri l'archivio AAB e individua il file r8.json.
  2. Cerca nel file l'array dexFiles, che elenca i file DEX generati.
  3. Cerca un oggetto dexFiles che contenga la coppia chiave-valore "startup": true. Ciò indica esplicitamente che le regole del profilo di avvio sono state applicate per ottimizzare il layout di quel file DEX specifico.

    "dexFiles": [
     {
       "checksum": "...",
       "startup": true // This flag confirms profile application to this DEX file
     },
     // ... other DEX files
    ]
    

Controlla la disposizione DEX per tutte le versioni di AGP

Se utilizzi una versione di AGP precedente alla 8.8, l'ispezione dei file DEX è il modo principale per verificare che il profilo di avvio sia stato applicato correttamente. Puoi utilizzare questo metodo anche se utilizzi AGP 8.8 o versioni successive e vuoi controllare manualmente il layout DEX. Ad esempio, se non noti i miglioramenti previsti del rendimento. Per esaminare l'accordo DEX:

  1. Apri l'AAB o l'APK utilizzando Build > Analyze APK (Build > Analizza APK) in Android Studio.
  2. Vai al primo file DEX. Ad esempio, classes.dex.
  3. Ispeziona i contenuti di questo file DEX. Dovresti essere in grado di verificare che le classi e i metodi critici definiti nel file del profilo di avvio (startup-prof.txt) siano presenti in questo file DEX principale. Un'applicazione riuscita significa che questi componenti critici per le startup vengono assegnati la priorità per un caricamento più rapido.

Problemi di prestazioni

Questa sezione mostra alcune best practice per definire e confrontare correttamente i profili di base per ottenere il massimo vantaggio.

Eseguire correttamente il benchmarking delle metriche di avvio

I profili di base saranno più efficaci se le metriche di avvio sono ben definite. Le due metriche chiave sono il tempo di attesa per la prima schermata (TTID) e il tempo di attesa per la visualizzazione completa (TTFD).

Il TTID si verifica quando l'app disegna il primo frame. È importante mantenere questo valore il più breve possibile perché la visualizzazione di qualcosa mostra all'utente che l'app è in esecuzione. Puoi anche visualizzare un indicatore di avanzamento indeterminato per mostrare che l'app è reattiva.

TTFD è il momento in cui è possibile interagire effettivamente con l'app. È importante mantenere questo periodo il più breve possibile per evitare la frustrazione degli utenti. Se segnali correttamente TTFD, indichi al sistema che il codice eseguito durante il caricamento di TTFD fa parte dell'avvio dell'app. Di conseguenza, il sistema ha più probabilità di inserire questo codice nel profilo.

Mantieni TTID e TTFD il più bassi possibile per rendere la tua app reattiva.

Il sistema è in grado di rilevare l'ID pubblicità, visualizzarlo in Logcat e segnalarlo come parte dei benchmark di avvio. Tuttavia, il sistema non è in grado di determinare il TTFD ed è responsabilità dell'app segnalare quando raggiunge uno stato interattivo completamente disegnato. Puoi farlo chiamando reportFullyDrawn() o ReportDrawn se utilizzi Jetpack Compose. Se hai più attività in background che devono essere completate prima che l'app venga considerata completamente disegnata, puoi utilizzare FullyDrawnReporter, come descritto in Migliorare l'accuratezza della tempistica di avvio.

Profili della biblioteca e profili personalizzati

Quando si esegue il benchmarking dell'impatto dei profili, può essere difficile separare i vantaggi dei profili della tua app da quelli forniti da librerie, come le librerie Jetpack. Quando crei l'APK, il plug-in Android per Gradle aggiunge tutti i profili nelle dipendenze di libreria, nonché il tuo profilo personalizzato. Questa opzione è utile per ottimizzare le prestazioni complessive ed è consigliata per le build di rilascio. Tuttavia, è difficile misurare l'aumento di rendimento aggiuntivo derivante dal tuo profilo personalizzato.

Un modo rapido per visualizzare manualmente l'ottimizzazione aggiuntiva fornita dal tuo profilo personalizzato è rimuoverlo ed eseguire i benchmark. Quindi, sostituiscilo ed esegui di nuovo i benchmark. Il confronto tra i due mostra le ottimizzazioni fornite dai soli profili delle librerie e dai profili delle librerie più il tuo profilo personalizzato.

Un modo automatizzabile per confrontare i profili è creare una nuova variante di build che contenga solo i profili della libreria e non il tuo profilo personalizzato. Confronta i benchmark di questa variante con la variante di rilascio che contiene sia i profili delle librerie sia i tuoi profili personalizzati. L'esempio seguente mostra come configurare la variante che include solo i profili della raccolta. Aggiungi una nuova variante denominata releaseWithoutCustomProfile al modulo consumer del profilo, che in genere è il modulo dell'app:

Kotlin

android {
  ...
  buildTypes {
    ...
    // Release build with only library profiles.
    create("releaseWithoutCustomProfile") {
      initWith(release)
    }
    ...
  }
  ...
}
...
dependencies {
  ...
  // Remove the baselineProfile dependency.
  // baselineProfile(project(":baselineprofile"))
}

baselineProfile {
  variants {
    create("release") {
      from(project(":baselineprofile"))
    }
  }
}

Groovy

android {
  ...
  buildTypes {
    ...
    // Release build with only library profiles.
    releaseWithoutCustomProfile {
      initWith(release)
    }
    ...
  }
  ...
}
...
dependencies {
  ...
  // Remove the baselineProfile dependency.
  // baselineProfile ':baselineprofile"'
}

baselineProfile {
  variants {
    release {
      from(project(":baselineprofile"))
    }
  }
}

L'esempio di codice precedente rimuove la dipendenza baselineProfile da tutte le varianti e la applica selettivamente solo alla variante release. Potrebbe sembrare controintuitivo che i profili della libreria vengano ancora aggiunti quando viene rimossa la dipendenza dal modulo del produttore del profilo. Tuttavia, questo modulo è responsabile solo della generazione del tuo profilo personalizzato. Il plug-in Android Gradle è ancora in esecuzione per tutte le varianti ed è responsabile dell'inclusione dei profili delle librerie.

Devi anche aggiungere la nuova variante al modulo del generatore di profili. In questo esempio, il modulo del produttore si chiama :baselineprofile.

Kotlin

android {
  ...
    buildTypes {
      ...
      // Release build with only library profiles.
      create("releaseWithoutCustomProfile") {}
      ...
    }
  ...
}

Groovy

android {
  ...
    buildTypes {
      ...
      // Release build with only library profiles.
      releaseWithoutCustomProfile {}
      ...
    }
  ...
}

Quando esegui il benchmark da Android Studio, seleziona una variante releaseWithoutCustomProfile per misurare il rendimento solo con i profili della libreria oppure seleziona una variante release per misurare il rendimento con i profili della libreria e quelli personalizzati.

Evita l'avvio di app con I/O limitato

Se la tua app esegue molte chiamate I/O o di rete durante l'avvio, ciò può influire negativamente sia sul tempo di avvio dell'app sia sull'accuratezza del benchmarking dell'avvio. Queste chiamate pesanti possono richiedere quantità di tempo indeterminate che possono variare nel tempo e persino tra le iterazioni dello stesso benchmark. Le chiamate I/O sono generalmente migliori delle chiamate di rete, perché queste ultime possono essere influenzate da fattori esterni al dispositivo e sul dispositivo stesso. Evita chiamate di rete durante l'avvio. Se l'utilizzo di uno o dell'altro è inevitabile, utilizza I/O.

Ti consigliamo di fare in modo che l'architettura dell'app supporti l'avvio dell'app senza chiamate di rete o I/O, anche se solo per utilizzarla durante il benchmarking dell'avvio. In questo modo, si garantisce la variabilità più bassa possibile tra le diverse iterazioni dei benchmark.

Se la tua app utilizza Hilt, puoi fornire implementazioni finte associate a operazioni di I/O durante il benchmarking in Microbenchmark e Hilt.

Coprire tutti i percorsi utente importanti

È importante coprire con precisione tutti i percorsi utente importanti nella generazione del profilo di base. I percorsi utente non coperti non verranno migliorati dai profili di base. I profili di base più efficaci includono tutti i percorsi utente di avvio comuni, nonché i percorsi utente in-app sensibili alle prestazioni, come lo scorrimento degli elenchi.

Modifiche al profilo in fase di compilazione dei test A/B

Poiché i profili di avvio e di base sono un'ottimizzazione in fase di compilazione, in genere non è supportato il test A/B diretto di APK diversi utilizzando il Google Play Store per le release di produzione. Per valutare l'impatto in un ambiente simile a quello di produzione, valuta i seguenti approcci:

  • Release fuori ciclo: carica una release fuori ciclo su una piccola percentuale della tua base utenti che include solo la modifica del profilo. In questo modo puoi raccogliere metriche reali sulla differenza di rendimento.

  • Benchmark locale: esegui il benchmark locale della tua app con e senza il profilo applicato. Tuttavia, tieni presente che il benchmarking locale mostra lo scenario migliore per i profili, in quanto non include gli effetti dei profili cloud di ART presenti nei dispositivi di produzione.