Principes de base de Cronet

1. Introduction

1ee223bf9e1b75fb.png

Dernière mise à jour : 06/05/2022

Cronet est la pile réseau Chromium mise à la disposition des applications Android en tant que bibliothèque. Cronet exploite plusieurs technologies qui réduisent la latence et augmentent le débit des requêtes réseau nécessaires au fonctionnement de votre application.

La bibliothèque Cronet gère les requêtes d'applications utilisées par des millions de personnes chaque jour, comme YouTube, l'appli Google, Google Photos et les transports en commun et le trafic dans Maps. Cronet est la bibliothèque réseau Android compatible HTTP3 la plus utilisée.

Pour en savoir plus, consultez la page Fonctionnalités Cronet.

Objectifs de l'atelier

Dans cet atelier de programmation, vous allez ajouter la prise en charge de Cronet par une application d'affichage d'images. Cette appli pourra :

  • Charger Cronet depuis les services Google Play ou revenir en arrière en toute sécurité si Cronet n'est pas disponible.
  • Envoyer des requêtes, recevoir et traiter les réponses à l'aide de Cronet.
  • Afficher les résultats dans une interface utilisateur simple.

28b0fcb0fed5d3e0.png

Points abordés

  • Comment inclure Cronet en tant que dépendance à votre application
  • Comment configurer le moteur Cronet
  • Comment envoyer des requêtes à l'aide de Cronet
  • Écrire des rappels Cronet pour traiter les réponses

Cet atelier de programmation porte sur l'utilisation de Cronet. La majeure partie de l'application est pré-implémentée et vous pourrez terminer cet atelier même si vous débutez le développement sur Android. Pour profiter pleinement de cet atelier de programmation, vous devez connaître les principes de base du développement Android et de la bibliothèque Jetpack Compose.

Ce dont vous avez besoin

2. Obtenir le code

Pour ce projet, nous avons regroupé tout ce dont vous avez besoin dans un dépôt Git. Pour commencer, clonez le dépôt et ouvrez le code dans Android Studio.

git clone https://github.com/android/codelab-cronet-basics

3. Établir une référence

Quel est notre point de départ ?

Notre point de départ est une application d'affichage d'images de base, conçue spécialement pour cet atelier de programmation. Si vous cliquez sur le bouton Add an image (Ajouter une image), une image est ajoutée à la liste et le temps nécessaire pour récupérer l'image sur Internet s'affiche. L'application utilise une bibliothèque HTTP intégrée fournie par Kotlin, qui ne prend en charge aucune fonctionnalité avancée.

Tout au long de cet atelier de programmation, nous allons étendre l'application à Cronet et à certaines de ses fonctionnalités.

4. Ajouter des dépendances à votre script Gradle

Vous pouvez intégrer Cronet en tant que bibliothèque autonome intégrée à votre application, ou utiliser Cronet selon les modalités fournies par la plate-forme. L'équipe Cronet recommande d'utiliser le fournisseur de services Google Play. En utilisant les services Google Play, votre application n'a pas à subir les inconvénients liés à la taille binaire de Cronet (environ 5 mégaoctets), et la plate-forme assure la diffusion des dernières mises à jour et correctifs de sécurité disponibles.

Quelle que soit la façon dont vous décidez d'importer l'implémentation, vous devrez également ajouter une dépendance cronet-api pour inclure les API Cronet.

Ouvrez votre fichier build.gradle et ajoutez les deux lignes suivantes à la section dependencies.

implementation 'com.google.android.gms:play-services-cronet:18.0.1'
implementation 'org.chromium.net:cronet-api:101.4951.41'

5. Installer le fournisseur Cronet des services Google Play

Comme indiqué dans la section précédente, il existe plusieurs façons d'intégrer Cronet à votre application. Chacune de ces méthodes est extraite par un Provider, ce qui garantit la mise en place des liens requis entre la bibliothèque et votre application. Chaque fois que vous créez un moteur Cronet, tous les fournisseurs actifs sont examinés et le meilleur candidat est choisi pour instancier le moteur.

En règle générale, le fournisseur de services Google Play n'est pas disponible d'emblée. Vous devez donc l'installer au préalable. Trouvez la tâche à effectuer dans MainActivity et collez l'extrait suivant :

val ctx = LocalContext.current
CronetProviderInstaller.installProvider(ctx)

Cette action lance une tâche des services Google Play qui installe le fournisseur de manière asynchrone.

6. Gérer le résultat de l'installation du fournisseur

Le fournisseur a été installé avec succès… En êtes-vous sûr ? Task est asynchrone et vous n'avez pas géré le résultat de l'installation du fournisseur. Nous allons résoudre ce problème. Remplacez l'appel installProvider par l'extrait de code suivant :

CronetProviderInstaller.installProvider(ctx).addOnCompleteListener {
   if (it.isSuccessful) {
       Log.i(LOGGER_TAG, "Successfully installed Play Services provider: $it")
       // TODO(you): Initialize Cronet engine
   } else {
       Log.w(LOGGER_TAG, "Unable to load Cronet from Play Services", it.exception)
   }
}

