Android "Hanya utas asli yang membuat hierarki tampilan yang dapat menyentuh pandangannya."

941

Saya telah membangun pemutar musik sederhana di Android. Tampilan untuk setiap lagu berisi SeekBar, diimplementasikan seperti ini:

public class Song extends Activity implements OnClickListener,Runnable {
    private SeekBar progress;
    private MediaPlayer mp;

    // ...

    private ServiceConnection onService = new ServiceConnection() {
          public void onServiceConnected(ComponentName className,
            IBinder rawBinder) {
              appService = ((MPService.LocalBinder)rawBinder).getService(); // service that handles the MediaPlayer
              progress.setVisibility(SeekBar.VISIBLE);
              progress.setProgress(0);
              mp = appService.getMP();
              appService.playSong(title);
              progress.setMax(mp.getDuration());
              new Thread(Song.this).start();
          }
          public void onServiceDisconnected(ComponentName classname) {
              appService = null;
          }
    };

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.song);

        // ...

        progress = (SeekBar) findViewById(R.id.progress);

        // ...
    }

    public void run() {
    int pos = 0;
    int total = mp.getDuration();
    while (mp != null && pos<total) {
        try {
            Thread.sleep(1000);
            pos = appService.getSongPosition();
        } catch (InterruptedException e) {
            return;
        } catch (Exception e) {
            return;
        }
        progress.setProgress(pos);
    }
}

Ini berfungsi dengan baik. Sekarang saya ingin penghitung waktu menghitung detik / menit dari kemajuan lagu. Jadi saya menaruh TextViewdi tata letak, mendapatkannya dengan findViewById()di onCreate(), dan menempatkan ini dalam run()setelah progress.setProgress(pos):

String time = String.format("%d:%d",
            TimeUnit.MILLISECONDS.toMinutes(pos),
            TimeUnit.MILLISECONDS.toSeconds(pos),
            TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(
                    pos))
            );
currentTime.setText(time);  // currentTime = (TextView) findViewById(R.id.current_time);

Tapi kalimat terakhir itu memberi saya pengecualian:

android.view.ViewRoot $ CalledFromWrongThreadException: Hanya utas asli yang membuat hierarki tampilan yang dapat menyentuh pandangannya.

Namun pada dasarnya saya melakukan hal yang sama di sini seperti yang saya lakukan dengan SeekBar- membuat tampilan masuk onCreate, kemudian menyentuhnya run()- dan itu tidak memberi saya keluhan ini.

gembala
sumber

Jawaban:

1897

Anda harus memindahkan bagian dari tugas latar belakang yang memperbarui UI ke utas utama. Ada sepotong kode sederhana untuk ini:

runOnUiThread(new Runnable() {

    @Override
    public void run() {

        // Stuff that updates the UI

    }
});

Dokumentasi untuk Activity.runOnUiThread.

Hanya bersarang ini di dalam metode yang berjalan di latar belakang, dan lalu salin tempel kode yang mengimplementasikan pembaruan apa pun di tengah blok. Hanya sertakan jumlah kode sekecil mungkin, jika tidak, Anda mulai mengalahkan tujuan utas latar belakang.

penyediaan
sumber
5
bekerja seperti pesona. bagi saya satu-satunya masalah di sini adalah saya ingin melakukan metode error.setText(res.toString());inside the run (), tapi saya tidak bisa menggunakan res karena itu belum final .. terlalu buruk
noloman
64
Satu komentar singkat tentang ini. Saya memiliki utas terpisah yang mencoba memodifikasi UI, dan kode di atas berfungsi, tetapi saya memanggil runOnUiThread dari objek Activity. Saya harus melakukan sesuatu seperti myActivityObject.runOnUiThread(etc)
Kirby
1
@ Kirby Terima kasih untuk referensi ini. Anda cukup melakukan 'MainActivity.this' dan ini harus bekerja juga sehingga Anda tidak harus tetap merujuk ke kelas aktivitas Anda.
JRomero
24
Perlu beberapa saat bagi saya untuk mengetahui bahwa itu runOnUiThread()adalah metode Kegiatan. Saya menjalankan kode saya dalam sebuah fragmen. Saya akhirnya melakukan getActivity().runOnUiThread(etc)dan berhasil. Fantastis!;
lejonl
Bisakah kita menghentikan tugas yang sedang dilakukan yang ditulis dalam tubuh metode 'runOnUiThread'?
Karan Sharma
143

