web-dev-qa-db-fra.com

Le récepteur de diffusion ne fonctionne pas après le redémarrage de l'appareil dans Android

J'ai déjà vérifié toutes les questions et n'ai trouvé aucune solution à ce problème. C'est donc un problème absolument nouveau pour moi.

ce que j'ai

J'ai une application Android qui enregistre quelques récepteurs de diffusion dans son manifeste. Voici à quoi ressemble mon manifeste.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:tools="http://schemas.Android.com/tools"
    package="com.app.myapp">

    <uses-permission Android:name="Android.permission.PROCESS_OUTGOING_CALLS" />
    <uses-permission Android:name="Android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission Android:name="Android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission Android:name="Android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission Android:name="Android.permission.USE_FINGERPRINT" />
    <uses-permission Android:name="Android.permission.INTERNET" />
    <uses-permission Android:name="Android.permission.READ_CALL_LOG" />
    <uses-permission Android:name="Android.permission.WRITE_CALL_LOG" />
    <uses-permission Android:name="com.Android.vending.BILLING" />
    <uses-permission Android:name="Android.permission.INTERNET" />
    <uses-permission Android:name="Android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission Android:name="Android.permission.GET_ACCOUNTS" />

    <uses-feature
        Android:name="Android.hardware.telephony"
        Android:required="false" />

    <uses-feature
        Android:name="Android.hardware.screen.portrait"
        Android:required="false" />

    <application
        Android:name=".base.MyApp"
        Android:allowBackup="false"
        Android:icon="@drawable/ic_launcher"
        Android:label="@string/label_app_name"
        Android:largeHeap="true"
        Android:supportsRtl="true"
        Android:theme="@style/AppTheme"
        tools:replace="label, allowBackup">

        <receiver Android:name=".mics.BootReceiver">
            <intent-filter>
                <action Android:name="Android.intent.action.BOOT_COMPLETED" />
                <action Android:name="Android.intent.action.QUICKBOOT_POWERON" />
            </intent-filter>
        </receiver>

        <receiver Android:name=".PhoneCallReceiver">
            <intent-filter>
                <action Android:name="Android.intent.action.NEW_OUTGOING_CALL" />
            </intent-filter>
        </receiver>

        <receiver
            Android:name=".mics.DeviceAdminReceiver"
            Android:permission="Android.permission.BIND_DEVICE_ADMIN">
            <intent-filter>
                <action Android:name="Android.app.action.DEVICE_ADMIN_ENABLED" />
            </intent-filter>

            <meta-data
                Android:name="Android.app.device_admin"
                Android:resource="@xml/device_admin" />
        </receiver>

        <receiver
            Android:name="com.clevertap.Android.sdk.InstallReferrerBroadcastReceiver"
            Android:exported="true">
            <intent-filter>
                <action Android:name="com.Android.vending.INSTALL_REFERRER" />
            </intent-filter>
        </receiver>

        <meta-data
            Android:name="com.app.myapp.utils.ImageLoaderModule"
            Android:value="GlideModule" />

        <meta-data
            Android:name="com.app.myapp.utils.AudioCoverLoaderModule"
            Android:value="GlideModule" />

        <provider
            Android:name="Android.support.v4.content.FileProvider"
            Android:authorities="${applicationId}.provider"
            Android:exported="false"
            Android:grantUriPermissions="true">

            <meta-data
                Android:name="Android.support.FILE_PROVIDER_PATHS"
                Android:resource="@xml/provider_paths" />
        </provider>

        <activity
            Android:name=".core.activities.SplashActivity"
            Android:excludeFromRecents="true"
            Android:label="@string/label_app_name"
            Android:screenOrientation="portrait">
            <intent-filter>
                <action Android:name="Android.intent.action.MAIN" />
            </intent-filter>
        </activity>

        <activity-alias
            Android:name=".core.activities.SplashActivity-Alias"
            Android:icon="@drawable/ic_launcher"
            Android:label="@string/label_app_name"
            Android:noHistory="true"
            Android:targetActivity="com.app.myapp.core.activities.SplashActivity">

            <intent-filter>
                <action Android:name="Android.intent.action.MAIN" />

                <category Android:name="Android.intent.category.LAUNCHER" />
                <category Android:name="Android.intent.category.DEFAULT" />
                <category Android:name="Android.intent.category.MONKEY" />
            </intent-filter>

        </activity-alias>

        <activity
            Android:name=".core.flow.authFlow.activities.AuthFlowActivity"
            Android:excludeFromRecents="true"
            Android:label="@string/label_app_name"
            Android:screenOrientation="portrait" />

        <service Android:name=".features.fileCloudSync.KillNotificationService" />

    </application>