Pour cet atelier de programmation, nous continuerons d'utiliser l'outil de téléchargement d'images natif si le chargement Cronet échoue. Si les performances de mise en réseau sont essentielles à votre application, vous pouvez installer ou mettre à jour les Services Play. Pour en savoir plus, consultez la documentation intitulée CronetProviderInstaller.

Exécutez l'application maintenant. Si tout fonctionne correctement, vous devriez voir une instruction de journalisation indiquant que le fournisseur a bien été installé.

7. Créer un moteur Cronet

Un moteur Cronet est l'objet principal qui vous permettra d'envoyer des requêtes avec Cronet. Le moteur est construit à l'aide du schéma de compilateur, qui vous permet de configurer diverses options Cronet. Pour l'instant, nous allons continuer à utiliser les options par défaut. Instanciez un nouveau moteur Cronet en remplaçant TODO par l'extrait de code suivant :

val cronetEngine = CronetEngine.Builder(ctx).build()
// TODO(you): Initialize the Cronet image downloader

8. Implémenter un rappel Cronet

En raison de la nature asynchrone de Cronet, le traitement des réponses est contrôlé à l'aide de rappels, à savoir des instances de UrlRequest.Callback. Dans cette section, vous allez implémenter un rappel d'assistance qui lit toute la réponse en mémoire.

Créez une classe abstraite appelée ReadToMemoryCronetCallback, puis prolongez UrlRequest.Callback et laissez Android Studio générer automatiquement les bouchons de la méthode. Votre nouvelle classe devrait ressembler à l'extrait suivant :

abstract class ReadToMemoryCronetCallback : UrlRequest.Callback() {
   override fun onRedirectReceived(
       request: UrlRequest,
       info: UrlResponseInfo,
       newLocationUrl: String?
   ) {
       TODO("Not yet implemented")
   }

   override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) {
       TODO("Not yet implemented")
   }

   override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) {
       TODO("Not yet implemented")
   }

   override fun onResponseStarted(request: UrlRequest, info: UrlResponseInfo) {
       TODO("Not yet implemented")
   }

   override fun onReadCompleted(
       request: UrlRequest,
       info: UrlResponseInfo,
       byteBuffer: ByteBuffer
   ) {
       TODO("Not yet implemented")
   }
}

Les méthodes onRedirectReceived, onSucceeded et onFailed sont explicites. Nous ne les examinerons donc pas maintenant, et nous nous concentrerons sur onResponseStarted et onReadCompleted.

onResponseStarted est appelé après que Cronet a envoyé la requête et reçu tous les en-têtes de réponse, mais avant de commencer à lire le corps. Cronet ne lit pas automatiquement l'intégralité du corps, comme d'autres bibliothèques le font (p. ex. Volley). Utilisez plutôt UrlRequest.read() pour lire le prochain bloc du corps dans un tampon que vous fournissez. Une fois que Cronet a fini de lire le fragment du corps de la réponse, il appelle la méthode onReadCompleted. Le processus se répète jusqu'à ce que toutes les données soient lues.

39d71a5e85f151d8.png

Commençons à implémenter le cycle de lecture. Tout d'abord, instanciez un nouveau ByteArrayOutputStream et un canal qui l'utilise. Nous utiliserons le canal comme récepteur pour le corps de la réponse.

private val bytesReceived = ByteArrayOutputStream()
private val receiveChannel = Channels.newChannel(bytesReceived)

Implémentez ensuite la méthode onReadCompleted pour copier les données du tampon d'octets dans notre récepteur et appeler la lecture suivante.

// The byte buffer we're getting in the callback hasn't been flipped for reading,
// so flip it so we can read the content.
byteBuffer.flip()
receiveChannel.write(byteBuffer)

// Reset the buffer to prepare it for the next read
byteBuffer.clear()

// Continue reading the request
request.read(byteBuffer)

Pour terminer la boucle de lecture du corps, appelez la lecture initiale à partir de la méthode de rappel onResponseStarted. Notez que vous devez utiliser un tampon d'octets direct avec Cronet. La capacité du tampon n'a pas d'importance dans cet atelier, mais la plupart du temps, la valeur par défaut est de 16 Kio en production.

request.read(ByteBuffer.allocateDirect(BYTE_BUFFER_CAPACITY_BYTES))

Poursuivons notre atelier. Puisque les redirections ne présentent pas beaucoup d'intérêt pour vous, il suffit de suivre la redirection comme le ferait votre navigateur Web.

override fun onRedirectReceived(
   request: UrlRequest, info: UrlResponseInfo?, newLocationUrl: String?
) {
   request.followRedirect()
}

Enfin, nous devons gérer les méthodes onSucceeded et onFailed. onFailed correspond à la signature que vous devez fournir aux utilisateurs de votre rappel d'assistance et qui vous permettra de supprimer la définition et de laisser les classes étendues remplacer la méthode. onSucceeded doit transmettre le corps en aval en tant que ByteArray (tableau d'octets). Ajoutez une méthode abstraite qui inclut le corps dans sa signature.

abstract fun onSucceeded(
   request: UrlRequest, info: UrlResponseInfo, bodyBytes: ByteArray)