Saya memecahkan ini dengan memasukkan ke runOnUiThread( new Runnable(){ ..dalam run():

thread = new Thread(){
        @Override
        public void run() {
            try {
                synchronized (this) {
                    wait(5000);

                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            dbloadingInfo.setVisibility(View.VISIBLE);
                            bar.setVisibility(View.INVISIBLE);
                            loadingText.setVisibility(View.INVISIBLE);
                        }
                    });

                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Intent mainActivity = new Intent(getApplicationContext(),MainActivity.class);
            startActivity(mainActivity);
        };
    };  
    thread.start();
Günay Gültekin
sumber
2
Yang ini bergoyang. Terima kasih atas informasi itu, ini juga dapat digunakan di dalam utas lainnya.
Nabin
Terima kasih, sungguh menyedihkan untuk membuat utas untuk kembali ke Utas UI tetapi hanya solusi ini yang menyelamatkan kasus saya.
Pierre Maoui
2
Salah satu aspek penting adalah yang wait(5000);tidak ada di dalam Runnable, jika tidak UI Anda akan membeku selama periode tunggu. Anda harus mempertimbangkan untuk menggunakan AsyncTaskUtas untuk operasi seperti ini.
Martin
ini sangat buruk untuk kebocoran memori
Rafael Lima
Mengapa repot dengan blok yang disinkronkan? Kode di dalamnya terlihat cukup aman (meskipun saya sepenuhnya siap untuk memakan kata-kata saya).
David
69

Solusi saya untuk ini:

private void setText(final TextView text,final String value){
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            text.setText(value);
        }
    });
}

Panggil metode ini di utas latar belakang.

Angelo Angeles
sumber
Kesalahan: (73, 67) kesalahan: set metode non-statis (String) tidak dapat direferensikan dari konteks statis
1
Saya memiliki masalah yang sama dengan kelas tes saya. Ini bekerja seperti pesona bagi saya. Namun, ganti runOnUiThreaddengan runTestOnUiThread. Terima kasih
Ayah
28

Biasanya, tindakan apa pun yang melibatkan antarmuka pengguna harus dilakukan di utas utama atau UI, yaitu tindakan di mana onCreate()dan penanganan peristiwa dieksekusi. Salah satu cara untuk memastikannya adalah menggunakan runOnUiThread () , yang lain adalah menggunakan Handler.

ProgressBar.setProgress() memiliki mekanisme yang akan selalu dijalankan pada utas utama, jadi itu sebabnya ia bekerja.

Lihat Threading Tanpa Rasa Sakit .

batu besar
sumber
Artikel Painless Threading di tautan itu sekarang menjadi 404. Berikut ini tautan ke bagian blog (lebih lama?) Di Painless Threading - android-developers.blogspot.com/2009/05/painless-threading.html
Tony Adams
20

Saya sudah dalam situasi ini, tetapi saya menemukan solusi dengan Object Handler.

Dalam kasus saya, saya ingin memperbarui ProgressDialog dengan pola pengamat . Pandangan saya mengimplementasikan pengamat dan mengganti metode pembaruan.

Jadi, utas utama saya membuat tampilan dan utas lainnya memanggil metode pembaruan yang memperbarui ProgressDialop dan ....:

Hanya utas asli yang membuat hierarki tampilan yang dapat menyentuh pandangannya.

Dimungkinkan untuk menyelesaikan masalah dengan Object Handler.

Di bawah ini, berbagai bagian kode saya:

public class ViewExecution extends Activity implements Observer{

    static final int PROGRESS_DIALOG = 0;
    ProgressDialog progressDialog;
    int currentNumber;

    public void onCreate(Bundle savedInstanceState) {

        currentNumber = 0;
        final Button launchPolicyButton =  ((Button) this.findViewById(R.id.launchButton));
        launchPolicyButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                showDialog(PROGRESS_DIALOG);
            }
        });
    }

    @Override
    protected Dialog onCreateDialog(int id) {
        switch(id) {
        case PROGRESS_DIALOG:
            progressDialog = new ProgressDialog(this);
            progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            progressDialog.setMessage("Loading");
            progressDialog.setCancelable(true);
            return progressDialog;
        default:
            return null;
        }
    }

    @Override
    protected void onPrepareDialog(int id, Dialog dialog) {
        switch(id) {
        case PROGRESS_DIALOG:
            progressDialog.setProgress(0);
        }

    }

    // Define the Handler that receives messages from the thread and update the progress
    final Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            int current = msg.arg1;
            progressDialog.setProgress(current);
            if (current >= 100){
                removeDialog (PROGRESS_DIALOG);
            }
        }
    };

    // The method called by the observer (the second thread)
    @Override
    public void update(Observable obs, Object arg1) {

        Message msg = handler.obtainMessage();
        msg.arg1 = ++currentPluginNumber;
        handler.sendMessage(msg);
    }
}

