Как запустить поток Runnable в Android через определенные промежутки времени?


351

Я разработал приложение для отображения текста через определенные промежутки времени на экране эмулятора Android. Я использую Handlerкласс. Вот фрагмент из моего кода:

handler = new Handler();
Runnable r = new Runnable() {
    public void run() {
        tv.append("Hello World");               
    }
};
handler.postDelayed(r, 1000);

Когда я запускаю это приложение, текст отображается только один раз. Почему?


109
Я никогда не могу вспомнить, как сделать готовый к запуску, поэтому я всегда посещаю ваш пост о том, как это сделать :))
— Adrian Sicaru

2
хаха то же самое, приятель, это правда
— NoXSaeeD

1
Лямбды - это путь, которым можно поехать большую часть времени;)
— Xerus

@AdrianSicaru: то же самое
— Совандара ЛЕНГ

Ответы:


535

Простое исправление к вашему примеру:

handler = new Handler();

final Runnable r = new Runnable() {
    public void run() {
        tv.append("Hello World");
        handler.postDelayed(this, 1000);
    }
};

handler.postDelayed(r, 1000);

Или мы можем использовать нормальный поток, например (с оригинальным Runner):

Thread thread = new Thread() {
    @Override
    public void run() {
        try {
            while(true) {
                sleep(1000);
                handler.post(this);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
};

thread.start();

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

Более подробная информация здесь http://developer.android.com/reference/android/os/Handler.html


Алекс, у меня есть одно небольшое сомнение. Теперь поток работает отлично и непрерывно отображает текст, если я хочу остановить это, значит, что я должен делать? Пожалуйста, помогите мне.
— Раджапандян

11
Вы можете определить логическую переменную _stop и установить ее в значение «true», когда хотите остановить. И замените 'while (true)' на 'while (! _ Stop)', или, если первый использованный пример, просто измените на 'if (! _ Stop) handler.postDelayed (this, 1000)'.
— alex2k8

А что если я хочу перезапустить сообщение?
— Соня

и если мне нужен runnable для установки 8 различных ImageViews, видимых один за другим, затем установить их все невидимыми одинаково и так далее (для создания «мигающей» анимации), как я могу это сделать?
— Droidman

1
Если вы хотите быть уверены, что Handler будет присоединен к основному потоку, вы должны инициализировать его следующим образом: handler = new Handler (Looper.getMainLooper ());
— Яир Кукелька

47
new Handler().postDelayed(new Runnable() {
    public void run() {
        // do something...              
    }
}, 100);

2
Если вы хотите быть уверены, что Handler будет присоединен к основному потоку, вы должны инициализировать его следующим образом: new Handler (Looper.getMainLooper ());
— Яир Кукелька

1
Разве это решение не эквивалентно оригинальному сообщению? Runnable запускается только один раз через 100 миллисекунд.
— Тронман

@YairKukielka ответ это решение! вам нужно прикрепить MainLooper. такой спаситель жизни!
— Houssem Chlegou

40

Я думаю, что может улучшить первое решение Alex2k8 для обновления исправить каждую секунду

1. Оригинальный код:

public void run() {
    tv.append("Hello World");
    handler.postDelayed(this, 1000);
}

2. Анализ

  • В приведенной выше стоимости предположим, что tv.append("Hello Word")стоимость составляет T миллисекунд, после отображения время задержки в 500 раз составляет 500 * T миллисекунд.
  • Это увеличит задержку при длительном запуске

3. Решение

Чтобы избежать этого, просто измените порядок postDelayed (), чтобы избежать задержки:

public void run() {
    handler.postDelayed(this, 1000);
    tv.append("Hello World");
}

6
-1 вы предполагаете, что задача, которую вы выполняете в run (), стоит постоянную сумму за каждый запуск, если бы это была операция с динамическими данными (как правило, это так), то вы бы в конечном итоге имели более одного run (), происходящего в один раз. Вот почему postDelayed обычно помещается в конце.
— Джей

1
@Jay К сожалению, вы не правы. Обработчик связан с одним потоком (и Looper, который является методом выполнения этого потока) + MessageQueue. Каждый раз, когда вы публикуете сообщение, вы ставите его в очередь, и в следующий раз, когда петлитель проверяет очередь, он выполняет метод run опубликованного вами Runnable. Так как все это происходит в одном потоке, вы не можете выполнять более 1 одновременно. Кроме того, выполнение postDelayed первым приблизит вас к 1000 мс на выполнение, поскольку внутренне оно использует текущее время + 1000 в качестве времени выполнения. Если вы ставите код перед публикацией, вы добавляете дополнительную задержку.
— Запл

1
@zapl спасибо за совет о обработчике, я предполагал, что он будет выполнять несколько исполняемых модулей и, следовательно, несколько потоков. Внутренне, хотя, например, условие ((currenttime - lastruntime)> 1000) будет работать нормально, когда длительности выполнения меньше или равны 1000 мс, однако, если это превышено, таймер будет происходить с нелинейными интервалами полностью зависит от времени выполнения метода запуска (отсюда моя точка зрения на непредсказуемые вычислительные затраты)
— Джей

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

27

Для повторения задания вы можете использовать

new Timer().scheduleAtFixedRate(task, runAfterADelayForFirstTime, repeaingTimeInterval);

называть это как

new Timer().scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {

            }
        },500,1000);

Приведенный выше код будет запущен первый раз через полсекунды (500) и повторяется через каждую секунду (1000)

куда

задача является методом, который будет выполнен

по истечении времени до первоначального исполнения

( интервал времени для повторения исполнения)

во-вторых

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

    new CountDownTimer(40000, 1000) { //40000 milli seconds is total time, 1000 milli seconds is time interval

     public void onTick(long millisUntilFinished) {
      }
      public void onFinish() {
     }
    }.start();

//Above codes run 40 times after each second

И вы также можете сделать это с помощью runnable. создать работоспособный метод, такой как

Runnable runnable = new Runnable()
    {
        @Override
        public void run()
        {

        }
    };

И назовите это обоими этими способами

new Handler().postDelayed(runnable, 500 );//where 500 is delayMillis  // to work on mainThread

ИЛИ

new Thread(runnable).start();//to work in Background 

Для варианта № 3, как я могу приостановить / возобновить, а также остановить навсегда?
— Si8

создайте экземпляр Handler как Handler handler = new Handler () и удалите его как handler.removeCallbacksAndMessages (null);
— Зар Э Ахмер

24

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

myTimer = new Timer();
myTimer.schedule(new TimerTask() {          
@Override
public void run() {
    // If you want to modify a view in your Activity
    MyActivity.this.runOnUiThread(new Runnable()
        public void run(){
            tv.append("Hello World");
        });
    }
}, 1000, 1000); // initial delay 1 second, interval 1 second

Использование Timerимеет несколько преимуществ:

  • Начальная задержка и интервал могут быть легко указаны в scheduleаргументах функции
  • Таймер можно остановить, просто позвонив myTimer.cancel()
  • Если вы хотите, чтобы выполнялся только один поток, не забудьте вызвать его myTimer.cancel() перед планированием нового (если myTimer не равен NULL)

7
Я не верю, что таймер более уместен, так как он не учитывает жизненный цикл андроида. Когда вы делаете паузу и возобновляете, нет никакой гарантии, что таймер будет работать правильно. Я бы поспорил, что лучший способ - запустить.
— Янпан

1
Означает ли это, что когда приложение находится в фоновом режиме, обработчик будет приостановлен? и когда он вновь обретает фокус, он будет продолжать (более или менее), как будто ничего не произошло?
— Эндрю Галлаш

17
Handler handler=new Handler();
Runnable r = new Runnable(){
    public void run() {
        tv.append("Hello World");                       
        handler.postDelayed(r, 1000);
    }
}; 
handler.post(r);

5
Это должно дать ошибку. Во второй строке вы вызываете переменную, rкоторая еще определена.
— Сеул

Если вы хотите быть уверены, что Handler будет присоединен к основному потоку, вы должны инициализировать его следующим образом: handler = new Handler (Looper.getMainLooper ());
— Яир Кукелька

просто повторяющийся ответ!
— Хамид

Как я могу приостановить / возобновить запуск с помощью щелчка по изображению?
— Si8

4

Если я правильно понимаю документацию метода Handler.post ():

Заставляет Runnable r быть добавленным в очередь сообщений. Runnable будет запущен в потоке, к которому прикреплен этот обработчик.

Таким образом, примеры, предоставленные @ alex2k8, хотя и работают правильно, не совпадают. В случае, где Handler.post()используется, новые потоки не создаются . Вы просто публикуете Runnableв теме Handlerсообщения, которые будут выполнены EDT . После этого EDT только исполняется Runnable.run(), больше ничего.

Помните: Runnable != Thread.


1
Правда что. Не создавайте новую тему каждый раз, никогда. Весь смысл Handler и других пулов выполнения состоит в том, чтобы один или два потока извлекали задачи из очереди, чтобы избежать создания потока и GC. Если у вас действительно дырявое приложение, дополнительный GC может помочь скрыть ситуации OutOfMemory, но лучшее решение в обоих случаях - не создавать больше работы, чем нужно.
— Ajax

Так что лучший способ сделать это с помощью обычной темы, основанной на ответе alex2k8?
— Compaq LE2202x

4

Котлин

private lateinit var runnable: Runnable
override fun onCreate(savedInstanceState: Bundle?) {
    val handler = Handler()
    runnable = Runnable {
        // do your work
        handler.postDelayed(runnable, 2000)
    }
    handler.postDelayed(runnable, 2000)
}

Ява

Runnable runnable;
Handler handler;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    handler = new Handler();
    runnable = new Runnable() {
        @Override
        public void run() {
            // do your work
            handler.postDelayed(this, 1000);
        }
    };
    handler.postDelayed(runnable, 1000);
}

