[JAVA] Points clés du développement d'applications Android où les programmeurs à l'ancienne ont trébuché

Qu'est-ce que c'est?

J'écrivais principalement des applications qui s'exécutaient sur le bureau du PC ou en ligne de commande en C / C ++ ou Java, mais maintenant j'écris principalement des applications Android. Du point de vue des programmeurs qui ont commencé à développer des applications de bureau et de ligne de commande à l'ancienne, il existe un grand fossé culturel dans le développement d'applications Android. Je me demande si c'est pourquoi certaines personnes ne font pas ce que le développement Android veut ou sont frustrées. Donc, pour ces programmeurs à l'ancienne, je voudrais écrire quelques points de développement Android que je voudrais garder à l'esprit.

Où est le point d'entrée?

Pour les programmeurs à l'ancienne, les programmes informatiques sont «des débuts et des fins clairs». Par exemple, prenez le programme C suivant.

#include <stdio.h>

int main(int argc, char* argv[]){
    printf("Hello, world!");
    return 0;
}

Ce programme démarre au début de la fonction «main» et se termine en quittant la fonction «main». C'est aussi clair que possible. La même chose est vraie pour les programmes GUI. Par exemple, si vous utilisez le système X window, le code ressemblerait à ceci:

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/Label.h>

int main(int argc, char* argv[]){
    XtAppContext context;
    Widget root, label;

    root = XtVaAppInitialize(&context, "Hello, world!", NULL, 0, &argc, argv, NULL, NULL);
    label = XtVaCreateManagedWidget("Hello, world!", labelWidgetClass, root, NULL);
    XtRealizeWidget(root);
    XtAppMainLoop(context);
    return 0;
}

Cela commence également au début de la fonction main et se termine par la sortie de la boucle d'événements et la sortie de la fonction main.

Mais qu'en est-il d'Android? Pour l'instant, créons un nouveau projet dans Android Studio. Vous avez le choix entre plusieurs modèles lors de la création d'un projet, mais pour l'instant, choisissons Activité vide. Ensuite, divers fichiers seront créés, mais MainActivity.kt (dans le cas de Kotlin) avec le contenu suivant sera affiché à l'écran.

MainActivity.kt


package test.app.helloworld

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

Il ne serait pas étrange pour certaines personnes de penser que:

Je suis curieux que divers fichiers soient créés sans autorisation, mais peut-être que ʻonCreate () ʻest le point d'entrée! Donc, si vous quittez ce ʻonCreate () `, l'application doit être fermée!

** Malheureusement non. ** **

Pas de point d'entrée ni de boucle d'événement (invisible)

Les applications Android ont également un «début». Mais ce n'est pas le MainActivity # onCreate () dans le code ci-dessus. Cette méthode est un gestionnaire qui est appelé lorsque l'un des écrans de l'application est créé, et non au démarrage de l'application. Alors, où est appelée la méthode lorsque l'application démarre?

En fait, lors du développement d'applications Android, vous n'écrivez pas de point d'entrée qui correspond à la fonction main en langage C. En parlant du traitement interne du système d'exploitation, l'application Android fonctionne comme un processus Linux indépendant, il devrait donc y avoir un point d'entrée qui correspond à la fonction main en interne, mais le système d'exploitation Android (framework Android) le fait. Je le cache. Par conséquent, le développeur de l'application n'écrit pas de pointeur d'entrée. De même, les boucles d'événements sont masquées par le système d'exploitation et ne sont pas écrites. Les développeurs d'applications se concentreront sur l'écriture de gestionnaires pour les événements distribués à partir de la boucle d'événements.

Hmmm, mais n'est-ce pas une évidence?

Je suis d'accord. Masquer les points d'entrée et les boucles d'événements n'a rien de spécial pour le framework GUI d'Imadoki. Cependant, je pense que même de tels cadres fournissent souvent un moyen d'accéder aux boucles d'événements. Android, en revanche, ne permet pas aux applications d'accéder aux boucles d'événements. Par conséquent, il n'est pas possible de créer votre propre boucle d'événements, ce qui est possible sur d'autres plates-formes. peut être.

Le gestionnaire d'événements pour démarrer l'application est ʻApplication # onCreate () `

J'espère que vous comprenez que vous écrirez des gestionnaires d'événements lors du développement d'applications Android. Alors, où est appelé le gestionnaire lorsque l'application démarre? C'est la méthode ʻonCreate () d'une classe qui hérite de la classe ʻandroid.app.Application.

