Exploration Technique des Composants, du Cycle de Vie et des Mécanismes IPC sous Android

Le Rôle Central d'Intent et la Communication Inter-Composants

L'objet Intent agit comme le mécanisme de liaison fondamental entre les quatre composants majeurs d'Android (Activity, Service, BroadcastReceiver, ContentProvider). Il permet non seulement la navigation entre les interfaces, mais aussi le transport d'actions et de données. Bien qu'il puisse faciliter le partage de données via des Extras, son rôle principal reste la communication inter-composants, le partage de données complexe étant plutôt dévolu aux ContentProvider.

Pour illustrer le partage de données via un Intent implicite, voici une approche modernisée utilisant Kotlin :

class MediaDispatcherActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        val textPayload = "Contenu textuel à transmettre"
        val mediaUri = Uri.parse("content://mon.app.fictif/ressources/456")

        val dispatchIntent = Intent(Intent.ACTION_SEND).apply {
            type = "image/*"
            putExtra(Intent.EXTRA_TEXT, textPayload)
            putExtra(Intent.EXTRA_STREAM, mediaUri)
            // Autorisation temporaire de lecture pour l'URI
            addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
        }
        
        startActivity(Intent.createChooser(dispatchIntent, "Sélectionner une cible de partage"))
    }
}

Pour la navigation interne (Intent explicite), les données sont extraites dans l'activité cible :

class DashboardActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val navigationIntent = Intent(this, DetailsScreen::class.java).apply {
            putExtra("EXTRA_PAYLOAD", "Données initiales")
        }
        startActivity(navigationIntent)
    }
}

class DetailsScreen : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val receivedData = intent.getStringExtra("EXTRA_PAYLOAD") ?: "Aucune donnée"
        // Configuration de l'interface utilisateur avec receivedData
    }
}

Gestion des Diffusions (BroadcastReceiver) et IPC

Les BroadcastReceiver peuvent être enregistrés de deux manières : statiquement dans le AndroidManifest.xml ou dynamiquement via le code. L'enregistrement dynamique ne nécessite aucune déclaration dans le manifeste. De plus, les broadcasts ne sont pas exclusivement implicites ; il est tout à fait possible d'envoyer des broadcasts explicites en ciblant directement un composant.

Exemple d'enregistrement dynamique pour surveiller la connectivité :

class NetworkStateMonitor : AppCompatActivity() {
    private val connectivityReceiver = object : BroadcastReceiver() {
        override fun onReceive(ctx: Context?, intent: Intent?) {
            // Traitement du changement de réseau
        }
    }

    override fun onResume() {
        super.onResume()
        registerReceiver(connectivityReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
    }

    override fun onPause() {
        unregisterReceiver(connectivityReceiver)
        super.onPause()
    }
}

Pour l'envoi d'un broadcast explicite, le composant cible est défini via ComponentName :

class TargetedUpdateReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        // Traitement de la commande ciblée
    }
}

class CommandDispatcher : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val targetedIntent = Intent().apply {
            component = ComponentName(this@CommandDispatcher, TargetedUpdateReceiver::class.java)
        }
        sendBroadcast(targetedIntent)
    }
}

Concernant les permissions sensibles, comme l'interception des SMS, la déclaration de android.permission.RECEIVE_SMS dans le manifeste est obligatoire. Les broadcasts se divisent en deux catégories : les broadcasts normaux (asynchrones) et les broadcasts ordonnés (séquentiels, avec priorité définie par android:priority).

Pour la Communication Inter-Processus (IPC) complexe, AIDL est utilisé. Les règles strictes d'AIDL incluent : le nom de l'interface doit correspondre exactement au nom du fichier .aidl, les méthodes ne doivent pas avoir de modificateurs d'accès (publiques par défaut), et le Service doit retourner l'implémentation du Stub dans onBind(). D'autres frameworks de hooking comme Xposed, Substrate, Cydia et Frida exploitent également ces mécanismes pour l'instrumentation dynamique.

Architecture des Services et Cycle de Vie des Activités

Un Service ne doit jamais manipuler directement l'interface utilisateur d'une Activity. Passer l'instance de l'activité au service crée un couplage fort et des risques de fuites de mémoire. La communication doit être découplée via des broadcasts, LiveData, ou des bus d'événements.

