ทำซ้ำงานด้วยการหน่วงเวลาหรือไม่


216

ฉันมีตัวแปรในรหัสของฉันบอกว่ามันคือ "สถานะ"

ฉันต้องการแสดงข้อความในแอปพลิเคชันขึ้นอยู่กับค่าตัวแปรนี้ สิ่งนี้จะต้องดำเนินการด้วยการหน่วงเวลาเฉพาะ

มันเหมือนกับ,

  • ตรวจสอบค่าตัวแปรสถานะ

  • แสดงข้อความ

  • รอ 10 วินาที

  • ตรวจสอบค่าตัวแปรสถานะ

  • แสดงข้อความ

  • รอ 15 วินาที

และอื่น ๆ เวลาล่าช้าอาจแตกต่างกันไปและจะถูกตั้งค่าเมื่อข้อความปรากฏขึ้น

ฉันพยายามThread.sleep(time delay)แล้วและก็ล้มเหลว มีวิธีใดที่จะทำให้ดีขึ้นได้ดีกว่านี้?


คำตอบ:


448

คุณควรใช้HandlerของpostDelayedฟังก์ชั่นเพื่อการนี้ มันจะเรียกใช้รหัสของคุณด้วยการหน่วงเวลาที่ระบุในเธรด UI หลักดังนั้นคุณจะสามารถอัปเดตการควบคุม UI ได้

private int mInterval = 5000; // 5 seconds by default, can be changed later
private Handler mHandler;

@Override
protected void onCreate(Bundle bundle) {

    // your code here

    mHandler = new Handler();
    startRepeatingTask();
}

@Override
public void onDestroy() {
    super.onDestroy();
    stopRepeatingTask();
}

Runnable mStatusChecker = new Runnable() {
    @Override 
    public void run() {
          try {
               updateStatus(); //this function can change value of mInterval.
          } finally {
               // 100% guarantee that this always happens, even if
               // your update method throws an exception
               mHandler.postDelayed(mStatusChecker, mInterval);
          }
    }
};

void startRepeatingTask() {
    mStatusChecker.run(); 
}

void stopRepeatingTask() {
    mHandler.removeCallbacks(mStatusChecker);
}

