开发者

Repeat a task with a time delay?

开发者 https://www.devze.com 2023-03-11 15:39 出处:网络
I have a variable in my code say it is \"status\". I want to display some text in the application depending on this variable value. This has to be done with a specific time delay.

I have a variable in my code say it is "status".

I want to display some text in the application depending on this variable value. This has to be done with a specific time delay.

It's like,

  • Check status variable value

  • Display some t开发者_如何学运维ext

  • Wait for 10 seconds

  • Check status variable value

  • Display some text

  • Wait for 15 seconds

and so on. The time delay may vary and it is set once the text is displayed.

I have tried Thread.sleep(time delay) and it failed. Any better way to get this done?


You should use Handler's postDelayed function for this purpose. It will run your code with specified delay on the main UI thread, so you will be able to update UI controls.

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


To anyone interested, here's a class I created using inazaruk's code that creates everything needed (I called it UIUpdater because I use it to periodically update the UI, but you can call it anything you like):

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

You can then create a UIUpdater object inside your class and use it like so:

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

// Start updates
mUIUpdater.startUpdates();

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

If you want to use this as an activity updater, put the start call inside the onResume() method and the stop call inside the onPause(), so the updates start and stop according to the activity visibility.


I think the new hotness is to use a ScheduledThreadPoolExecutor. Like so:

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


There are 3 ways to do it:

Use ScheduledThreadPoolExecutor

A bit of overkill since you don't need a pool of 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();
    }

Use Timer Task

Old Android Style

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

Use Handler and Runnable

Modern Android Style

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

Non-Leaky Handler with Activity / Context

Declare an inner Handler class which does not leak Memory in your Activity/Fragment class

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

Declare a runnable which will perform your repetitive task in your Activity/Fragment class

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

         //DO YOUR THINGS
        }
    };

Initialize Handler object in your Activity/Fragment (here FlashActivity is my activity class)

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

To repeat a task after fix time interval

taskHandler.postDelayed(repeatativeTaskRunnable , DELAY_MILLIS);

To stop the repetition of task

taskHandler .removeCallbacks(repeatativeTaskRunnable );

UPDATE: In 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);
    }


Timer works fine. Here, I use Timer to search text after 1.5s and update UI. Hope that helps.

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


Using kotlin and its Coroutine its quite easy, first declare a job in your class (better in your viewModel) like this:

private var repeatableJob: Job? = null

then when you want to create and start it do this:

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

and if you want to finish it:

repeatableJob?.cancel()

PS: viewModelScope is only available in view models, you can use other Coroutine scopes such as withContext(Dispatchers.IO)

More information: Here


Timer is another way to do your work but be quiet sure to add runOnUiThread if you are working with 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);
    }});
  }

 }

}

and xml is...

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

Another Way to use CountDownTimer

new CountDownTimer(30000, 1000) {

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

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

Schedule a countdown until a time in the future, with regular notifications on intervals along the way. Example of showing a 30 second countdown in a text field:

For Details


Try following example it works !!!

Use [Handler] in onCreate() method which makes use of postDelayed() method that Causes the Runnable to be added to the message queue, to be run after the specified amount of time elapses that is 0 in given example. 1

Refer this code :

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


You can use a Handler to post runnable code. This technique is outlined very nicely here: https://guides.codepath.com/android/Repeating-Periodic-Tasks


Based on the above post concerning the ScheduledThreadPoolExecutor, I came up with a utility that suited my needs (wanted to fire a method every 3 seconds):

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


In my case, I had to execute a process if one of these conditions were true: if a previous process was completed or if 5 seconds had already passed. So, I did the following and worked pretty well:

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


}

If process1 is read, it executes process2. If not, it increments the variable times, and make the Handler be executed after one second. It maintains a loop until process1 is read or times is 5. When times is 5, it means that 5 seconds passed and in each second, the if clause of process1.isRead() is executed.


For people using Kotlin, inazaruk's answer will not work, the IDE will require the variable to be initialized, so instead of using the postDelayed inside the Runnable, we'll use it in an separate method.

  • Initialize your Runnable like this :

    private var myRunnable = Runnable {
        //Do some work
        //Magic happens here ↓
        runDelayedHandler(1000)   }
    
  • Initialize your runDelayedHandler method like this :

     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)
        }
    }
    
  • As you can see, this approach will make you able to control the lifetime of the task, keeping track of keepRunning and changing it during the lifetime of the application will do the job for you.

0

精彩评论

暂无评论...
验证码 换一张
取 消