การจัดกำหนดการงานที่เกิดขึ้นประจำใน Android


122

ฉันกำลังออกแบบแอปที่มีงานประจำในการส่งสถานะไปยังเซิร์ฟเวอร์เฉพาะตราบเท่าที่แอปนั้นอยู่เบื้องหน้า

ในการค้นหาของฉันบนเว็บฉันเห็นแนวทางที่แตกต่างกันเล็กน้อยและต้องการทราบว่าวิธีใดดีที่สุดในการทำเช่นนี้

วิธีที่ดีที่สุดในการกำหนดเวลาการโทรจากเซิร์ฟเวอร์คืออะไร?

ตัวเลือกที่ฉันเห็นคือ:

  1. เครื่องจับเวลา

  2. ScheduledThreadPoolExecutor

  3. บริการ .

  4. BroadcastReciever กับAlarmManager

ความคิดเห็นของคุณคืออะไร?

แก้ไข:
เหตุผลที่ฉันต้องการสิ่งนี้สำหรับแอพที่ใช้แชทที่ส่งการกระทำทั้งหมดของผู้ใช้ไปยังเซิร์ฟเวอร์ระยะไกล
เช่นผู้ใช้กำลังพิมพ์ข้อความผู้ใช้กำลังอ่านข้อความผู้ใช้ออนไลน์ผู้ใช้ออฟไลน์เป็นต้น

ซึ่งหมายความว่าทุกครั้งที่ฉันต้องส่งเซิร์ฟเวอร์ว่าฉันกำลังทำอะไรอยู่เนื่องจากฉันเปิดห้องสนทนากับคนอื่นพวกเขาจำเป็นต้องรู้ว่าฉันกำลังทำอะไรอยู่

คล้ายกับกลไกการตอบกลับข้อความ whatsapp: ข้อความถูกส่งแล้ว

แก้ไข # 2:
ขณะนี้ควรกำหนดเวลางานที่เกิดซ้ำเกือบตลอดเวลาผ่านJobSchedulerAPI (หรือFirebaseJobDispatcherสำหรับ API ที่ต่ำกว่า) เพื่อป้องกันปัญหาการระบายแบตเตอรี่โดยสามารถอ่านได้ในส่วน Vitalsของการฝึกอบรม Android

แก้ไข # 3:
FirebaseJobDispatcher เลิกใช้งานแล้วและแทนที่ด้วยWorkmanagerซึ่งรวมคุณสมบัติของ JobScheduler ไว้ด้วย


2
BroaccastReceiver พร้อม AlarmManager ค่อนข้างตรงไปตรงมาที่จะใช้ เป็นทางเลือกเดียวข้างต้นที่ฉันเคยลอง

1
มีเหตุผลเพียงเล็กน้อยที่จะใช้ Timer บน ScheduledThreadPoolExecutor ซึ่งมีความยืดหยุ่นมากกว่าเนื่องจากอนุญาตให้มีเธรดพื้นหลังมากกว่าหนึ่งเธรดและมีความละเอียดที่ดีกว่า (มีประโยชน์สำหรับความละเอียด ms เท่านั้น) และอนุญาตให้จัดการข้อยกเว้นได้ สำหรับ AlarmManager โพสต์นี้ให้ข้อมูลเกี่ยวกับความแตกต่าง
assylias

สำหรับวงจรการใช้งานที่สั้นกล่าวคือดำเนินการบางอย่างทุกๆ 30 วินาทีในกิจกรรมที่อยู่เบื้องหน้าการใช้ ScheduledThreadPoolExecutor (หรือ Timer) จะมีประสิทธิภาพมากกว่า สำหรับวงจรการใช้งานที่ยาวนานเช่นทำงานบางอย่างทุกๆ 1 ชั่วโมงในบริการเบื้องหลังการใช้ AlarmManager จะให้ความน่าเชื่อถือมากขึ้น
yorkw

