Bagaimana cara menjalankan utas Runnable di Android pada interval yang ditentukan?

351

Saya mengembangkan aplikasi untuk menampilkan beberapa teks pada interval yang ditentukan di layar Android emulator. Saya menggunakan Handlerkelas. Ini cuplikan dari kode saya:

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

Ketika saya menjalankan aplikasi ini teks hanya ditampilkan sekali. Mengapa?

Rajapanda
sumber
109
Saya tidak pernah ingat bagaimana cara menjalankan runnable, jadi saya selalu mengunjungi posting Anda tentang bagaimana melakukannya :))
Adrian Sicaru
2
haha sama di sini sobat begitu benar
NoXSaeeD
1
lambdas adalah cara untuk pergi sekarang sebagian besar waktu;)
Xerus
@AdrianSicaru: sama sama
Sovandara LENG

Jawaban:

534

Perbaikan sederhana untuk contoh Anda adalah:

handler = new Handler();

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

handler.postDelayed(r, 1000);

Atau kita dapat menggunakan utas normal misalnya (dengan Runner asli):

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

thread.start();

Anda dapat mempertimbangkan objek runnable Anda hanya sebagai perintah yang dapat dikirim ke antrian pesan untuk dieksekusi, dan handler hanya sebagai objek pembantu yang digunakan untuk mengirim perintah itu.

Detail lebih lanjut ada di sini http://developer.android.com/reference/android/os/Handler.html

alex2k8
sumber
Alex, saya punya satu keraguan kecil. Sekarang utasnya berjalan dengan sempurna dan menampilkan teks secara terus menerus, jika saya ingin menghentikan ini berarti apa yang harus saya lakukan? Tolong bantu saya.
Rajapandian
11
Anda dapat mendefinisikan variabel boolean _stop, dan mengaturnya 'benar' ketika Anda ingin berhenti. Dan ubah 'while (true)' menjadi 'while (! _ Stop)', atau jika sampel pertama digunakan, ubah saja ke 'if (! _ Stop) handler.postDelayed (this, 1000)'.
alex2k8
Bagaimana jika saya ingin me-restart pesan?
Sonhja
dan jika saya membutuhkan runnable untuk mengatur 8 ImageViews yang berbeda terlihat satu demi satu, kemudian mengatur semuanya tidak terlihat dengan cara yang sama dan seterusnya (untuk membuat animasi "berkedip"), bagaimana saya bisa melakukan itu?
Droidman
1
Jika Anda ingin memastikan bahwa Handler akan dilampirkan ke utas utama, Anda harus menginisialisasi seperti ini: handler = new Handler (Looper.getMainLooper ());
Yair Kukielka
47
new Handler().postDelayed(new Runnable() {
    public void run() {
        // do something...              
    }
}, 100);
pengguna2212515
sumber
2
Jika Anda ingin memastikan bahwa Handler akan dilampirkan ke utas utama, Anda harus menginisialisasi seperti ini: new Handler (Looper.getMainLooper ());
Yair Kukielka
1
Bukankah solusi ini setara dengan pos asli? Itu hanya akan menjalankan Runnable sekali setelah 100 milidetik.
tronman
Jawaban @YairKukielka adalah solusinya! Anda perlu melampirkan MainLooper. penyelamat hidup seperti itu!
Houssem Chlegou
40

Saya pikir dapat meningkatkan solusi pertama Alex2k8 untuk memperbarui yang benar setiap detik

1. kode asli:

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

2. Analisis

  • Dalam biaya di atas, anggap tv.append("Hello Word")biaya T milidetik, setelah tampilan 500 kali waktu tunda adalah 500 * T milidetik
  • Ini akan meningkat tertunda saat berjalan lama

3. Solusi

Untuk menghindarinya Cukup ubah urutan postDelayed (), untuk menghindari penundaan:

public void run() {
    handler.postDelayed(this, 1000);
    tv.append("Hello World");
}
NguyenDat
sumber
6
-1 Anda mengasumsikan tugas yang Anda lakukan dalam menjalankan () adalah biaya jumlah konstan setiap kali berjalan, jika ini adalah operasi pada data dinamis (yang biasanya itu) maka Anda akan berakhir dengan lebih dari satu kali () terjadi pada sekali. Inilah sebabnya mengapa postDelayed biasanya ditempatkan di akhir.
Jay
1
@ Jay Sayangnya kamu salah. Handler dikaitkan dengan Thread tunggal (dan Looper yang merupakan metode run dari Thread itu) + MessageQueue. Setiap kali Anda memposting Pesan, Anda akan menerimanya dan saat berikutnya looper memeriksa antrian, ia menjalankan metode run dari Runnable yang Anda poskan. Karena semua itu terjadi hanya dalam satu utas, Anda tidak dapat mengeksekusi lebih dari 1 sekaligus. Juga melakukan postDelayed pertama akan membuat Anda lebih dekat ke 1000 ms per eksekusi karena secara internal ia menggunakan waktu saat ini + 1000 sebagai waktu eksekusi. Jika Anda memasukkan kode sebelum posting, Anda menambahkan penundaan tambahan.
zapl
1
@ zapl terima kasih atas tip tentang handler, saya berasumsi akan menjalankan beberapa runnables dan karenanya, banyak utas. Namun secara internal, kondisi seperti jika ((waktu sekarang - jam kerja terakhir)> 1000) akan berfungsi dengan baik ketika durasi lari kurang dari atau sama dengan 1000 ms, namun, ketika ini terlampaui, pasti timer akan terjadi pada interval non linier sepenuhnya bergantung pada waktu pelaksanaan metode jalankan (maka poin saya tentang biaya komputasi tidak terduga)
Jay
Jika Anda menginginkan periode tertentu, tanpa konflik, ukur waktu mulai sebelum melakukan pekerjaan, dan sesuaikan penundaan Anda. Anda masih akan melihat sedikit latensi jika cpu sedang sibuk, tetapi itu dapat membuat Anda periode yang lebih ketat, dan untuk mendeteksi apakah sistem kelebihan beban (mungkin untuk memberi sinyal hal-hal prioritas rendah untuk mundur).
Ajax
27

Untuk pengulangan tugas yang bisa Anda gunakan

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

menyebutnya seperti

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

            }
        },500,1000);

Kode di atas akan berjalan pertama kali setelah setengah detik (500) dan ulangi sendiri setelah setiap detik (1000)

Dimana

tugas menjadi metode yang akan dieksekusi

setelah waktu untuk eksekusi awal

( interval waktu untuk mengulangi eksekusi)

Kedua

Dan Anda juga dapat menggunakan CountDownTimer jika Anda ingin menjalankan tugas beberapa kali.

    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

Dan Anda juga bisa melakukannya dengan runnable. buat metode runnable seperti

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

        }
    };

Dan menyebutnya dengan kedua cara ini

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

ATAU

new Thread(runnable).start();//to work in Background 
Zar E Ahmer
sumber
Untuk opsi # 3 bagaimana saya bisa berhenti / melanjutkan dan juga berhenti secara permanen?
Si8
buat instance Handler seperti Handler handler = new Handler () dan hapus seperti handler.removeCallbacksAndMessages (null);
Zar E Ahmer
24

Saya percaya untuk kasus khas ini, yaitu menjalankan sesuatu dengan interval tetap, Timerlebih tepat. Ini adalah contoh sederhana:

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

Menggunakan Timermemiliki beberapa keuntungan:

  • Penundaan awal dan interval dapat dengan mudah ditentukan dalam scheduleargumen fungsi
  • Penghitung waktu dapat dihentikan hanya dengan menelepon myTimer.cancel()
  • Jika Anda hanya ingin menjalankan satu utas, ingat untuk menelepon myTimer.cancel() sebelum menjadwalkan utas baru (jika myTimer bukan nol)
