每天同时通知

时间:2014-11-07 19:00:29

标签: android service android-notifications android-broadcast autostart

我想要什么

我希望每天都能同时收到通知 我已经阅读了一些帖子和教程/示例,但它无法正常工作。

版本1

错误:重启/启动后每隔约3分钟Android进程/服务就会消失

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

这两种方式(在最后一行有和没有所有者)
这个bug只在我的S3上如此极端,在我的N7(2013)上有点好看

每次重启后我都会收到通知。 (只是一个想法:如果我删除它,崩溃的可能性会更高。)

每3分钟收到一次通知有点烦人^ - ^

守则

version 1 - with service

更新1

Larry Schiefer 这样的更新代码告诉了 new full log

更新2

NotifyManager
请参阅下面的最新版本
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
    }
}

更新3

自动启动工作..
但现在,它也死了 此代码中没有任何变化;只有上面的代码

<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");
        }
    }
}

目录下载
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

更新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);
    }
}

2 个答案:

答案 0 :(得分:3)

A点:服务代码缺少关键组件

在上面的代码中,服务有一个onCreateonDestroy,它们将在创建和销毁服务时触发。但是,如果服务已触发并且已在运行,则它将不会通过onCreate。但是,它将通过onstartCommandonStart pre android 2.0)。代码的实际结构应该是:

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
}

B点:这不是一项服务的设计

你不能依赖这种长期存在的服务。通常会删除非活动服务以为其他事物腾出空间。鉴于该服务的功能很少,使用BroadcastReceiver可能会更好,因为BroadcastReceiver是专门为偶尔需要触发的东西而设计的,但实际上并不需要在那里。所以:

  1. 使用BroadcastRecevier来捕获触发器并发出通知。像这样:

    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
            <...>
        }
    }
    

    请记住将其设置为在清单中接收广播:

    <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. 要触发此操作,您需要一个会触发广播的意图:

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

    如果您要将其设置为稍后通过PendingIntent调用(如果您希望可重复使用的PendingIntent重复发生事件,则将最终标志更改为零):

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

    如果您希望稍后更改或取消某些内容,或者您​​只是需要知道系统的观点是否存在待处理意图:

    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);
    }
    

    就个人而言,我会使用这种方法而不是initializePendingIntent,即:

    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. 使用SharedPreferences(正如您所做)来跟踪发生的事情(发出警报的时间)

  4. 我的偏好是只创建一次性警报,一次性警报应该发出下一个警报。如果更改,请删除此警报并创建一个新警报。当它触发时,创建一个新的。这样,您可以最大限度地减少必须长时间保持活动状态的数量。

答案 1 :(得分:0)

检查logcat以查找堆栈跟踪。它将在您提供的活动管理器服务条目之前。这条线看起来很可疑,特别是setAction,因为它没有为图标提供适当的资源值:

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)