Как вызвать метод после задержки в Android

Я хочу иметь возможность вызывать следующий метод после указанной задержки. В цели c было что-то вроде:

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

Есть ли эквивалент этого метода в Android с Java? Например, мне нужно иметь возможность вызывать метод через 5 секунд.

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

36 ответов

Решение

Лучшая версия:

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

Я не мог использовать другие ответы в моем случае. Я использовал нативный таймер Java вместо этого.

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

Примечание. Этот ответ был дан, когда в вопросе не был указан 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);
  ⋮
}

Для выполнения чего-либо в потоке пользовательского интерфейса через 5 секунд:

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

Котлин

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)

Джава

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

Вы можете использовать Handler внутри 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);

    }
});

Спасибо за все отличные ответы, я нашел решение, которое наилучшим образом соответствует моим потребностям.

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

Больше безопасности - с Kotlin

В большинстве ответов используется обработчик, но я предлагаю другое решение для задержки активности, фрагментации, модели просмотра с помощью Android Lifecycle ext. Этот способ автоматически отменит, когда жизненный цикл начинает разрушаться.

В действии или фрагменте:

      lifecycleScope.launch { 
  delay(DELAY_MS)
  doSomething()
}

В ViewModel:

      viewModelScope.lanch {
  delay(DELAY_MS)
  doSomething()
}

В режиме приостановки: (Kotlin Coroutine)

      suspend fun doSomethingAfter(){
    delay(DELAY_MS)
    doSomething()
}

Если вы получаете ошибку с жизненным циклом, который не найден - импортируйте в файл gradle:

      implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"

Смотрите это демо:

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

Если вам нужно использовать обработчик, но вы находитесь в другом потоке, вы можете использовать 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);
    }
});

Выглядит довольно грязно, но это один из способов.

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

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

Вот мое самое короткое решение:

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

Если вы используете 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);
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); 

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

Другой вариант - это wait (); метод, это заблокирует текущий поток на указанный промежуток времени. Это приведет к тому, что ваш пользовательский интерфейс перестанет отвечать, если вы сделаете это в потоке пользовательского интерфейса.

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

Итак, сначала давайте обсудим простой ответ с задержкой после публикации, который является ответом, выбранным победителем в целом в этой теме.

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

Ради современного развития я поставлю в КОТЛИН

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

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

Конечно, так же, как и Post Delayed, вы должны убедиться, что вы обрабатываете отмену, чтобы вы могли либо выполнять проверки активности после задержки вызова, либо вы можете отменить ее в 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}")
            }
        }

Для простой строки Handle Post delay вы можете сделать следующее:

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

надеюсь, это поможет

Вы можете сделать это намного чище, используя недавно введенные лямбда-выражения:

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

Вы можете использовать это для простейшего решения:

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

Если вы используете RxAndroid, то обработка потоков и ошибок станет намного проще. Следующий код выполняется с задержкой

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

Используя Kotlin, мы можем сделать следующее

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

Ниже один работает, когда вы получаете,

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

Я создал более простой метод для вызова этого.

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

  • Котлин
  • runOnUiThread из фрагмента
  • Таймер

пример:

      Timer().schedule(500) {
    activity?.runOnUiThread {
        // code                                    
    }
}

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

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

Это очень легко, используя CountDownTimer, Для получения дополнительной информации https://developer.android.com/reference/android/os/CountDownTimer.html

import android.os.CountDownTimer;

// calls onTick every second, finishes after 3 seconds
new CountDownTimer(3000, 1000) { 

   public void onTick(long millisUntilFinished) {
      Log.d("log", millisUntilFinished / 1000);
   }

   public void onFinish() {
      // called after count down is finished
   } 
}.start();

Activity/Fragment — в этом случае , если вы делаете что-то с изменениями пользовательского интерфейса , вам следует использовать lifecycleScope viewLifecycleOwner , потому что в случае уничтожения Activity/Fragment эта операция будет безопасной и не будет выполняться, в противном случае без viewLifecycleOwner приложение произойдет сбой при изменении пользовательского интерфейса, который ни к чему не привязан.

      private val DELAY_MS = 5000

viewLifecycleOwner.lifecycleScope.launch {
   delay(DELAY_MS)
   doSomething()
}

Кажется, что все забывают очистить обработчик, прежде чем публиковать на нем новый исполняемый файл или сообщение. В противном случае они могут потенциально накапливаться и вызывать плохое поведение.

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.

Вот ответ в Kotlin вы ленивые, ленивые люди:

Handler().postDelayed({
//doSomethingHere()
}, 1000)

Вот еще один хитрый способ: он не будет генерировать исключение при изменении элементов интерфейса.

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

Анимацию можно прикрепить к любому виду.

Другие вопросы по тегам