Quoi? Une telle classe n'existe-t-elle nulle part?

Oui, il n'y a pas de source pour ces classes dans les projets créés à partir de modèles. Dans de tels cas, Android accepte l'implémentation de la classe ʻandroid.app.Application. En conséquence, le programmeur ne semble pas avoir de "gestionnaire appelé au démarrage de l'application". Dans la plupart des développements d'applications Android, vous n'avez pas besoin de connaître ce gestionnaire. Cependant, dans certains cas, vous souhaiterez peut-être écrire "processus d'initialisation pour l'ensemble de l'application". Dans de tels cas, implémentez une classe qui hérite de la classe ʻandroid.app.Application et spécifiez cette classe dans ʻAndroidManifest.xml`.

MainApplication.kt


package test.app.helloworld

import android.app.Application
import android.util.Log

class MainApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        Log.d("MainApplication", "Start application!")
    }
}

AndroidManifest.xml


    <application
        android:name=".MainApplication"

Laissez la sortie de l'application au système d'exploitation

Je comprends le début du programme. Et la fin? Dans de nombreux cas, comme le système X window, lorsqu'il n'y a plus de fenêtres à afficher à l'écran, l'application quitte la boucle d'événements et se termine. Alors, Android, comme ça, quitte automatiquement l'application quand il n'y a rien à afficher à l'écran?

Ce point est un peu difficile à répondre. La réponse est non si vous pensez "fin du programme = fin du processus". Sur Android, le processus de l'application ne se termine pas immédiatement lorsque tous les écrans (Activité) sont fermés. Mais si tous les écrans (Activité) et services (Service) sont fermés, l'application ne fera pratiquement rien. En ce sens, vous pouvez considérer l'application comme fermée.

Sur Android, la fin du processus d'application ne peut pas (et ne doit pas être) contrôlée par l'application. Le processus de l'application sera "se terminer lorsque le système d'exploitation veut se terminer". Il n'est pas possible pour l'application de se terminer explicitement. Il sera terminé par le système d'exploitation.

Un tel dominateur!

Je comprends bien ce sentiment. Mais Android a ce genre de mécanisme. Cependant, les applications de premier plan (actuellement en cours d'exécution) sont rarement interrompues brutalement. Normalement, les applications qui passent en arrière-plan et qui n'ont pas été utilisées depuis longtemps sont sujettes à résiliation. Le système d'exploitation fait du bon travail partout.

Le fait que vous ne puissiez pas explicitement terminer le processus d'une application peut sembler très étrange à un programmeur à l'ancienne. Cependant, compte tenu des points suivants, ce mécanisme semble raisonnable.

En d'autres termes, vous ne pouvez pas vous attendre à une «résiliation de l'application par l'utilisateur» sur votre smartphone. Si tel est le cas, il est logique que le système d'exploitation ferme l'application au bon moment.

Le fait qu'il soit interrompu sans autorisation par le système d'exploitation signifie que dans le développement d'applications, il est nécessaire de l'implémenter afin qu'il puisse être arrêté à tout moment. Fondamentalement, la méthode suivante est utilisée.

--Persist state variables si la conservation d'état est requise

De plus, j'ai écrit que vous ne pouvez pas explicitement mettre fin à un processus d'application, mais que vous pouvez terminer une activité normalement. Je parlerai de ce qu'est une activité plus tard, mais en un mot, c'est un «composant qui représente un seul écran». Une application peut avoir plusieurs écrans, ou activités, et chaque activité peut être explicitement arrêtée (ʻActivity # finish () `). Par conséquent, il est possible de fermer efficacement l'application en mettant fin à toutes les activités.

Qu'est-ce qu'une activité ou un service?

Je pense qu'il y a pas mal de gens qui s'interrogent également à ce sujet. Je pense qu'il y a beaucoup de gens qui peuvent imaginer le service à partir du nom, mais l'activité ressemble à "Hmm?".

