Дата и время смены слушателя в Android?
В моем приложении есть служба сигнализации, и я обнаружил, что если пользователь меняет дату или время на прошедшее время. Моя тревога не будет срабатывать в то время, когда я ожидаю.
Так что, возможно, мне придется сбросить все тревоги снова. Есть ли слушатель смены даты и времени в андроиде?
2 ответа
Создайте фильтр намерений:
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);
}
и приемник вещания:
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();
}
}
};
зарегистрировать получателя:
public void onCreate() {
super.onCreate();
registerReceiver(m_timeChangedReceiver, s_intentFilter);
}
РЕДАКТИРОВАТЬ:
и отмените регистрацию:
public void onDestroy() {
super.onDestroy();
unregisterReceiver(m_timeChangedReceiver);
}
В дополнение к принятому ответу
Если вы хотите прослушивать изменения времени, пока ваше приложение не запущено, я бы зарегистрировался в манифесте:
<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>
Если вы сделаете это, не регистрируйте получателя в коде явно registerReceiver
а также unregisterReceiver
,
Опять же, это всего лишь дополнение к принятому ответу.
Чтобы обнаружить изменение даты, вам необходимо зарегистрироваться на следующие действия:
- Intent.ACTION_TIME_CHANGED - "Время установлено".
- Intent.ACTION_DATE_CHANGED - "Дата изменилась."
- Intent.ACTION_TIMEZONE_CHANGED - "Часовой пояс изменился"
Вот одно решение, которое я написал, поэтому все, что вам нужно сделать, это расширить класс, а также зарегистрироваться и отменить его регистрацию в 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)
}
}
}
Если вы не можете использовать LocalDate (поскольку он использует относительно новый API: 26, который в настоящее время используется примерно на 21% устройств), вы можете использовать это вместо:
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)
}
}
}
Пример использования:
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)
}
}
Java-версия ответа Бена с незначительным исправлением. исправление заключается в добавлении ACTION_TIME_TICK в качестве одного из действий, которые мы должны позаботиться о широковещательном приемнике.
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;
}
}
Еще одно дополнение при регистрации BroadcastReceiver в Manifest
если в данный момент у вас нет активных сигналов тревоги, возможно, вы захотите прекратить прослушивание трансляции и повторно включить ее позже, когда будет активный сигнал тревоги.
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
)
Используется Android TextClock View по умолчанию
<TextClock
android:id="@+id/textClock"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:format12Hour="hh:mm:ss a"
android:gravity="center_horizontal"
android:textColor="#d41709"
android:textSize="44sp"
android:textStyle="bold" />
Измените свой формат даты здесь
android:format12Hour="hh:mm:ss a"