Как приостановить / спать поток или процесс в Android?

294

Я хочу сделать паузу между двумя строками кода, позвольте мне объяснить немного:

-> пользователь нажимает кнопку (фактически карту), и я показываю ее, изменяя фон этой кнопки:

thisbutton.setBackgroundResource(R.drawable.icon);

-> после, скажем, 1 секунды, мне нужно вернуться к предыдущему состоянию кнопки, изменив ее фон:

thisbutton.setBackgroundResource(R.drawable.defaultcard);

-> Я пытался приостановить поток между этими двумя строками кода с помощью:

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

Однако это не работает. Может быть, мне нужно приостановить процесс, а не поток?

Я также пытался (но это не работает):

new Reminder(5);

С этим:

public class Reminder {

Timer timer;

        public Reminder(int seconds) {
            timer = new Timer();
            timer.schedule(new RemindTask(), seconds*1000);
        }

        class RemindTask extends TimerTask {
            public void run() {
                System.out.format("Time's up!%n");
                timer.cancel(); //Terminate the timer thread
            }
        }  
    }

Как я могу приостановить / спать поток или процесс?

Хьюберт
источник
4
О, просто используйте классический блок паузы потока: while (true) {}
KristoferA
6
@ KristoferA-Huagati.com Я не уверен, что вы саркастичны или действительно есть какая-то магия Dalvik / Android, так что это приемлемо для Android. Можете ли вы уточнить? Извините за сомнение, но я спрашиваю, потому что, как (!conditionCheck()) {}правило, не рекомендуется.
Унылая переменная
«Однако это не работает». «Я также пытался (но это не работает)» Это классический пример того, что есть проблема без указания симптомов. Каким образом эти попытки не соответствовали вашим требованиям? Неужели нить не остановилась? Вы получили сообщение об ошибке?
LarsH

Ответы:

454

Одним из решений этой проблемы является использование метода Handler.postDelayed () . Некоторые учебные материалы Google предлагают такое же решение.

@Override
public void onClick(View v) {
    my_button.setBackgroundResource(R.drawable.icon);

    Handler handler = new Handler(); 
    handler.postDelayed(new Runnable() {
         @Override 
         public void run() { 
              my_button.setBackgroundResource(R.drawable.defaultcard); 
         } 
    }, 2000); 
}

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

Более сложное решение , которое позволяет избежать утечек памяти подкласса Handlerи Runnableсо статическими внутренними классами внутри деятельности , так как статические внутренних классы не занимающим неявную ссылки на их внешний класс:

private static class MyHandler extends Handler {}
private final MyHandler mHandler = new MyHandler();

public static class MyRunnable implements Runnable {
    private final WeakReference<Activity> mActivity;

    public MyRunnable(Activity activity) {
        mActivity = new WeakReference<>(activity);
    }

    @Override
    public void run() {
        Activity activity = mActivity.get();
        if (activity != null) {
            Button btn = (Button) activity.findViewById(R.id.button);
            btn.setBackgroundResource(R.drawable.defaultcard);
        }
    }
}

private MyRunnable mRunnable = new MyRunnable(this);

public void onClick(View view) {
    my_button.setBackgroundResource(R.drawable.icon);

    // Execute the Runnable in 2 seconds
    mHandler.postDelayed(mRunnable, 2000);
}

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

tronman
источник
3
Это работает, но это довольно неудобный способ ввести задержки во всем коде, верно?
Этеш Чоудхури
4
Я не уверен, что вы подразумеваете под "неудобно". Метод PostDelayed обработчика разработан для того, чтобы сообщить Android, что вы хотите, чтобы по прошествии определенного промежутка времени выполнялся фрагмент кода.
Тронман
27
через 2 года и этот код просто помог мне! спасибо @tronman !! :)
Мелвин Лай
2
Вы можете просто скопировать его в другую (окончательную) переменную, как в этом случае, final Button mynewbutton = mybutton;и использовать его mynewbuttonв обработчике и Runnable.
Джунейт
2
@MelvinLai через 5 лет, и этот код просто помог мне! :)
gabrieloliveira
191

Вы можете попробовать это короткое

SystemClock.sleep(7000);

ВНИМАНИЕ : Никогда, никогда не делайте этого в потоке пользовательского интерфейса.

Используйте это, чтобы спать, например. фоновый поток.


Полное решение для вашей проблемы будет: Это доступно API 1

findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View button) {
                button.setBackgroundResource(R.drawable.avatar_dead);
                final long changeTime = 1000L;
                button.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        button.setBackgroundResource(R.drawable.avatar_small);
                    }
                }, changeTime);
            }
        });