1

Интересным примером является то, что вы можете постоянно видеть счетчик / секундомер, работающий в отдельном потоке. Также показывает GPS-местоположение. В то время как основной вид деятельности User Interface Thread уже существует.

Выдержка:

try {    
    cnt++; scnt++;
    now=System.currentTimeMillis();
    r=rand.nextInt(6); r++;    
    loc=lm.getLastKnownLocation(best);    

    if(loc!=null) { 
        lat=loc.getLatitude();
        lng=loc.getLongitude(); 
    }    

    Thread.sleep(100); 
    handler.sendMessage(handler.obtainMessage());
} catch (InterruptedException e) {   
    Toast.makeText(this, "Error="+e.toString(), Toast.LENGTH_LONG).show();
}

Чтобы посмотреть код см. Здесь:

Пример потока, показывающий местоположение GPS и текущее время, работающее вместе с потоком пользовательского интерфейса основной деятельности


1
Подсказка: если вы хотите сделать свой ответ полезным - узнайте, как форматировать ввод здесь. Это окно предварительного просмотра существует по причине.
— GhostCat

0

теперь в Kotlin вы можете запускать темы следующим образом:

class SimpleRunnable: Runnable {
    public override fun run() {
        println("${Thread.currentThread()} has run.")
    }
}
fun main(args: Array<String>) {
    val thread = SimpleThread()
    thread.start() // Will output: Thread[Thread-0,5,main] has run.
    val runnable = SimpleRunnable()
    val thread1 = Thread(runnable)
    thread1.start() // Will output: Thread[Thread-1,5,main] has run
}

0

Котлин с сопрограммами

В Kotlin, используя сопрограммы, вы можете сделать следующее:

CoroutineScope(Dispatchers.Main).launch { // Main, because UI is changed
    ticker(delayMillis = 1000, initialDelayMillis = 1000).consumeEach {
        tv.append("Hello World")
    }
}

Попробуйте это здесь !

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.