Powtórzyć zadanie z opóźnieniem?


216

Mam zmienną w kodzie, która mówi, że to „status”.

Chcę wyświetlić tekst w aplikacji w zależności od tej zmiennej. Należy tego dokonać z określonym opóźnieniem czasowym.

To jest jak,

  • Sprawdź wartość zmiennej statusu

  • Wyświetl trochę tekstu

  • Poczekaj 10 sekund

  • Sprawdź wartość zmiennej statusu

  • Wyświetl trochę tekstu

  • Poczekaj 15 sekund

i tak dalej. Opóźnienie może się różnić i jest ustawiane po wyświetleniu tekstu.

Próbowałem Thread.sleep(time delay)i nie udało się. Jest lepszy sposób, aby to zrobić?


Odpowiedzi:


448

Należy użyć Handler„s postDelayedfunkcję do tego celu. Spowoduje to uruchomienie kodu z określonym opóźnieniem w głównym wątku interfejsu użytkownika, dzięki czemu będzie można aktualizować kontrolki interfejsu użytkownika.

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
Dziękuję Inazaruk, udało się go uruchomić. Znaleziono 2 małe literówki (u góry jego „Handler”, nie „Handle”, a na dole „removeCallbacks”, nie usuwaj „removeecallback”. Ale w każdym przypadku kod był dokładnie tym, czego szukałem. Próbowałem zastanowić się, co mogę zrobić, aby odwdzięczyć się. Przynajmniej zdobyłeś mój szacunek. Pozdrawiam Aubrey Bourke.
aubreybourke

20
Fajny program, działa absolutnie dobrze. Ale startRepeatingTask () musiał zostać wywołany z wątku metody / interfejsu użytkownika onCreate (zajęło mi to trochę czasu, aby to zrozumieć!), Być może gdzieś wspomniano o tym punkcie. Pozdrawiam
gkris

1
twoja odpowiedź wciąż daje. To pomogło mi dzisiaj wyjść z dziury. dzięki.
Dean Blakely,

Czy istnieje sposób, aby powtarzalna funkcja Runnable była dostępna w metodzie getView () adaptera?
toobsco42,

1
Tutaj, kiedy importujemy klasy, co powinniśmy importować? android.os.Handler czy java.util.logging.Handler?
EJ Chathuranga,

34

Wszystkim zainteresowanym, oto klasa, którą utworzyłem za pomocą kodu inazaruk, który tworzy wszystko, co potrzebne (nazwałem go UIUpdater, ponieważ używam go do okresowej aktualizacji interfejsu, ale możesz go nazwać jak tylko chcesz):

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

Następnie możesz utworzyć obiekt UIUpdater w swojej klasie i użyć go w następujący sposób:

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

// Start updates
mUIUpdater.startUpdates();

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

Jeśli chcesz użyć tego jako aktualizatora aktywności, umieść wywołanie start w metodzie onResume (), a wywołanie stop wewnątrz onPause (), aby aktualizacje uruchamiały się i zatrzymywały zgodnie z widocznością aktywności.


1
Edytowane: UPDATE_INTERVAL = interval;powinno być wcześniej this(uiUpdater); w UIUpdater(Runnable uiUpdater, int interval)(ponieważ UPDATE_INTERVALużywana jest wartość i powinna być tą, która została przekazana jako parametr interval;). W miarę możliwości unikaj także szerokości ponad 80 znaków w kodzie (prawie zawsze;)
Mr_and_Mrs_D

5
Ta klasa ma wiele problemów. Po pierwsze, należy utworzyć instancję w głównym wątku, aby móc zaktualizować GUI. Mogłeś to rozwiązane poprzez przepuszczenie główną looper do konstruktora Handler: new Handler(Looper.getMainLooper()). Po drugie, nie sprawdza poprawności argumentów, więc połyka null Runnables i ujemne interwały. Wreszcie, nie uwzględnia czasu spędzonego na uiUpdater.run()linii, ani nie obsługuje możliwych wyjątków zgłaszanych przez tę metodę. Nie jest też wątkowo bezpieczny, powinieneś tworzyć starti stopsynchronizować metody.
Mister Smith,

2
Edytowane aż do części sprawdzania poprawności argumentów, ponieważ nie mam tutaj Eclipse do testowania kodu. Dziękujemy za opinię! Czy o to ci chodziło? Zsynchronizowane startUpdates i stopUpdates i umieść wywołanie Looper.getMainLooper () wewnątrz konstruktora Handler (mam nadzieję, że możesz to nazwać bezpośrednio z deklaracji terenowej)
ravemir

2
Rozumiem: error: call to this must be first statement in constructormoże jest łatwa naprawa.
msysmilu,

4
Upvoting za importowanie - wymaga czasu, aby dowiedzieć się, skąd pochodzi Handler podczas programowania w Javie
Roman Susi

23

Myślę, że nową popularnością jest użycie ScheduledThreadPoolExecutor . Tak jak:

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

Executors.newSingleThreadScheduledExecutor()może być tutaj inną opcją.
Gulshan

13

Timer działa dobrze. Tutaj używam Timera do wyszukiwania tekstu po 1.5s i aktualizacji interfejsu użytkownika. Mam nadzieję, że to pomaga.

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

gdzie podałeś czas przerwy?
Nathiel Barros,

1
Cześć Nathiel, właśnie zaktualizowałem swój post, mam nadzieję, że to pomoże! Czas interwału jest drugim parametrem Timer.schedule ().
Kai Wang,

7

Można to zrobić na 3 sposoby:

Użyj ScheduledThreadPoolExecutor

Trochę przesady, ponieważ nie potrzebujesz puli Wątków

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

Użyj zadania czasowego

Stary styl Androida

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

Użyj Handler i Runnable

Nowoczesny styl Androida

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

Nieszczelny program obsługi z aktywnością / kontekstem

Zadeklaruj wewnętrzną klasę Handler, która nie przecieka Pamięć w swojej klasie Activity / Fragment

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

Zadeklaruj wykonalność, która wykona powtarzalne zadanie w klasie Activity / Fragment

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

         //DO YOUR THINGS
        }
    };

