web-dev-qa-db-fra.com

Utilisation du service pour exécuter en arrière-plan et créer une notification

Je souhaite que mon application démarre le service lorsque l'utilisateur clique sur le bouton. Le service doit être exécuté en arrière-plan pour afficher une notification à une heure donnée de la journée. J'ai le code suivant pour faire ceci. Mais cela montre des erreurs que je ne comprends pas. Je suis nouveau sur Android et c'est ma première application utilisant Service. Toute aide serait appréciée. Merci d'avance.

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:Android="http://schemas.Android.com/apk/res/Android"
    package="com.example.newtrial"
    Android:versionCode="1"
    Android:versionName="1.0" >

    <uses-sdk
        Android:minSdkVersion="8"
        Android:targetSdkVersion="18" />

    <application
        Android:allowBackup="true"
        Android:icon="@drawable/ic_launcher"
        Android:label="@string/app_name"
        Android:theme="@style/AppTheme" >
        <activity
            Android:name="com.example.newtrial.CreateNotificationActiviy"
            Android:label="@string/app_name" >
            <intent-filter>
                <action Android:name="Android.intent.action.MAIN" />

                <category Android:name="Android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            Android:name="com.example.newtrial.ResultActivity"
            Android:label="@string/title_activity_result" >

        </activity>

        <service Android:enabled="true" Android:name=".UpdaterServiceManager" />

    </application>

</manifest>

CreateNotificationActiviy.Java

package com.example.newtrial;

import Android.os.Bundle;
import Android.app.Activity;
import Android.app.Notification;
import Android.app.NotificationManager;
import Android.app.PendingIntent;
import Android.content.Intent;
import Android.view.Menu;
import Android.view.View;
import Android.view.View.OnClickListener;
import Android.widget.Button;

public class CreateNotificationActiviy extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.create_notification_activiy);

        Button b=(Button)findViewById(R.id.button1);
        b.setOnClickListener(new OnClickListener(){

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub      
                startService(new Intent(CreateNotificationActiviy.this, UpdaterServiceManager.class));
            }

        });

    }

    public void createNotification(View view) {
        // Prepare intent which is triggered if the
        // notification is selected
        Intent intent = new Intent(this, ResultActivity.class);
        PendingIntent pIntent = PendingIntent.getActivity(this, 0, intent, 0);

        // Build notification
        // Actions are just fake
        Notification noti = new Notification.Builder(this)
            .setContentTitle("Notification Title")
            .setContentText("Click here to read").setSmallIcon(R.drawable.ic_launcher)
            .setContentIntent(pIntent)
            .build();
        NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        // hide the notification after its selected
        noti.flags |= Notification.FLAG_AUTO_CANCEL;

        notificationManager.notify(0, noti);

      } 

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.create_notification_activiy, menu);
        return true;
    }

}

UpdaterServiceManager.Java

package com.example.newtrial;

import Java.util.Calendar;
import Java.util.Timer;
import Java.util.TimerTask;

import Android.app.AlertDialog;
import Android.app.Notification;
import Android.app.NotificationManager;
import Android.app.PendingIntent;
import Android.app.Service;
import Android.content.Context;
import Android.content.Intent;
import Android.os.IBinder;
import Android.util.Log;
import Android.view.View;

public class UpdaterServiceManager extends Service {

    private final int UPDATE_INTERVAL = 60 * 1000;
    private Timer timer = new Timer();
    private static final int NOTIFICATION_EX = 1;
    private NotificationManager notificationManager;
    CreateNotificationActiviy not;

    public UpdaterServiceManager() {
        not=new CreateNotificationActiviy();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void onCreate() {
        // code to execute when the service is first created
        super.onCreate();
        Log.i("MyService", "Service Started.");
        showNotification();
    }

    public void showNotification()
    {
        final Calendar cld = Calendar.getInstance();

        int time = cld.get(Calendar.HOUR_OF_DAY);
        if(time>12)
        {
                  not.createNotification(null); 

        }
        else
        {
            AlertDialog.Builder alert=new AlertDialog.Builder(this);
            alert.setMessage("Not yet");
            alert.setTitle("Error");
            alert.setPositiveButton("OK", null);
            alert.create().show();
        }
    }

    @Override
    public void onDestroy() {
        if (timer != null) {
            timer.cancel();
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startid) 
    {
        return START_STICKY;
    }

    private void stopService() {
        if (timer != null) timer.cancel();
    }

}

ResultActivity.Java

package com.example.newtrial;

import Android.os.Bundle;
import Android.app.Activity;
import Android.view.Menu;
import Android.widget.TextView;

public class ResultActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_result);
        TextView tv=(TextView)findViewById(R.id.textView1);
        tv.setText("After notification is clicked" );
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.result, menu);
        return true;
    }

}

