Як викликати метод після затримки в Android


769

Я хочу мати змогу викликати наступний метод після вказаної затримки. У об'єктиві c було щось на кшталт:

[self performSelector:@selector(DoSomething) withObject:nil afterDelay:5];

Чи є еквівалент цього методу в андроїді з java? Наприклад, мені потрібно мати можливість викликати метод через 5 секунд.

public void DoSomething()
{
     //do something here
}

Відповіді:


1858

Котлін

Handler().postDelayed({
  //Do something after 100ms
}, 100)


Java

final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
  @Override
  public void run() {
    //Do something after 100ms
  }
}, 100);



109
Це рішення корисне лише для потоку інтерфейсу користувача. Інакше на звичайній темі вам потрібно реалізувати петлю, що не найкраща версія, я думаю
olivier_sdg

2
@olivier_sdg, чому потрібно реалізувати петлю?
djechlin

37
@djechlin Обробник повинен завжди бути пов'язаний із Looper, який фактично буде обробляти виконувану вами публікацію (). Нитка користувальницького інтерфейсу вже поставляється із Looper, тому ви можете просто створити новий обробник () на потоці інтерфейсу та розмістити () Runnables безпосередньо до нього. Ці Runnables виконується на потоці інтерфейсу користувача. Щоб Runnables виконував інший потік, потрібно створити нову нитку, потім Looper.prepare (), зробити новий Handler (), а потім Looper.loop (). Будь-які випускні файли, розміщені на цьому новому обробнику, виконуватимуться на цій новій темі. Якщо ви цього не зробите, публікація () видасть виняток.
Дороро

12
Якщо вам потрібно, ви також можете скасувати виконання, доки Runnable все ще знаходиться в черзі повідомлень, зателефонувавши removeCallbacks(Runnable r)на Handler.
Денніс

9
повиненimport android.os.handler
KaKa

322

Я не міг використати жодної з інших відповідей у ​​своєму випадку. Я замість цього використовував рідний Java-таймер.

new Timer().schedule(new TimerTask() {          
    @Override
    public void run() {
        // this code will be executed after 2 seconds       
    }
}, 2000);

43
це краще, ніж ті, що використовують Handler, оскільки він не має проблем із Looper, коли обробник не запускається на потоці інтерфейсу користувача.
Бен Х

32
Ви повинні зберігати посилання на свій таймер, щоб скасувати його, коли він більше не потрібен, оскільки згідно з документом Android: "Коли таймер більше не потрібен, користувачі повинні викликати cancel (), що звільняє потік таймера та інші ресурси. Таймери, явно не скасовані, можуть зберігати ресурси нескінченно ".
Pooks

14
Увага! Це не працює на потоці інтерфейсу користувача. Запуск цього потоку в інтерфейсі інтерфейсу спричинив фатальну помилку: android.view.ViewRootImpl $ CalledFromWrongThreadException: Тільки оригінальний потік, який створив ієрархію перегляду, може торкатися його поглядів.
vovahost

13
@vovahost - це лише тому, що ви оновлюєте компоненти інтерфейсу всередині блоку таймера
Тім

10
Зауважте, що java.util.Timer (і TimerTask) буде застарілим у JDK 9. TimerTask створює нові теми для завдань, що не дуже добре.
Варвара Калініна

183

Примітка. Ця відповідь була надана, коли питання не визначало Android як контекст. Відповідь, специфічну для потоку інтерфейсу Android, дивіться тут.


Схоже, API Mac OS дозволяє продовжувати поточний потік і планує виконання завдання асинхронно. У Java еквівалентну функцію забезпечує java.util.concurrentпакет. Я не впевнений, які обмеження може накласти Android.

private static final ScheduledExecutorService worker = 
  Executors.newSingleThreadScheduledExecutor();

void someMethod() {
  
  Runnable task = new Runnable() {
    public void run() {
      /* Do something… */
    }
  };
  worker.schedule(task, 5, TimeUnit.SECONDS);
  
}

3
Це ніколи для мене не називає Runnable
Supuhstar

14
Як бічна примітка: це також дозволяє пізніше скасувати завдання, що може бути корисним у деяких ситуаціях. Просто збережіть посилання на ScheduledFuture<?>повернутий worker.schedule()і називайте його cancel(boolean)метод.
Денніс

Я думаю, що ця відповідь застаріла. .schedule вже не здається методом Runnable ...? : /
буряк