</manifest>

Il existe également 10 à 15 autres activités, mais elles ont été supprimées pour des raisons de simplicité. Et c'est la classe de base du récepteur d'amorçage. Je commence un service d'ici.

public class BootReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
            AlertUtils.showToast(context, "BOOT COMPLETED", Toast.LENGTH_LONG);
        }
    }
}

et la classe du récepteur d'appels téléphoniques ressemble à ceci (elle a également été simplifiée),

public class PhoneCallReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
            AlertUtils.showToast(context, "PHONE CALL RECEIVED", Toast.LENGTH_LONG);
            // Simplified for brevity
        }
    }
}

Le problème

Tous ces récepteurs fonctionnent correctement lorsque j'installe l'application et que je la lance une fois. Mais après le redémarrage de mon appareil, ces récepteurs ne fonctionnent plus du tout. Ni les BootCompleteReceiver ni les PhoneCallReceiver ne reçoivent leur méthode appelée onReceive().

Mon hypothèse était que ces récepteurs seraient enregistrés automatiquement après le redémarrage, mais cela ne fonctionne tout simplement pas. J'ai besoin de BootCompleteReceiver pour fonctionner afin de pouvoir démarrer un service important dans mon application.

Mes observations

J'ai testé cela à fond. Après le redémarrage de l'appareil, les récepteurs fonctionnent correctement dans mon Nexus 5X (Nougat), Nexus 6P (Nougat), YU Yuphoria (Lollipop) mais pas dans mon OnePlus 3 (Nougat) et Mi 4i (sucette).

Comment le même code peut-il fonctionner parfaitement sur quelques appareils et ne pas fonctionner du tout sur les autres? Je n'ai rien changé du tout.

Qu'est-ce que je fais mal ici? Mon application est fortement dépendante de ces émissions et lance des services basés sur celles-ci. Toute aide sera grandement appréciée.

EDIT 1

Pour mieux comprendre le problème, je viens de créer un très petit projet test avec une seule activité et exactement la même chose BootCompleteReceiver et PhoneCallReceiver.

Mais bizarrement, ce projet fonctionne parfaitement sur mon OnePlus 3, où les récepteurs de mon application réelle ne fonctionnent pas après un redémarrage. Au départ, je pensais que le problème venait du système d'exploitation ou du périphérique, mais ce n'est pas le cas.

Alors, où est le problème actuel? Est-ce dans mon application (mais cela fonctionne parfaitement sur d'autres appareils) ou dans le système d'exploitation et l'appareil (le petit projet de test fonctionne correctement sur le même système d'exploitation et le même appareil)?

C'est vraiment déroutant pour moi. J'aurais besoin de l'aide d'experts à ce sujet.

EDIT 2

J'ai essayé la suggestion donnée par @shadygoneinsane. Voici mes observations.

1) J'ai essayé d'envoyer la diffusion BOOT_COMPLETED via ADB.

./adb Shell am broadcast -a Android.intent.action.BOOT_COMPLETED -p com.app.myapp

Et j'ai cette trace de pile,

