Lập lịch tác vụ định kỳ trong Android


122

Tôi đang thiết kế một ứng dụng có nhiệm vụ định kỳ là gửi sự hiện diện đến một máy chủ chuyên dụng miễn là ứng dụng đó ở nền trước.

Trong các tìm kiếm của tôi trên web, tôi thấy một số cách tiếp cận khác nhau và muốn biết cách tốt nhất để thực hiện việc này là gì.

Cách tốt nhất để lên lịch cuộc gọi máy chủ là gì?

Các tùy chọn tôi thấy là:

  1. Hẹn giờ .

  2. SchedisedThreadPoolExecutor .

  3. Dịch vụ .

  4. BroadcastReciever với AlarmManager .

Ý kiến ​​của bạn là gì?

CHỈNH SỬA:
Lý do tôi cần điều này là vì một ứng dụng dựa trên trò chuyện gửi tất cả các hành động của người dùng đến một máy chủ từ xa.
tức là người dùng đang nhập tin nhắn, người dùng đang đọc tin nhắn, người dùng đang trực tuyến, người dùng đang ngoại tuyến, v.v.

Điều này có nghĩa là cứ sau mỗi khoảng thời gian, tôi cần gửi cho máy chủ những gì tôi đang làm, vì tôi mở phòng trò chuyện với những người khác, họ cần biết tôi đang làm gì.

Tương tự như cơ chế phản hồi tin nhắn whatsapp: tin nhắn có vẻ đã được gửi

CHỈNH SỬA # 2:
Các tác vụ định kỳ giờ đây phải được lên lịch hầu như luôn luôn thông qua JobSchedulerAPI (hoặc FirebaseJobDispatcherđối với các API thấp hơn) để ngăn vấn đề tiêu hao pin như bạn có thể đọc trong phần quan trọng của khóa đào tạo Android

CHỈNH SỬA # 3:
FirebaseJobDispatcher đã không được dùng nữa và được thay thế bằng Workmanager , tính năng này cũng tích hợp các tính năng của JobScheduler.


2
BroaccastReceiver với AlarmManager khá dễ sử dụng. Đó là lựa chọn thay thế duy nhất ở trên mà tôi đã thử.

1
Có rất ít lý do để sử dụng Bộ hẹn giờ trên SchedisedThreadPoolExecutor, tính năng này linh hoạt hơn vì nó cho phép nhiều hơn một luồng nền và có độ phân giải tốt hơn (chỉ hữu ích cho độ phân giải ms) và cho phép xử lý ngoại lệ. Đối với AlarmManager, bài đăng này cung cấp một số thông tin về sự khác biệt.
assylias

Đối với vòng đời hoạt động ngắn, tức là thực hiện một số tác vụ cứ sau 30 giây trong một hoạt động hiện ở phía trước, sử dụng SchedisedThreadPoolExecutor (hoặc Timer) sẽ hiệu quả hơn. Đối với vòng đời hoạt động dài, tức là thực hiện một số tác vụ cứ sau 1 giờ trong dịch vụ nền, sử dụng AlarmManager mang lại độ tin cậy cao hơn.
yorkw

Tại sao bạn thậm chí cần phải lên lịch gửi? Từ mô tả ứng dụng của bạn, tại sao bạn không gửi nó trong thời gian thực?
iTech 18/02/13

vì người dùng cho rằng bạn đang trực tuyến, sử dụng thời gian chờ. nghĩa là, nếu tôi không nhận được thông báo "hiện diện" hoặc "đang nhập" trong khoảng thời gian X vừa qua, tôi tự động cho rằng bạn không làm điều đó
thepoosh 19/02/13

Câu trả lời:


164

Tôi không chắc nhưng theo hiểu biết của tôi, tôi chia sẻ quan điểm của mình. Tôi luôn chấp nhận câu trả lời tốt nhất nếu tôi sai.

Trình quản lý báo thức

Trình quản lý Báo động giữ khóa đánh thức CPU miễn là onReceive()phương thức của người nhận cảnh báo đang thực thi. Điều này đảm bảo rằng điện thoại sẽ không ngủ cho đến khi bạn xử lý xong chương trình phát sóng. Sau khi onReceive()quay lại, Trình quản lý báo thức sẽ giải phóng khóa đánh thức này. Điều này có nghĩa là trong một số trường hợp, điện thoại sẽ ngủ ngay sau khi onReceive()phương pháp của bạn hoàn tất. Nếu bộ nhận báo thức của bạn gọi Context.startService(), có thể điện thoại sẽ ở chế độ ngủ trước khi khởi chạy dịch vụ được yêu cầu. Để ngăn chặn điều này, bạn BroadcastReceiverServicesẽ cần triển khai chính sách khóa chế độ thức riêng biệt để đảm bảo rằng điện thoại tiếp tục chạy cho đến khi dịch vụ khả dụng.

Lưu ý: Trình quản lý Báo thức dành cho các trường hợp bạn muốn mã ứng dụng của mình chạy vào một thời điểm cụ thể, ngay cả khi ứng dụng của bạn hiện không chạy. Đối với các hoạt động định thời bình thường (tích tắc, hết giờ, v.v.), việc sử dụng Trình xử lý sẽ dễ dàng và hiệu quả hơn nhiều.

Hẹn giờ

