#android #mvvm #android-livedata #android-viewmodel
Вопрос:
Как рекомендовано в последней документации, я использую ViewModel/LiveData для отображения некоторых данных:
public class EventDatesViewModel extends AndroidViewModel {
private final MutableLiveData<String> timeToEvent = new MutableLiveData<>();
...
public LiveData<String> getTimeToEvent() {
if (timeToEvent == null)
new Thread(this::calculateTimeToEvent).start();
return timeToEvent;
}
private void calculateTimeToEvent() {
...
timeToEvent.postValue(t);
}
}
Я использую это в своем фрагменте:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_garbage, container, false);
EventDatesViewModel model = new ViewModelProvider(requireActivity(), new ViewModelProvider.AndroidViewModelFactory(requireActivity().getApplication())).get(EventDatesViewModel.class);
model.getTimeToEvent().observe(this, timeToEvent -> {
((TextView)root.findViewById(R.id.textTimeToEvent)).setText(timeToEvent);
});
return root;
}
Теперь проблема в том, что это что timeToEvent
-то вроде «2 дня 4 часа». Поэтому, если я оставлю приложение открытым, в какой-то момент оно устареет и его нужно будет пересчитать заново. Если приложение не открыто или находится в фоновом режиме, я не хочу выполнять довольно дорогостоящие вычисления.
Как я могу добиться этого периодического перерасчета?
Что не сработало
Я добавил обработчик, который используется postDelayed()
для планирования пересчета.
private final Handler handler = new Handler();
private final Runnable runnable = new Runnable() {
@Override
public void run() {
new Thread(() -> calculateTimeToEvent()).start();
handler.postDelayed(this, 10000);
}
};
public LiveData<String> getTimeToEvent() {
if (timeToEvent == null) {
new Thread(this::calculateTimeToEvent).start();
handler.postDelayed(runnable, 10000);
}
return timeToEvent;
}
@Override
protected void onCleared() {
super.onCleared();
handler.removeCallbacks(runnable);
}
К сожалению, это запускает вычисления, даже когда приложение находится в фоновом режиме или устройство спит. Я записал запуски calculateTimeToEvent()
с отметками времени в файл и вижу, что они происходят каждые 10 секунд без перерыва.
Комментарии:
1. Вы спрашиваете, как планировать повторяющиеся операции каждый
2 days 4 hours
раз ?2. @tyczj Я спрашиваю, как запланировать повторяющуюся операцию каждый час или около того таким образом, чтобы она имела доступ к модели представления для публикации нового значения и выполнялась только в том случае, если модель представления фактически используется в данный момент.
3. 1.) почему вы используете «новый ViewModelProvider». androidviewмоделфактор(требуемая активность().getApplication ()))` явно? У фрагмента уже есть фабрика поставщиков моделей представления по умолчанию, которая имеет лучшее поведение 2.) почему вы используете
this
в качестве владельца жизненного цикла вместоgetViewLifecycleOwner()
?4. @EpicPandaForce 1) В моей модели представления мне нужно
Context
, чтобы показывать тосты, так что этоAndroidViewModel
и был самый короткий код, который я смог найти, который может создать экземплярAndroidViewModel
. 2) Я скопировал этот код отсюда , но, похоже, я перепутал два примера.5. @EpicPandaForce Действительно, после обновления appcompat до 1.3.1 (с 1.2.0) завод по умолчанию работает , и это каким-то образом вызвало появление ворса, который я должен использовать
getViewLifecycleOwner()
вместоthis
, как вы и сказали. Спасибо.
Ответ №1:
Поскольку вы используете Java, вы можете использовать обработчик, используемый postDelayed
для запуска работающего через X промежуток времени, а затем запланировать его снова после запуска
Это котлин, но его можно легко превратить обратно в Java
В вашем представлении модель
private val _handler = Handler()
private val _runnable = object : Runnable {
override fun run() {
calculateTimeToEvent()
_handler.postDelayed(this, 3600000)
}
}
Затем в какой-то момент вызовите это в своей модели просмотра, чтобы начать повторяющуюся операцию
_handler.postDelayed(_runnable, 3600000)
Затем в вашей модели onCleared
просмотра вы можете удалить обратные вызовы, чтобы остановить его
_handler.removeCallbacks(_runnable)
Также есть возможность использовать RxJava и использовать interval
Комментарии:
1. Это
postDelayed
автоматически приостанавливается, если представление не отображается? Например, когда у пользователя открыто другое приложение или экран телефона выключен?2. Так как действие больше не находится вокруг ViewModel, то больше не требуется, поэтому onCleared (где вы вызываете
removeCallbacks
) называется остановкой повторяющейся операции3. @AndreKR, тогда это означает, что модель представления все еще используется ie. активность все еще находится в состоянии, когда она может быть возобновлена. Вы всегда можете создать метод в своей модели просмотра, чтобы отменить обратные вызовы от вашей активности в onPause, например, когда активность больше не активна (приостановлена), но не уничтожена
4. Тогда почему бы просто не попросить фрагмент вызвать метод обновления в ViewModel? Но мне кажется, что я должен использовать эту информацию о владельце/наблюдателе жизненного цикла?
5. Вы, конечно, могли бы, но вы сказали, что хотите, чтобы он работал, пока модель просмотра активна. Поскольку при изменении конфигурации, таком как поворот экрана, действие/фрагмент отменит/перезапустит таймеры, так как они воссоздаются, в отличие от объектов ViewModel, сохраняются вместе с их данными об изменении конфигурации, поэтому таймер на 1 час теоретически может превратиться в таймер на 2 часа