Penjelasan ini dapat ditemukan di halaman ini , dan Anda harus membaca "Contoh ProgressDialog dengan utas kedua".

Jonathan
sumber
10

Anda dapat menggunakan Handler untuk Menghapus Tampilan tanpa mengganggu Utas UI utama. Berikut ini contoh kode

new Handler(Looper.getMainLooper()).post(new Runnable() {
                                                        @Override
                                                        public void run() {
                                                           //do stuff like remove view etc
                                                            adapter.remove(selecteditem);
                                                        }
                                                    });
Bilal Mustafa
sumber
7

Saya melihat bahwa Anda telah menerima jawaban @ providence. Untuk jaga-jaga, Anda juga dapat menggunakan pawang juga! Pertama, lakukan bidang int.

    private static final int SHOW_LOG = 1;
    private static final int HIDE_LOG = 0;

Selanjutnya, buat instance handler sebagai bidang.

    //TODO __________[ Handler ]__________
    @SuppressLint("HandlerLeak")
    protected Handler handler = new Handler()
    {
        @Override
        public void handleMessage(Message msg)
        {
            // Put code here...

            // Set a switch statement to toggle it on or off.
            switch(msg.what)
            {
            case SHOW_LOG:
            {
                ads.setVisibility(View.VISIBLE);
                break;
            }
            case HIDE_LOG:
            {
                ads.setVisibility(View.GONE);
                break;
            }
            }
        }
    };

Buat metode.

//TODO __________[ Callbacks ]__________
@Override
public void showHandler(boolean show)
{
    handler.sendEmptyMessage(show ? SHOW_LOG : HIDE_LOG);
}

Akhirnya, letakkan ini di onCreate()metode.

showHandler(true);
David Dimalanta
sumber
7

Saya memiliki masalah serupa, dan solusi saya jelek, tetapi berhasil:

void showCode() {
    hideRegisterMessage(); // Hides view 
    final Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            showRegisterMessage(); // Shows view
        }
    }, 3000); // After 3 seconds
}
Błażej
sumber
2
@ R.jzadeh senang mendengarnya. Sejak saat saya menulis jawaban itu, mungkin sekarang Anda dapat melakukannya dengan lebih baik :)
Błażej
6

Saya gunakan Handlerdengan Looper.getMainLooper(). Ini bekerja dengan baik untukku.

    Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
              // Any UI task, example
              textView.setText("your text");
        }
    };
    handler.sendEmptyMessage(1);
Sankar Behera
sumber
5

Gunakan kode ini, dan tidak perlu runOnUiThreadberfungsi:

private Handler handler;
private Runnable handlerTask;

void StartTimer(){
    handler = new Handler();   
    handlerTask = new Runnable()
    {
        @Override 
        public void run() { 
            // do something  
            textView.setText("some text");
            handler.postDelayed(handlerTask, 1000);    
        }
    };
    handlerTask.run();
}
Hamid
sumber
5

Ini secara eksplisit melempar kesalahan. Ia mengatakan utas mana saja yang menciptakan tampilan, hanya yang bisa menyentuh pandangannya. Itu karena tampilan yang dibuat ada di dalam ruang utas itu. Pembuatan view (GUI) terjadi di utas UI (utama). Jadi, Anda selalu menggunakan utas UI untuk mengakses metode tersebut.

Masukkan deskripsi gambar di sini

Pada gambar di atas, variabel progres berada di dalam ruang utas UI. Jadi, hanya utas UI yang dapat mengakses variabel ini. Di sini, Anda mengakses kemajuan melalui Thread baru (), dan itulah sebabnya Anda mendapat kesalahan.

Uddhav Gautam
sumber
4

Ini terjadi pada saya ketika saya meminta perubahan UI dari doInBackgrounddari Asynctaskalih-alih menggunakanonPostExecute .