L'activité, comme je l'ai écrit ci-dessus, est un composant qui représente un écran. Il conserve tous les états de l'écran et reçoit et traite les événements qui se produisent sur cet écran. Les développeurs d'applications créent une classe dérivée de ʻandroid.app.Activity`, ont une instance de cette classe contenant des variables d'état et implémentent un gestionnaire d'événements pour atteindre l'écran souhaité.

Le service est un composant sans écran qui est principalement utilisé pour le traitement d'arrière-plan indépendant de l'écran. Le développeur de l'application crée une classe dérivée de ʻandroid.app.Service`, fait qu'une instance de cette classe contient la variable d'état et implémente un gestionnaire d'événements pour obtenir le traitement d'arrière-plan souhaité.

Une application peut avoir plusieurs activités et plusieurs services. Vous pouvez créer une application d'activité uniquement, une application de service uniquement ou, bien sûr, une application qui a à la fois une activité et un service.

Vous pouvez créer une autre activité à partir de l'activité pour réaliser la "transition d'écran". Vous pouvez également créer un service à partir d'une activité pour exécuter un ensemble de processus en arrière-plan. Vous pouvez également créer un autre service à partir du service pour traiter différents processus en parallèle. Vous pouvez également créer une activité à partir du service pour afficher l'écran (avec les restrictions d'Android 10).

Hmm. Je comprends que l'activité est liée à l'écran. Mais avez-vous besoin d'un service? Pourquoi ne pas créer un fil et le traiter en arrière-plan?

Oui, vous pouvez créer des threads normalement et vous pouvez les utiliser pour un traitement parallèle en arrière-plan. Cependant, les threads créés par l'application ne sont pas gérés par le système d'exploitation (framework Android). Une application qui a terminé toutes les activités et services est plus susceptible d'être obligée de mettre fin à son processus par le système d'exploitation. Dans ce cas, ** même si le thread reste, il se terminera sans aucun problème. ** **

Par conséquent, les threads sont généralement utilisés pour effectuer le traitement qui est terminé dans Activity ou Service. Par exemple, un thread démarré dans une activité doit être arrêté au moment où l'activité se termine, ou si cela n'est pas possible, il doit être arrêté dès que possible. Comme on ne sait pas quand le thread sera arrêté de force après la fin de l'activité, il est nécessaire d'en tenir compte dans l'implémentation. [^ fil]

Le service, quant à lui, est géré par le système d'exploitation. Il est (très) peu probable que les services soient supprimés tant qu'ils ne sont pas terminés. [^ service_finish] Par conséquent, le traitement qui ne dépend pas de l'activité et le traitement que vous souhaitez continuer même si l'activité se termine sont implémentés en tant que service.

[^ thread]: en fait, même si l'activité se termine, le thread continuera à fonctionner si d'autres activités et services dans l'application restent. Cependant, les activités et services doivent être mis en œuvre de manière à être aussi indépendants (faiblement couplés) que possible des autres activités et services, de sorte qu'ils ne doivent pas être mis en œuvre dans l'espoir que d'autres activités / services survivront. ..

[^ service_finish]: Le service (service Foreground) peut être interrompu de force par le système d'exploitation lorsque tout le système manque de mémoire. Ce n'est pas un dysfonctionnement du système, et Android a été conçu à l'origine de cette façon. Par conséquent, lors de la mise en œuvre du service, il est nécessaire de l'implémenter en supposant qu'il peut être interrompu de force par le système d'exploitation.

Est-ce que ʻApplication possède ʻActivity ou Service?

Si vous écoutez l'histoire jusqu'à présent, je pense que certaines personnes pensent comme suit.

Une instance de la classe ʻApplication (ou sa classe dérivée) contient et possède une instance de ʻActivity et Service!

Cela ressemblerait-il à ceci lorsqu'il est écrit dans le code?

Ça ne marche pas


class MainApplication : Application() {
    private var mainActivity: MainActivity? = null

    override fun onCreate() {
        super.onCreate()

        mainActivity = MainActivity()
        startActivity(mainActivity)
    }
}

Du point de vue de quelqu'un qui développait une plate-forme autre qu'Android, n'est-ce pas "assez probablement du code"? Mais sur Android, ce n'est pas autorisé. L'instanciation de ʻActivity et Service` est le travail du système d'exploitation, et les applications ne devraient pas le faire seules.

Alors, comment appelez-vous activité ou service?

** Utilisez l'intention. ** **

