Debug dei profili di riferimento

Questo documento mostra le best practice per aiutare a diagnosticare i problemi e garantire che i profili di riferimento funzionino correttamente per fornire il massimo vantaggio.

Problemi relativi alla build

Se hai copiato l'esempio di Profili di riferimento nell'app di esempio Ora in Android, potresti riscontrare errori di test durante l'attività Profilo di riferimento, in cui si 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 riferimento. Gli errori sono previsti, perché in genere non dovresti eseguire i benchmark delle prestazioni su un emulatore. Tuttavia, poiché non raccogli metriche sul rendimento quando generi profili di riferimento, per praticità puoi eseguire la raccolta dei profili di riferimento su emulatori. Per utilizzare i profili di riferimento con un emulatore, esegui la build e l'installazione dalla riga di comando e imposta un argomento per abilitare le regole dei profili di riferimento:

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

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

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

Problemi di installazione

Verifica che l'APK o l'AAB che stai creando provenga da una variante di build che include i profili di riferimento. Il modo più semplice per verificarlo è aprire l'APK in Android Studio selezionando Crea > Analizza APK, aprendo il tuo APK e cercando il profilo nel file /assets/dexopt/baseline.prof:

Controllare se è presente un profilo di riferimento utilizzando il Visualizzatore APK in Android Studio
Figura 2. Verifica la presenza di un profilo di riferimento utilizzando il Visualizzatore APK in Android Studio.

I profili di riferimento devono essere compilati sul dispositivo su cui è in esecuzione l'app. Sia per le installazioni di app store sia per le app installate utilizzando PackageInstaller, la compilazione on-device avviene come parte del processo di installazione dell'app. Tuttavia, quando l'app viene installata tramite sideload da Android Studio o mediante strumenti a riga di comando, la libreria Jetpack ProfileInstaller è responsabile di accodare i profili per la compilazione durante il successivo processo di ottimizzazione DEX in background. In questi casi, per assicurarti che vengano utilizzati i profili di riferimento, potrebbe essere necessario forzare la compilazione dei profili di riferimento. 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 risultato forniscono suggerimenti sulla causa di alcuni problemi:

RESULT_CODE_COMPILED_WITH_PROFILE
Il profilo viene installato, compilato e utilizzato ogni volta che l'app viene eseguita. Questo è il risultato che vuoi vedere.
RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED
Nessun profilo trovato nell'APK o nell'AAB in esecuzione. Se visualizzi questo errore, assicurati di utilizzare una variante di build che includa Profili di riferimento e che l'APK contenga un profilo.
RESULT_CODE_NO_PROFILE
Nessun profilo è stato installato per questa app durante l'installazione tramite lo store o il gestore di pacchetti. Il motivo principale di questo codice di errore è che il programma di installazione del profilo non è stato eseguito a causa della disattivazione di ProfileInstallerInitializer. Tieni presente che quando viene segnalato questo errore, è stato ancora 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
Nell'APK o nell'AAB viene trovato un profilo ed è in coda per la compilazione. Quando un profilo viene installato da ProfileInstaller, viene messo in coda per la compilazione la prossima volta che il sistema esegue l'ottimizzazione DEX in background. Il profilo non è attivo fino al completamento della compilazione. Non provare a confrontare i tuoi profili di riferimento fino al termine della compilazione. Potrebbe essere necessario forzare la compilazione dei profili di riferimento. Questo errore non si verifica se l'app viene installata dallo store o dal gestore di pacchetti su dispositivi con Android 9 (API 28) e versioni successive, perché la compilazione viene eseguita durante l'installazione.
RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING
Viene installato un profilo non corrispondente e l'app è stata compilata. Questo è il risultato dell'installazione tramite Google Play Store o gestore di pacchetti. Tieni presente che questo risultato è diverso da RESULT_CODE_COMPILED_WITH_PROFILE perché il profilo non corrispondente compilerà solo i metodi che sono ancora condivisi tra il profilo e l'app. Il profilo è effettivamente di dimensioni inferiori a quanto previsto e verranno compilati meno metodi di quelli inclusi nel Profilo di riferimento.
RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE
ProfileVerifier non può scrivere il file della cache dei risultati della verifica. Questo può essere dovuto a un problema con le autorizzazioni della cartella dell'app o se non c'è abbastanza spazio libero su disco 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 generato un PackageManager.NameNotFoundException quando si esegue una query su PackageManager per il pacchetto dell'app. Questo dovrebbe accadere di rado. Prova a disinstallare l'app e a reinstallare tutto.
RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ
Esiste un file precedente della cache dei risultati della verifica, ma non può essere letto. Questo dovrebbe accadere di rado. Prova a disinstallare l'app e a reinstallare tutto.

Utilizzare ProfileVerifier in produzione

In produzione, puoi utilizzare ProfileVerifier insieme alle librerie 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 i profili di riferimento.

Forza la compilazione dei profili di riferimento

Se lo stato di compilazione dei profili di riferimento è 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 della compilazione senza ProfileVerifier

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

adb shell dumpsys package dexopt | grep -A 2 PACKAGE_NAME