Broadcasting: Intent { act=Android.intent.action.BOOT_COMPLETED pkg=com.app.myapp }
Java.lang.SecurityException: Permission Denial: not allowed to send broadcast Android.intent.action.BOOT_COMPLETED from pid=25378, uid=2000
    at Android.os.Parcel.readException(Parcel.Java:1683)
    at Android.os.Parcel.readException(Parcel.Java:1636)
    at Android.app.ActivityManagerProxy.broadcastIntent(ActivityManagerNative.Java:3696)
    at com.Android.commands.am.Am.sendBroadcast(Am.Java:778)
    at com.Android.commands.am.Am.onRun(Am.Java:404)
    at com.Android.internal.os.BaseCommand.run(BaseCommand.Java:51)
    at com.Android.commands.am.Am.main(Am.Java:121)
    at com.Android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
    at com.Android.internal.os.RuntimeInit.main(RuntimeInit.Java:276)

Peut-être parce que mon appareil n'est pas enraciné. Je ne peux en aucun cas envoyer cette émission.

2) J'ai essayé avec la diffusion PROCESS_OUTGOING_CALLS par la suite.

./adb Shell am broadcast -a Android.intent.action.PROCESS_OUTGOING_CALLS -p com.app.myapp

J'ai eu ça,

Broadcasting: Intent { act=Android.intent.action.PROCESS_OUTGOING_CALLS pkg=com.app.myapp }
Broadcast completed: result=0

Il semble que la diffusion a été un succès, mais je ne vois ni Toast ni aucun journal. J'ai ensuite ouvert mon numéroteur pour composer un numéro et je peux alors voir le pain grillé et le journal.

Il semble donc que l'envoi de l'émission par la BAD n'a pas fonctionné, mais ouvrir le numéroteur et composer un numéro a fonctionné.

EDIT

Selon la suggestion de @ChaitanyaAtkuri, j'ai également essayé d'ajouter la priorité aux filtres d'intention, mais cela n'a pas fonctionné aussi bien.

J'ai utilisé des priorités telles que 500, 999 et même la valeur entière la plus élevée, mais rien ne fonctionne. Ce problème se produit également dans certaines applications de mes amis. Ils fonctionnent dans certains appareils et ne fonctionne pas dans d'autres.

EDIT 4

J'ai enfin découvert la cause du problème qui se produit dans mon OnePlus. Mon OnePlus 3 a récemment été mis à jour vers Nougat et a introduit une fonctionnalité similaire aux appareils Mi, qui empêche certaines applications de démarrer automatiquement après le redémarrage.

Après avoir désactivé cette fonctionnalité, mon application a commencé à recevoir des émissions après un redémarrage parfait. Mais cela n'explique toujours pas deux choses.

1) Mon petit projet de test est automatiquement ajouté à la liste blanche dans la liste des applications AutoLaunch. C'est pourquoi il fonctionne comme prévu. Mais comment est-ce possible? Pourquoi le système d'exploitation considère-t-il que cette petite application mérite d'être lancée automatiquement?

2) Certaines applications, telles que LockDown Pro, 500 Firepaper, figurent sur la liste noire de l'écran des applications AutoLaunch, mais reçoivent néanmoins les émissions après le redémarrage dans mon OnePlus 3 et Mi 4i. Comment est-ce possible maintenant? Est-il possible, d'une manière ou d'une autre, d'autoriser le lancement automatique de mon application sur ces appareils (OnePlus et Mi)?

EDIT 5

J'ai essayé la solution proposée par @Rahul Chowdhury et cela semble vraiment très bien fonctionner. Après avoir ajouté le service d'accessibilité, le problème est résolu.

Mais si l'utilisateur révoque l'autorisation d'accès après l'avoir accordée, puis-je disposer d'un moyen de vérifier par programme si l'autorisation d'accès est disponible pour mon application?

47
Aritra Roy

Voici une solution testée et fonctionnelle sur les appareils que vous avez mentionnés, OnePlus et Mi.

Comme vous l'avez dit, la fonction de prévention du démarrage automatique sur les appareils OnePlus et Mi empêche les applications de démarrer leurs services automatiquement au démarrage, de manière à améliorer la vitesse de démarrage globale de l'appareil et la batterie. performance. Toutefois, il existe une solution pour que votre application fonctionne même lorsque cette fonctionnalité est activée.

