Як запустити Runnable нитку в Android через визначені інтервали?


351

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

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

Коли я запускаю цю програму, текст відображається лише один раз. Чому?


109
Я ніколи не можу згадати, як зробити біг, тому я завжди відвідую ваш пост про те, як це зробити :))
Адріан Сікару

2
ха-ха тут товариш так правдиво
NoXSaeeD

1
лямбда - це шлях, який потрібно пройти зараз більшу частину часу;)
Xerus

@AdrianSicaru: те саме
Sovandara LENG

Відповіді:


534

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

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

Що робити, якщо я хочу перезапустити повідомлення?
Sonhja

і якщо мені потрібен запуск, щоб встановити 8 різних ImageView, видимих ​​один за одним, а потім встановити їх усі невидимі таким же чином і так далі (щоб створити "миготливу" анімацію), як це зробити?
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
Хіба це рішення не еквівалентно оригінальній публікації? Він запускається лише один раз через 100 мілісекунд.
tronman

Відповідь @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 ви припускаєте, що завдання, яке ви виконуєте під час виконання (), - це витрати, що є постійною сумою кожного запуску, якби це була операція над динамічними даними (що це, як правило,), тоді ви закінчили б більше одного запуску (), що виникає в один раз. Ось чому postDelayed зазвичай розміщується в кінці.
Jay

1
@Jay На жаль, ви помиляєтесь. Обробник асоціюється з однією ниткою (і петлею, що є методом запуску цієї теми) + повідомленнями черги. Кожен раз, коли ви публікуєте повідомлення, ви запускаєте його, і наступного разу, коли петлю перевіряє чергу, вона виконує метод запуску виконуваного вами запуску. Оскільки це все відбувається лише в одній темі, ви не можете виконати більше 1 одночасно. Також виконавши постDelayed спочатку, ви наблизитеся до 1000мс за виконання, оскільки внутрішньо він використовує поточний час + 1000 як час виконання. Якщо ви поставите код перед публікацією, ви додасте додаткову затримку.
запл

1
@zapl спасибі за підказку щодо обробника, я припустив, що це буде виконувати кілька запусків і, отже, кілька потоків. Внутрішнє, однак, така умова, як, якщо ((currenttime - lastruntime)> 1000) буде працювати нормально, коли тривалість запуску менше або дорівнює 1000 мс, однак, коли це буде перевищено, безумовно, таймер буде виникати з нелінійними інтервалами повністю залежить від часу виконання методу запуску (звідси мій погляд на непередбачувані обчислювальні витрати)
Jay,

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

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 = 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);
Zar E Ahmer

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 не є нульовим)

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 до черги повідомлень. Виконання буде виконуватися на нитці, до якої прикріплений цей обробник.

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

Пам'ятайте: Runnable != Thread.


1
Правда, що. Не створюйте нову нитку кожен раз і ніколи. Вся суть обробника та інших пулів виконання полягає в тому, щоб одна-дві нитки витягували завдання з черги, щоб уникнути створення потоку та GC. Якщо у вас дійсно витік додаток, додатковий GC може допомогти приховати ситуації OutOfMemory, але краще рішення в обох випадках - уникнути створення більшої кількості роботи, ніж вам потрібно.
Аякс

Тож кращий спосіб зробити це за допомогою звичайної нитки, заснованої на відповіді 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)
}

Java

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-місцезнаходження. Хоча основна діяльність Низький інтерфейс користувача вже є.

Витяг:

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

тепер у Котліні ви можете запускати теми таким чином:

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

Котлін з Корутинами

У Котліні, використовуючи супровідні програми, ви можете зробити наступне:

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.