timer = new Timer();

    timer.scheduleAtFixedRate(new TimerTask() {

        synchronized public void run() {

            \\ here your todo;
            }

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

Timercó một số nhược điểm được giải quyết bằng cách ScheduledThreadPoolExecutor. Vì vậy, nó không phải là sự lựa chọn tốt nhất

SchedisedThreadPoolExecutor .

Bạn có thể sử dụng java.util.Timerhoặc ScheduledThreadPoolExecutor(ưu tiên) để lên lịch cho một hành động xảy ra theo khoảng thời gian đều đặn trên một chuỗi nền.

Đây là một mẫu sử dụng cái sau:

ScheduledExecutorService scheduler =
    Executors.newSingleThreadScheduledExecutor();

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

Vì vậy, tôi thích ScheduledExecutorService

Nhưng cũng hãy nghĩ về điều đó nếu các bản cập nhật sẽ xảy ra trong khi ứng dụng của bạn đang chạy, bạn có thể sử dụng Timercâu trả lời, như được đề xuất trong các câu trả lời khác hoặc mới hơn ScheduledThreadPoolExecutor. Nếu ứng dụng của bạn sẽ cập nhật ngay cả khi nó không chạy, bạn nên sử dụng AlarmManager.

Trình quản lý Báo thức dành cho các trường hợp bạn muốn mã ứng dụng của mình chạy vào một thời điểm cụ thể, ngay cả khi ứng dụng của bạn hiện không chạy.

Hãy lưu ý rằng nếu bạn dự định cập nhật khi ứng dụng của bạn bị tắt, thì cứ mười phút một lần là khá thường xuyên và do đó có thể hơi tốn điện.


Tôi đang cố gắng phương pháp này ra cho một nhiệm vụ tuần hoàn, nhưng nó dường như không làm việc stackoverflow.com/questions/27872016/...
dowjones123

Đối với những việc đơn giản - như kiểm tra trạng thái sau mỗi n-giây -Timer sẽ làm.
IgorGanapolsky

1
@ Maid786 Chúng ta nên sử dụng gì nếu chúng ta muốn thực hiện một số tác vụ (như gửi Thông báo) trong khoảng thời gian một Tuần hoặc khoảng thời gian tính bằng Ngày? Trình quản lý báo động có mất quá nhiều tính toán hoặc xử lý nền cho việc đó không?
Chintan Shah

30

Hẹn giờ

Như đã đề cập trên javadocs, tốt hơn hết bạn nên sử dụng SchedonedThreadPoolExecutor.

SchedisedThreadPoolExecutor

Sử dụng lớp này khi trường hợp sử dụng của bạn yêu cầu nhiều luồng công nhân và khoảng thời gian nghỉ là nhỏ. Nhỏ như thế nào ? Tôi sẽ nói khoảng 15 phút. Các AlarmManagerbắt đầu lịch trình khoảng vào thời điểm này và nó có vẻ gợi ý rằng cho khoảng thời gian ngủ nhỏ lớp này có thể được sử dụng. Tôi không có dữ liệu để sao lưu báo cáo cuối cùng. Đó là một linh cảm.

Dịch vụ

VM có thể đóng dịch vụ của bạn bất cứ lúc nào. Không sử dụng dịch vụ cho các nhiệm vụ định kỳ. Một nhiệm vụ lặp lại có thể bắt đầu một dịch vụ, đây hoàn toàn là một vấn đề khác.

BroadcastReciever với AlarmManager

Đối với khoảng thời gian ngủ dài hơn (> 15 phút), đây là cách để thực hiện. AlarmManagerđã có hằng số ( AlarmManager.INTERVAL_DAY) gợi ý rằng nó có thể kích hoạt các tác vụ vài ngày sau khi nó đã được lên lịch ban đầu. Nó cũng có thể đánh thức CPU để chạy mã của bạn.

Bạn nên sử dụng một trong những giải pháp đó dựa trên nhu cầu về thời gian và luồng nhân viên của mình.


1
Vì vậy, điều gì sẽ xảy ra nếu tôi muốn sử dụng ứng dụng và cứ sau nửa giờ tôi sẽ thực hiện một bản sao lưu. Nhưng tôi không muốn sao lưu trong khi ứng dụng không được sử dụng (điều đó sẽ hoàn toàn lãng phí). Alarmmanager sẽ liên tục lặp lại hành động cho đến khi khởi động lại (đó là ít nhất những gì tôi đã nghe). Bạn muốn giới thiệu điều gì? SchedisedThreadPoolExecutor hay Alarmmanager?
hasdrubal

13

Tôi nhận ra đây là một câu hỏi cũ và đã được trả lời nhưng điều này có thể giúp ích cho ai đó. Trong của bạnactivity

private ScheduledExecutorService scheduleTaskExecutor;

Trong 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

Trích dẫn Lập lịch cảnh báo lặp lại - Hiểu tài liệu về Đánh đổi:

Một trường hợp phổ biến để kích hoạt hoạt động bên ngoài vòng đời của ứng dụng là đồng bộ hóa dữ liệu với máy chủ. Đây là trường hợp mà bạn có thể bị cám dỗ để sử dụng báo thức lặp lại. Nhưng nếu bạn sở hữu máy chủ đang lưu trữ dữ liệu của ứng dụng, thì việc sử dụng Google Cloud Messaging (GCM) kết hợp với bộ điều hợp đồng bộ là giải pháp tốt hơn AlarmManager. Bộ điều hợp đồng bộ cung cấp cho bạn tất cả các tùy chọn lập lịch giống như AlarmManager, nhưng nó cung cấp cho bạn sự linh hoạt hơn đáng kể.

Vì vậy, dựa trên điều này, cách tốt nhất để lên lịch cuộc gọi máy chủ là sử dụng Google Cloud Messaging (GCM) kết hợp với bộ điều hợp đồng bộ hóa .


1

Tôi đã tạo tác vụ đúng giờ, trong đó tác vụ mà người dùng muốn lặp lại, hãy thêm vào phương thức Nhiệm vụ thời gian tùy chỉnh run (). nó đang tái xuất hiện thành công.

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

}

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.