1
ขอบคุณ inazaruk ที่จัดการเพื่อให้ทำงานได้ พบ 2 ตัวพิมพ์เล็กขนาดเล็ก (ที่ด้านบน "Handler" ไม่ใช่ "Handle" และที่ด้านล่าง "removeCallbacks" ไม่ลบ "removecallback" แต่ในรหัสใด ๆ ก็เป็นสิ่งที่ฉันกำลังมองหาพยายามคิดว่าฉันสามารถ ทำเพื่อคืนความโปรดปรานอย่างน้อยที่สุดคุณก็ชนะความเคารพนับถือนับถือ Aubrey Bourke
aubreybourke

20
โปรแกรม Nice ทำงานได้ดีอย่างแน่นอน แต่ startRepeatingTask () ต้องถูกเรียกจากเมธอด onCreate / UI UI (ใช้เวลาพอสมควรในการตระหนักถึงสิ่งนี้!) บางทีจุดนี้อาจถูกกล่าวถึงที่ไหนสักแห่ง ขอแสดงความนับถือ
gkris

1
คำตอบของคุณยังคงให้ สิ่งนี้ช่วยฉันออกจากหลุมในวันนี้ ขอบคุณ
Dean Blakely

มีวิธีใดบ้างที่จะเรียกใช้ Runnable ซ้ำภายในเมธอด getView () ของอะแดปเตอร์ได้หรือไม่?
toobsco42

1
ในที่นี้เมื่อเรานำเข้าคลาสเราควรนำเข้าอะไร android.os.Handler หรือ java.util.logging.Handler?
EJ Chathuranga

34

สำหรับผู้ที่สนใจนี่คือคลาสที่ฉันสร้างขึ้นโดยใช้โค้ดของ inazaruk ที่สร้างทุกสิ่งที่ต้องการ (ฉันเรียกมันว่า UIUpdater เพราะฉันใช้เพื่ออัปเดต UI เป็นระยะ แต่คุณสามารถเรียกมันว่าอะไรก็ได้ที่คุณชอบ):

import android.os.Handler;
/**
 * A class used to perform periodical updates,
 * specified inside a runnable object. An update interval
 * may be specified (otherwise, the class will perform the 
 * update every 2 seconds).
 * 
 * @author Carlos Simões
 */
public class UIUpdater {
        // Create a Handler that uses the Main Looper to run in
        private Handler mHandler = new Handler(Looper.getMainLooper());

        private Runnable mStatusChecker;
        private int UPDATE_INTERVAL = 2000;

        /**
         * Creates an UIUpdater object, that can be used to
         * perform UIUpdates on a specified time interval.
         * 
         * @param uiUpdater A runnable containing the update routine.
         */
        public UIUpdater(final Runnable uiUpdater) {
            mStatusChecker = new Runnable() {
                @Override
                public void run() {
                    // Run the passed runnable
                    uiUpdater.run();
                    // Re-run it after the update interval
                    mHandler.postDelayed(this, UPDATE_INTERVAL);
                }
            };
        }

        /**
         * The same as the default constructor, but specifying the
         * intended update interval.
         * 
         * @param uiUpdater A runnable containing the update routine.
         * @param interval  The interval over which the routine
         *                  should run (milliseconds).
         */
        public UIUpdater(Runnable uiUpdater, int interval){
            UPDATE_INTERVAL = interval;
            this(uiUpdater);
        }

        /**
         * Starts the periodical update routine (mStatusChecker 
         * adds the callback to the handler).
         */
        public synchronized void startUpdates(){
            mStatusChecker.run();
        }

        /**
         * Stops the periodical update routine from running,
         * by removing the callback.
         */
        public synchronized void stopUpdates(){
            mHandler.removeCallbacks(mStatusChecker);
        }
}

จากนั้นคุณสามารถสร้างวัตถุ UIUpdater ภายในชั้นเรียนของคุณและใช้งานได้ตามต้องการดังนี้

...
mUIUpdater = new UIUpdater(new Runnable() {
         @Override 
         public void run() {
            // do stuff ...
         }
    });

// Start updates
mUIUpdater.startUpdates();

// Stop updates
mUIUpdater.stopUpdates();
...

หากคุณต้องการใช้สิ่งนี้เป็นตัวอัปเดตกิจกรรมให้วางการโทรเริ่มภายในเมธอด onResume () และการหยุดการโทรภายใน onPause () เพื่อให้การอัพเดตเริ่มต้นและหยุดตามการมองเห็นกิจกรรม


1
แก้ไข: UPDATE_INTERVAL = interval;ควรอยู่ก่อน this(uiUpdater);ในUIUpdater(Runnable uiUpdater, int interval)(ตามค่าที่ UPDATE_INTERVALใช้และควรเป็นค่าที่ส่งผ่านเป็นพารามิเตอร์interval;) นอกจากนี้หลีกเลี่ยงความกว้างมากกว่า 80 ตัวอักษรในรหัสเมื่อเป็นไปได้ (เกือบตลอดเวลา)
Mr_and_Mrs_D

5
คลาสนี้มีปัญหามากมาย ในสถานที่แรกก็ควรจะยกตัวอย่างในหัวข้อหลักเพื่อให้สามารถปรับปรุง GUI คุณอาจจะได้รับการแก้ไขนี้โดยผ่าน Looper new Handler(Looper.getMainLooper())หลักในการจัดการคอนสตรัค: ประการที่สองมันไม่ได้ตรวจสอบข้อโต้แย้งดังนั้นมันกลืน null Runnables และช่วงเวลาเชิงลบ ในที่สุดมันไม่ได้คำนึงถึงเวลาที่ใช้ในuiUpdater.run()บรรทัดและไม่สามารถจัดการกับข้อยกเว้นที่เป็นไปได้ที่ถูกโยนโดยวิธีการนั้น นอกจากนี้ยังไม่ปลอดภัยเธรดคุณควรทำstartและstopวิธีการทำข้อมูลให้ตรงกัน
มิสเตอร์สมิ ธ

2
แก้ไขจนถึงส่วนการตรวจสอบอาร์กิวเมนต์เนื่องจากฉันไม่มี Eclipse ที่นี่เพื่อทดสอบโค้ด ขอบคุณสำหรับความคิดเห็น! นี่คือสิ่งที่คุณหมายถึง? ซิงโครไนซ์ startUpdates และ stopUpdates และวางสาย Looper.getMainLooper () ภายในตัวจัดการโครงสร้าง (ฉันหวังว่าคุณสามารถเรียกมันได้โดยตรงจากการประกาศภาคสนาม)
ravemir

2
ฉันได้รับสิ่งนี้: error: call to this must be first statement in constructorอาจจะมีวิธีแก้ไขที่ง่าย
msysmilu

4
การ upvoting สำหรับการนำเข้า - ใช้เวลาในการพิจารณาว่าตัวจัดการมาจากไหนเมื่อการเขียนโปรแกรมใน Java ตั้งใจ
Roman Susi

23

ผมคิดว่าความร้อนใหม่คือการใช้ScheduledThreadPoolExecutor ชอบมาก

private final ScheduledThreadPoolExecutor executor_ = 
        new ScheduledThreadPoolExecutor(1);
this.executor_.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
    update();
    }
}, 0L, kPeriod, kTimeUnit);

