Как запустить поток 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.