Без создания tmp Handler. Также это решение лучше, чем @tronman, потому что мы не сохраняем представление Handler. Также у нас нет проблем с обработчиком, созданным в плохом потоке;)

Документация

public static void sleep (длинная мс)

Добавлено на уровне API 1

Ожидает заданное количество миллисекунд (uptimeMillis) перед возвратом. Похоже на sleep (long), но не выдает InterruptedException ; События interrupt () откладываются до следующей прерываемой операции. Не возвращается, пока не истечет хотя бы указанное количество миллисекунд.

параметры

мс спать перед возвратом, в миллисекундах.

Код для postDelayed из класса View:

/**
 * <p>Causes the Runnable to be added to the message queue, to be run
 * after the specified amount of time elapses.
 * The runnable will be run on the user interface thread.</p>
 *
 * @param action The Runnable that will be executed.
 * @param delayMillis The delay (in milliseconds) until the Runnable
 *        will be executed.
 *
 * @return true if the Runnable was successfully placed in to the
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.  Note that a
 *         result of true does not mean the Runnable will be processed --
 *         if the looper is quit before the delivery time of the message
 *         occurs then the message will be dropped.
 *
 * @see #post
 * @see #removeCallbacks
 */
public boolean postDelayed(Runnable action, long delayMillis) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.postDelayed(action, delayMillis);
    }
    // Assume that post will succeed later
    ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
    return true;
}
Давид Дрозд
источник
22
И пусть пользовательский интерфейс Android зависает? Не делай этого.
shkschneider
3
Ctrl + Shift + O (автоматический импорт Eclipse)
Давид Дрозд
13
Геллдур, ты упускаешь смысл комментария @ RichieHH. По сравнению с ответом, который был принят за 3 года до публикации , то, что вы предлагаете, не помогает ОП решить его проблему. Причина: код, показанный OP и показанный в принятом ответе, выполняется внутри обработчика пользовательского интерфейса . Говорить не OMG of course do this in background threadимеет значения, если только вы не показываете, КАК поместить его в фоновый поток. В это время вы обнаружите, что у вас есть ответ более сложный, чем уже принятый ответ. Я упоминал, что лучшее решение было принято три года назад? : P
ToolmakerSteve
2
... забыл упомянуть, что то, что делает это предложение особенно нецелевым для вопроса, заключается в том, что OP явно хочет выполнить действие пользовательского интерфейса после задержки. Итак, у вас было бы решение, в котором вы создали фоновый поток (уже более тяжелый, чем требуется для решения проблемы), спали, а затем вы должны (каким-то образом) вернуться к потоку пользовательского интерфейса. Handler + postRunnableвыполняет все это, в один шаг. Без системных накладных расходов на создание второго потока.
ToolmakerSteve
2
@ToolmakerSteve счастливы сейчас: D? Главный вопрос: «Как приостановить / перевести нить или процесс в Android?» поэтому мой ответ был прост :). Если кто-то гуглит за "как спать нить" этот вопрос покажет. Он / она, вероятно, ищет мой ответ: P. Но хорошо, я добавил полный ответ;)
Давид Дрозд
28

Я использую это:

Thread closeActivity = new Thread(new Runnable() {
  @Override
  public void run() {
    try {
      Thread.sleep(3000);
      // Do some stuff
    } catch (Exception e) {
      e.getLocalizedMessage();
    }
  }
});
Byt3
источник
4
Что e.getLocalizedMessage()должен делать?
Филипп Рейхарт
я использую e.getLocalizedMessage (), когда мне нужно простое, общее и быстрое сообщение об исключении
Byt3
1
Но держу пари, что вы не делаете этого в ситуации, о которой спрашивает OP, в методе щелчка пользовательского интерфейса, который будет производить дальнейшие обновления пользовательского интерфейса. Принятое Handler/postDelayedрешение имеет два преимущества: (1) избегает системных издержек 2-го потока, (1) выполняется в потоке пользовательского интерфейса, поэтому может вносить изменения в пользовательский интерфейс, не вызывая исключения.
ToolmakerSteve
1
Не имеет прямого отношения к основной цели вопроса, но вы не должны ловить исключение. Скорее лови InterruptedException. Поймав исключение, вы поймаете все, что может пойти не так, скрывая проблемы, которые вы бы иначе поймали.
Эрв Чт
16