Logcat

12-10 12:14:04.286: I/Process(872): Sending signal. PID: 872 SIG: 9
12-10 12:14:11.774: I/MyService(893): Service Started.
12-10 12:14:12.094: D/AndroidRuntime(893): Shutting down VM
12-10 12:14:12.094: W/dalvikvm(893): threadid=1: thread exiting with uncaught exception (group=0x414c4700)
12-10 12:14:12.124: E/AndroidRuntime(893): FATAL EXCEPTION: main
12-10 12:14:12.124: E/AndroidRuntime(893): Java.lang.RuntimeException: Unable to create service com.example.newtrial.UpdaterServiceManager: Android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application
12-10 12:14:12.124: E/AndroidRuntime(893):  at Android.app.ActivityThread.handleCreateService(ActivityThread.Java:2587)
12-10 12:14:12.124: E/AndroidRuntime(893):  at Android.app.ActivityThread.access$1600(ActivityThread.Java:141)
12-10 12:14:12.124: E/AndroidRuntime(893):  at Android.app.ActivityThread$H.handleMessage(ActivityThread.Java:1338)
12-10 12:14:12.124: E/AndroidRuntime(893):  at Android.os.Handler.dispatchMessage(Handler.Java:99)
12-10 12:14:12.124: E/AndroidRuntime(893):  at Android.os.Looper.loop(Looper.Java:137)
12-10 12:14:12.124: E/AndroidRuntime(893):  at Android.app.ActivityThread.main(ActivityThread.Java:5103)
12-10 12:14:12.124: E/AndroidRuntime(893):  at Java.lang.reflect.Method.invokeNative(Native Method)
12-10 12:14:12.124: E/AndroidRuntime(893):  at Java.lang.reflect.Method.invoke(Method.Java:525)
12-10 12:14:12.124: E/AndroidRuntime(893):  at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:737)
12-10 12:14:12.124: E/AndroidRuntime(893):  at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:553)
12-10 12:14:12.124: E/AndroidRuntime(893):  at dalvik.system.NativeStart.main(Native Method)
12-10 12:14:12.124: E/AndroidRuntime(893): Caused by: Android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application
12-10 12:14:12.124: E/AndroidRuntime(893):  at Android.view.ViewRootImpl.setView(ViewRootImpl.Java:563)
12-10 12:14:12.124: E/AndroidRuntime(893):  at Android.view.WindowManagerGlobal.addView(WindowManagerGlobal.Java:269)
12-10 12:14:12.124: E/AndroidRuntime(893):  at Android.view.WindowManagerImpl.addView(WindowManagerImpl.Java:69)
12-10 12:14:12.124: E/AndroidRuntime(893):  at Android.app.Dialog.show(Dialog.Java:281)
12-10 12:14:12.124: E/AndroidRuntime(893):  at com.example.newtrial.UpdaterServiceManager.showNotification(UpdaterServiceManager.Java:65)
12-10 12:14:12.124: E/AndroidRuntime(893):  at com.example.newtrial.UpdaterServiceManager.onCreate(UpdaterServiceManager.Java:41)
12-10 12:14:12.124: E/AndroidRuntime(893):  at Android.app.ActivityThread.handleCreateService(ActivityThread.Java:2577)
12-10 12:14:12.124: E/AndroidRuntime(893):  ... 10 more
44
user2648852

La question est relativement ancienne, mais j'espère que ce message pourra toujours être pertinent pour d'autres.

TL; DR: utilisez AlarmManager pour planifier une tâche, utilisez IntentService, voir l'exemple de code ici ;

En quoi consiste ce test-application (et cette instruction):

Application simple de helloworld, qui vous envoie une notification toutes les 2 heures. En cliquant sur notification - ouvre l'activité secondaire dans l'application; supprimer les traces de notification.

Quand devriez-vous l'utiliser:

Une fois que vous devez exécuter une tâche sur une base programmée. Mon cas personnel: une fois par jour, je veux récupérer du nouveau contenu sur le serveur, composer une notification en fonction du contenu que j'ai reçu et le montrer à l'utilisateur.