Je pense que l'existence d'Intent est l'un des points sur lesquels les personnes qui développent des applications Android pour la première fois trébuchent. C'est un concept que vous ne voyez pas souvent sur d'autres plateformes. Par exemple, si vous avez une application qui a deux écrans, ʻActivity1 et ʻActivity2, et que vous voulez faire la transition de l'écran de ʻActivity1 à ʻActivity2, vous écrirez le code suivant.

class Activity1 : AppCompatActivity() {
    fun callActivity2() {
        val intent = Intent(this, Activity2::class.java)
        intent.putExtra("param1", "some data")
        startActivity(intent)
    }
}

Ce code (méthode callActivity2 ()) effectue les opérations suivantes:

  1. Créez un Intent contenant les informations de la classe appelée (ʻActivity2`).
  2. Définissez l'intention sur le paramètre que vous souhaitez transmettre à l'appelé.
  3. Envoyez l'intention au système d'exploitation.

Le système d'exploitation effectuera alors les opérations suivantes:

  1. Sélectionnez la classe à appeler à partir des informations d'intention envoyées. Cette fois, la classe est explicitement spécifiée, donc la classe ʻActivity2` est sélectionnée.
  2. Instanciez cette classe.
  3. Transmettez l'intention envoyée par l'appelant à l'activité instanciée.
  4. Affichez l'activité instanciée au premier plan.

Cela réalisera la transition d'écran. Dans l'activité appelée, l'intention reçue par la méthode ʻActivity # getIntent () `peut être obtenue, il est donc possible de récupérer les paramètres de cette intention.

        val param1 = getIntent().getStringExtra("param1")

La même chose est vraie lorsque vous appelez le service. Par exemple, pour appeler Service1 depuis ʻActivity1`, vous avez besoin du code suivant.

    fun callService1() {
        val intent = Intent(this, Service1::class.java)
        intent.putExtra("param1", "some data")
        startService(intent)
    }

Comme vous pouvez le voir, c'est presque la même chose que dans Activity. [^ launch_service]

[^ launch_service]: Cet exemple utilise la méthode startService (), mais le service démarré par cette méthode ne peut pas être traité pendant longtemps sur Android 8.0 ou version ultérieure. Si vous voulez que le service démarré reste actif pendant une longue période, vous devez le démarrer en tant que service Foreground avec la méthode startForegroundService ().

Dans l'exemple ci-dessus, la classe de l'activité ou du service appelé a été explicitement spécifiée, mais vous pouvez également spécifier la catégorie, etc. au lieu de la spécifier explicitement. Vous pouvez également appeler les activités et services mis en œuvre dans d'autres applications. Il est facile de considérer l'intention comme un moyen d'échange d'informations entre activités / services.

** L'important ici est que le concept d'intention soit utilisé comme coussin au lieu d'appeler directement, qu'il s'agisse d'activité ou de service. ** **

Ce mécanisme ne permet pas à l'appelant de recevoir une instance de l'activité ou du service appelant. L'appelé ne peut pas non plus recevoir l'instance appelante. Par conséquent, ils ne peuvent pas accéder aux méthodes d'instance et aux variables de l'autre. Lorsque la coopération entre Activité / Service est requise, définissez les paramètres dans Intent et transmettez-le comme dans l'exemple ci-dessus, ou utilisez un autre mécanisme (méthode startActivityForResult () pour renvoyer les informations de résultat, Vous allez utiliser (comme communiquer via une classe dérivée d'Application, utiliser Broadcast, etc.)

En outre, les paramètres qui peuvent être définis dans l'intention (les données qui peuvent être échangées via l'intention) sont les types primitifs Java, les types de chaîne et leurs types de tableau. Les données d'autres types doivent être transmises en série. Cela signifie que vous ne pouvez pas simplement passer une instance de l'objet.

Grâce à cela, chaque activité / service est faiblement couplé à un niveau élevé.

Il semble y avoir plusieurs raisons à cette architecture, mais je pense que la raison principale est qu'Android ciblait à l'origine le matériel avec une mémoire faible. Il est nécessaire de libérer efficacement la mémoire dans le terminal avec une petite mémoire. Si chaque activité est faiblement couplée (sans référence directe l'une à l'autre), vous pouvez toujours ignorer toute activité qui est passée en arrière-plan et libérer de la mémoire (à l'exception bien sûr). Cela vous permet de libérer efficacement de la mémoire sans laisser tomber l'application elle-même. Je pense que c'est un gros avantage de cette architecture. La plupart des matériels modernes avec un excès de mémoire peuvent ne pas profiter très souvent de cet avantage. .. ..

Qu'est-ce que "lancer une application" en premier lieu?

Je vois. Le truc est de créer une classe dérivée de la classe ʻApplication, de créer un Intent qui spécifie l'activité de l'écran à afficher en premier dans la méthode ʻonCreate () , et de fairestartActivity ()! ??

Je pense qu'il est naturel de penser de cette façon à partir du flux de l'histoire jusqu'à présent, mais ** ce n'est pas le cas. ** ** Comme je l'ai mentionné plus tôt, il est rare de créer une classe dérivée de ʻApplication. Vous pouvez lancer l'activité sur le premier écran sans faire cela. Pour ce faire, dans ʻAndroidManifest.xml, spécifiez l'activité que vous souhaitez lancer en premier.

AndroidManifest.xml


        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

Cette description est la même qu'elle a été créée lors de la création du projet à partir du modèle. Cette description définit les paramètres pour la classe MainActivity, mais l'important est le <action android: name =" android.intent.action.MAIN "/> ʻ dans l'élément ʻelement. C'est une description. Cette description spécifie que «MainActivity» est la première activité à démarrer. (À propos, la description <category android: name =" android.intent.category.LAUNCHER "/> ci-dessous spécifie que MainActivity est une activité qui peut être lancée à partir du lanceur.)

Lorsque l'application est lancée, le système d'exploitation crée une intention qui "capture" cette instruction et exécute startActivity (). Par conséquent, la classe «MainActivity» est identifiée comme l'activité à lancer et «MainActivity» est instanciée et affichée. C'est ce que fait le système d'exploitation, vous n'avez donc pas à le coder côté application.

Hmm? startActivity () est juste une méthode "start Activity", n'est-ce pas? "Lancer une application" n'est-il pas autre chose?

C'est un peu déroutant. Lors du développement d'applications Android, nous ne prêtons pas beaucoup d'attention au "lancement d'applications". Au lieu de cela, sachez que vous lancez des activités et des services dans votre application.

Par exemple, supposons que vous souhaitiez lancer une autre application à partir de votre propre application. Pour démarrer d'autres applications, écrivez le code suivant.

        val intent = packageManager.getLaunchIntentForPackage("test.app.other_app")
        startActivity(intent)

PackageManager # getLaunchIntentForPackage () renvoie un Intent qui identifie la première activité à lancer pour l'application spécifiée dans l'argument. Donc, en passant cette intention à startActivity (), la première activité de l'application cible sera lancée. En d'autres termes, il est considéré comme "la première activité est lancée" et "l'application est lancée".

Ouais! ?? Mais l'application n'est-elle pas encore en cours? Est-il possible d'envoyer une intention aux applications qui n'ont pas démarré le processus? ??

C'est vrai. Lors de l'envoi d'une intention, vous n'avez pas à vous soucier de l'exécution de l'application de destination. En effet, le système d'exploitation exécutera le processus ** "Si le processus de l'application de destination n'est pas démarré, démarrez-le". ** En d'autres termes, si le processus de l'application de destination n'est pas démarré, startActivity () sera exécuté pour l'activité de cette application comme suit.

  1. Le processus d'application démarre.
  2. La classe ʻApplication(ou sa classe dérivée) est instanciée et ʻApplication # onCreate ()est appelée.
  3. La classe d'Activité spécifiée par Intent est instanciée et ʻActivity # onCreate () `est appelée et affichée au premier plan.

Il s'agit d'un "lancement d'application" courant sur Android.

De plus, il est normal de lancer directement une activité spécifique dans une autre application. Vous pouvez également transmettre des paramètres.

        val intent = Intent().apply {
            setClassName("test.app.other_app", "test.app.other_app.Activity2")
            flags = Intent.FLAG_ACTIVITY_NEW_TASK
            putExtra("param1", "hoge")
        }
        startActivity(intent)

Lorsque vous faites cela, vous serez redirigé vers les écrans d'autres applications comme si les transitions d'écran se produisaient normalement dans votre application. Peut-être que l'utilisateur ne sait pas que l'application a changé.

De même, il est possible de lancer directement un service spécifique d'une autre application.

Comme vous pouvez le voir, sur Android, les frontières entre les applications sont très minces. Vous pouvez effectuer des transitions d'écran indépendamment de votre propre application ou d'autres applications. Vous pouvez appeler la fonction indépendamment de votre propre application ou d'autres applications. Je pense qu'il est possible de lier des applications sur d'autres plates-formes telles quelles, mais Android n'est-il pas le seul à pouvoir être aussi flexible? Je pense que c'est ce qui rend Android si intéressant et addictif.

Que signifie interagir via une classe dérivée de ʻApplication`?

Dans la section précédente, j'ai mentionné "l'échange via une classe dérivée de ʻApplication`" comme l'un des moyens d'échanger des données entre les activités et les services. Qu'est-ce que ça veut dire??

Dans l'explication précédente, j'ai écrit que «Application» ne «possède» pas «Activité» ou «Service». Cependant, ʻApplication, ʻActivity et Service ont la relation suivante.

Par conséquent, les données et les objets que vous souhaitez partager dans toute l'application doivent être stockés dans l'instance de la classe dérivée de ʻApplication`.

MainApplication.kt


class MainApplication : Application() {
    val sharedHoge = Hoge()
}

Activity1.kt


class Activity1 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_1)
        
        (application as MainApplication).sharedHoge.fuga()
    }
}

Service1.kt


class Service1 : Service() {
    override fun onCreate() {
        super.onCreate()

        (application as MainApplication).sharedHoge.fuga()
    }
}

Cependant, il va sans dire que cette technique ne peut être utilisée que pour partager des données entre les activités et les services au sein de l'application. Si vous souhaitez partager des données avec d'autres applications, vous pouvez soit donner à l'intention un paramètre comme décrit ci-dessus, soit l'appeler fournisseurs de contenu (https://developer.android.com/guide/topics/providers/content-providers). Vous utiliserez le mécanisme.

Ne devrions-nous pas simplement utiliser des variables statiques sans un tel tracas?

** Ne l'utilisez pas. ** **

Les membres statiques Java et les objets Kotlin sont accessibles sans spécifier d'instance de la classe, et je suis sûr que certaines personnes essaieront de l'utiliser pour partager des données. Par exemple, le code ci-dessous.

SharedData.kt


object SharedData {
    var hoge: String = ""
}

Activity1.kt


class Activity1 : AppCompatActivity() {
    fun onClickFugaButton() {
        SharedData.hoge = "fuga"
    }
}

Activity2.kt


class Activity2 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_2)
        findViewById<TextView>(R.id.textViewHoge).text = SharedData.hoge
    }
}

Dans cet exemple, nous essayons de partager des données en utilisant SharedData.hoge comme une variable globale. C'est bien sur d'autres plates-formes, mais Android ne le permet pas.

Quoi? Mais il n'y a pas d'erreur de compilation et cela fonctionne normalement?

Oui, il n'y a pas d'erreurs. Parfois, cela fonctionne comme prévu. Mais parfois, cela ne fonctionne pas comme prévu. Comme cela fonctionne normalement ou ne fonctionne pas, il est facile de semer la confusion.

Contrairement au garbage collector Java typique, le garbage collector Android décharge les informations de la mémoire ainsi que l'instance elle-même ainsi que la classe elle-même. Et les données détenues par la variable statique lorsque la classe est déchargée sont également détruites. Le code ci-dessus oblige SharedData.hoge à contenir les données de chaîne, mais le garbage collector peut décharger l'objet SharedData (classe). Ensuite, les données de chaîne de caractères stockées seront également détruites. Si vous essayez ensuite de lire SharedData.hoge, l'objet SharedData (classe) sera chargé et initialisé à nouveau, et des caractères vides seront renvoyés.

C'est peut-être le plus gros point que les personnes qui sont passées au développement Android à partir d'autres plates-formes tomberont. Pour ces raisons, les variables statiques Java et les objets Kotlin ne doivent être utilisés que de la manière suivante lors du développement d'applications Android.

De cette manière, l'utilisation de variables et d'objets statiques pour le partage de données (transfert de données) entre les activités et les services doit être fondamentalement évitée. Comme expliqué jusqu'ici, l'intention passée à startActivity () ou startService () devrait l'avoir comme paramètre, la classe dérivée de ʻApplication devrait avoir des données partagées, ou les données devraient être stockées dans SharedPreferences`. Vous pouvez le partager avec d'autres ou l'enregistrer en tant que fichier normal.

Et si vous souhaitez que le service déclenche un événement dans Activity, par exemple?

Jusqu'à présent, nous avons expliqué comment démarrer une activité ou un service. Pour récapituler rapidement, vous savez que vous pouvez créer une intention et la transmettre aux méthodes startActivity () et startService ().

Cependant, lors de la liaison entre les activités et les services, il peut ne pas être possible d'atteindre l'objectif simplement en "démarrant l'activité / le service cible". Par exemple, envisagez de lancer un service à partir d'une activité et de faire savoir à l'activité que le service a terminé le traitement en arrière-plan et qu'il est "terminé". L'Activité peut démarrer le Service avec startService () (ou startForegroundService ()) comme nous l'avons vu. Alors, comment le service informe-t-il l'activité que c'est fait?

Dans ce cas, il n'est pas toujours approprié d'utiliser startActivity () car Activity a déjà démarré. La méthode sendBroadcast () est utilisée dans de tels cas. Cette méthode, comme startActivity () et startService (), sert à envoyer des intentions, mais à un BroadcastReceiver au lieu d'une activité ou d'un service. BroadcastReceiver peut être généré et enregistré dans Activity ou Service, donc il est utilisé pour recevoir Intent.

MainActivity.kt


class MainActivity : AppCompatActivity() {
    private val myBroadcastReceiver = object: BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            findViewById<TextView>(R.id.textView1).text = "Traitement terminé"
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //Enregistrer BroadcastReceiver
        LocalBroadcastManager.getInstance(this).registerReceiver(
            myBroadcastReceiver,
            IntentFilter("test.app.actions.SERVICE_FINISHED")
        )
        
        //Début du service
        startService(Intent(this, Service3::class.java))
    }

    override fun onDestroy() {
        LocalBroadcastManager.getInstance(this).unregisterReceiver(myBroadcastReceiver)
        super.onDestroy()
    }
}