Les deux modes de démarrage d'un service ont des cycles de vie distincts :

  • startService() : Déclenche onCreate()onStartCommand()onDestroy(). Le service survit à la destruction du composant appelant.
  • bindService() : Déclenche onCreate()onBind()onUnbind()onDestroy(). Le service est lié au cycle de vie des clients.

IntentService simplifie ce modèle en traitant les requêtes de manière séquentielle dans un thread d'arrière-plan et en s'arrêtant automatiquement via stopSelf() une fois la file d'attente vidée.

Le cycle de vie des activités présente des nuances importantes. Lorsqu'une Activity A lance une Activity B avec un thème transparent, Activity A exécute onPause() mais pas onStop(), car elle reste partiellement visible. Lors du retour via la touche arrière, la séquence typique est : Second.onPause()First.onRestart()First.onStart()First.onResume()Second.onStop()Second.onDestroy().

Persistance des Données, Réseau et Sérialisation

SQLite3 est la base de données embarquée standard sur Android. Pour interagir avec elle, la classe ContentValues est utilisée ; elle stocke des paires clé-valeur où les clés sont strictement des String et les valeurs des types primitifs.

SharedPreferences est une solution de stockage léger basée sur des paires clé-valeur, sauvegardées sous format XML dans le répertoire interne /data/data/<package>/shared_prefs/, et non sur la carte SD.

Concernant les ressources, le répertoire assets est destiné aux fichiers bruts (multimédia, HTML, JSON) non compilés, tandis que res contient les ressources compilées (layouts, chaînes de caractères). Pour les échanges de données, JSON est généralement préféré à XML pour sa légèreté et sa rapidité de parsing, bien que XML offre une meilleure descriptivité structurelle.

Pour les requêtes réseau via HttpURLConnection :

  • setRequestMethod() attend une String (ex: "GET").
  • getResponseCode() récupère le code HTTP.
  • doInput est true par défaut, doOutput est false.
  • Les cookies du serveur s'obtiennent via getHeaderField("Set-Cookie").

En Java, la sérialisation d'objets repose sur les flux ObjectInputStream et ObjectOutputStream du package java.io.

Architecture, Mémoire et Concurence

Sous Android, chaque application s'exécute dans son propre processus Linux, hébergeant une instance indépendante de la machine virtuelle (Dalvik ou ART). Le Handler est utilisé pour la communication intra-processus (entre threads), et non inter-processus. Il s'appuie sur une MessageQueue pour éviter de bloquer le thread principal (UI).

Les dialogues ANR (Application Not Responding) sont déclenchés lorsque le thread principal est bloqué : plus de 5 secondes pour une entrée dans une Activity, ou plus de 10 secondes dans onReceive() d'un BroadcastReceiver. Le blocage de onStartCommand() d'un service ne provoque une ANR qu'après 20 secondes.

La gestion de la mémoire (Garbage Collection) est entièrement contrôlée par la VM sur des threads d'arrière-plan ; le programmeur ne peut ni forcer ni planifier précisément la libération de la mémoire. Pour la concurrence, CyclicBarrier permet à N threads de s'attendre à un point de synchronisation donné, contrairement à CountDownLatch qui est à usage unique.

Le calcul de la mémoire pour une image non échantillonnée de 72x72 pixels en ARGB_8888 (4 octets par pixel) est de : 72 * 72 * 4 = 20 736 octets, indépendamment de la densité de l'écran si le chargement brut est spécifié.

Enfin, pour les opérations mathématiques, Math.round(11.5) retourne 12, et Math.round(-11.5) retourne -11, car l'implémentation ajoute 0.5 avant d'appliquer Math.floor().

Interface Utilisateur et Animations

LayoutInflater est essentiel pour instancier des layouts XML. La méthode from(Context) est statique, et inflate() accepte trois paramètres (ID de ressource, ViewGroup parent, booléen d'attachement). Il peut être obtenu via getLayoutInflater() dans une activité.

Les animations de type "Tween" (comme TranslateAnimation, RotateAnimation, AlphaAnimation) calculent les transitions entre un état initial et final. Elles se distinguent des "Frame Animations" qui séquencient simplement une série d'images.

Étiquettes: android-intent BroadcastReceiver android-service Android-Lifecycle sqlite

Publié le 31 août à 16h30