5
@beetree - це метод на ScheduledExecutorService.
Еріксон

3
Це не працює, якщо задіяні об’єкти потоку ui, ви повинні викликати runOnUIThread (новий runnable () {run () ....}); або опублікувати прогонний об’єкт із обробкою зсередини run () {}
Jayant Arora

107

Щоб виконати щось у потоці інтерфейсу користувача через 5 секунд:

new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
    @Override
    public void run() {
        //Do something here
    }
}, 5000);

8
Підтвердьте, що це найкраще рішення для запобігання виклику looper.preprema та розміщення всієї речі в потоці інтерфейсу користувача.
Тобліг

Дякую за це, допомогли мені з питаннями Looper :)
Тіа,

1
Я був би обережний щодо створення обробника на головному петлі, тоді в цій темі не слід виконувати
давніх

40

ви можете використовувати обробник всередині UIThread:

runOnUiThread(new Runnable() {

    @Override
    public void run() {
         final Handler handler = new Handler();
         handler.postDelayed(new Runnable() {
           @Override
           public void run() {
               //add your code here
           }
         }, 1000);

    }
});

36

Дякую за всі чудові відповіді, я знайшов рішення, яке найкраще відповідає моїм потребам.

Handler myHandler = new DoSomething();
Message m = new Message();
m.obj = c;//passing a parameter here
myHandler.sendMessageDelayed(m, 1000);

class DoSomething extends Handler {
    @Override
    public void handleMessage(Message msg) {
      MyObject o = (MyObject) msg.obj;
      //do something here
    }
}

Чи добре, якщо я використовую такий підхід, щоб мати зворотний зв’язок із натисканням на клацання предмета .. view.setColor (some_color), а потім видалити цей колір у Handler через x секунд ...?
eRaisedToX

24

Kotlin& JavaБагато способів

1. Використання Handler

Handler().postDelayed({
    TODO("Do something")
    }, 2000)

2. Використання програми TimerTask

Timer().schedule(object : TimerTask() {
    override fun run() {
        TODO("Do something")
    }
}, 2000)

Або навіть коротше

Timer().schedule(timerTask {
    TODO("Do something")
}, 2000)

Або найкоротшим було б

Timer().schedule(2000) {
    TODO("Do something")
}

3. Використання Executors

Executors.newSingleThreadScheduledExecutor().schedule({
    TODO("Do something")
}, 2, TimeUnit.SECONDS)

На Java

1. Використання Handler

new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        //Do something
    }
}, 2000);

2. Використання Timer

new Timer().schedule(new TimerTask() {          
    @Override
    public void run() {
        // Do something
    }
}, 2000);

3. Використання ScheduledExecutorService

private static final ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor();

Runnable runnable = new Runnable() {
  public void run() {
      // Do something
  }
  };
worker.schedule(runnable, 2, TimeUnit.SECONDS);

1
@JanRabe Дякуємо за вашу пропозицію. Я оцінюю це. Однак питання є How to call a method after a delay in Android. Тому я зосередився на цьому. До суті. Інакше течі Java - це велика тема, яку слід зрозуміти окремо для розробників.
Хемрайдж

20

Дивіться цю демонстрацію:

import java.util.Timer;
import java.util.TimerTask;

class Test {
     public static void main( String [] args ) {
          int delay = 5000;// in ms 

          Timer timer = new Timer();

          timer.schedule( new TimerTask(){
             public void run() { 
                 System.out.println("Wait, what..:");
              }
           }, delay);

           System.out.println("Would it run?");
     }
}

20

Якщо вам доведеться використовувати обробник, але ви перебуваєте в іншому потоці, ви можете використовувати його runonuithreadдля запуску обробника в потоці інтерфейсу користувача. Це позбавить вас від винятків, кинутих з проханням подзвонитиLooper.Prepare()

runOnUiThread(new Runnable() {
    @Override
    public void run() {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                //Do something after 1 second
            }
        }, 1000);
    }
});

Виглядає досить безладно, але це один із способів.


4
Це працює, я не можу редагувати вашу публікацію через дурні правила SO із мінімальними 6 символами для редагування, але для 'нового обробника' відсутнє '()', це повинно бути 'новий Хендлер ()'
Джонатан Мюллер,

2
Замість того, щоб розміщувати все в потоці інтерфейсу користувача, ви можете зробити: new Handler (Looper.getMainLooper ())
Tobliug

17

Я вважаю за краще використовувати View.postDelayed()метод, простий код нижче:

mView.postDelayed(new Runnable() {
    @Override
    public void run() {
        // Do something after 1000 ms
    }
}, 1000);

1
Чи не заморожується сам елемент інтерфейсу, тому що це буде заплановано на оброблювачі подань?
JacksOnF1re

1
Ні, розміщене завдання буде виконано за 1 секунду, але під час цього другого потоку інтерфейсу виконуються інші корисні роботи
demaksee

14

Ось моє найкоротше рішення:

new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        //Do something after 100ms
    }
}, 100);

10
final Handler handler = new Handler(); 
Timer t = new Timer(); 
t.schedule(new TimerTask() { 
    public void run() { 
        handler.post(new Runnable() { 
            public void run() { 
                //DO SOME ACTIONS HERE , THIS ACTIONS WILL WILL EXECUTE AFTER 5 SECONDS...
            }
        }); 
    } 
}, 5000); 

10

Якщо ви використовуєте Android Studio 3.0 і вище, ви можете використовувати лямбда-вирази. Метод callMyMethod()викликається через 2 секунди:

new Handler().postDelayed(() -> callMyMethod(), 2000);

Якщо вам потрібно скасувати затримку запуску, скористайтеся цим:

Handler handler = new Handler();
handler.postDelayed(() -> callMyMethod(), 2000);

// When you need to cancel all your posted runnables just use:
handler.removeCallbacksAndMessages(null);

Як ми можемо скасувати це?
Дамія Фуентес

Я здивований, скільки тут із задоволенням перейдеться на Котлін, але повністю ігнорує Lambda Expressions, які є стандартною Java.
TomDK

6

Я пропоную Таймер , він дозволяє запланувати метод виклику на дуже конкретний інтервал. Це не заблокує ваш інтерфейс користувача та збереже ваш додаток неабияким під час виконання методу.

Інший варіант - це wait (); метод, це заблокує поточну нитку протягом визначеного проміжку часу. Це призведе до того, що ваш інтерфейс припинить відповідати, якщо це зробити в потоці користувальницького інтерфейсу.


2
Thread.sleep () краще, ніж Object.wait (). Очікування означає, що ви очікуєте отримання сповіщення та синхронізації навколо деякої активності. Сон означає, що ви просто хочете нічого не робити протягом певного визначеного часу. Таймер - це шлях, якщо ви хочете, щоб дія відбувалося асинхронно в якийсь пізній момент часу.
Тім Бендер

1
Це правда. Ось чому я перерахував це як інший варіант ;-)
Нейт

6

Для затримки простої лінійки Handle Post можна зробити наступне:

new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        // Do someting
    }
}, 3000);

Я сподіваюся, що це допомагає


5

Ви можете використовувати це для найпростішого рішення:

new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        //Write your code here
    }
}, 5000); //Timer is in ms here.

Інше, нижче може бути ще одне чисте корисне рішення:

new Handler().postDelayed(() -> 
{/*Do something here*/}, 
5000); //time in ms

5

Ви можете зробити його значно чистішим, використовуючи нещодавно введені лямбда-вирази:

new Handler().postDelayed(() -> {/*your code here*/}, time);

5

Отож, тут слід врахувати кілька речей, оскільки існує стільки способів зняти шкіру з цією кішкою. Хоча відповіді всі вже надані вибрані та обрані. Я думаю, що важливо, щоб це було переглянуто за допомогою правильних правил кодування, щоб уникнути того, щоб хтось пішов у неправильному напрямку саме через "обрану більшістю просту відповідь".

Тож спочатку давайте обговоримо просту відповідь із затримкою після публікації, яка є загальною відповіддю на цю тему.

Пару речей. Після затримки повідомлення можна зіткнутися з витоком пам’яті, мертвими предметами, життєвими циклами, що відійшли, та ін. Тому важливим є також поводження з ним належним чином. Це можна зробити двома способами.

Заради сучасного розвитку я поставляюсь у KOTLIN

Ось простий приклад використання потоку користувальницького інтерфейсу під час зворотного дзвінка та підтвердження того, що ваша активність залишається активною та доброю при натисканні зворотного дзвінка.

  Handler(Looper.getMainLooper()).postDelayed({
            if(activity != null && activity?.isFinishing == false){
                txtNewInfo.visibility = View.GONE
            }
        }, NEW_INFO_SHOW_TIMEOUT_MS)