Berurusan dengan UI dalam onPostExecutememecahkan masalah saya.

Jonathan dos Santos
sumber
1
Terima kasih Jonathan. Ini adalah masalah saya juga, tetapi saya harus membaca lebih sedikit untuk memahami apa yang Anda maksudkan di sini. Untuk orang lain, onPostExecuteini juga merupakan metode AsyncTasktetapi berjalan pada utas UI. Lihat di sini: blog.teamtreehouse.com/all-about-android-asynctasks
ciaranodc
4

Kotlin coroutine dapat membuat kode Anda lebih ringkas dan mudah dibaca seperti ini:

MainScope().launch {
    withContext(Dispatchers.Default) {
        //TODO("Background processing...")
    }
    TODO("Update UI here!")
}

Atau sebaliknya:

GlobalScope.launch {
    //TODO("Background processing...")
    withContext(Dispatchers.Main) {
        // TODO("Update UI here!")
    }
    TODO("Continue background processing...")
}
Kenchi
sumber
3

Saya bekerja dengan kelas yang tidak mengandung referensi ke konteksnya. Jadi tidak mungkin bagi saya untuk menggunakan yang runOnUIThread();saya gunakan view.post();dan itu diselesaikan.

timer.scheduleAtFixedRate(new TimerTask() {

    @Override
    public void run() {
        final int currentPosition = mediaPlayer.getCurrentPosition();
        audioMessage.seekBar.setProgress(currentPosition / 1000);
        audioMessage.tvPlayDuration.post(new Runnable() {
            @Override
            public void run() {
                audioMessage.tvPlayDuration.setText(ChatDateTimeFormatter.getDuration(currentPosition));
            }
        });
    }
}, 0, 1000);
Ifta
sumber
Apa analogi dari audioMessagedan tvPlayDurationdengan kode pertanyaan?
Gotwo
audioMessageadalah objek penahan tampilan teks. tvPlayDurationadalah tampilan teks yang ingin kami perbarui dari utas non UI. Dalam pertanyaan di atas, currentTimeapakah tampilan teks tetapi tidak memiliki objek pemegang.
Ifta
3

Saat menggunakan AsyncTask Perbarui UI dalam metode onPostExecute

    @Override
    protected void onPostExecute(String s) {
   // Update UI here

     }
Deepak Kataria
sumber
ini terjadi pada saya. saya memperbarui ui di doinbackground tugas asynk.
mehmoodnisar125
3

Saya menghadapi masalah yang sama dan tidak ada metode yang disebutkan di atas bekerja untuk saya. Pada akhirnya, ini berhasil bagi saya:

Device.BeginInvokeOnMainThread(() =>
    {
        myMethod();
    });

Saya menemukan permata ini di sini .

Hagbard
sumber
2

Ini adalah jejak tumpukan pengecualian yang disebutkan

        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6149)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:843)
        at android.view.View.requestLayout(View.java:16474)
        at android.view.View.requestLayout(View.java:16474)
        at android.view.View.requestLayout(View.java:16474)
        at android.view.View.requestLayout(View.java:16474)
        at android.widget.RelativeLayout.requestLayout(RelativeLayout.java:352)
        at android.view.View.requestLayout(View.java:16474)
        at android.widget.RelativeLayout.requestLayout(RelativeLayout.java:352)
        at android.view.View.setFlags(View.java:8938)
        at android.view.View.setVisibility(View.java:6066)

Jadi jika Anda pergi dan menggali maka Anda akan tahu

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

Di mana mThread diinisialisasi dalam konstruktor seperti di bawah ini

mThread = Thread.currentThread();

Yang ingin saya katakan adalah ketika kami membuat tampilan tertentu, kami membuatnya di UI Thread dan kemudian mencoba untuk memodifikasi di Worker Thread.

Kami dapat memverifikasinya melalui cuplikan kode di bawah ini

Thread.currentThread().getName()

saat kami mengembang tata letak dan nanti di mana Anda mendapatkan pengecualian.

Amit Yadav
sumber
2

Jika Anda tidak ingin menggunakan runOnUiThreadAPI, Anda sebenarnya dapat menerapkan AsynTaskuntuk operasi yang membutuhkan beberapa detik untuk menyelesaikannya. Tetapi dalam hal itu, juga setelah memproses pekerjaan doinBackground()Anda, Anda harus mengembalikan tampilan yang telah selesai onPostExecute(). Implementasi Android memungkinkan hanya utas UI utama untuk berinteraksi dengan tampilan.

