在Android中安排重复任务


122

我正在设计一个具有重复任务的应用程序,只要该应用程序处于前台,它就会将状态发送到专用服务器。

在网上搜索中,我看到了几种不同的方法,并且想知道什么是最好的方法。

安排服务器呼叫的最佳方法是什么?

我看到的选项是:

  1. 计时器

  2. ScheduledThreadPoolExecutor

  3. 服务

  4. AlarmManager的 BroadcastReciever 。

你怎么看?

编辑:
我需要它的原因是基于聊天的应用程序,该应用程序将所有用户操作发送到远程服务器。
即用户正在键入消息,用户正在阅读消息,用户在线,用户离线等。

这意味着每隔一个间隔,我需要向服务器发送我在做什么,因为我与其他人打开聊天室,所以他们需要知道我在做什么。

类似于whatsapp消息反馈机制: 邮件看起来已传递

编辑#2:
现在应该几乎总是通过JobSchedulerAPI(或FirebaseJobDispatcher针对较低的API)安排重复执行的任务,以防止电池耗尽的问题,如Android培训的“ 生命周期”部分所述

编辑#3:
FirebaseJobDispatcher已被不推荐使用,并由Workmanager取代,后者还结合了JobScheduler的功能。


2
带AlarmManager的BroaccastReceiver非常简单易用。这是我尝试过的唯一以上选择之一。

1
几乎没有理由在ScheduledThreadPoolExecutor上使用Timer,它具有更大的灵活性,因为它允许多个后台线程并且具有更好的分辨率(仅对ms分辨率有用)并允许异常处理。至于AlarmManager,这篇文章提供了一些有关差异的信息。
assylias 2013年

对于较短的生命周期,即在当前处于前台的活动中,每30秒执行一些任务,请使用ScheduledThreadPoolExecutor(或Timer)更为有效。对于较长的生命周期,即在后台服务中每1小时执行一些任务,使用AlarmManager可提供更高的可靠性。
yorkw

为什么甚至需要安排发送时间?从您的应用说明中,您为什么不只是实时发送它?
iTech

因为用户使用超时功能假设您在线。意思是,如果我在过去的X时间内没有收到“状态”或“键入”消息,我会自动假定您没有这样做
thepoosh 2013年

Answers:


164

我不确定,但是据我所知我同意我的观点。如果我错了,我总是接受最佳答案。

警报管理器

只要警报接收器的onReceive()方法正在执行,警报管理器就会保持CPU唤醒锁。这样可以确保手机在完成广播处理之前不会进入睡眠状态。一旦onReceive()返回,警报管理器将释放此唤醒锁。这意味着在您的onReceive()方法完成后,手机在某些情况下会进入睡眠状态。如果您的警报接收器叫Context.startService(),则电话可能会在启动请求的服务之前进入睡眠状态。为防止这种情况,您BroadcastReceiver和您Service将需要实施单独的唤醒锁定策略,以确保手机继续运行,直到该服务可用为止。

注意:警报管理器适用于希望在特定时间运行应用程序代码的情况,即使您的应用程序当前未运行。对于正常的计时操作(滴答声,超时等),使用Handler会更容易且效率更高。

计时器

timer = new Timer();

    timer.scheduleAtFixedRate(new TimerTask() {

        synchronized public void run() {

            \\ here your todo;
            }

        }}, TimeUnit.MINUTES.toMillis(1), TimeUnit.MINUTES.toMillis(1));

Timer有一些可以解决的缺点ScheduledThreadPoolExecutor。所以这不是最好的选择

ScheduledThreadPoolExecutor

您可以使用java.util.TimerScheduledThreadPoolExecutor(首选)将操作安排在后台线程上定期执行。

这是使用后者的示例:

ScheduledExecutorService scheduler =
    Executors.newSingleThreadScheduledExecutor();

scheduler.scheduleAtFixedRate
      (new Runnable() {
         public void run() {
            // call service
         }
      }, 0, 10, TimeUnit.MINUTES);

所以我更喜欢 ScheduledExecutorService

但也要考虑一下,如果更新将在应用程序运行时进行,则可以使用Timer其他答案中建议的或较新的ScheduledThreadPoolExecutor。如果您的应用程序即使没有运行也会更新,则应使用AlarmManager

警报管理器适用于您希望在特定时间运行应用程序代码的情况,即使您的应用程序当前未运行。

请注意,如果您计划在关闭应用程序时进行更新,则十分频繁,每十分钟一次,因此可能会有点耗电。


我正在尝试将此方法用于定期任务,但似乎无法正常工作stackoverflow.com/questions/27872016/…–
dowjones123

对于简单的事情-例如每隔n秒检查一次状态-计时器将执行此操作。
IgorGanapolsky

1
@ Maid786如果要间隔一周或几天的时间执行某些任务(例如发送通知),应该使用什么?警报管理器会为此进行过多的后台计算或处理吗?
Chintan Shah 2015年

30

计时器

javadocs上所述,最好使用ScheduledThreadPoolExecutor。

ScheduledThreadPoolExecutor

当您的用例需要多个工作线程并且睡眠间隔小时,请使用此类。多么小 ?好吧,我想说大约15分钟。此时的AlarmManager开始计划间隔时间,似乎暗示对于较小的睡眠间隔时间,可以使用此类。我没有数据可以支持最后的声明。这是预感。

服务

VM可以随时关闭您的服务。不要将服务用于重复性任务。重复执行的任务可以启动服务,这完全是另一回事。

带AlarmManager的BroadcastReciever

