Notification every day at the same time

有些话、适合烂在心里 提交于 2019-12-24 06:46:14

问题


What I want

I want a notification every day at the same time.
I already read some posts and tutorials/examples but it won't work correctly.

Version 1

The Error: Android process / service dies every ~3 minutes after re/starting

11-07 07:33:05.725  4611  6121 I ActivityManager: Process at.htl3r.appmosphere (pid 5238) has died.
11-07 07:33:05.725  4611  6121 W ActivityManager: Scheduling restart of crashed service at.htl3r.appmosphere/.notify.NotifyService in 14648ms
11-07 07:33:20.400  4611  4632 I ActivityManager: Start proc at.htl3r.appmosphere for service at.htl3r.appmosphere/.notify.NotifyService: pid=5463 uid=10096 gids={50096}

---

11-07 07:33:41.580  4611  4623 I ActivityManager: Process at.htl3r.appmosphere (pid 5463) has died.
11-07 07:33:41.580  4611  4623 W ActivityManager: Scheduling restart of crashed service at.htl3r.appmosphere/.notify.NotifyService in 73293ms
11-07 07:33:44.310  4611  5385 F ProcessStats: Starting service ServiceState{43760cf0 at.htl3r.appmosphere.notify.NotifyService pkg=at.htl3r.appmosphere proc=43760cf0} without owner

these are the two ways (with and without owner in last line)
This bug is only on my S3 so extrem, on my N7 (2013) is it a bit better

After every restart I get a notification. (just a thought: And if I delete it, the possibility is higher to make a crash.)

A bit annoying to receive a notification every 3 minutes ^-^

The Code

version 1 - with service

UPDATE 1

updated code like Larry Schiefer told
new full log

UPDATE 2

NotifyManager
see below for newest version
version from this update

NotifyReceiver

public class NotifyReceiver extends BroadcastReceiver {
    private static final String TAG = "NotifyReceiver";

    public static final int ID_NEWHINTAVAILABLE = 1;

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "onReceive");
        SharedPreferences spref = PreferenceManager.getDefaultSharedPreferences(context);

        NotificationManager mNM = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        Intent i = new Intent(context.getApplicationContext(), MainActivity.class);
        PendingIntent pIntent = PendingIntent.getActivity(context, 0, i, 0);

        Notification.Builder mNotifyBuilder = new Notification.Builder(context);

        mNotifyBuilder.setSmallIcon(R.drawable.ic_stat_name);
        mNotifyBuilder.setContentTitle(context.getString(R.string.app_name));
        mNotifyBuilder.setContentText(context.getString(R.string.notification_contenttext));
        mNotifyBuilder.setContentIntent(pIntent);

        mNotifyBuilder.setAutoCancel(true);

        // has to have an icon - now the app icon
        // auto cancel after click: in main use cancel(int id);
        // mNotifyBuilder.addAction(R.drawable.ic_stat_name, getString(R.string.notification_action), pIntent);

        // mNotifyBuilder.setTicker(getString(R.string.app_name));
        // mNotifyBuilder.setTicker(getString(R.string.app_name)+" "+getString(R.string.notification_contenttext));

        // mNotifyBuilder.setWhen(System.currentTimeMillis());

        // mNotifyBuilder.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE | Notification.DEFAULT_LIGHTS);
        // http://stackoverflow.com/questions/2724871/how-to-bring-up-list-of-available-notification-sounds-on-android
        String sound = spref.getString(SettingsFragment.pref_notify_sound, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION).toString());
        mNotifyBuilder.setSound(Uri.parse(sound));

        if (spref.getBoolean(SettingsFragment.pref_notify_vibrate, true)) {
            // mNotifyBuilder.setVibrate(new long[] { 0, 1000 });
            mNotifyBuilder.setDefaults(Notification.DEFAULT_VIBRATE);
        }
        if (spref.getBoolean(SettingsFragment.pref_notify_light, true)) {
            mNotifyBuilder.setLights(Color.GREEN, 3000, 3000);
        }

        Notification mNotify = mNotifyBuilder.build();

        mNM.notify(ID_NEWHINTAVAILABLE, mNotify);

        NotifyManager.startAlarm(context, true);
        // wenn aktiviert: ausgeführt & neu gestartet
        // bei Deaktiviertung: abgebrochen - demnach kein Neustart
    }
}

Update 3

