Date and time change listener in Android?
In my application, there's a alarm service, and I find that if user change it's date or time to a passed time. My alarm will not be triggered at the time I expect.
So, I may have to reset all the alarms again. Is there an da开发者_开发技巧te and time change listener in android?
Create an intent filter :
static {
    s_intentFilter = new IntentFilter();
    s_intentFilter.addAction(Intent.ACTION_TIME_TICK);
    s_intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
    s_intentFilter.addAction(Intent.ACTION_TIME_CHANGED);
}
and a broadcast receiver:
private final BroadcastReceiver m_timeChangedReceiver = new BroadcastReceiver() {                                                                                             
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        if (action.equals(Intent.ACTION_TIME_CHANGED) ||
                    action.equals(Intent.ACTION_TIMEZONE_CHANGED)) {
            doWorkSon();
        }
    }
};
register the receiver:
public void onCreate() {
    super.onCreate();
    registerReceiver(m_timeChangedReceiver, s_intentFilter);     
}
EDIT:
and unregister it:
public void onDestroy() {
    super.onDestroy();
    unregisterReceiver(m_timeChangedReceiver);     
}
In addition to the accepted answer
If you want to listen to time changes while your app is not running I would register in the manifest:
<receiver android:name="com.your.pacakge.TimeChangeBroadcastReceiver">
    <intent-filter>
        <action android:name="android.intent.action.TIME_SET"/>
        <action android:name="android.intent.action.TIMEZONE_CHANGED"/>
    </intent-filter>