Вы, вероятно, не хотите делать это таким образом. Помещая явное sleep()в обработчик событий, который нажимают кнопки, вы фактически блокируете весь интерфейс на секунду. Одна альтернатива состоит в том, чтобы использовать какой-то один таймер . Создайте TimerTask, чтобы изменить цвет фона обратно на цвет по умолчанию, и запланируйте его на таймере.

Другая возможность - использовать обработчик . Есть учебник о ком-то, кто переключился с использования таймера на использование обработчика.

Кстати, вы не можете приостановить процесс. Процесс Java (или Android) имеет как минимум 1 поток, и вы можете только спящие потоки.

Даниэль Янковский
источник
14

Я использую CountDownTime

new CountDownTimer(5000, 1000) {

    @Override
    public void onTick(long millisUntilFinished) {
        // do something after 1s
    }

    @Override
    public void onFinish() {
        // do something end times 5s
    }

}.start(); 
vudandroid
источник
это полезно
UzaySan
9

Это то, что я сделал в конце дня - теперь работает нормально:

@Override
    public void onClick(View v) {
        my_button.setBackgroundResource(R.drawable.icon);
        // SLEEP 2 SECONDS HERE ...
        final Handler handler = new Handler(); 
        Timer t = new Timer(); 
        t.schedule(new TimerTask() { 
                public void run() { 
                        handler.post(new Runnable() { 
                                public void run() { 
                                 my_button.setBackgroundResource(R.drawable.defaultcard); 
                                } 
                        }); 
                } 
        }, 2000); 
    }
Хьюберт
источник
2
Почему не postDelayed? Таймер не требуется.
RichieHH
8

В дополнение к ответам г-на Янковского, вы также можете использовать postDelayed(). Это доступно на любом View(например, на вашей карте) и занимает a Runnableи период задержки. Выполняется Runnableпосле этой задержки.

CommonsWare
источник
5

Это мой пример

Создайте Java Utils

    import android.app.ProgressDialog;
    import android.content.Context;
    import android.content.Intent;

    public class Utils {

        public static void showDummyWaitingDialog(final Context context, final Intent startingIntent) {
            // ...
            final ProgressDialog progressDialog = ProgressDialog.show(context, "Please wait...", "Loading data ...", true);

            new Thread() {
                public void run() {
                    try{
                        // Do some work here
                        sleep(5000);
                    } catch (Exception e) {
                    }
                    // start next intent
                    new Thread() {
                        public void run() {
                        // Dismiss the Dialog 
                        progressDialog.dismiss();
                        // start selected activity
                        if ( startingIntent != null) context.startActivity(startingIntent);
                        }
                    }.start();
                }
            }.start();  

        }

    }    
Стефано
источник
3
Еще один ответ был добавлен спустя годы после того, как вопрос уже был хорошо решен, что не имеет отношения к вопросу, потому что оно не касается заявленного желания иметь возможность выполнять работу с пользовательским интерфейсом. Вы не можете работать с пользовательским интерфейсом здесь, потому что вы работаете в новом потоке. Не в потоке пользовательского интерфейса.
ToolmakerSteve
1
Что касается UX, показывать пользователю блокировку диалогового окна для загрузки данных ... нехорошо.
2Dee
4

Или вы можете использовать:

android.os.SystemClock.sleep(checkEvery)

который имеет то преимущество, что не требует упаковки try ... catch.

аааа
источник
0

Если вы используете Kotlin и сопрограммы , вы можете просто сделать

GlobalScope.launch {
   delay(3000) // In ms
   //Code after sleep
}

И если вам нужно обновить интерфейс

GlobalScope.launch {
  delay(3000)
  GlobalScope.launch(Dispatchers.Main) {
    //Action on UI thread
  }
}
Гийом
источник
0

Я знаю, что это старая ветка, но в документации по Android я нашел решение, которое мне очень помогло ...

new CountDownTimer(30000, 1000) {

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

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

https://developer.android.com/reference/android/os/CountDownTimer.html

Надеюсь, это поможет кому-то ...

Джон Бирн
источник
0
  class MyActivity{
    private final Handler handler = new Handler();
    private Runnable yourRunnable;
    protected void onCreate(@Nullable Bundle savedInstanceState) {
       // ....
       this.yourRunnable = new Runnable() {
               @Override
               public void run() {
                   //code
               }
            };

        this.handler.postDelayed(this.yourRunnable, 2000);
       }


     @Override
  protected void onDestroy() {
      // to avoid memory leaks
      this.handler.removeCallbacks(this.yourRunnable);
      }
    }

И чтобы быть вдвойне уверенным, вы можете комбинировать его с методом «статического класса», как описано в ответе tronman.

начинающий
источник