Dasar-dasar Android: menjalankan kode di utas UI

450

Dalam sudut pandang menjalankan kode di utas UI, apakah ada perbedaan antara:

MainActivity.this.runOnUiThread(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

atau

MainActivity.this.myView.post(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

dan

private class BackgroundTask extends AsyncTask<String, Void, Bitmap> {
    protected void onPostExecute(Bitmap result) {
        Log.d("UI thread", "I am the UI thread");
    }
}
Luky
sumber
Untuk memperjelas pertanyaan saya: Saya kira kode itu dipanggil dari utas layanan, biasanya pendengar. Saya juga mengira ada pekerjaan berat untuk diselesaikan baik dalam fungsi doInBackground () dari AsynkTask atau dalam Tugas baru (...) yang dipanggil sebelum dua cuplikan pertama. Pokoknya onPostExecute () dari AsyncTask diletakkan di akhir antrian acara, bukan?
Luky

Jawaban:

288

Tidak satu pun dari mereka yang persis sama, meskipun mereka semua akan memiliki efek bersih yang sama.

Perbedaan antara yang pertama dan yang kedua adalah bahwa jika Anda berada di utas aplikasi utama saat mengeksekusi kode, yang pertama ( runOnUiThread()) akan menjalankan Runnablesegera. Yang kedua ( post()) selalu menempatkan Runnabledi akhir antrian acara, bahkan jika Anda sudah di utas aplikasi utama.

Yang ketiga, dengan asumsi Anda membuat dan mengeksekusi instance BackgroundTask, akan membuang banyak waktu untuk mengambil thread dari kumpulan thread, untuk mengeksekusi no-op default doInBackground(), sebelum akhirnya melakukan apa yang berarti a post(). Sejauh ini yang paling tidak efisien dari ketiganya. Gunakan AsyncTaskjika Anda benar-benar memiliki pekerjaan untuk dilakukan di utas latar belakang, bukan hanya untuk penggunaan onPostExecute().

CommonsWare
sumber
27
Perhatikan juga bahwa AsyncTask.execute()Anda tetap harus menelepon dari utas UI, yang menjadikan opsi ini tidak berguna untuk kasus penggunaan dengan hanya menjalankan kode pada utas UI dari utas latar kecuali jika Anda memindahkan semua pekerjaan latar belakang doInBackground()dan menggunakannya AsyncTaskdengan benar.
kabuko
@ Kabuko bagaimana saya bisa memeriksa bahwa saya menelepon AsyncTaskdari utas UI?
Neil Galiaskarov
@NeilGaliaskarov Ini terlihat seperti opsi padat: stackoverflow.com/a/7897562/1839500
Dick Lucas
1
@NeilGaliaskarovboolean isUiThread = (Looper.getMainLooper().getThread() == Thread.currentThread());
ban-geoengineering
1
@NeilGaliaskarov untuk versi yang lebih besar dari atau sama dengan penggunaan MLooper.getMainLooper().isCurrentThread
Balu Sangem
252

Saya suka komentar HPP , bisa digunakan di mana saja tanpa parameter apa pun:

new Handler(Looper.getMainLooper()).post(new Runnable() {
    @Override
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});
pomber
sumber
2
Seberapa efisien ini? Apakah sama dengan opsi lain?
EmmanuelMess
59

Ada cara keempat menggunakan Handler

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});
vasart
sumber
56
Anda harus berhati-hati dengan ini. Karena jika Anda membuat handler di utas non UI Anda akan memposting pesan ke utas Non UI. Penangan dengan pesan posting default ke utas tempat pesan itu dibuat.
lujop
108
untuk mengeksekusi pada utas utama UI lakukan new Handler(Looper.getMainLooper()).post(r), yang merupakan cara yang disukai sebagai Looper.getMainLooper()membuat panggilan statis ke utama, sedangkan postOnUiThread()harus memiliki instance MainActivitydalam lingkup.
HPP
1
@ HPP Saya tidak tahu metode ini, akan menjadi cara yang bagus ketika Anda tidak memiliki atau Aktivitas tidak ada Tampilan. Bagus sekali! terima kasih banyak!
Sulphkain
@ lujop Sama halnya dengan metode panggilan balik onPreExecute dari AsyncTask.
Sreekanth Karumanaghat
18

Jawaban oleh Pomber dapat diterima, namun saya bukan penggemar berat membuat objek baru berulang kali. Solusi terbaik adalah selalu yang mencoba untuk mengurangi memori babi. Ya, ada pengumpulan sampah otomatis tetapi konservasi memori dalam perangkat seluler berada dalam batas praktik terbaik. Kode di bawah ini memperbarui TextView di layanan.