J'ai remarqué que si vous avez un AccessibilityService dans votre application et que celle-ci est activée par l'utilisateur, votre application transmet le filtre que ces fabricants appliquent et l'application reçoit son événement de fin de démarrage et tout autre BroadcastReceiver fonctionne comme prévu.

L’explication possible de cette astuce peut être que puisque AccessibilityService est un service de niveau système, donc en enregistrant votre propre service, vous êtes en passant le filtre spécifique appliqué par ces fabricants et dès que votre custom AccessibilityService est déclenché par le système d'exploitation, votre application devient active pour recevoir le BroadcastReceiver éligible que vous aviez enregistré.

Alors, voici comment le faire,

Commencez par ajouter cette permission à votre AndroidManifest.xml,

<uses-permission Android:name="Android.permission.BIND_ACCESSIBILITY_SERVICE"/>

Cela vous permettra d'enregistrer le AccessibilityService de votre application auprès du système.

Maintenant, ajoutez une configuration très basique pour votre AccessibilityService en créant un fichier, par exemple my_accessibility_service.xml dans le dossier XML sous votre dossier res dans votre projet.

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:accessibilityFeedbackType="feedbackSpoken"
    Android:description="@string/service_desc"
    Android:notificationTimeout="100"/>

Il ne reste plus qu’une étape à faire: définissez votre AccessibilityService personnalisé dans votre projet,

public class MyAccessibilityService extends AccessibilityService {

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) { }

    @Override
    public void onInterrupt() {

    }
}

Notez que, puisque vous n’avez pas besoin de AccessibilityService à des fins autres que cette solution de contournement, vous pouvez laisser les méthodes remplacées vides.

Enfin, déclarez simplement votre AccessibilityService dans votre AndroidManifest.xml,

<service
    Android:name=".MyAccessibilityService"
    Android:label="@string/app_name"
    Android:permission="Android.permission.BIND_ACCESSIBILITY_SERVICE">
    <intent-filter>
        <action Android:name="Android.accessibilityservice.AccessibilityService"/>
    </intent-filter>

    <meta-data
        Android:name="Android.accessibilityservice"
        Android:resource="@xml/my_accessibility_service"/>
</service>

C'est tout. Maintenant, dans votre application, demandez simplement à vos utilisateurs d'activer le service d'accessibilité pour votre application à partir des paramètres et laissez-le activé et le tour est joué! Votre application fonctionne correctement sur tous les appareils, même lorsque le système d'exploitation place un filtre sur lequel les applications doivent démarrer automatiquement au démarrage.

EDIT 1

Voici comment vérifier si le service d'accessibilité est activé ou non pour votre application,

private static final int ACCESSIBILITY_ENABLED = 1;