Однак це все ще не ідеально, оскільки немає ніяких причин вдаряти про зворотний дзвінок, якщо активність припинилася. тож кращим способом було б зберігати посилання на нього та видаляти подібні зворотні дзвінки.

    private fun showFacebookStylePlus1NewsFeedOnPushReceived(){
        A35Log.v(TAG, "showFacebookStylePlus1NewsFeedOnPushReceived")
        if(activity != null && activity?.isFinishing == false){
            txtNewInfo.visibility = View.VISIBLE
            mHandler.postDelayed({
                if(activity != null && activity?.isFinishing == false){
                    txtNewInfo.visibility = View.GONE
                }
            }, NEW_INFO_SHOW_TIMEOUT_MS)
        }
    }

і, звичайно, обробляти очищення в режимі onPause, щоб воно не потрапило до зворотного дзвінка.

    override fun onPause() {
        super.onPause()
        mHandler.removeCallbacks(null)
    }

Тепер, коли ми поговорили через очевидне, поговоримо про більш чистий варіант із сучасними супроводами та котліном :). Якщо ви їх ще не використовуєте, ви справді пропали.

   fun doActionAfterDelay() 
        launch(UI) {
            delay(MS_TO_DELAY)           
            actionToTake()
        }
    }

або якщо ви хочете завжди робити запуск інтерфейсу користувача за цим методом, ви можете просто зробити:

  fun doActionAfterDelay() = launch(UI){ 
      delay(MS_TO_DELAY)           
      actionToTake()
  }

Звичайно, як і PostDelayed, ви повинні переконатися, що ви обробляєте скасування, щоб ви могли або перевірити активність після виклику затримки, або можете скасувати його в onPause, як і інший маршрут.

var mDelayedJob: Job? = null
fun doActionAfterDelay() 
   mDelayedJob = launch(UI) {
            try {
               delay(MS_TO_DELAY)           
               actionToTake()
            }catch(ex: JobCancellationException){
                showFancyToast("Delayed Job canceled", true, FancyToast.ERROR, "Delayed Job canceled: ${ex.message}")
            }
        }
   }
}

// обробка очищення

override fun onPause() {
   super.onPause()
   if(mDelayedJob != null && mDelayedJob!!.isActive) {
      A35Log.v(mClassTag, "canceling delayed job")
      mDelayedJob?.cancel() //this should throw CancelationException in coroutine, you can catch and handle appropriately
   }
}

Якщо ви ставите запуск (UI) у підпис методу, завдання може бути призначене у виклику рядка коду.

тому мораль історії полягає в тому, щоб бути в безпеці від затримок дій, переконайтесь, що ви видалили зворотні дзвінки або скасували роботу, і, звичайно, підтвердите, що у вас є правильний життєвий цикл, щоб торкнутися предметів у зворотному зворотному зворотному звороті. Coroutines також пропонує скасувати дії.

Також варто зауважити, що ти, як правило, маєш справу з різними винятками, які можуть бути пов'язані з супрограмами. Наприклад, скасування, виняток, тайм-аут, що б ви не вирішили використовувати. Ось більш просунутий приклад, якщо ви вирішили по-справжньому почати використовувати супроводи.

   mLoadJob = launch(UI){
            try {
                //Applies timeout
                withTimeout(4000) {
                    //Moves to background thread
                    withContext(DefaultDispatcher) {
                        mDeviceModelList.addArrayList(SSDBHelper.getAllDevices())
                    }
                }

                //Continues after async with context above
                showFancyToast("Loading complete", true, FancyToast.SUCCESS)
            }catch(ex: JobCancellationException){
                showFancyToast("Save canceled", true, FancyToast.ERROR, "Save canceled: ${ex.message}")
            }catch (ex: TimeoutCancellationException) {
                showFancyToast("Timed out saving, please try again or press back", true, FancyToast.ERROR, "Timed out saving to database: ${ex.message}")
            }catch(ex: Exception){
                showFancyToast("Error saving to database, please try again or press back", true, FancyToast.ERROR, "Error saving to database: ${ex.message}")
            }
        }

1
Немає жодної проблеми Раджив, я б зробив це на крок далі і зазначив, що за допомогою даних "Дані в реальному житті" супроводи можуть знати життєвий цикл і самостійно скасовувати, щоб уникнути викликів очищення, але не хочу кидати занадто багато кривих навчання в одну відповідь;)
Сам

3

Я створив більш простий метод, щоб викликати це.