L'uso di adb produce qualcosa di simile a quanto segue:

  [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 di stato indica lo stato di compilazione del profilo ed è uno dei seguenti valori:

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

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

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

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

Problemi di prestazioni

Questa sezione mostra alcune best practice per definire e confrontare in modo corretto i profili di riferimento per trarne il massimo vantaggio.

Confronta correttamente le metriche di avvio

I profili di riferimento saranno più efficaci se le metriche di startup sono ben definite. Le due metriche chiave sono il tempo alla visualizzazione iniziale (TTID) e il tempo alla visualizzazione completa (TTFD).

Il TTID si riferisce al momento in cui l'app disegna il primo frame. È importante che questo sia quanto più breve possibile perché la visualizzazione di un elemento indica all'utente che l'app è in esecuzione. Puoi anche visualizzare un indicatore di avanzamento indeterminato per indicare che l'app è reattiva.

Si parla di TTFD quando è possibile effettivamente interagire con l'app. È importante che questo sia quanto più breve possibile per evitare frustrazione degli utenti. Se segnali correttamente TTFD, stai comunicando al sistema che il codice eseguito lungo il percorso per arrivare a TTFD fa parte dell'avvio dell'app. Di conseguenza, è più probabile che il sistema inserisca questo codice nel profilo.

Mantieni TTID e TTFD i più bassi possibile per far sembrare la tua app reattiva.

Il sistema è in grado di rilevare il TTID, visualizzarlo in Logcat e segnalarlo come parte dei benchmark di avvio. Tuttavia, il sistema non è in grado di determinare la TTFD ed è responsabilità dell'app generare report quando raggiunge uno stato interattivo completamente tracciato. Puoi farlo chiamando il numero reportFullyDrawn() oppure ReportDrawn se utilizzi Jetpack Compose. Se hai più attività in background che devono essere tutte completate prima che l'app venga considerata completamente disegnata, puoi utilizzare FullyDrawnReporter, come descritto in Migliorare l'accuratezza delle tempistiche di avvio.

Profili della raccolta e profili personalizzati

Quando esegui il benchmarking dell'impatto dei profili, può essere difficile separare i vantaggi dei profili della tua app dai profili forniti dalle librerie, ad esempio le librerie Jetpack. Quando crei il tuo APK, il plug-in Android Gradle aggiunge eventuali profili nelle dipendenze di libreria, nonché il tuo profilo personalizzato. È utile per ottimizzare le prestazioni complessive ed è consigliata per le build di release. Tuttavia, rende difficile misurare il miglioramento del rendimento aggiuntivo derivante dal tuo profilo personalizzato.

Un modo rapido per visualizzare manualmente l'ottimizzazione aggiuntiva fornita dal profilo personalizzato consiste nel rimuoverla ed eseguire i benchmark. Poi sostituiscilo ed esegui di nuovo i benchmark. Il confronto tra i due ti consente di visualizzare le ottimizzazioni fornite solo dai profili delle librerie e i profili della libreria più il tuo profilo personalizzato.

Un modo automatizzabile di confrontare i profili consiste nel creare una nuova variante di compilazione che contenga solo i profili della libreria e non il tuo profilo personalizzato. Confronta i benchmark di questa variante con la variante di release che contiene sia i profili della libreria sia i tuoi profili personalizzati. L'esempio seguente mostra come configurare la variante che include solo i profili delle librerie. Aggiungi una nuova variante denominata releaseWithoutCustomProfile al modulo consumer del tuo profilo, che in genere corrisponde al 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"))
    }
  }
}

trendy

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 delle librerie vengano ancora aggiunti quando la dipendenza dal modulo di producer del profilo viene rimossa. Tuttavia, questo modulo è responsabile solo di generare il 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 producer è denominato :baselineprofile.

Kotlin

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

trendy

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 raccolta oppure seleziona una variante release per misurare il rendimento con la raccolta e i profili personalizzati.

Evita l'avvio dell'app legato a I/O

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 sulla precisione del benchmark di avvio dell'app. Queste chiamate possono richiedere indeterminati periodi di tempo che possono variare nel tempo e anche tra iterazioni dello stesso benchmark. Le chiamate I/O di solito sono migliori delle chiamate di rete, perché queste ultime possono essere influenzate da fattori esterni al dispositivo e al dispositivo stesso. Evita le chiamate di rete durante l'avvio. Dove è inevitabile utilizzare l'una o l'altra opzione, utilizza l'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 da utilizzare solo all'avvio del benchmarking. Ciò contribuisce a garantire la minore variabilità possibile tra le diverse iterazioni dei benchmark.

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

Tratta tutti i percorsi degli utenti importanti

È importante coprire con precisione tutti i percorsi dell'utente importanti nella generazione del profilo di riferimento. I percorsi dell'utente non inclusi non saranno migliorati dai profili di riferimento. I profili di riferimento più efficaci includono tutti i percorsi comuni degli utenti di startup, nonché i percorsi degli utenti in-app sensibili alle prestazioni, come gli elenchi di scorrimento.