</receiver>
If you do this, do not explicitly register the receiver in the code with registerReceiver and unregisterReceiver.
Again, this is just an addition to the accepted answer.
In order to detect a change in date, you need to register to these actions:
- Intent.ACTION_TIME_CHANGED - "The time was set."
- Intent.ACTION_DATE_CHANGED - "The date has changed."
- Intent.ACTION_TIMEZONE_CHANGED - "The timezone has changed"
Here's one solution that I wrote, so all you have to do is to extend the class, and register&unregister from it on the Activity/Fragment :
abstract class DateChangedBroadcastReceiver : BroadcastReceiver() {
    private var curDate = LocalDate.now()
    /**called when the receiver detected the date has changed. You should still check it yourself, because you might already be synced with the new date*/
    abstract fun onDateChanged(previousDate: LocalDate, newDate: LocalDate)
    @Suppress("MemberVisibilityCanBePrivate")
    fun register(context: Context, date: LocalDate) {
        curDate = date
        val filter = IntentFilter()
        filter.addAction(Intent.ACTION_TIME_CHANGED)
        filter.addAction(Intent.ACTION_DATE_CHANGED)
        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED)
        context.registerReceiver(this, filter)
        val newDate = LocalDate.now()
        if (newDate != curDate) {
            curDate = newDate
            onDateChanged(date, newDate)
        }
    }
    /**a convenient way to auto-unregister when activity/fragment has stopped. This should be called on the onResume method of the fragment/activity*/
    fun registerOnResume(activity: AppCompatActivity, date: LocalDate, fragment: androidx.fragment.app.Fragment? = null) {
        register(activity, date)
        val lifecycle = fragment?.lifecycle ?: activity.lifecycle
        lifecycle.addObserver(object : LifecycleObserver {
            @Suppress("unused")
            @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
            fun onPause() {
//                Log.d("AppLog", "onPause, so unregistering")
                lifecycle.removeObserver(this)
                activity.unregisterReceiver(this@DateChangedBroadcastReceiver)
            }
        })
    }
    override fun onReceive(context: Context, intent: Intent) {
        val newDate = LocalDate.now()
//        Log.d("AppLog", "got intent:" + intent.action + " curDate:" + curDate + " newDate:" + newDate)
        if (newDate != curDate) {
//            Log.d("AppLog", "cur date is different, so posting event")
            val previousDate = curDate
            curDate = newDate
            onDateChanged(previousDate, newDate)
        }
    }
}
If you can't use LocalDate (as it uses relatively new API : 26 , which is currently used on about 21% of devices), you can use this instead:
abstract class DateChangedBroadcastReceiver : BroadcastReceiver() {
    private var curDate = Calendar.getInstance()
    /**called when the receiver detected the date has changed. You should still check it yourself, because you might already be synced with the new date*/
    abstract fun onDateChanged(previousDate: Calendar, newDate: Calendar)
    companion object {
        fun toString(cal: Calendar): String {
            return "${cal.get(Calendar.YEAR)}-${cal.get(Calendar.MONTH)}-${cal.get(Calendar.DAY_OF_MONTH)}"
        }
        fun resetDate(date: Calendar) {
            date.set(Calendar.HOUR_OF_DAY, 0)
            date.set(Calendar.MINUTE, 0)
            date.set(Calendar.SECOND, 0)
            date.set(Calendar.MILLISECOND, 0)
        }
        fun areOfSameDate(date: Calendar, otherDate: Calendar) =
            date.get(Calendar.DAY_OF_YEAR) == otherDate.get(Calendar.DAY_OF_YEAR) &&
                    date.get(Calendar.YEAR) == otherDate.get(Calendar.YEAR)
    }
    @Suppress("MemberVisibilityCanBePrivate")
    fun register(context: Context, date: Calendar) {
        curDate = date.clone() as Calendar
        resetDate(curDate)
        val filter = IntentFilter()
        filter.addAction(Intent.ACTION_TIME_CHANGED)
        filter.addAction(Intent.ACTION_DATE_CHANGED)
        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED)
        context.registerReceiver(this, filter)
        val newDate = Calendar.getInstance()
        resetDate(newDate)
        if (!areOfSameDate(newDate, curDate)) {
            val previousDate = curDate.clone() as Calendar
            curDate = newDate
            onDateChanged(previousDate, curDate)
        }
    }
    /**a convenient way to auto-unregister when activity/fragment has stopped. This should be called on the onResume method of the fragment/activity*/
    fun registerOnResume(activity: AppCompatActivity, date: Calendar, fragment: Fragment? = null) {
        register(activity as Context, date)
        val lifecycle = fragment?.lifecycle ?: activity.lifecycle
        lifecycle.addObserver(object : LifecycleObserver {
            @Suppress("unused")
            @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
            fun onPause() {
//                Log.d("AppLog", "onPause, so unregistering")
                lifecycle.removeObserver(this)
                activity.unregisterReceiver(this@DateChangedBroadcastReceiver)
            }
        })
    }
    override fun onReceive(context: Context, intent: Intent) {
        val newDate = Calendar.getInstance()
        resetDate(newDate)
//        Log.d("AppLog", "got intent:${intent.action} curDate:${toString(curDate)} newDate:${toString(newDate)}")
        if (!areOfSameDate(newDate, curDate)) {
//            Log.d("AppLog", "cur date is different, so posting event")
            val previousDate = curDate.clone() as Calendar
            curDate = newDate
            onDateChanged(previousDate, newDate)
        }
    }
}
Example usage:
class MainActivity : AppCompatActivity() {
    var curDate = Calendar.getInstance()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
    override fun onResume() {
        super.onResume()
        object : DateChangedBroadcastReceiver() {
            override fun onDateChanged(previousDate: Calendar, newDate: Calendar) {
                Log.d("AppLog", "MainActivity: ${DateChangedBroadcastReceiver.toString(previousDate)} -> ${DateChangedBroadcastReceiver.toString(newDate)}")
                curDate = newDate.clone() as Calendar
                //TODO handle date change
            }
        }.registerOnResume(this, curDate)
    }
}
The Java version of Ben's answer with a minor fix. the fix is about adding ACTION_TIME_TICK as one of the actions that we should care for the broadcast receiver.
public abstract class DayChangedBroadcastReceiver extends BroadcastReceiver {
private Date date = new Date();
private DateFormat dateFormat = new SimpleDateFormat("yyMMdd", Locale.getDefault());
public abstract void onDayChanged();
@Override
public void onReceive(Context context, Intent intent) {
    String action = intent.getAction();
    Date currentDate = new Date();
    if (action != null && !isSameDay(currentDate) &&
            (action.equals(Intent.ACTION_TIME_CHANGED)
                    || action.equals(Intent.ACTION_TIMEZONE_CHANGED)
                    || action.equals(Intent.ACTION_TIME_TICK))) {
        date = currentDate;
        onDayChanged();
    }
}
private boolean isSameDay(Date currentDate) {
    return dateFormat.format(currentDate).equals(dateFormat.format(date));
}
public static IntentFilter getIntentFilter() {
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(Intent.ACTION_TIME_TICK);
    intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
    intentFilter.addAction(Intent.ACTION_TIME_CHANGED);
    return intentFilter;
}
}
Another addition when registering BroadcastReceiver in the Manifest
when you currently don't have active alarms at the time, maybe you want to stop listening to the broadcast, and re-enabled it later when there is an active alarm.
val receiver = ComponentName(context, SampleBootReceiver::class.java)
context.packageManager.setComponentEnabledSetting(
        receiver,
        PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
        // or PackageManager.COMPONENT_ENABLED_STATE_DISABLED to disable it
        PackageManager.DONT_KILL_APP
)
Sauce
 
         加载中,请稍侯......
 加载中,请稍侯......
      
精彩评论