public static void CallWithDelay(long miliseconds, final Activity activity, final String methodName)
    {
        new Handler().postDelayed(new Runnable() {

            @Override
            public void run() {
                try {
                    Method method =  activity.getClass().getMethod(methodName);
                    method.invoke(activity);
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }, miliseconds);
    }

Щоб скористатися ним, просто зателефонуйте: .CallWithDelay(5000, this, "DoSomething");


3
Рефлексія на таке основне завдання?
Макс Ч

Так як питання викликати метод схожий на iOS performSelector. це найкращий спосіб зробити.
HelmiB

3

Нижче одна робота, коли ви отримаєте,

java.lang.RuntimeException: Неможливо створити обробник всередині потоку, який не викликав Looper.prepare ()

final Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
  @Override
  public void run() {
    //Do something after 100ms
  }
}, 100);

3

Використовуючи Котлін, ми можемо досягти, виконавши наступне

Handler().postDelayed({
    // do something after 1000ms 
}, 1000)


2

Якщо ви використовуєте RxAndroid, тоді обробка потоків та помилок стає набагато простішою. Наступний код виконується після затримки

   Observable.timer(delay, TimeUnit.SECONDS)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(aLong -> {
           // Execute code here
        }, Throwable::printStackTrace);

1

всі, здається, забули очистити обробник, перш ніж розміщувати на ньому новий прогон або повідомлення. В іншому випадку вони потенційно можуть накопичуватися і викликати погану поведінку.

handler.removeMessages(int what);
// Remove any pending posts of messages with code 'what' that are in the message queue.

handler.removeCallbacks(Runnable r)
// Remove any pending posts of Runnable r that are in the message queue.

1

Ось ще один хитрий спосіб: він не кине винятку, коли виконувані змінити елементи інтерфейсу користувача.

public class SimpleDelayAnimation extends Animation implements Animation.AnimationListener {

    Runnable callBack;

    public SimpleDelayAnimation(Runnable runnable, int delayTimeMilli) {
        setDuration(delayTimeMilli);
        callBack = runnable;
        setAnimationListener(this);
    }

    @Override
    public void onAnimationStart(Animation animation) {

    }

    @Override
    public void onAnimationEnd(Animation animation) {
        callBack.run();
    }

    @Override
    public void onAnimationRepeat(Animation animation) {

    }
}

Ви можете назвати анімацію так:

view.startAnimation(new SimpleDelayAnimation(delayRunnable, 500));

Анімація може додаватися до будь-якого перегляду.



1

Мені подобаються речі чистіші: ось моя реалізація, вбудований код, який потрібно використовувати у вашому методі

new Handler().postDelayed(new Runnable() {
  @Override
  public void run() {
    //Do something after 100ms
  }
}, 100);

0

Підходяще рішення для андроїд:

private static long SLEEP_TIME = 2 // for 2 second
.
.
MyLauncher launcher = new MyLauncher();
            launcher.start();
.
.
private class MyLauncher extends Thread {
        @Override
        /**
         * Sleep for 2 seconds as you can also change SLEEP_TIME 2 to any. 
         */
        public void run() {
            try {
                // Sleeping
                Thread.sleep(SLEEP_TIME * 1000);
            } catch (Exception e) {
                Log.e(TAG, e.getMessage());
            }
            //do something you want to do
           //And your code will be executed after 2 second
        }
    }

0

Подібне рішення, але набагато чистіше у використанні

Запишіть цю функцію поза класом

fun delay(duration: Long, `do`: () -> Unit) {

    Handler().postDelayed(`do`, duration)

}

Використання:

delay(5000) {
    //Do your work here
}

Що означає "робити"?
Skizo-ozᴉʞS

просто ім'я, будьте що там. doце вбудований метод, тому ми повинні використовувати `, щоб використовувати його як ім'я змінної
Манохар Редді

Дякую, але чому використовується ця назва змінної? Я маю на увазі, яка функція цього.
Skizo-ozᴉʞS

1
Я подумав так doпісля затримки на 3 сек
Манохар Редді

0

В Android ми можемо записати нижче котлін код для затримки виконання будь-якої функції

class MainActivity : AppCompatActivity() {

private lateinit var handler: Handler

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    handler= Handler()
    handler.postDelayed({
        doSomething()
    },2000)
}

private fun doSomething() {
    Toast.makeText(this,"Hi! I am Toast Message",Toast.LENGTH_SHORT).show()
}
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.