Executors.newSingleThreadScheduledExecutor()สามารถเป็นตัวเลือกอื่นได้ที่นี่
Gulshan

13

จับเวลาทำงานได้ดี ที่นี่ฉันใช้ตัวจับเวลาเพื่อค้นหาข้อความหลังจาก 1.5s และอัปเดต UI หวังว่าจะช่วย

private Timer _timer = new Timer();

_timer.schedule(new TimerTask() {
    @Override
    public void run() {
        // use runOnUiThread(Runnable action)
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                search();
            }
        });
    }
}, timeInterval);

คุณวางช่วงเวลาไว้ที่ไหน
Nathiel Barros

1
สวัสดี Nathiel ฉันเพิ่งอัปเดตโพสต์หวังว่าจะช่วยได้! ช่วงเวลาเป็นพารามิเตอร์ตัวที่สองของ Timer.schedule ()
Kai Wang

7

มี 3 วิธีในการทำ:

ใช้ ScheduledThreadPoolExecutor

overkill เล็กน้อยเนื่องจากคุณไม่ต้องการกลุ่มของเธรด

   //----------------------SCHEDULER-------------------------
    private final ScheduledThreadPoolExecutor executor_ =
            new ScheduledThreadPoolExecutor(1);
     ScheduledFuture<?> schedulerFuture;
   public void  startScheduler() {
       schedulerFuture=  executor_.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                //DO YOUR THINGS
                pageIndexSwitcher.setVisibility(View.GONE);
            }
        }, 0L, 5*MILLI_SEC,  TimeUnit.MILLISECONDS);
    }


    public void  stopScheduler() {
        pageIndexSwitcher.setVisibility(View.VISIBLE);
        schedulerFuture.cancel(false);
        startScheduler();
    }

ใช้งานตัวตั้งเวลา

สไตล์ Android เก่า

    //----------------------TIMER  TASK-------------------------

    private Timer carousalTimer;
    private void startTimer() {
        carousalTimer = new Timer(); // At this line a new Thread will be created
        carousalTimer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                //DO YOUR THINGS
                pageIndexSwitcher.setVisibility(INVISIBLE);
            }
        }, 0, 5 * MILLI_SEC); // delay
    }

    void stopTimer() {
        carousalTimer.cancel();
    }

ใช้ตัวจัดการและ Runnable

สไตล์ Android ที่ทันสมัย

    //----------------------HANDLER-------------------------

    private Handler taskHandler = new android.os.Handler();

    private Runnable repeatativeTaskRunnable = new Runnable() {
        public void run() {
            //DO YOUR THINGS
        }
    };

   void startHandler() {
        taskHandler.postDelayed(repeatativeTaskRunnable, 5 * MILLI_SEC);
    }

    void stopHandler() {
        taskHandler.removeCallbacks(repeatativeTaskRunnable);
    }

ตัวจัดการที่ไม่รั่วไหลพร้อมกิจกรรม / บริบท

ประกาศคลาสตัวจัดการภายในซึ่งไม่ทำให้หน่วยความจำรั่วในคลาสกิจกรรม / แฟรกเมนต์ของคุณ

/**
     * Instances of static inner classes do not hold an implicit
     * reference to their outer class.
     */
    private static class NonLeakyHandler extends Handler {
        private final WeakReference<FlashActivity> mActivity;

        public NonLeakyHandler(FlashActivity activity) {
            mActivity = new WeakReference<FlashActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            FlashActivity activity = mActivity.get();
            if (activity != null) {
                // ...
            }
        }
    }