ITech
sumber
7
Saya tidak percaya bahwa timer lebih tepat karena tidak mempertimbangkan siklus hidup android. Ketika Anda berhenti dan melanjutkan, tidak ada jaminan bahwa penghitung waktu akan berjalan dengan benar. Saya berpendapat bahwa runnable adalah pilihan yang lebih baik.
Janpan
1
Apakah itu berarti ketika aplikasi diletakkan di latar belakang Handler akan dijeda? dan ketika mendapatkan kembali fokus itu akan terus (lebih atau kurang) seolah-olah tidak ada yang terjadi?
Andrew Gallasch
17
Handler handler=new Handler();
Runnable r = new Runnable(){
    public void run() {
        tv.append("Hello World");                       
        handler.postDelayed(r, 1000);
    }
}; 
handler.post(r);
singh arjun
sumber
5
Ini seharusnya memberikan kesalahan. Di baris kedua Anda sedang memanggil variabel ryang belum ditentukan.
seoul
Jika Anda ingin memastikan bahwa Handler akan dilampirkan ke utas utama, Anda harus menginisialisasi seperti ini: handler = new Handler (Looper.getMainLooper ());
Yair Kukielka
hanya jawaban berulang-ulang!
Hamid
Bagaimana saya bisa menjeda / melanjutkan runnable dengan klik tampilan gambar?
Si8
4

Jika saya mengerti dengan benar dokumentasi metode Handler.post ():

Menyebabkan Runnable r ditambahkan ke antrian pesan. Runnable akan dijalankan pada utas yang dilampirkan pawang ini.

Jadi contoh yang diberikan oleh @ alex2k8, meskipun berfungsi dengan benar, tidak sama. Jika Handler.post()digunakan, tidak ada utas baru yang dibuat . Anda cukup memposting Runnableke utas dengan Handlerdieksekusi oleh EDT . Setelah itu, EDT hanya mengeksekusi Runnable.run(), tidak ada yang lain.

Ingat: Runnable != Thread.

Damian Walczak
sumber
1
Benar bahwa. Jangan membuat utas baru setiap saat. Inti dari Handler dan kumpulan eksekusi lainnya adalah memiliki satu atau dua utas menarik tugas dari antrian, untuk menghindari pembuatan utas dan GC. Jika Anda memiliki aplikasi yang benar-benar bocor, GC tambahan mungkin membantu menutupi situasi OutOfMemory, tetapi solusi yang lebih baik dalam kedua kasus adalah menghindari menciptakan lebih banyak pekerjaan daripada yang Anda butuhkan.
Ajax
Jadi cara yang lebih baik untuk melakukan ini adalah dengan menggunakan utas normal berdasarkan jawaban alex2k8?
Compaq LE2202x
4

Kotlin

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

Jawa

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);
}
Khemraj
sumber
1

Contoh yang menarik adalah Anda dapat terus melihat konter / stop-watch berjalan di utas terpisah. Juga menunjukkan Lokasi GPS. Sementara aktivitas utama User Interface Thread sudah ada di sana.

Kutipan:

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

Untuk melihat kode lihat di sini:

Contoh utas menampilkan Lokasi GPS dan Waktu Saat Ini dapat dijalankan bersamaan dengan Utas Antarmuka Pengguna aktivitas utama

Animesh Shrivastav
sumber
1
Petunjuk: jika Anda ingin menjadikan jawaban Anda bermanfaat - pelajari cara memformat input di sini. Itu pratinjau jendela ada karena suatu alasan.
GhostCat
0

sekarang di Kotlin Anda dapat menjalankan utas dengan cara ini:

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
}
André Abboud
sumber
0

Kotlin dengan Coroutines

Di Kotlin, menggunakan coroutine Anda dapat melakukan hal berikut:

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

Cobalah di sini !

Willi Mentzel
sumber