125

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

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

安排服务器调用的最佳方式是什么?

我看到的选项是:

  1. 定时器

  2. ScheduledThreadPoolExecutor

  3. 服务

  4. BroadcastReciever 与AlarmManager

你怎么看?

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

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

类似于whatsapp消息反馈机制: 消息看起来已送达

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

编辑#3:
FirebaseJobDispatcher 已被弃用并被Workmanager取代,它还包含 JobScheduler 的功能。

4

5 回答 5

167

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

报警管理器

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

注意:警报管理器适用于您希望应用程序代码在特定时间运行的情况,即使您的应用程序当前没有运行。对于正常的计时操作(滴答声、超时等),使用 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

但是还要考虑一下,如果在您的应用程序运行时会发生更新,您可以使用 a Timer,如其他答案中所建议的那样,或者更新的ScheduledThreadPoolExecutor. 如果您的应用程序即使在未运行时也会更新,您应该使用AlarmManager.

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

请注意,如果您计划在应用程序关闭时更新,那么每十分钟一次是非常频繁的,因此可能有点太耗电了。

于 2013-01-17T11:17:08.787 回答
30

定时器

javadocs中所述,您最好使用 ScheduledThreadPoolExecutor。

ScheduledThreadPoolExecutor

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

服务

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

带有 AlarmManager 的广播接收器

对于更长的睡眠间隔(>15 分钟),这是要走的路。AlarmManager已经有常量 ( AlarmManager.INTERVAL_DAY) 表明它可以在最初安排好几天后触发任务。它还可以唤醒 CPU 以运行您的代码。

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

于 2013-02-20T13:11:40.673 回答
14

我意识到这是一个古老的问题并且已经得到解答,但这可以帮助某人。在你的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.
于 2016-01-09T00:24:47.667 回答
2

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

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

因此,基于此,安排服务器调用的最佳方式是将Google Cloud Messaging (GCM)Sync Adapter结合使用。

于 2014-12-23T12:55:37.937 回答
1

我已经创建了用户想要重复的任务的准时任务,在自定义 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);
            }});
    }
}

}

于 2015-04-13T12:57:51.967 回答