ทำไมคุณถึงต้องกำหนดเวลาการส่งด้วย? จากคำอธิบายแอปของคุณทำไมคุณไม่ส่งแบบเรียลไทม์ล่ะ
iTech

เนื่องจากผู้ใช้ถือว่าคุณออนไลน์โดยใช้การหมดเวลา หมายความว่าถ้าฉันไม่ได้รับข้อความ "แสดงตน" หรือ "พิมพ์" ในช่วงเวลา X ที่ผ่านมาฉันจะถือว่าคุณไม่ได้ทำโดยอัตโนมัติ
thepoosh

คำตอบ:


164

ฉันไม่แน่ใจ แต่ตามความรู้ของฉันฉันแบ่งปันมุมมองของฉัน ฉันยอมรับคำตอบที่ดีที่สุดเสมอหากฉันผิด

ตัวจัดการสัญญาณเตือน

Alarm Manager จะล็อคการปลุกของ CPU ไว้ตราบเท่าที่onReceive()วิธีการของตัวรับสัญญาณเตือนกำลังดำเนินการอยู่ สิ่งนี้รับประกันได้ว่าโทรศัพท์จะไม่เข้าสู่โหมดสลีปจนกว่าคุณจะจัดการการออกอากาศเสร็จสิ้น เมื่อonReceive()กลับมาตัวจัดการสัญญาณเตือนจะปลดล็อกการปลุกนี้ ซึ่งหมายความว่าในบางกรณีโทรศัพท์จะเข้าสู่โหมดสลีปทันทีที่onReceive()วิธีการของคุณเสร็จสมบูรณ์ หากเครื่องรับสัญญาณเตือนของคุณโทรContext.startService()มาอาจเป็นไปได้ว่าโทรศัพท์จะเข้าสู่โหมดสลีปก่อนที่จะเปิดบริการที่ร้องขอ เพื่อป้องกันการนี้ของคุณBroadcastReceiverและServiceจะต้องดำเนินการตามนโยบายการปลุกล็อคแยกต่างหากเพื่อให้มั่นใจว่าโทรศัพท์ยังคงทำงานจนกว่าบริการจะกลายเป็นใช้ได้

หมายเหตุ: Alarm Manager มีไว้สำหรับกรณีที่คุณต้องการให้รหัสแอปพลิเคชันของคุณทำงานในช่วงเวลาที่กำหนดแม้ว่าแอปพลิเคชันของคุณจะไม่ทำงานในขณะนี้ก็ตาม สำหรับการทำงานตามเวลาปกติ (เห็บการหมดเวลา ฯลฯ ) การใช้ Handler นั้นง่ายกว่าและมีประสิทธิภาพกว่ามาก

เครื่องจับเวลา