Sam
sumber
2

Jika Anda hanya ingin membatalkan (fungsi panggil ulang / redraw) dari Thread non UI Anda, gunakan postInvalidate ()

myView.postInvalidate();

Ini akan mengirim permintaan yang tidak valid pada utas UI.

Untuk informasi lebih lanjut: apa-apa-postinvalidate-do

Nalin
sumber
1

Bagi saya masalahnya adalah bahwa saya menelepon onProgressUpdate()secara eksplisit dari kode saya. Ini seharusnya tidak dilakukan. Saya menelepon publishProgress()sebaliknya dan itu menyelesaikan kesalahan.

pembaca pikiran
sumber
1

Dalam kasus saya, saya punya EditText di Adapter, dan itu sudah ada di utas UI. Namun, ketika Kegiatan ini dimuat, itu crash dengan kesalahan ini.

Solusi saya adalah saya harus menghapus <requestFocus />dari EditText dalam XML.

Sruit A.Suk
sumber
1

Untuk orang-orang yang berjuang di Kotlin, kerjanya seperti ini:

lateinit var runnable: Runnable //global variable

 runOnUiThread { //Lambda
            runnable = Runnable {

                //do something here

                runDelayedHandler(5000)
            }
        }

        runnable.run()

 //you need to keep the handler outside the runnable body to work in kotlin
 fun runDelayedHandler(timeToWait: Long) {

        //Keep it running
        val handler = Handler()
        handler.postDelayed(runnable, timeToWait)
    }
Tarun Kumar
sumber
0

Dipecahkan: Masukkan metode ini ke dalam doInBackround Class ... dan sampaikan pesannya

public void setProgressText(final String progressText){
        Handler handler = new Handler(Looper.getMainLooper()) {
            @Override
            public void handleMessage(Message msg) {
                // Any UI task, example
                progressDialog.setMessage(progressText);
            }
        };
        handler.sendEmptyMessage(1);

    }
Kaushal Sachan
sumber
0

Dalam kasus saya, pemanggil memanggil terlalu banyak kali dalam waktu singkat akan mendapatkan kesalahan ini, saya hanya menempatkan waktu berlalu memeriksa untuk tidak melakukan apa-apa jika terlalu pendek, misalnya mengabaikan jika fungsi dipanggil kurang dari 0,5 detik:

    private long mLastClickTime = 0;

    public boolean foo() {
        if ( (SystemClock.elapsedRealtime() - mLastClickTime) < 500) {
            return false;
        }
        mLastClickTime = SystemClock.elapsedRealtime();

        //... do ui update
    }
Buah
sumber
Solusi yang lebih baik adalah dengan menonaktifkan tombol pada klik dan mengaktifkannya lagi ketika tindakan selesai.
lsrom
@lsrom Dalam kasus saya tidak sesederhana itu karena pemanggil adalah perpustakaan pihak ke-3 internal dan di luar kendali saya.
Buah
0

Jika Anda tidak dapat menemukan UIThread, Anda dapat menggunakan cara ini.

maksud kontekscurrent Anda, Anda harus menguraikan Konteks Saat Ini

 new Thread(new Runnable() {
        public void run() {
            while (true) {
                (Activity) yourcurrentcontext).runOnUiThread(new Runnable() {
                    public void run() { 
                        Log.d("Thread Log","I am from UI Thread");
                    }
                });
                try {
                    Thread.sleep(1000);
                } catch (Exception ex) {

                }
            }
        }
    }).start();
Udara Kasun
sumber
0

Jawab Kotlin

Kita harus menggunakan Thread UI untuk pekerjaan dengan cara yang benar. Kita dapat menggunakan Thread UI di Kotlin:

runOnUiThread(Runnable {
   //TODO: Your job is here..!
})

@canerkaseler

canerkaseler
sumber
0

Di Kotlin cukup masukkan kode Anda dalam metode aktivitas runOnUiThread

runOnUiThread{
    // write your code here, for example
    val task = Runnable {
            Handler().postDelayed({
                var smzHtcList = mDb?.smzHtcReferralDao()?.getAll()
                tv_showSmzHtcList.text = smzHtcList.toString()
            }, 10)

        }
    mDbWorkerThread.postTask(task)
}
Raheel Khan
sumber