• preface
    • The solution
      • The normal thread sleep implements the scheduled task
      • Timer Indicates the Timer task
      • ScheduledExecutorService Implements scheduled tasks
      • Handler Implements scheduled tasks
      • AlarmManager Implements precise timing operations

preface

Projects will always because of various needs to add a variety of timing tasks, so I plan to how to realize the timing in the subtotal Android task, the following solutions for most cases has been put in practice in a real project and hereby list for need friend reference, if there is any improper use or what problems, welcome message points out! Go straight to the dry stuff!

The solution

The normal thread sleep implements the scheduled task

Creating a thread and letting it run in a while loop, using sleep to achieve the effect of a timed task, is the most common and can be quickly and easily implemented. However, this is implemented in Java and is not recommended

public class ThreadTask { public static void main(String[] args) { final long timeInterval = 1000; Runnable runnable = new Runnable() { public void run() { while (true) { System.out.println("execute task"); try { Thread.sleep(timeInterval); } catch (InterruptedException e) { e.printStackTrace(); }}}}; Thread thread = new Thread(runnable); thread.start(); }}Copy the code

Timer Indicates the Timer task

Compared to normal thread +sleep (long) +Handler

  • You can control the start and cancel of TimerTask
  • You can specify the delay time when executing the task for the first time.

At implementation time, the Timer class schedules tasks, while TimerTask implements specific tasks in the Run () method (and then works with the thread via a Handler, receiving messages from the thread to update the contents of the main UI thread).

  • The Timer instance can schedule multiple tasks and is thread-safe. When the Timer’s constructor is called, it creates a thread that can be used to schedule tasks.