Autostart worked..
but now, it dies too nothing changed in this code; only the code above

<receiver android:name="at.htl3r.appmosphere.notify.Autostart" >
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
</receiver>

Autostart.java

public class Autostart extends BroadcastReceiver {
    private static final String TAG = "autostart";

    @Override
    public void onReceive(Context context, Intent intent) {
        if (NotifyManager.isNotificationEnabled(context)) {
            NotifyManager.startAlarm(context);
            Log.i(TAG, "started");
        }
    }
}

CatLog
s3 - full
n7

12-14 23:15:19.227  1452  1679 I ActivityManager: Start proc at.htl3r.appmosphere for broadcast at.htl3r.appmosphere/.notify.Autostart: pid=5837 uid=10391 gids={50391, 3003}
12-14 23:15:42.300  1452  4109 I ActivityManager: Killing 5837:at.htl3r.appmosphere/u0a391 (adj 15): empty #17
12-15 06:43:47.501 18799 18819 D JsonParser: at.htl3r.appmosphere: publishState=6
12-15 06:43:47.501 18799 18819 D JsonParser: Skipping app 0 with state != 1: package name=at.htl3r.appmosphere: state=6

Update 4

NotifyManager

public class NotifyManager {
    private static final String TAG = "NotifyManager";

    /**
     * {@link #startAlarm(Context, boolean)}<br>
     * default: restart: true
     * 
     * @param context Context of activity
     * @return alarm started: true<br>
     *         is running: false
     */
    public static boolean startAlarm(Context context) {
        return startAlarm(context, false);
    }

    /**
     * @param context Context of activity
     * @param restart start the alarm even when already running
     * @return true if started | false if running and not started
     */
    public static boolean startAlarm(Context context, boolean restart) {// todo restart alarm on settings change
        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        SharedPreferences spref = PreferenceManager.getDefaultSharedPreferences(context);

        String time = spref.getString(SettingsFragment.pref_notify_time, TimePreference.notify_default);
        int hour = Integer.parseInt(time.split("\\:")[0]);
        int minute = Integer.parseInt(time.split("\\:")[1]);

        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MINUTE, minute);
        calendar.set(Calendar.HOUR_OF_DAY, hour);
        // alternative: HOUR and AM_PM
        if (calendar.getTimeInMillis() < Calendar.getInstance().getTimeInMillis()) {
            calendar.add(Calendar.DAY_OF_MONTH, 1);
        }

        // String time = new SimpleDateFormat("hh:mm", Locale.getDefault()).format(calendar.getTime());

        if (!isAlarmRunning(context) || restart) {
            alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), getPendingIntent(context));
            Log.d(TAG, "Start Alarm at " + time);
            // Toast.makeText(context, "Start Alarm at " + time, Toast.LENGTH_LONG).show();
            return true;
        }
        Log.d(TAG, "Service already running");
        return false;
    }

    /**
     * @param context Context of activity
     * @return true if running and canceled
     */
    public static boolean cancelAlarm(Context context) {
        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

        if (isAlarmRunning(context)) {
            alarmManager.cancel(getPendingIntent(context));
            Log.d(TAG, "Cancel Alarm");
            NotifyManager.isAlarmRunning(context);
            // Toast.makeText(context, "Cancel Alarm from " + time, Toast.LENGTH_LONG).show();
            return true;
        }
        Log.d(TAG, "Service already canceled");
        return false;
    }

    /**
     * @param context Context of activity
     * @return if alarm is running
     */
    public static boolean isAlarmRunning(Context context) {
        Intent intent_service = new Intent(context, NotifyReceiver.class);
        Log.d(TAG, "isAlarmRunning:" + (PendingIntent.getBroadcast(context, 0, intent_service, PendingIntent.FLAG_NO_CREATE) != null));
        return (PendingIntent.getBroadcast(context, 0, intent_service, PendingIntent.FLAG_NO_CREATE) != null);
    }

    /**
     * @param context Context of activity
     * @return PendingIntent
     */
    public static PendingIntent getPendingIntent(Context context) {
        Intent intent = new Intent(context, NotifyReceiver.class);
        PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_NO_CREATE);

        // If it exists return it
        if (pi != null)
            return pi;

        // It doesn't exist, make it (last parameter to 0 for reusable):
        return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_ONE_SHOT);
    }

    /**
     * @return yyMMdd
     */
    public static String getCurrentTimeStamp() {
        SimpleDateFormat sdfDate = new SimpleDateFormat("yyMMdd", Locale.getDefault());
        Date now = new Date();
        String strDate = sdfDate.format(now);
        return strDate;
    }

    /**
     * @param context Context of the activity
     * @return if notification is enabled or not
     */
    public static boolean isNotificationEnabled(Context context) {
        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SettingsFragment.pref_notify, true);
    }
}