public static boolean isAccessibilitySettingsOn(Context context) {
    int accessibilityEnabled = 0;
    final String service = context.getPackageName() + "/" + MyAccessibilityService.class.getCanonicalName();
    try {
        accessibilityEnabled = Settings.Secure.getInt(
                context.getApplicationContext().getContentResolver(),
                Android.provider.Settings.Secure.ACCESSIBILITY_ENABLED);
    } catch (Settings.SettingNotFoundException e) {
        Log.e("AU", "Error finding setting, default accessibility to not found: "
                + e.getMessage());
    }
    TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');

    if (accessibilityEnabled == ACCESSIBILITY_ENABLED) {
        String settingValue = Settings.Secure.getString(
                context.getApplicationContext().getContentResolver(),
                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
        if (settingValue != null) {
            mStringColonSplitter.setString(settingValue);
            while (mStringColonSplitter.hasNext()) {
                String accessibilityService = mStringColonSplitter.next();

                if (accessibilityService.equalsIgnoreCase(service)) {
                    return true;
                }
            }
        }
    }

    return false;
}

J'espère que cela t'aides.

33
Rahul Chowdhury

Bonjour, je suis en retard à la fête, mais je suivais cette question depuis le début. Je sais que One-plus et certains autres constructeurs gèrent une liste d'applications pouvant recevoir la diffusion BOOT_COMPLETED. Si votre application n'est pas sur la liste blanche, votre application ne sera pas démarrée au démarrage. Maintenant, j'ai une solution qui est très efficace en termes de mémoire et de ressources et qui garantit de démarrer votre tâche ou service après un redémarrage ou un démarrage dur n'a pas non plus besoin de AccessibilityService comme proposé dans ce réponse =. Ici ça va ..

  1. Ajouter <uses-permission Android:name="Android.permission.RECEIVE_BOOT_COMPLETED"/> autorisation dans votre fichier manifest.

2.Si vous n'avez pas de dépendance sur com.google.Android.gms:play-services-gcm, ajoutez les éléments suivants à la section des dépendances de votre build.gradle:

compile 'com.firebase:firebase-jobdispatcher:0.5.2'

Sinon, ajoutez ce qui suit:

compile 'com.firebase:firebase-jobdispatcher-with-gcm-dep:0.5.2'

Ceci est une bibliothèque de firebase équipe qui dépend de google-play-service bibliothèque pour planifier vos travaux et de mon point de vue google-play-service a l'autorisation de démarrer au démarrage, donc au lieu du système google-play-service exécutera votre travail dès que le périphérique sera redémarré.

  1. Maintenant, cette étape est facile Définissez simplement une classe JobService

    public class MyJobService extends JobService {
    
    @Override
    public boolean onStartJob(JobParameters job) {
        Log.v("Running", "====>>>>MyJobService");
    
        return false; // Answers the question: "Is there still work going on?"
    }
    
    @Override
    public boolean onStopJob(JobParameters job) {
        Log.v("Stopping", "====>>>>MyJobService");
        return true; // Answers the question: "Should this job be retried?"
    }
    

}

  1. Ajoutez votre service de travail dans le fichier manifeste.

    <service
      Android:exported="false"
      Android:name=".MyJobService">
    <intent-filter>
        <action Android:name="com.firebase.jobdispatcher.ACTION_EXECUTE"/>
    </intent-filter>
    </service>
    
  2. Planifiez ce travail où vous voulez, par exemple lorsque votre application démarre.

           FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(getApplicationContext()));
    
            Bundle myExtrasBundle = new Bundle();
    
            myExtrasBundle.putString("some_key", "some_value");
    
            Job myJob = dispatcher.newJobBuilder()
                    // the JobService that will be called
                    .setService(MyJobService.class)
                    // uniquely identifies the job
                    .setTag("my-unique-tag-test")
                    // repeat the job
                    .setRecurring(true)
                    // persist past a device reboot
                    .setLifetime(Lifetime.FOREVER)
                    // start between 0 and 60 seconds from now
                    .setTrigger(Trigger.executionWindow(0, 60))
                    // don't overwrite an existing job with the same tag
                    .setReplaceCurrent(false)
                    // retry with exponential backoff
                    .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
                    // constraints that need to be satisfied for the job to run
                    .setExtras(myExtrasBundle)
    
                    .build();
    
            dispatcher.mustSchedule(myJob);
    

6. C'est ça !! Maintenant, vous pouvez exécuter votre tâche ou service au démarrage du périphérique, que vous soyez dans la liste blanche ou non.

Il convient de noter que le service Google Play doit être installé sur l'appareil, sinon il ne fonctionnera pas.

5
Sunny

@Aritra, essayez ceci

<receiver
            Android:name=".mics.BootReceiver"
            Android:enabled="true"
            Android:exported="true" >
            <intent-filter Android:priority="500" >
                <action Android:name="Android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

Supprimez le filtre d’intention quickBoot et essayez de l’exécuter, conformément à la documentation que nous avions seulement besoin de BootCompleted pour l’atteindre. Peut-être que cela l’interrompt.

Également un autre point important à noter:

Ne vous fiez pas complètement aux tests sur les appareils Mi, car ils ont leur propre système d'exploitation qui stoppe les fonctions de base d'Android, comme ils arrêtent les services de notifications Push et les services d'arrière-plan simplement pour optimiser l'utilisation de la batterie. Pour le tester sur les appareils Mi , marquez votre application comme "AutoStart" dans l'application de sécurité, puis essayez.

3
ChaitanyaAtkuri

Le fonctionnement de IntentFilters est que chaque <intent-filter></intent-filter> Contient un moyen d'allumer le composant. Si vous avez plusieurs façons de le déclencher (comme deux actions que vous souhaitez écouter dans une BroadcastReceiver), vous aurez besoin d'une définition indépendante <intent-filter></intent-filter> Pour chacune.

Par conséquent, vous pouvez essayer de changer:

<receiver Android:name=".mics.BootReceiver">
    <intent-filter>
        <action Android:name="Android.intent.action.BOOT_COMPLETED" />
        <action Android:name="Android.intent.action.QUICKBOOT_POWERON" />
    </intent-filter>
</receiver>

à:

<receiver Android:name=".mics.BootReceiver">
    <intent-filter>
        <action Android:name="Android.intent.action.BOOT_COMPLETED" />
    </intent-filter>

    <intent-filter>
        <action Android:name="Android.intent.action.QUICKBOOT_POWERON" />
    </intent-filter>
</receiver>

Lisez plus ici: Filtres d'intentions et d'intentions | Android Développeurs

[~ # ~] éditer [~ # ~]

Si cela ne fonctionne toujours pas, vous pouvez essayer de vérifier si votre déclaration de manifeste est faite correctement. Essayez d’exécuter la commande suivante sur votre terminal tout en maintenant le périphérique de test connecté à l’ordinateur:

adb Shell am broadcast -a Android.intent.action.BOOT_COMPLETED -n com.app.myapp/.mics.BootReceiver

Si cela ne fonctionne pas, vous devez revérifier la déclaration relative au paquet du destinataire dans votre fichier manifeste.

EDIT 2

Cela peut sembler étrange mais essayez de suivre ces étapes:

  • Désinstallez l'application de votre téléphone (assurez-vous qu'elle est désinstallée pour tous les utilisateurs).
  • Redémarrez votre téléphone
  • Nettoyer le projet
  • Générez et exécutez à nouveau le projet dans votre appareil
2
Kamran Ahmed

Je suis aux prises avec ce problème depuis presque un an. Dans toutes mes applications, j'indique aux utilisateurs de désactiver l'optimisation de la batterie pour mon application.

Après de nombreux tests sur les appareils One Plus, je suis en mesure de recevoir la diffusion amorcée lorsque l'optimisation de la batterie est désactivée pour mon application. À mon avis, il est bien meilleur que le service d’accessibilité mentionné ci-dessus.

Le moyen le plus simple de demander à l'utilisateur de désactiver l'optimisation de la batterie pour votre application consiste à afficher une sorte de notification et à ouvrir la page d'optimisation de la batterie lorsque l'utilisateur clique dessus. Vous pouvez utiliser le code ci-dessous pour le faire.

public void openPowerSettings(View v) {

    /* Make Sure to add below code to manifest
    <uses-permission Android:name="Android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
    */

    try {
        Intent i = new Intent(Android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
        startActivityForResult(i, 1);
    }  catch (Exception e) {
        Log.e (TAG, "Exception: " + e.toString());
    }

}

Et vous pouvez également masquer la notification si la fonction ci-dessous renvoie true.

public static boolean is_ignoring_battery_optimizations(Context context) {
    String PACKAGE_NAME = context.getPackageName();
    PowerManager pm = (PowerManager) context.getSystemService(context.POWER_SERVICE);
    boolean status = true;
    if (Android.os.Build.VERSION.SDK_INT >= Android.os.Build.VERSION_CODES.M) {
        status = pm.isIgnoringBatteryOptimizations(PACKAGE_NAME);
    }
    return status;
}
1
mstoic