• 前言
    • 解决方案
      • 普通线程sleep的方式实现定时任务
      • Timer实现定时任务
      • ScheduledExecutorService实现定时任务
      • Handler实现定时任务
      • AlarmManager实现精确定时操作

前言

项目中总是会因为各种需求添加各种定时任务,所以就打算小结一下Android中如何实现定时任务,下面的解决方案的案例大部分都已在实际项目中实践,特此列出供需要的朋友参考,如果有什么使用不当或者存在什么问题,欢迎留言指出!直接上干货!

解决方案

普通线程sleep的方式实现定时任务

创建一个thread,然后让它在while循环里一直运行着,通过sleep方法来达到定时任务的效果,这是最常见的,可以快速简单地实现。但是这是java中的实现方式,不建议使用

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

Timer实现定时任务

和普通线程+sleep(long)+Handler的方式比,优势在于

  • 可以控制TimerTask的启动和取消
  • 第一次执行任务时可以指定delay的时间。

在实现时,Timer类调度任务,TimerTask则是通过在run()方法里实现具体任务(然后通过Handler与线程协同工作,接收线程的消息来更新主UI线程的内容)。

  • Timer实例可以调度多任务,它是线程安全的。当Timer的构造器被调用时,它创建了一个线程,这个线程可以用来调度任务。
   /**    * 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        }    }

然而Timer是存在一些缺陷的

  • Timer在执行定时任务时只会创建一个线程,所以如果存在多个任务,且任务时间过长,超过了两个任务的间隔时间,会发生一些缺陷
  • 如果Timer调度的某个TimerTask抛出异常,Timer会停止所有任务的运行
  • Timer执行周期任务时依赖系统时间,修改系统时间容易导致任务被挂起(如果当前时间小于执行时间)

注意:

  • Android中的Timer和java中的Timer还是有区别的,但是大体调用方式差不多
  • Android中需要根据页面的生命周期和显隐来控制Timer的启动和取消
    java中Timer:



    Android中的Timer:


ScheduledExecutorService实现定时任务

ScheduledExecutorService是从JDK1.5做为并发工具类被引进的,存在于java.util.concurrent,这是最理想的定时任务实现方式。
相比于上面两个方法,它有以下好处:

  • 相比于Timer的单线程,它是通过线程池的方式来执行任务的,所以可以支持多个任务并发执行 ,而且弥补了上面所说的Timer的缺陷
  • 可以很灵活的去设定第一次执行任务delay时间
  • 提供了良好的约定,以便设定执行的时间间隔

简例:

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

实际案例背景:

  • 应用中多个页面涉及比赛信息展示(包括比赛状态,比赛结果),因此需要实时更新这些数据

思路分析:

  • 多页面多接口刷新
    • 就是每个需要刷新的页面基于自身需求基于特定接口定时刷新
    • 每个页面要维护一个定时器,然后基于页面的生命周期和显隐进行定时器的开启和关闭(保证资源合理释放)
    • 而且这里的刷新涉及到是刷新局部数据还是整体数据,刷新整体数据效率会比较低,显得非常笨重
  • 多页面单接口刷新
    • 接口给出一组需要实时进行刷新的比赛数据信息,客户端基于id进行统一过滤匹配
    • 通过单例封装统一定时刷新回调接口(注意内存泄露的问题,页面销毁时关闭ScheduledExecutorService )
    • 需要刷新的item统一调用,入口唯一,方便维护管理,扩展性好
    • 局部刷新,效率高
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> 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  void subscibeMatch(VIEW view, OnViewRefreshStatus onViewRefreshStatus) {        subscibe(TYPE_MATCH,view,onViewRefreshStatus);    }    private  void subscibe(int type, VIEW view, OnViewRefreshStatus onViewRefreshStatus) {        view.setTag(onViewRefreshStatus);        if (type == TYPE_MATCH) {            onViewRefreshStatus.update(view, matchMap);        }         for (WeakReference viewSoftReference : list) {            View textView = viewSoftReference.get();            if (textView == view) {                return;            }        }        WeakReference viewSoftReference = new WeakReference(view);        list.add(viewSoftReference);    }    public void updateView(final int type) {        Iterator> iterator = list.iterator();        while (iterator.hasNext()) {            WeakReference 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);    }}

需要刷新的item调用

PollingStateMachine.getInstance().subscibeMatch(tvScore, new OnViewRefreshStatus(matchViewItem.getMatchID()) {            @Override            public void OnViewRefreshStatus(TextView view, ScoreItem scoreItem) {                //刷新处理                }        });

网络数据回调接口

public interface INetCallback {    void onNetCallback(int type,Map msg);}

刷新回调接口:

public abstract class OnViewRefreshStatus {    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 map) {        final VALUE value = map.get(key);        if (value == null) {            return;        }        OnViewRefreshStatus(view, value);    }    public abstract void OnViewRefreshStatus(VIEW view, VALUE value);}

Handler实现定时任务

通过Handler延迟发送消息的形式实现定时任务。

  • 这里通过一个定时发送socket心跳包的案例来介绍如何通过Handler完成定时任务

案例背景

  • 由于移动设备的网络的复杂性,经常会出现网络断开,如果没有心跳包的检测, 客户端只会在需要发送数据的时候才知道自己已经断线,会延误,甚至丢失服务器发送过来的数据。 所以需要提供心跳检测

下面的代码只是用来说明大体实现流程

  • WebSocket初始化成功后,就准备发送心跳包
  • 每隔30s发送一次心跳
  • 创建的时候初始化Handler,销毁的时候移除Handler消息队列
private static final long HEART_BEAT_RATE = 30 * 1000;//目前心跳检测频率为30sprivate Handler mHandler;private Runnable heartBeatRunnable = new Runnable() {        @Override        public void run() {            // excute task            mHandler.postDelayed(this, HEART_BEAT_RATE);        }    };public void onCreate() {     //初始化Handler    }//初始化成功后,就准备发送心跳包public void onConnected() {        mHandler.removeCallbacks(heartBeatRunnable);        mHandler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE);    }public void onDestroy() {        mHandler.removeCallbacks(heartBeatRunnable)    }

注意:这里开启的runnable会在这个handler所依附线程中运行,如果handler是在UI线程中创建的,那么postDelayed的runnable自然也依附在主线程中。

AlarmManager实现精确定时操作

我们使用Timer或者handler的时候会发现,delay时间并没有那么准。如果我们需要一个严格准时的定时操作,那么就要用到AlarmManager,AlarmManager对象配合Intent使用,可以定时的开启一个Activity,发送一个BroadCast,或者开启一个Service.

案例背景

  • 在比赛开始前半个小时本地定时推送关注比赛的提醒信息

AndroidManifest.xml中声明一个全局广播接收器

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

接收到action为IntentConst.Action.matchRemind的广播就展示比赛
提醒的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)) {            //通知比赛开始            long matchID = intent.getLongExtra(Net.Param.ID, 0);            showNotificationRemindMe(context, matchID);        }    }}

AlarmUtil提供设定闹铃和取消闹铃的两个方法

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

关注比赛的时候,会设置intent的action为IntentConst.Action.matchRemind,会根据比赛的开始时间提前半小时设定闹铃时间,取消关注比赛同时取消闹铃

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

更多相关文章

  1. Android通信方式篇(一)-消息机制(Java层)
  2. Android最佳实践之响应灵敏性(ANR)
  3. Android中Sqlite数据库多线程并发问题
  4. [置顶] Android(安卓)面试那些事之android基础
  5. Android(安卓)不可缺少的异步(Thread、Handler、AsyncTask)实例解
  6. 关于Android的线程问题
  7. Android(安卓)LayoutInflater原理解析
  8. Android(安卓)按键事件处理详解
  9. Android(安卓)内存浅析【泄漏、溢出】【一】

随机推荐

  1. Android中的适配器(Adapter)
  2. Android音频开发(2):使用AudioRecord录制pcm
  3. android:gravity / android:layout_Gravi
  4. xml字段含义
  5. 在程序中设置android:gravity 和 android
  6. mk中的android:sharedUserId和LOCAL_CERT
  7. Android在走下坡路吗?
  8. Android(安卓)官方命令深入分析之android
  9. android 文件读取
  10. android高级应用课程