Lặp lại một nhiệm vụ với thời gian trễ?


216

Tôi có một biến trong mã của tôi nói rằng đó là "trạng thái".

Tôi muốn hiển thị một số văn bản trong ứng dụng tùy thuộc vào giá trị biến này. Điều này phải được thực hiện với một độ trễ thời gian cụ thể.

Nó giống như,

  • Kiểm tra giá trị biến trạng thái

  • Hiển thị một số văn bản

  • Đợi 10 giây

  • Kiểm tra giá trị biến trạng thái

  • Hiển thị một số văn bản

  • Đợi 15 giây

và như thế. Thời gian trễ có thể thay đổi và nó được đặt sau khi văn bản được hiển thị.

Tôi đã thử Thread.sleep(time delay)và nó đã thất bại. Bất kỳ cách nào tốt hơn để thực hiện điều này?


Câu trả lời:


448

Bạn nên sử dụng Handler's postDelayedchức năng cho mục đích này. Nó sẽ chạy mã của bạn với độ trễ được chỉ định trên luồng UI chính, do đó bạn sẽ có thể cập nhật các điều khiển 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
Cảm ơn bạn inazaruk, quản lý để làm cho nó hoạt động. Đã tìm thấy 2 v typo nhỏ (ở đầu "Handler" không phải là "Xử lý" và ở phía dưới "removeCallbacks" không xóa "removeecallback". làm để trả lại sự ưu ái. Ít nhất là bạn đã giành được sự tôn trọng của tôi. Trân trọng Aubrey Bourke.
aubreybourke

20
Chương trình đẹp, hoạt động hoàn toàn tốt. Nhưng startRepeatTask () phải được gọi từ luồng phương thức / giao diện người dùng onCreate (tôi phải mất một thời gian để nhận ra điều này!), Có lẽ điểm này có thể đã được đề cập ở đâu đó. Trân trọng
gkris

1
câu trả lời của bạn tiếp tục đưa ra. Điều này đã giúp tôi thoát khỏi một lỗ hổng ngày hôm nay. cảm ơn.
Dean Blakely

Có cách nào để có một Runnable lặp lại bên trong phương thức getView () của một bộ chuyển đổi không?
toobsco42

1
Ở đây khi chúng ta nhập các lớp, chúng ta nên nhập gì? android.os.Handler hoặc java.util.logging.Handler?
EJ Chathuranga

34

Đối với bất kỳ ai quan tâm, đây là một lớp tôi đã tạo bằng mã của inazaruk để tạo mọi thứ cần thiết (tôi gọi nó là UIUpdater vì tôi sử dụng nó để cập nhật định kỳ UI, nhưng bạn có thể gọi nó là bất cứ thứ gì bạn thích):

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

Sau đó, bạn có thể tạo một đối tượng UIUpdater bên trong lớp của mình và sử dụng nó như sau:

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

// Start updates
mUIUpdater.startUpdates();

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

Nếu bạn muốn sử dụng điều này như một trình cập nhật hoạt động, hãy đặt cuộc gọi bắt đầu bên trong phương thức onResume () và cuộc gọi dừng bên trong onPause (), để các cập nhật bắt đầu và dừng theo mức độ hiển thị của hoạt động.


1
Đã chỉnh sửa: UPDATE_INTERVAL = interval;nên ở trước (vì giá trị của được sử dụng và phải là giá trị được truyền dưới dạng tham số ). Đồng thời tránh độ rộng của hơn 80 ký tự trong mã khi có thể (hầu như luôn luôn;)this(uiUpdater);UIUpdater(Runnable uiUpdater, int interval)UPDATE_INTERVALinterval;
Mr_and_Mrs_D

5
Lớp học này có một số vấn đề. Ở nơi đầu tiên, nó cần được khởi tạo trong luồng chính để có thể cập nhật GUI. Bạn có thể đã giải quyết điều này bằng cách chuyển bộ xử lý chính cho hàm tạo của trình xử lý : new Handler(Looper.getMainLooper()). Thứ hai, nó không xác nhận các đối số, vì vậy nó nuốt null Runnables và các khoảng âm. Cuối cùng, nó không tính đến thời gian trong uiUpdater.run()dòng, cũng không xử lý các trường hợp ngoại lệ có thể xảy ra bởi phương thức đó. Ngoài ra, nó không phải là chủ đề an toàn, bạn nên thực hiện startstopđồng bộ hóa các phương thức.
Smith

2
Đã chỉnh sửa cho đến khi phần xác thực đối số, vì tôi không có Eclipse ở đây để kiểm tra mã. Cảm ơn vì bạn đã phản hồi! Đây có phải là những gì bạn có ý nghĩa? StartUpdates và stopUpdates đã đồng bộ hóa và thực hiện cuộc gọi Looper.getMainLooper () bên trong bộ điều khiển Handler (Tôi hy vọng bạn có thể gọi nó trực tiếp từ khai báo trường)
ravemir

2
Tôi nhận được điều này: error: call to this must be first statement in constructorcó thể có một sửa chữa dễ dàng.
msysmilu

4
Nâng cao khả năng nhập khẩu - cần có thời gian để tìm hiểu Handler đến từ đâu khi lập trình bằng Java một cách tình cờ
Roman Susi

23

Tôi nghĩ rằng sự hấp dẫn mới là sử dụng một Lịch trìnhThreadPoolExecutor . Thích như vậy:

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

Executors.newSingleThreadScheduledExecutor()có thể là một lựa chọn khác ở đây.
Gul Sơn

13

Hẹn giờ hoạt động tốt. Ở đây, tôi sử dụng Timer để tìm kiếm văn bản sau 1,5 giây và cập nhật UI. Mong rằng sẽ giúp.

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

bạn đã đặt khoảng thời gian ở đâu?
Nathiel Barros

1
Xin chào Nathiel, tôi vừa cập nhật bài viết của mình, hy vọng nó có ích! Thời gian giữa là tham số thứ hai của Timer.schedule ().
Kai Wang

7

Có 3 cách để làm điều đó:

Sử dụng lên lịchThreadPoolExecutor

Một chút quá mức vì bạn không cần một nhóm Thread

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

Sử dụng tác vụ hẹn giờ

Phong cách Android cũ

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

Sử dụng Handler và Runnable

Phong cách Android hiện đại

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

Xử lý không rò rỉ với Hoạt động / Bối cảnh

Khai báo một lớp Handler bên trong không rò rỉ Bộ nhớ trong lớp Activity / Fragment của bạn

/**
     * 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) {
                // ...
            }
        }
    }

Khai báo một runnable sẽ thực hiện nhiệm vụ lặp đi lặp lại của bạn trong lớp Activity / Fragment của bạn

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

         //DO YOUR THINGS
        }
    };

Khởi tạo đối tượng Handler trong Activity / Fragment của bạn (ở đây FlashActivity là lớp hoạt động của tôi)

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

Để lặp lại một nhiệm vụ sau khoảng thời gian sửa chữa

taskHandler.postDelayed (lặp lạiTaskRunnable, DELAY_MILLIS);

Để ngăn chặn sự lặp lại của nhiệm vụ

taskHandler .removeCallbacks (lặp lạiTaskRunnable);

CẬP NHẬT: Trong 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

Hẹn giờ là một cách khác để thực hiện công việc của bạn nhưng hãy yên tâm thêm runOnUiThreadnếu bạn đang làm việc với 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);
    }});
  }

 }

}

và xml là ...

<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"/>

Một cách khác để sử dụng CountDownTimer

new CountDownTimer(30000, 1000) {

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

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

Lên lịch đếm ngược cho đến một thời điểm trong tương lai, với các thông báo thường xuyên trên các khoảng thời gian trên đường đi. Ví dụ về hiển thị đếm ngược 30 giây trong trường văn bản:

Để biết chi tiết


1
Handler được ưa thích hơn Timer. Xem Timer vs Handler
Suragch

4

Hãy thử ví dụ sau đây nó hoạt động !!!

Sử dụng [Handler] trong phương thức onCreate () sử dụng phương thức postDelayed () khiến Runnable được thêm vào hàng đợi tin nhắn, để được chạy sau khoảng thời gian đã chỉ định là 0 trong ví dụ đã cho. 1

Tham khảo mã này:

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

Dựa trên bài đăng trên liên quan đến lên lịchThreadPoolExecutor , tôi đã tìm ra một tiện ích phù hợp với nhu cầu của tôi (muốn kích hoạt một phương thức cứ sau 3 giây):

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

Trong trường hợp của tôi, tôi đã phải thực hiện một quy trình nếu một trong những điều kiện này là đúng: nếu một quy trình trước đó đã hoàn thành hoặc nếu 5 giây đã trôi qua. Vì vậy, tôi đã làm như sau và làm việc khá tốt:

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


}

Nếu process1 được đọc, nó sẽ thực thi process2. Nếu không, nó sẽ tăng thời gian biến và làm cho Handler được thực thi sau một giây. Nó duy trì một vòng lặp cho đến khi process1 được đọc hoặc thời gian là 5. Khi thời gian là 5, có nghĩa là 5 giây trôi qua và trong mỗi giây, mệnh đề if của process1.isRead () được thực thi.


1

Sử dụng kotlin và Coroutine của nó khá dễ dàng, trước tiên hãy khai báo một công việc trong lớp của bạn (tốt hơn trong viewModel của bạn) như thế này:

private var repeatableJob: Job? = null

sau đó khi bạn muốn tạo và bắt đầu, hãy làm điều này:

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

và nếu bạn muốn hoàn thành nó:

repeatableJob?.cancel()

PS: viewModelScopechỉ khả dụng trong các mô hình xem, bạn có thể sử dụng các phạm vi Coroutine khác nhưwithContext(Dispatchers.IO)

Thêm thông tin: Tại đây


0

Đối với những người sử dụng Kotlin, câu trả lời của inazaruk sẽ không hoạt động, IDE sẽ yêu cầu biến được khởi tạo, vì vậy thay vì sử dụng postDelayedbên trong Runnable, chúng tôi sẽ sử dụng nó theo một phương pháp riêng.

  • Khởi tạo Runnablenhư thế này:

    private var myRunnable = Runnable {
        //Do some work
        //Magic happens here ↓
        runDelayedHandler(1000)   }
  • Khởi tạo runDelayedHandlerphương thức của bạn như thế này:

     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)
        }
    }
  • Như bạn có thể thấy, cách tiếp cận này sẽ giúp bạn có thể kiểm soát thời gian thực hiện nhiệm vụ, theo dõi keepRunningvà thay đổi nó trong suốt vòng đời của ứng dụng sẽ thực hiện công việc cho bạn.

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.