/** * start Timer */ protected synchronized void startPlayerTimer() { stopPlayerTimer(); if (playTimer == null) { playTimer = new PlayerTimer(); Timer m_musictask = new Timer(); m_musictask.schedule(playTimer, 5000, 5000); } } /** * stop Timer */ protected synchronized void stopPlayerTimer() { try { if (playTimer ! = null) { playTimer.cancel(); playTimer = null; } } catch (Exception e) { e.printStackTrace(); } } public class PlayerTimer extends TimerTask { public PlayerTimer() { } public void run() { //execute task } }Copy the code

However, Timer has some drawbacks

  • A Timer creates only one thread when executing a scheduled task, so some defects can occur if there are multiple tasks that take longer than the interval between the two tasks
  • If one of the TimerTasks scheduled by the Timer throws an exception, the Timer stops all tasks
  • Timer The execution of periodic tasks depends on the system time. Changing the system time may cause tasks to be suspended (if the current time is less than the execution time).

Note:

  • The Timer in Android is different from the Timer in Java, but it is called in the same way
  • Android needs to control Timer startup and cancellation based on page life cycle and implicit

    In Java Timer:







    Timer on Android:





ScheduledExecutorService Implements scheduled tasks

ScheduledExecutorService was introduced from JDK1.5 as a concurrent utility class in java.util.concurrent, which is the ideal way to implement scheduled tasks. Compared with the above two methods, it has the following advantages:

  • Instead of a single thread, a Timer executes tasks through a thread pool, so multiple tasks can be executed concurrently, and it compensates for the shortcomings of the Timer mentioned above
  • Can be very flexible to set the first execution of the task delay time
  • Good conventions are provided for setting time intervals for execution

Jane:

public class ScheduledExecutorServiceTask { public static void main(String[] args) { final TimerTask task = new TimerTask() { @Override public void run() { //execute task } }; ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); pool.scheduleAtFixedRate(task, 0 , 1000, TimeUnit.MILLISECONDS); }}Copy the code

Actual case Background:

  • Multiple pages in the application display match information (including match status, match results), so you need to update this data in real time

Analysis of ideas:

  • Refresh multiple pages and interfaces

    • That is, each page to be refreshed periodically based on its own needs and specific interfaces
    • Each page should maintain a timer, and then open and close the timer based on the page life cycle and implicit (to ensure reasonable release of resources).
    • In addition, the refresh here involves whether to refresh local data or overall data, which is inefficient and cumbersome
  • Refresh multiple pages and single interface

    • The interface provides a group of match data information that needs to be refreshed in real time, and the client filters and matches uniformly based on ID
    • Unified timed refresh callback interface via singleton encapsulation (note memory leaks and disable ScheduledExecutorService when pages are destroyed)
    • Unified call of items that need to be refreshed, unique entry, convenient maintenance and management, good scalability
    • Local refresh, high efficiency
public class PollingStateMachine implements INetCallback { private static volatile PollingStateMachine instance = null; private ScheduledExecutorService pool; public static final int TYPE_MATCH = 1; private Map matchMap = new HashMap<>(); private List<WeakReference<View>> list = new ArrayList<>(); private Handler handler; // private constructor suppresses private PollingStateMachine() { defineHandler(); pool = Executors.newSingleThreadScheduledExecutor(); pool.scheduleAtFixedRate(new Runnable() { @Override public void run() { doTasks(); } }, 0, 10, TimeUnit.SECONDS); } private void doTasks() { ThreadPoolUtils.execute(new PollRunnable(this)); } public static PollingStateMachine getInstance() { // if already inited, no need to get lock everytime if (instance == null) { synchronized (PollingStateMachine.class) { if (instance == null) {  instance = new PollingStateMachine(); } } } return instance; } public <VIEW extends View> void subscibeMatch(VIEW view, OnViewRefreshStatus onViewRefreshStatus) { subscibe(TYPE_MATCH,view,onViewRefreshStatus); } private <VIEW extends View> void subscibe(int type, VIEW view, OnViewRefreshStatus onViewRefreshStatus) { view.setTag(onViewRefreshStatus); if (type == TYPE_MATCH) { onViewRefreshStatus.update(view, matchMap); } for (WeakReference<View> viewSoftReference : list) { View textView = viewSoftReference.get(); if (textView == view) { return; } } WeakReference<View> viewSoftReference = new WeakReference<View>(view); list.add(viewSoftReference); } public void updateView(final int type) { Iterator<WeakReference<View>> iterator = list.iterator(); while (iterator.hasNext()) { WeakReference<View> next = iterator.next(); final View view = next.get(); if (view == null) { iterator.remove(); continue; } Object tag = view.getTag(); if (tag == null || ! (tag instanceof OnViewRefreshStatus)) { continue; } final OnViewRefreshStatus onViewRefreshStatus = (OnViewRefreshStatus) tag; handler.post(new Runnable() { @Override public void run() { if (type == TYPE_MATCH) { onViewRefreshStatus.update(view, matchMap); }}}); } } public void clear() { pool.shutdown(); instance = null; } private Handler defineHandler() { if (handler == null && Looper.myLooper() == Looper.getMainLooper()) { handler = new Handler(); } return handler; } @Override public void onNetCallback(int type, Map msg) { if (type == TYPE_MATCH) { matchMap=msg; } updateView(type); }}Copy the code

Item call to refresh

PollingStateMachine.getInstance().subscibeMatch(tvScore, new OnViewRefreshStatus<ScoreItem, TextView>(matchViewItem.getMatchID()) { @Override public void OnViewRefreshStatus(TextView view, ScoreItem ScoreItem) {// Refresh processing}});Copy the code

Network data callback interface

public interface INetCallback {

    void onNetCallback(int type,Map msg);

}Copy the code

Refresh callback interface:

public abstract class OnViewRefreshStatus<VALUE, VIEW extends View> {
    private static final String TAG = OnViewRefreshStatus.class.getSimpleName();
    private long key;

    public OnViewRefreshStatus(long key) {
        this.key = key;
    }

    public long getKey() {
        return key;
    }

    public void update(final VIEW view, Map<Long, VALUE> map) {
        final VALUE value = map.get(key);

        if (value == null) {
            return;
        }
        OnViewRefreshStatus(view, value);

    }


    public abstract void OnViewRefreshStatus(VIEW view, VALUE value);

}Copy the code

Handler Implements scheduled tasks

A scheduled task is implemented in the form of Handler delay sending messages.

  • This section uses an example to introduce how to complete the scheduled task through the Handler

Case background

  • Due to the complexity of the network of mobile devices, network disconnection often occurs. If there is no heartbeat packet detection, the client will only know that it is disconnected when it needs to send data, which will delay or even lose the data sent by the server. Therefore, heartbeat detection is required

The following code is intended only to illustrate the general implementation process

  • After the WebSocket is successfully initialized, it is ready to send heartbeat packets
  • Send a heartbeat every 30 seconds
  • Initialize the Handler on creation and remove the Handler message queue on destruction
private static final long HEART_BEAT_RATE = 30 * 1000; // Current heartbeat detection frequency is 30s private Handler mHandler; private Runnable heartBeatRunnable = new Runnable() { @Override public void run() { // excute task mHandler.postDelayed(this, HEART_BEAT_RATE); }}; Public void onCreate() {// Initialize Handler} // After initialization, Public void onConnected() {mhandler.removecallbacks (heartBeatRunnable); mHandler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE); } public void onDestroy() { mHandler.removeCallbacks(heartBeatRunnable) }Copy the code

Note that the runnable enabled here will run in the thread on which the handler is attached. If the handler was created in the UI thread, the postDelayed runnable will also be attached to the main thread.

AlarmManager Implements precise timing operations

When we use Timer or handler, we find that the delay time is not so accurate. If we need a strictly punctual scheduled operation, we use AlarmManager. The object is used with intEnts to start an Activity, send a BroadCast, or start a Service.

Case background

  • Notifications will be sent half an hour before the start of the game

A global broadcast receiver is declared in androidmanifest.xml

  <receiver
            android:name=".receiver.AlarmReceiver"
            android:exported="false">
            <intent-filter>
                <action android:name="${applicationId}.BROADCAST_ALARM" />
            </intent-filter>
        </receiver>Copy the code

Receives the action for IntentConst. Action. MatchRemind radio show reminded the Notification

public class AlarmReceiver extends BroadcastReceiver { private static final String TAG = "AlarmReceiver"; @Override public void onReceive(Context context, Intent intent) { if (intent == null) { return; } String action = intent.getAction(); if (TextUtils.equals(action, IntentConst. Action. MatchRemind)) {/ / notify race long matchID = intent. GetLongExtra (Net. Param. ID, 0). showNotificationRemindMe(context, matchID); }}}Copy the code

AlarmUtil provides two methods for setting and canceling an alarm

public class AlarmUtil { private static final String TAG = "AlarmUtil"; public static void controlAlarm(Context context, long startTime,long matchId, Intent nextIntent) { AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, (int) matchId, nextIntent, PendingIntent.FLAG_ONE_SHOT); alarmManager.set(AlarmManager.RTC_WAKEUP, startTime, pendingIntent); } public static void cancelAlarm(Context context, String action,long matchId) { Intent intent = new Intent(action); PendingIntent sender = PendingIntent.getBroadcast( context, (int) matchId, intent, 0); // And cancel the alarm. AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); alarmManager.cancel(sender); }}Copy the code

Focus on games, will set the intent of the action to IntentConst. Action. MatchRemind, according to the beginning of the game time half an hour early to set the alarm time, focus on the competition at the same time to cancel the alarm

   if (isSetAlarm) {
            long start_tm = matchInfo.getStart_tm();
            long warmTime = start_tm - 30 * 60;

            Intent intent = new Intent(IntentConst.Action.matchRemind);
            intent.putExtra(Net.Param.ID, matchInfo.getId());
            AlarmUtil.controlAlarm(context, warmTime * 1000,matchInfo.getId(), intent);

            Gson gson = new Gson();
            String json = gson.toJson(matchInfo);
            SportDao.getInstance(context).insertMatchFollow(eventID, matchInfo.getId(), json, matchInfo.getType());
        } else {
            AlarmUtil.cancelAlarm(context, IntentConst.Action.matchRemind,matchInfo.getId());
            SportDao.getInstance(context).deleteMatchFollowByID(matchInfo.getId());
        }Copy the code