回答1:


Point A: The service code is missing a key component

In the code above, the service has an onCreate and onDestroy, which will be triggered when the service is created and destroyed. However, if a service is triggered and it is already running, then it will not go through onCreate. It will, however, go through onstartCommand (onStart pre android 2.0). The actual structure of your code should be:

onCreate() {
    // Stuff you only do when this class is instantiated the first time
    // and don't need to do if it is called (started in android terminology)
    // thereafter
}

// The next two are >=2.0 and then <2.0
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
      startHandleIntent(intent);
      return START_STICKY; // If you want the service to hang around
  }

@Override
public void onStart(Intent intent, int startId) {
  startHandleIntent(intent);        
}

void startHandleIntent(Intent intent) {
    // Do things that shiould happen every time here
    // eg. in your case, the notification
}

Point B: This isn't really what a service was designed for

You cannot rely on a service hanging around for that long. Inactive services will often be removed to make space for other things. Given that the the service does very little, it would probably be better to use a BroadcastReceiver, which was designed specifically for things that need triggering occasionally but don't really need to be there otherwise. So:

  1. Use a BroadcastRecevier to catch the triggers and issue a notification. Something like this:

    class MyBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            // Issue the notidfication
            <...>
    
            // Reissue a request for a future alarm call here if needed
            <...>
        }
    }
    

    Remember to set it up to receive broadcasts in the manifest:

    <application>
        ... other stuff ...
        <receiver android:name=".MyBroadcastReceiver" android:enabled="true">
            <intent-filter>
                <action android:name="com.mystuff.coolapp.ACTION_TIME_FOR_NOTIFICATION"/>
            </intent-filter>
        </receiver>    
    </application>
    
  2. To trigger that, you need an intent that will trigger a broadcast:

    Intent intent = new Intent("com.mystuff.coolapp.ACTION_TIME_FOR_NOTIFICATION");
    context.sendBroadcast(intent);
    

    If you are setting it up to call later via a PendingIntent (change the final flag to zero if you want a reusable PendingIntent for a recurring event):

    Intent intent = new Intent("com.mystuff.coolapp.ACTION_TIME_FOR_NOTIFICATION");
    PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_ONE_SHOT)
    

    If later on you wish to change, or cancel somehting, or if you simply need to know if the Pending Intent exists from the system's point of view:

    Intent intent = new Intent("com.mystuff.coolapp.ACTION_TIME_FOR_NOTIFICATION");
    PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_NO_CREATE);
    if (pi != null) {
        // It exists. If you want then to cancel the alarm that triggers it:
        alarmManager.cancel(pi);
    }
    else {
        // It doesn't exist. If you need to create a reusable PendingIntent:
        PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0);
    }
    

    Personally, I would use this approach instead of initializePendingIntent, ie:

    public static PendingIntent getPendingIntent() {
        Intent intent = new Intent("com.mystuff.coolapp.ACTION_TIME_FOR_NOTIFICATION");
        PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_NO_CREATE);
    
        // If it exists return it
        if (pi != null) return pi;
    
        // It doesn't exist, make it (last parameter to 0 for reusable):
        return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_ONE_SHOT);
    }
    
  3. Use SharedPreferences (as you already do) to keep track of what is going on (time of alarm)

My preference would be to only create a one shot alarm with a one shot intent for when the next alarm should sound. If it changes, remove this alarm and create a new one. When it triggers, crate a new one. This way you minimise the number of things that have to stay alive for lengths of time.




回答2:


Check your logcat for a stack trace. It will be before the activity manager service entries you have provided. This line looks suspect to me, specifically the setAction as it is not providing a proper resource value for the icon:

mNotifyBuilder.setContentTitle(getString(R.string.app_name)).setContentText(getString(R.string.notification_contenttext)).setContentIntent(pIntent).addAction(0, getString(R.string.notification_action), pIntent).setAutoCancel(true)


来源:https://stackoverflow.com/questions/26808230/notification-every-day-at-the-same-time

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!