TextViewUpdater textViewUpdater = new TextViewUpdater();
Handler textViewUpdaterHandler = new Handler(Looper.getMainLooper());
private class TextViewUpdater implements Runnable{
    private String txt;
    @Override
    public void run() {
        searchResultTextView.setText(txt);
    }
    public void setText(String txt){
        this.txt = txt;
    }

}

Itu dapat digunakan dari mana saja seperti ini:

textViewUpdater.setText("Hello");
        textViewUpdaterHandler.post(textViewUpdater);
Joe
sumber
7
+1 untuk menyebutkan GC dan pembuatan objek. NAMUN, saya belum tentu setuju The best solutions are always the ones that try to mitigate memory hog. Ada banyak kriteria lain untuk best, dan ini memiliki sedikit bau premature optimization. Yaitu, kecuali Anda tahu Anda menyebutnya cukup sehingga jumlah objek yang dibuat adalah masalah (dibandingkan dengan sepuluh ribu cara lain di mana aplikasi Anda mungkin membuat sampah.), Yang mungkin bestadalah menulis yang paling sederhana (termudah untuk dipahami ) kode, dan beralih ke beberapa tugas lain.
ToolmakerSteve
2
BTW, textViewUpdaterHandlerakan lebih baik diberi nama seperti uiHandleratau mainHandler, karena umumnya berguna untuk setiap posting ke utas UI utama; sama sekali tidak terkait dengan kelas TextViewUpdater Anda. Saya akan memindahkannya dari sisa kode itu, dan membuatnya jelas bahwa itu dapat digunakan di tempat lain ... Sisa kode itu meragukan, karena untuk menghindari membuat satu objek secara dinamis, Anda memecahkan apa yang bisa menjadi satu panggilan ke dua langkah setTextdan post, itu bergantung pada objek berumur panjang yang Anda gunakan sebagai sementara. Kompleksitas yang tidak perlu, dan tidak aman untuk thread. Tidak mudah dirawat.
ToolmakerSteve
Jika Anda benar-benar memiliki situasi di mana ini disebut begitu banyak kali bahwa itu adalah layak caching uiHandlerdan textViewUpdater, kemudian meningkatkan kelas Anda dengan mengubah ke public void setText(String txt, Handler uiHandler)dan menambahkan metode garis uiHandler.post(this); Kemudian penelepon dapat melakukan dalam satu langkah: textViewUpdater.setText("Hello", uiHandler);. Kemudian di masa depan, jika perlu thread aman, metode dapat membungkus pernyataannya di dalam kunci aktif uiHandler, dan pemanggil tetap tidak berubah.
ToolmakerSteve
Saya cukup yakin bahwa Anda hanya dapat menjalankan Runnable sekali. Yang benar-benar menghancurkan ide Anda, yang secara keseluruhan bagus.
Nativ
@Nativ Tidak, Runnable dapat dijalankan beberapa kali. Thread tidak bisa.
Zbyszek
8

Pada Android P Anda dapat menggunakan getMainExecutor():

getMainExecutor().execute(new Runnable() {
  @Override public void run() {
    // Code will run on the main thread
  }
});

Dari dokumen pengembang Android :

Kembalikan seorang Pelaksana yang akan menjalankan tugas enqueued pada utas utama yang terkait dengan konteks ini. Ini adalah utas yang digunakan untuk mengirim panggilan ke komponen aplikasi (aktivitas, layanan, dll).

Dari CommonsBlog :

Anda dapat memanggil getMainExecutor () pada Konteks untuk mendapatkan seorang Executor yang akan menjalankan tugasnya di utas aplikasi utama. Ada cara lain untuk mencapai ini, menggunakan Looper dan implementasi kustom Pelaksana, tetapi ini lebih sederhana.

Dick Lucas
sumber
7

Jika Anda perlu menggunakan dalam Fragmen yang harus Anda gunakan

private Context context;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        this.context = context;
    }


    ((MainActivity)context).runOnUiThread(new Runnable() {
        public void run() {
            Log.d("UI thread", "I am the UI thread");
        }
    });

dari pada

getActivity().runOnUiThread(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

Karena akan ada pengecualian pointer nol dalam beberapa situasi seperti pager fragmen

Beyaz
sumber
1

hi guys yang satu ini adalah pertanyaan dasar yang saya katakan

gunakan Handler

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});
raghu kambaa
sumber
Apakah ada alasan mengapa Anda memilih untuk menggunakan Handler tujuan umum daripada runOnUiThread?
ThePartyTurtle
Ini akan memberi java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()jika tidak dipanggil dari utas UI.
Roc Boronat