Que faire:

  1. Commençons par créer 2 activités: MainActivity, qui lance le service de notification et NotificationActivity, qui sera démarré en cliquant sur notification:

    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:padding="16dp">
        <Button
            Android:id="@+id/sendNotifications"
            Android:onClick="onSendNotificationsButtonClick"
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="Start Sending Notifications Every 2 Hours!" />
    </RelativeLayout>
    

    MainActivity.Java

    public class MainActivity extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    
        public void onSendNotificationsButtonClick(View view) {
            NotificationEventReceiver.setupAlarm(getApplicationContext());
        }   
    }
    

    et NotificationActivity est une activité aléatoire que vous pouvez réaliser. NB ! N'oubliez pas d'ajouter les deux activités dans AndroidManifest.

  2. Ensuite, créons WakefulBroadcastReceiver récepteur, j’ai appelé NotificationEventReceiver dans le code ci-dessus.

    Ici, nous allons configurer AlarmManager pour déclencher PendingIntent toutes les 2 heures (ou à toute autre fréquence), et spécifier les actions gérées pour cette intention dans la méthode onReceive(). Dans notre cas - wakefully start IntentService, que nous préciserons dans les étapes suivantes. Ce IntentService générerait des notifications pour nous.

    En outre, ce récepteur contiendrait des méthodes d'assistance telles que la création de PendintIntents, que nous utiliserons plus tard.

    NB1 ! Comme j'utilise WakefulBroadcastReceiver , je dois ajouter une permission supplémentaire dans mon manifeste: <uses-permission Android:name="Android.permission.WAKE_LOCK" />

    NB2 ! J'utilise la version de veille du récepteur de diffusion, car je veux m'assurer que le périphérique ne se rende pas en veille pendant l'opération de mon IntentService. Dans le monde des salutations, ce n'est pas si important (notre service ne fonctionne pas depuis longtemps, mais imaginez si vous devez extraire des fichiers relativement volumineux du serveur au cours de cette opération). En savoir plus sur Device Awake here .

    NotificationEventReceiver.Java

    public class NotificationEventReceiver extends WakefulBroadcastReceiver {
    
        private static final String ACTION_START_NOTIFICATION_SERVICE = "ACTION_START_NOTIFICATION_SERVICE";
        private static final String ACTION_DELETE_NOTIFICATION = "ACTION_DELETE_NOTIFICATION";
        private static final int NOTIFICATIONS_INTERVAL_IN_HOURS = 2;
    
        public static void setupAlarm(Context context) {
            AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
            PendingIntent alarmIntent = getStartPendingIntent(context);
            alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,
                    getTriggerAt(new Date()),
                    NOTIFICATIONS_INTERVAL_IN_HOURS * AlarmManager.INTERVAL_HOUR,
                    alarmIntent);
        }
    
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            Intent serviceIntent = null;
            if (ACTION_START_NOTIFICATION_SERVICE.equals(action)) {
                Log.i(getClass().getSimpleName(), "onReceive from alarm, starting notification service");
                serviceIntent = NotificationIntentService.createIntentStartNotificationService(context);
            } else if (ACTION_DELETE_NOTIFICATION.equals(action)) {
                Log.i(getClass().getSimpleName(), "onReceive delete notification action, starting notification service to handle delete");
                serviceIntent = NotificationIntentService.createIntentDeleteNotification(context);
            }
    
            if (serviceIntent != null) {
                startWakefulService(context, serviceIntent);
            }
        }
    
        private static long getTriggerAt(Date now) {
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(now);
            //calendar.add(Calendar.HOUR, NOTIFICATIONS_INTERVAL_IN_HOURS);
            return calendar.getTimeInMillis();
        }
    
        private static PendingIntent getStartPendingIntent(Context context) {
            Intent intent = new Intent(context, NotificationEventReceiver.class);
            intent.setAction(ACTION_START_NOTIFICATION_SERVICE);
            return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        }
    
        public static PendingIntent getDeleteIntent(Context context) {
            Intent intent = new Intent(context, NotificationEventReceiver.class);
            intent.setAction(ACTION_DELETE_NOTIFICATION);
            return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        }
    }
    
  3. Créons maintenant un IntentService pour créer des notifications.

    Ici, nous spécifions onHandleIntent() qui correspond aux réponses sur NotificationEventReceiver l'intention que nous avons passée dans la méthode startWakefulService.

    S'il s'agit d'une action de suppression, nous pouvons la connecter à nos analyses, par exemple. Si c'est l'intention de notification de démarrage - en utilisant NotificationCompat.Builder, Nous composons une nouvelle notification et la montrons avec NotificationManager.notify. Lors de la rédaction de la notification, nous définissons également les intentions en attente pour les actions de clic et de suppression. Assez facile.

    NotificationIntentService.Java

    public class NotificationIntentService extends IntentService {
    
        private static final int NOTIFICATION_ID = 1;
        private static final String ACTION_START = "ACTION_START";
        private static final String ACTION_DELETE = "ACTION_DELETE";
    
        public NotificationIntentService() {
            super(NotificationIntentService.class.getSimpleName());
        }
    
        public static Intent createIntentStartNotificationService(Context context) {
            Intent intent = new Intent(context, NotificationIntentService.class);
            intent.setAction(ACTION_START);
            return intent;
        }
    
        public static Intent createIntentDeleteNotification(Context context) {
            Intent intent = new Intent(context, NotificationIntentService.class);
            intent.setAction(ACTION_DELETE);
            return intent;
        }
    
        @Override
        protected void onHandleIntent(Intent intent) {
            Log.d(getClass().getSimpleName(), "onHandleIntent, started handling a notification event");
            try {
                String action = intent.getAction();
                if (ACTION_START.equals(action)) {
                    processStartNotification();
                }
                if (ACTION_DELETE.equals(action)) {
                    processDeleteNotification(intent);
                }
            } finally {
                WakefulBroadcastReceiver.completeWakefulIntent(intent);
            }
        }
    
        private void processDeleteNotification(Intent intent) {
            // Log something?
        }
    
        private void processStartNotification() {
            // Do something. For example, fetch fresh data from backend to create a rich notification?
    
            final NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
            builder.setContentTitle("Scheduled Notification")
                    .setAutoCancel(true)
                    .setColor(getResources().getColor(R.color.colorAccent))
                    .setContentText("This notification has been triggered by Notification Service")
                    .setSmallIcon(R.drawable.notification_icon);
    
            PendingIntent pendingIntent = PendingIntent.getActivity(this,
                    NOTIFICATION_ID,
                    new Intent(this, NotificationActivity.class),
                    PendingIntent.FLAG_UPDATE_CURRENT);
            builder.setContentIntent(pendingIntent);
            builder.setDeleteIntent(NotificationEventReceiver.getDeleteIntent(this));
    
            final NotificationManager manager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
            manager.notify(NOTIFICATION_ID, builder.build());
        }
    }
    
  4. Presque fini. Maintenant, j'ajoute également un récepteur de diffusion pour les événements BOOT_COMPLETED, TIMEZONE_CHANGED et TIME_SET afin de reconfigurer mon AlarmManager, une fois le périphérique redémarré ou le fuseau horaire modifié (par exemple, un utilisateur a voyagé des États-Unis vers l'Europe et vous ne souhaitez pas qu'une notification s'affiche. au milieu de la nuit, mais était collé à l'heure locale :-)).

    NotificationServiceStarterReceiver.Java

    public final class NotificationServiceStarterReceiver extends BroadcastReceiver {
    
        @Override
        public void onReceive(Context context, Intent intent) {
            NotificationEventReceiver.setupAlarm(context);
        }
    }
    
  5. Nous devons également enregistrer tous nos services, récepteurs de radiodiffusion dans AndroidManifest:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:Android="http://schemas.Android.com/apk/res/Android"
        package="klogi.com.notificationbyschedule">
    
        <uses-permission Android:name="Android.permission.INTERNET" />
        <uses-permission Android:name="Android.permission.ACCESS_NETWORK_STATE" />
        <uses-permission Android:name="Android.permission.RECEIVE_BOOT_COMPLETED" />
        <uses-permission Android:name="Android.permission.WAKE_LOCK" />
    
        <application
            Android:allowBackup="true"
            Android:icon="@mipmap/ic_launcher"
            Android:label="@string/app_name"
            Android:supportsRtl="true"
            Android:theme="@style/AppTheme">
            <activity Android:name=".MainActivity">
                <intent-filter>
                    <action Android:name="Android.intent.action.MAIN" />
    
                    <category Android:name="Android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
    
            <service
                Android:name=".notifications.NotificationIntentService"
                Android:enabled="true"
                Android:exported="false" />
    
            <receiver Android:name=".broadcast_receivers.NotificationEventReceiver" />
            <receiver Android:name=".broadcast_receivers.NotificationServiceStarterReceiver">
                <intent-filter>
                    <action Android:name="Android.intent.action.BOOT_COMPLETED" />
                    <action Android:name="Android.intent.action.TIMEZONE_CHANGED" />
                    <action Android:name="Android.intent.action.TIME_SET" />
                </intent-filter>
            </receiver>
    
            <activity
                Android:name=".NotificationActivity"
                Android:label="@string/title_activity_notification"
                Android:theme="@style/AppTheme.NoActionBar"/>
        </application>
    
    </manifest>
    

C'est ça!

Le code source de ce projet, vous pouvez trouver ici . J'espère que vous trouverez ce post utile.

93
Konstantin Loginov

Votre erreur est dans UpdaterServiceManager dans les méthodes onCreate et showNotification.

Vous essayez de montrer notification de Service using Activity Context. Tandis que Every Service has its own Context, juste utiliser le ça. Vous n'avez pas besoin de pass a Service an Activity's Context.Je ne vois pas pourquoi vous avez besoin d’un Activity's Context to show Notification.

Mettez votre méthode createNotification dans UpdateServiceManager.class. Et retirez CreateNotificationActivity pas du service.

Vous ne pouvez pas afficher une fenêtre/boîte de dialogue d'application à travers un contexte qui n'est pas une activité. Essayez de passer une référence d'activité valide

2
Xar E Ahmer