ประกาศ runnable ที่จะทำงานซ้ำ ๆ ในคลาส Activity / Fragment

   private Runnable repeatativeTaskRunnable = new Runnable() {
        public void run() {
            new Handler(getMainLooper()).post(new Runnable() {
                @Override
                public void run() {

         //DO YOUR THINGS
        }
    };

เริ่มต้นวัตถุตัวจัดการในกิจกรรม / ส่วนของคุณ (ที่นี่ FlashActivity เป็นชั้นเรียนกิจกรรมของฉัน)

//Task Handler
private Handler taskHandler = new NonLeakyHandler(FlashActivity.this);

หากต้องการทำซ้ำภารกิจหลังจากช่วงเวลาแก้ไข

taskHandler.postDelayed (repeatativeTaskRunnable, DELAY_MILLIS);

เพื่อหยุดการทำซ้ำของงาน

taskHandler .removeCallbacks (repeatativeTaskRunnable);

อัปเดต: ใน Kotlin:

    //update interval for widget
    override val UPDATE_INTERVAL = 1000L

    //Handler to repeat update
    private val updateWidgetHandler = Handler()

    //runnable to update widget
    private var updateWidgetRunnable: Runnable = Runnable {
        run {
            //Update UI
            updateWidget()
            // Re-run it after the update interval
            updateWidgetHandler.postDelayed(updateWidgetRunnable, UPDATE_INTERVAL)
        }

    }

 // SATART updating in foreground
 override fun onResume() {
        super.onResume()
        updateWidgetHandler.postDelayed(updateWidgetRunnable, UPDATE_INTERVAL)
    }


    // REMOVE callback if app in background
    override fun onPause() {
        super.onPause()
        updateWidgetHandler.removeCallbacks(updateWidgetRunnable);
    }

6

ตัวจับเวลาเป็นอีกวิธีในการทำงานของคุณ แต่อย่าลืมเพิ่มrunOnUiThreadถ้าคุณทำงานกับ UI

    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;

public class MainActivity extends Activity {

 CheckBox optSingleShot;
 Button btnStart, btnCancel;
 TextView textCounter;

 Timer timer;
 MyTimerTask myTimerTask;

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

  btnStart.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {

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

  btnCancel.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View v) {
    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);
    }});
  }

 }

}

และ xml คือ ...

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:autoLink="web"
    android:text="http://android-er.blogspot.com/"
    android:textStyle="bold" />
<CheckBox 
    android:id="@+id/singleshot"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Single Shot"/>

อีกวิธีหนึ่งในการใช้ CountDownTimer

new CountDownTimer(30000, 1000) {

     public void onTick(long millisUntilFinished) {
         mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
     }

     public void onFinish() {
         mTextField.setText("done!");
     }
  }.start();

กำหนดเวลานับถอยหลังจนกว่าจะถึงเวลาในอนาคตโดยมีการแจ้งเตือนตามช่วงเวลาตามปกติ ตัวอย่างการแสดงการนับถอยหลัง 30 วินาทีในฟิลด์ข้อความ:

สำหรับรายละเอียด


1
ควรใช้ตัวจัดการมากกว่าตัวจับเวลา ดูตัวจับเวลา vs ผู้ดูแล
Suragch

4

ลองตัวอย่างต่อไปนี้ใช้งานได้ !!!

ใช้ [Handler] ในเมธอด onCreate () ซึ่งใช้ประโยชน์จากวิธี postDelayed () ที่ทำให้ Runnable ถูกเพิ่มลงในคิวข้อความที่จะทำงานหลังจากระยะเวลาที่ระบุที่ผ่านไปซึ่งเป็น 0 ในตัวอย่างที่กำหนด 1

อ้างอิงรหัสนี้:

public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
       setContentView(R.layout.main);
    //------------------
    //------------------
    android.os.Handler customHandler = new android.os.Handler();
            customHandler.postDelayed(updateTimerThread, 0);
}

private Runnable updateTimerThread = new Runnable()
{
        public void run()
        {
            //write here whaterver you want to repeat
            customHandler.postDelayed(this, 1000);
        }
};



4

จากการโพสต์ข้างต้นเกี่ยวกับScheduledThreadPoolExecutorฉันได้พบกับยูทิลิตี้ที่เหมาะสมกับความต้องการของฉัน (ต้องการเริ่มวิธีทุก 3 วินาที)

class MyActivity {
    private ScheduledThreadPoolExecutor mDialogDaemon;