Service3.kt


class Service3: IntentService("Service3") {
    override fun onHandleIntent(intent: Intent?) {
        //Traiter diverses choses
        Thread.sleep(1000)

        //Envoyer une diffusion
        LocalBroadcastManager.getInstance(this).sendBroadcast(
            Intent("test.app.actions.SERVICE_FINISHED")
        )
    }
}

Ici, nous avons utilisé LocalBroadcastManager # sendBroadcast () car la destination de la diffusion est l'activité dans l'application, mais si vous voulez l'envoyer à une autre application, utilisez Context # sendBroadcast () (Context est ʻActivity`. Et «Service» sont des classes parentes communes). [^ diffusion1] [^ diffusion2]

[^ broadcast1]: pour dire la vérité, si vous souhaitez envoyer un événement du service à l'activité dans votre application, vous ne devez pas nécessairement utiliser BroadcastReceiver. En effet, il est possible d'appeler une méthode d'instance Activity depuis Service via une classe dérivée de ʻApplication`. Cependant, même si vous faites cela, vous devez concevoir votre activité et votre service de manière aussi lâche que possible.

[^ broadcast2]: BroadcastReceiver peut également recevoir des événements système déclenchés par le système d'exploitation. Par exemple, il est possible d'exécuter certains traitements immédiatement après le démarrage du terminal. Cependant, si ni Activité ni Service n’existent, vous devez penser qu’il est inévitable que le processus se termine lorsque le traitement est quitté de BroadcastReceiver # onReceive (), donc si vous voulez traiter pendant une longue période, ` Vous devez démarrer le service dans BroadcastReceiver # onReceive () et effectuer des opérations chronophages dans ce service.

Résumé

Cela fait longtemps, donc je l'ai beaucoup plié, mais s'il y a quelque chose que je ne comprends pas, je lui demanderai de le rechercher sur Google.

L'architecture Android est assez unique, n'est-ce pas? Au début, je pouvais comprendre que l'événement du cycle de vie était "bien nécessaire", mais dès que j'ai essayé de lier Activité / Service, il est devenu "???". J'espère que cela aide les gens qui trébuchent au même endroit.

Recommended Posts

Points clés du développement d'applications Android où les programmeurs à l'ancienne ont trébuché
Lancement du développement personnel de l'application Android
Développement d'applications ROS sur Android
Points sur lesquels je suis tombé lors de la création d'une application Android [Mis à jour de temps en temps]
Développement (paramètres) Riot (application de chat) dans Android Studio