Zainicjuj obiekt modułu obsługi w swoim działaniu / fragmencie (tutaj FlashActivity to moja klasa aktywności)

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

Aby powtórzyć zadanie po ustalonym przedziale czasu

taskHandler.postDelayed (repeatativeTaskRunnable, DELAY_MILLIS);

Aby zatrzymać powtarzanie zadania

taskHandler .removeCallbacks (repeatativeTaskRunnable);

AKTUALIZACJA: W 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

Timer to kolejny sposób na wykonanie pracy, ale pamiętaj, aby dodać, runOnUiThreadjeśli pracujesz z interfejsem użytkownika.

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

 }

}

a xml jest ...

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

Kolejny sposób na użycie CountDownTimer

new CountDownTimer(30000, 1000) {

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

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

Zaplanuj odliczanie do czasu w przyszłości, z regularnymi powiadomieniami o interwałach po drodze. Przykład pokazujący 30-sekundowe odliczanie w polu tekstowym:

Dla szczegółów


1
Handler jest lepszy niż Timer. Zobacz Timer vs Handler
Suragch,

4

Wypróbuj następujący przykład, działa !!!

Użyj [Handler] w metodzie onCreate (), która wykorzystuje metodę postDelayed (), która powoduje, że Runnable jest dodawany do kolejki komunikatów, uruchamiany po upływie określonego czasu, który wynosi 0 w danym przykładzie. 1

Sprawdź ten kod:

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

W oparciu o powyższy post dotyczący ScheduledThreadPoolExecutor , stworzyłem narzędzie, które odpowiada moim potrzebom (chciałem uruchomić metodę co 3 sekundy):

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

W moim przypadku musiałem wykonać proces, jeśli jeden z tych warunków był spełniony: jeśli poprzedni proces został zakończony lub jeśli minęło już 5 sekund. Zrobiłem więc następujące i działałem całkiem dobrze:

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


}

Jeśli proces1 zostanie odczytany, wykonuje proces2. Jeśli nie, zwiększa zmienne czasy i powoduje, że moduł obsługi zostanie wykonany po jednej sekundzie. Utrzymuje pętlę, dopóki proces1 nie zostanie odczytany, a czas nie będzie wynosić 5. Gdy czas wynosi 5, oznacza to, że minęło 5 sekund, aw każdej sekundzie wykonywana jest klauzula if procesu1.isRead ().


1

Używając kotlin i jego Coroutine, jest to dość łatwe, najpierw zadeklaruj pracę w swojej klasie (lepiej w twoim modelu widzenia) w następujący sposób:

private var repeatableJob: Job? = null

następnie, gdy chcesz go utworzyć i uruchomić, wykonaj następujące czynności:

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

a jeśli chcesz to zakończyć:

repeatableJob?.cancel()

PS: viewModelScopejest dostępny tylko w modelach widoku, możesz używać innych zasięgów Coroutine, takich jakwithContext(Dispatchers.IO)

Więcej informacji: tutaj


0

Dla osób korzystających z Kotlina odpowiedź inazaruka nie zadziała, IDE będzie wymagało zainicjowania zmiennej, więc zamiast używać postDelayedwewnątrz Runnable, użyjemy jej w osobnej metodzie.

  • Zainicjuj swój w Runnableten sposób:

    private var myRunnable = Runnable {
        //Do some work
        //Magic happens here ↓
        runDelayedHandler(1000)   }
  • Zainicjuj swoją runDelayedHandlermetodę w następujący sposób:

     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)
        }
    }
  • Jak widać, takie podejście pozwoli ci kontrolować czas życia zadania, śledzić go keepRunningi zmieniać w trakcie życia aplikacji, wykona zadanie za Ciebie.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.