timer = new Timer();

    timer.scheduleAtFixedRate(new TimerTask() {

        synchronized public void run() {

            \\ here your todo;
            }

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

TimerScheduledThreadPoolExecutorมีข้อบกพร่องบางอย่างที่จะแก้ไขได้โดย ดังนั้นจึงไม่ใช่ทางเลือกที่ดีที่สุด

ScheduledThreadPoolExecutor

คุณสามารถใช้java.util.TimerหรือScheduledThreadPoolExecutor(แนะนำ) เพื่อกำหนดเวลาการดำเนินการที่จะเกิดขึ้นในช่วงเวลาปกติบนเธรดพื้นหลัง

นี่คือตัวอย่างโดยใช้หลัง:

ScheduledExecutorService scheduler =
    Executors.newSingleThreadScheduledExecutor();

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

ดังนั้นฉันจึงชอบ ScheduledExecutorService

แต่ยังคิดเกี่ยวกับว่าถ้าปรับปรุงจะเกิดขึ้นในขณะที่โปรแกรมของคุณทำงานคุณสามารถใช้Timerเป็นข้อเสนอแนะในคำตอบอื่น ๆ ScheduledThreadPoolExecutorหรือใหม่กว่า หากแอปพลิเคชันของคุณจะอัปเดตแม้ว่าจะไม่ได้ทำงานคุณควรไปกับไฟล์AlarmManager.

Alarm Manager มีไว้สำหรับกรณีที่คุณต้องการให้รหัสแอปพลิเคชันของคุณทำงานในช่วงเวลาที่กำหนดแม้ว่าแอปพลิเคชันของคุณจะไม่ทำงานก็ตาม

โปรดทราบว่าหากคุณวางแผนที่จะอัปเดตเมื่อปิดแอปพลิเคชันของคุณทุกๆสิบนาทีจะค่อนข้างบ่อยและอาจใช้พลังงานมากเกินไป


ฉันกำลังลองใช้วิธีนี้สำหรับงานเป็นระยะ แต่ดูเหมือนจะไม่ได้ผลstackoverflow.com/questions/27872016/…
dowjones123

สำหรับสิ่งง่ายๆเช่นการตรวจสอบสถานะทุกวินาที - ตัวจับเวลาจะทำ
IgorGanapolsky

1
@ Maid786 เราควรใช้อะไรหากต้องการทำงานบางอย่าง (เช่นการส่งการแจ้งเตือน) ในช่วงเวลาหนึ่งสัปดาห์หรือระยะเวลาเป็นวัน Alarm Manager จะคำนวณหรือประมวลผลพื้นหลังมากเกินไปหรือไม่?
Chintan Shah

30

เครื่องจับเวลา

ดังที่กล่าวไว้ในjavadocsคุณจะดีกว่าโดยใช้ ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor

ใช้คลาสนี้เมื่อกรณีการใช้งานของคุณต้องการเธรดผู้ปฏิบัติงานหลายเธรดและช่วงเวลาพักเครื่องมีขนาดเล็ก เล็กแค่ไหน? ฉันจะพูดประมาณ 15 นาที AlarmManagerเริ่มต้นกำหนดเวลาช่วงเวลาในขณะนี้และมันดูเหมือนว่าจะแนะนำว่าสำหรับช่วงเวลาการนอนหลับที่มีขนาดเล็กชั้นนี้สามารถนำมาใช้ ฉันไม่มีข้อมูลสำรองคำสั่งสุดท้าย มันเป็นลางสังหรณ์

บริการ

VM สามารถปิดบริการของคุณได้ทุกเมื่อ อย่าใช้บริการสำหรับงานที่เกิดซ้ำ งานที่เกิดซ้ำสามารถเริ่มบริการได้ซึ่งเป็นอีกเรื่องหนึ่งโดยสิ้นเชิง

BroadcastReciever พร้อม AlarmManager

สำหรับช่วงเวลาการนอนที่นานขึ้น (> 15 นาที) นี่คือวิธีที่จะไป AlarmManagerมีค่าคงที่ ( AlarmManager.INTERVAL_DAY) อยู่แล้วซึ่งแนะนำว่าสามารถทริกเกอร์งานได้หลายวันหลังจากที่กำหนดไว้ในตอนแรก นอกจากนี้ยังสามารถปลุก CPU เพื่อเรียกใช้รหัสของคุณ

คุณควรใช้หนึ่งในวิธีแก้ปัญหาเหล่านั้นตามเวลาและความต้องการเธรดของผู้ปฏิบัติงาน


1
แล้วถ้าฉันต้องการใช้แอพและทุกๆครึ่งชั่วโมงฉันจะต้องสำรองข้อมูล แต่ฉันไม่ต้องการสำรองข้อมูลในขณะที่แอปไม่ได้ใช้งาน (ซึ่งจะเป็นการสิ้นเปลืองโดยสิ้นเชิง) Alarmmanager จะดำเนินการซ้ำอย่างต่อเนื่องจนกว่าจะรีบูต (อย่างน้อยก็เป็นสิ่งที่ฉันเคยได้ยินมา) คุณจะแนะนำอะไร ScheduledThreadPoolExecutor หรือ Alarmmanager?
hasdrubal

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

}

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.