    private void initDebugButtons() {
        Button btnSpawnDialogs = (Button)findViewById(R.id.btn_spawn_dialogs);
        btnSpawnDialogs.setVisibility(View.VISIBLE);
        btnSpawnDialogs.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                spawnDialogs();
            }
        });
    }

    private void spawnDialogs() {
        if (mDialogDaemon != null) {
            mDialogDaemon.shutdown();
            mDialogDaemon = null;
        }
        mDialogDaemon = new ScheduledThreadPoolExecutor(1);
        // This process will execute immediately, then execute every 3 seconds.
        mDialogDaemon.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        // Do something worthwhile
                    }
                });
            }
        }, 0L, 3000L, TimeUnit.MILLISECONDS);
    }
}

4

ในกรณีของฉันฉันต้องดำเนินการกระบวนการหากเงื่อนไขข้อใดข้อหนึ่งเหล่านี้เป็นจริง: หากกระบวนการก่อนหน้านี้เสร็จสมบูรณ์หรือถ้าผ่านไป 5 วินาที ดังนั้นฉันทำสิ่งต่อไปนี้และทำงานได้ดี:

private Runnable mStatusChecker;
private Handler mHandler;

class {
method() {
  mStatusChecker = new Runnable() {
            int times = 0;
            @Override
            public void run() {
                if (times < 5) {
                    if (process1.isRead()) {
                        executeProcess2();
                    } else {
                        times++;
                        mHandler.postDelayed(mStatusChecker, 1000);
                    }
                } else {
                    executeProcess2();
                }
            }
        };

        mHandler = new Handler();
        startRepeatingTask();
}

    void startRepeatingTask() {
       mStatusChecker.run();
    }

    void stopRepeatingTask() {
        mHandler.removeCallbacks(mStatusChecker);
    }


}

หากอ่าน process1 จะดำเนินการ process2 ถ้าไม่ใช่มันจะเพิ่มเวลาตัวแปรและทำให้ตัวจัดการดำเนินการหลังจากผ่านไปหนึ่งวินาที มันจะรักษาลูปจนกว่า process1 จะอ่านหรือเวลาคือ 5 เมื่อเวลาคือ 5 ก็หมายความว่า 5 วินาทีผ่านไปและในแต่ละวินาทีจะดำเนินการถ้าประโยคของ process1.isRead ()


1

การใช้ kotlin และ Coroutine นั้นง่ายมาก ๆ ก่อนประกาศงานในชั้นเรียนของคุณ (ดีกว่าใน viewModel ของคุณ) ดังนี้:

private var repeatableJob: Job? = null

จากนั้นเมื่อคุณต้องการสร้างและเริ่มทำสิ่งนี้:

repeatableJob = viewModelScope.launch {
    while (isActive) {
         delay(5_000)
         loadAlbums(iImageAPI, titleHeader, true)
    }
}
repeatableJob?.start()

และถ้าคุณต้องการที่จะจบมัน:

repeatableJob?.cancel()

PS: viewModelScopeมีเฉพาะในรุ่นที่ดูเท่านั้นคุณสามารถใช้ขอบเขต Coroutine อื่น ๆ เช่นwithContext(Dispatchers.IO)

ข้อมูลเพิ่มเติม: ที่นี่


0

สำหรับคนที่ใช้ Kotlin คำตอบของ inazarukจะไม่ทำงาน IDE จะต้องการตัวแปรที่จะเริ่มต้นได้ดังนั้นแทนที่จะใช้postDelayedด้านในRunnableเราจะใช้ในวิธีแยกต่างหาก

  • เริ่มต้นRunnableเช่นนี้ของคุณ:

    private var myRunnable = Runnable {
        //Do some work
        //Magic happens here ↓
        runDelayedHandler(1000)   }
  • เริ่มต้นrunDelayedHandlerวิธีการของคุณเช่นนี้:

     private fun runDelayedHandler(timeToWait : Long) {
        if (!keepRunning) {
            //Stop your handler
            handler.removeCallbacksAndMessages(null)
            //Do something here, this acts like onHandlerStop
        }
        else {
            //Keep it running
            handler.postDelayed(myRunnable, timeToWait)
        }
    }
  • อย่างที่คุณเห็นวิธีการนี้จะทำให้คุณสามารถควบคุมอายุการใช้งานของงานติดตามkeepRunningและเปลี่ยนแปลงในช่วงอายุการใช้งานของแอปพลิเคชันที่จะทำงานให้คุณ

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