Ensuite, assurez-vous que la nouvelle méthode onSucceeded est correctement appelée lorsque la requête aboutit.

final override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) {
   val bodyBytes = bytesReceived.toByteArray()
   onSucceeded(request, info, bodyBytes)
}

Bravo, vous avez appris à implémenter un rappel Cronet !

9. Implémenter un outil de téléchargement d'images

Utilisons le rappel que nous avons créé dans la section précédente pour implémenter un outil de téléchargement d'images basé sur Cronet.

Créez une classe appelée CronetImageDownloader pour implémenter l'interface ImageDownloader et accepter un CronetEngine comme paramètre constructeur.

class CronetImageDownloader(val engine: CronetEngine) : ImageDownloader {
   override suspend fun downloadImage(url: String): ImageDownloaderResult {
       TODO("Not yet implemented")
   }
}

Pour implémenter la méthode downloadImage, vous devez apprendre à créer des requêtes Cronet. Un jeu d'enfant : appelez la méthode newUrlRequestBuilder() de votre CronetEngine. Cette méthode utilise l'URL, une instance de votre classe de rappel et un exécuteur qui exécute les méthodes de votre rappel.

val request = engine.newUrlRequestBuilder(url, callback, executor)

Le paramètre downloadImage nous permet de connaître l'URL. Pour l'exécuteur, nous allons créer un champ à l'échelle de l'instance.

private val executor = Executors.newSingleThreadExecutor()

Enfin, nous utilisons l'implémentation de rappel d'assistance de la section précédente pour implémenter callback. Nous n'aborderons pas les détails de son implémentation, car ce sujet est plutôt lié aux coroutines Kotlin. Vous pouvez considérer cont.resume comme un return de la méthode downloadImage.

L'implémentation de downloadImage doit se présenter comme suit :

override suspend fun downloadImage(url: String): ImageDownloaderResult {
   val startNanoTime = System.nanoTime()
   return suspendCoroutine {
       cont ->
       val request = engine.newUrlRequestBuilder(url, object: ReadToMemoryCronetCallback() {
       override fun onSucceeded(
           request: UrlRequest,
           info: UrlResponseInfo,
           bodyBytes: ByteArray) {
           cont.resume(ImageDownloaderResult(
               successful = true,
               blob = bodyBytes,
               latency = Duration.ofNanos(System.nanoTime() - startNanoTime),
               wasCached = info.wasCached(),
               downloaderRef = this@CronetImageDownloader))
       }

       override fun onFailed(
           request: UrlRequest,
           info: UrlResponseInfo,
           error: CronetException
       ) {
           Log.w(LOGGER_TAG, "Cronet download failed!", error)
           cont.resume(ImageDownloaderResult(
               successful = false,
               blob = ByteArray(0),
               latency = Duration.ZERO,
               wasCached = info.wasCached(),
               downloaderRef = this@CronetImageDownloader))
       }
   }, executor)
       request.build().start()
   }
}

10. Câblage final

Revenons au composable MainDisplay et gérons la dernière tâche à effectuer à l'aide de l'outil de téléchargement d'images que nous venons de créer.

imageDownloader = CronetImageDownloader(cronetEngine)

Et c'est fini ! Essayez d'exécuter l'application. Vos requêtes doivent être acheminées via l'outil de téléchargement d'images Cronet.

11. Personnalisation

Vous pouvez personnaliser le comportement des requêtes au niveau du moteur et au niveau de la requête. Nous allons le démontrer avec la mise en cache, mais il existe de nombreuses autres options. Pour en savoir plus, consultez la documentation sur UrlRequest.Builder et CronetEngine.Builder.

Pour activer la mise en cache au niveau du moteur, utilisez la méthode enableHttpCache du compilateur. Dans l'exemple ci-dessous, nous utilisons le cache en mémoire. Pour connaître les autres options disponibles, consultez la documentation. Voici à quoi ressemble alors la création du moteur Cronet :

val cronetEngine = CronetEngine.Builder(ctx)
   .enableHttpCache(CronetEngine.Builder.HTTP_CACHE_IN_MEMORY, 10 * 1024 * 1024)
   .build()

Exécutez l'application et ajoutez quelques images. Celles qui ont été ajoutées plusieurs fois devraient avoir une latence nettement plus faible et l'interface utilisateur devrait indiquer qu'elles ont été mises en cache.

Cette fonctionnalité peut être remplacée au fil des requêtes. Utilisons une petite astuce dans notre outil de téléchargement Cronet pour désactiver la mise en cache de l'image représentant un soleil, qui est la première de la liste d'URL.

if (url == CronetCodelabConstants.URLS[0]) {
   request.disableCache()
}

request.build().start()

Exécutez à nouveau l'application. Les images de soleil ne sont pas mises en cache.

d9d0163c96049081.png

12. Conclusion

Félicitations, vous avez terminé l'atelier de programmation ! Au cours de cet atelier, vous avez découvert les principes de base de l'utilisation de Cronet.

Pour en savoir plus sur Cronet, consultez le guide du développeur et le code source. Vous pouvez également vous abonner au blog des développeurs Android pour recevoir en exclusivité les dernières informations sur Cronet et les actualités d'Android.