对于较长的睡眠间隔(> 15分钟),这是一种方法。AlarmManager已经有常量(AlarmManager.INTERVAL_DAY),表明它可以在最初计划的几天后触发任务。它还可以唤醒CPU来运行您的代码。

您应该根据时间和工作线程需求使用其中一种解决方案。


1
因此,如果我想使用该应用程序,并且每半小时要备份一次,该怎么办?但我不想在不使用该应用程序时进行备份(那将是完全浪费)。Alarmmanager将不断重复该操作,直到重新启动为止(至少这是我所听到的)。你会推荐什么?ScheduledThreadPoolExecutor或Alarmmanager?
hasdrubal 2013年

13

我知道这是一个古老的问题,已经得到解答,但这可以帮助某人。在你的activity

private ScheduledExecutorService scheduleTaskExecutor;

onCreate

  scheduleTaskExecutor = Executors.newScheduledThreadPool(5);

    //Schedule a task to run every 5 seconds (or however long you want)
    scheduleTaskExecutor.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            // Do stuff here!

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    // Do stuff to update UI here!
                    Toast.makeText(MainActivity.this, "Its been 5 seconds", Toast.LENGTH_SHORT).show();
                }
            });

        }
    }, 0, 5, TimeUnit.SECONDS); // or .MINUTES, .HOURS etc.

2

引述调度重复报警-了解权衡文档:

在应用程序的生命周期之外触发操作的常见方案是与服务器同步数据。在这种情况下,您可能会想使用重复警报。但是,如果您拥有托管应用程序数据的服务器,则将Google Cloud Messaging(GCM)与同步适配器一起使用是比AlarmManager更好的解决方案。同步适配器为您提供与AlarmManager相同的调度选项,但是它为您提供了更大的灵活性。

因此,基于此,安排服务器调用的最佳方法是将Google Cloud Messaging(GCM)同步适配器结合使用。


1

我创建了准时任务,在该任务中用户要重复的任务添加到Custom TimeTask run()方法中。它已成功重现。

 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.Timer;
 import java.util.TimerTask;

 import android.os.Bundle;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.Button;
 import android.widget.CheckBox;
 import android.widget.TextView;
 import android.app.Activity;
 import android.content.Intent;

 public class MainActivity extends Activity {

     CheckBox optSingleShot;
     Button btnStart, btnCancel;
     TextView textCounter;

     Timer timer;
     MyTimerTask myTimerTask;

     int tobeShown = 0  ;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    optSingleShot = (CheckBox)findViewById(R.id.singleshot);
    btnStart = (Button)findViewById(R.id.start);
    btnCancel = (Button)findViewById(R.id.cancel);
    textCounter = (TextView)findViewById(R.id.counter);
    tobeShown = 1;

    if(timer != null){
        timer.cancel();
    }

    //re-schedule timer here
    //otherwise, IllegalStateException of
    //"TimerTask is scheduled already" 
    //will be thrown
    timer = new Timer();
    myTimerTask = new MyTimerTask();

    if(optSingleShot.isChecked()){
        //singleshot delay 1000 ms
        timer.schedule(myTimerTask, 1000);
    }else{
        //delay 1000ms, repeat in 5000ms
        timer.schedule(myTimerTask, 1000, 1000);
    }

    btnStart.setOnClickListener(new OnClickListener(){

        @Override
        public void onClick(View arg0) {


            Intent i = new Intent(MainActivity.this, ActivityB.class);
            startActivity(i);

            /*if(timer != null){
                timer.cancel();
            }

            //re-schedule timer here
            //otherwise, IllegalStateException of
            //"TimerTask is scheduled already" 
            //will be thrown
            timer = new Timer();
            myTimerTask = new MyTimerTask();

            if(optSingleShot.isChecked()){
                //singleshot delay 1000 ms
                timer.schedule(myTimerTask, 1000);
            }else{
                //delay 1000ms, repeat in 5000ms
                timer.schedule(myTimerTask, 1000, 1000);
            }*/
        }});

    btnCancel.setOnClickListener(new OnClickListener(){

        @Override
        public void onClick(View v) {
            if (timer!=null){
                timer.cancel();
                timer = null;
            }
        }
    });

}

@Override
protected void onResume() {
    super.onResume();

    if(timer != null){
        timer.cancel();
    }

    //re-schedule timer here
    //otherwise, IllegalStateException of
    //"TimerTask is scheduled already" 
    //will be thrown
    timer = new Timer();
    myTimerTask = new MyTimerTask();

    if(optSingleShot.isChecked()){
        //singleshot delay 1000 ms
        timer.schedule(myTimerTask, 1000);
    }else{
        //delay 1000ms, repeat in 5000ms
        timer.schedule(myTimerTask, 1000, 1000);
    }
}


@Override
protected void onPause() {
    super.onPause();

    if (timer!=null){
        timer.cancel();
        timer = null;
    }

}

@Override
protected void onStop() {
    super.onStop();

    if (timer!=null){
        timer.cancel();
        timer = null;
    }

}

class MyTimerTask extends TimerTask {

    @Override
    public void run() {

        Calendar calendar = Calendar.getInstance();
        SimpleDateFormat simpleDateFormat = 
                new SimpleDateFormat("dd:MMMM:yyyy HH:mm:ss a");
        final String strDate = simpleDateFormat.format(calendar.getTime());

        runOnUiThread(new Runnable(){

            @Override
            public void run() {
                textCounter.setText(strDate);
            }});
    }
}

}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.