Android “Yalnızca bir görünüm hiyerarşisi oluşturan orijinal iş parçacığı görünümlerine dokunabilir.”


940

Android'de basit bir müzik çalar geliştirdim. Her şarkının görünümü aşağıdaki gibi uygulanan bir SeekBar içerir:

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

Bu iyi çalışıyor. Şimdi şarkının ilerleyişinin saniye / dakikalarını sayan bir zamanlayıcı istiyorum. Ben koymak Yani TextViewdüzeninde, bunu almak findViewById()içinde onCreate()ve bu koymak run()sonra 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);

Ama bu son satır bana bir istisna veriyor:

android.view.ViewRoot $ CalledFromWrongThreadException: Yalnızca bir görünüm hiyerarşisi oluşturan orijinal iş parçacığı görünümlerine dokunabilir.

Yine de temelde burada yaptığımla aynı şeyi yapıyorum SeekBar- görünümü yaratmak onCreate, sonra ona dokunmak run()- ve bana bu şikayeti vermiyor.

Yanıtlar:


1894

Arka plan görevinin kullanıcı arayüzünü güncelleyen kısmını ana iş parçacığına taşımanız gerekir. Bunun için basit bir kod parçası var:

runOnUiThread(new Runnable() {

    @Override
    public void run() {

        // Stuff that updates the UI

    }
});

İçin belgeler Activity.runOnUiThread.

Bunu arka planda çalışan yöntemin içine yerleştirip bloğun ortasındaki güncellemeleri uygulayan kodu kopyalayıp yapıştırmanız yeterlidir. Mümkün olan en küçük kodu ekleyin, aksi takdirde arka plan iş parçacığının amacını yenmeye başlarsınız.


5
bir cazibe gibi çalıştı. benim için burada tek sorun ben bir error.setText(res.toString());run () yöntemi içinde yapmak istedim , ama nihai değildi çünkü res kullanamadık .. çok kötü
noloman

64
Bu konuda kısa bir yorum. Kullanıcı arabirimini değiştirmeye çalışan ayrı bir iş parçacığı vardı ve yukarıdaki kod çalıştı, ancak Activity nesnesinden runOnUiThread çağrısı vardı. Gibi bir şey yapmalıydım myActivityObject.runOnUiThread(etc)
Kirby

1
@Kirby Bu referans için teşekkür ederiz. Basitçe 'MainActivity.this' komutunu uygulayabilirsiniz ve aynı zamanda çalışmalıdır, böylece aktivite sınıfınıza başvurmak zorunda kalmazsınız.
JRomero

24
Bunun runOnUiThread()bir Faaliyet yöntemi olduğunu anlamak biraz zamanımı aldı . Kodumu bir parça halinde çalıştırıyordum. Sonunda yaptım getActivity().runOnUiThread(etc)ve işe yaradı. Fantastik!;
lejonl

'RunOnUiThread' yöntemi gövdesinde yazılan görevin yapılmasını durdurabilir miyiz?
Karan Sharma

143

Bunu runOnUiThread( new Runnable(){ ..içine koyarak çözdüm 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();

2
Bu sarsıldı. Bu bilgi için başka bir iş parçacığı içinde de kullanılabilir.
Nabin

Teşekkür ederim, UI İş Parçasına geri dönmek için bir iş parçacığı oluşturmak gerçekten üzücü ama durumumu sadece bu çözüm kurtardı.
Pierre Maoui

2
Önemli bir özellik wait(5000);Runnable'ın içinde olmamasıdır, aksi takdirde kullanıcı arayüzünüz bekleme süresi boyunca donar. Bu AsyncTaskgibi işlemler için Thread yerine kullanmayı düşünmelisiniz .
Martin

Bu bellek sızıntısı için çok kötü
Rafael Lima

Senkronize blok ile neden uğraşıyorsunuz? İçindeki kod makul iplik güvenli görünüyor (Her ne kadar tamamen benim kelimeleri yemeye hazır).
David

69

Benim çözümüm:

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

Arka plan iş parçacığında bu yöntemi çağırın.


Hata: (73, 67) hatası: statik olmayan yöntem kümesine (Dize) statik bir bağlamdan başvuru

1
Test derslerimde de aynı sorun var. Bu benim için bir cazibe gibi çalıştı. Ancak, değiştirilmesi runOnUiThreadile runTestOnUiThread. Teşekkürler
DaddyMoe

28

Genellikle, kullanıcı arabirimini içeren herhangi bir eylem ana veya UI iş parçacığında, yani onCreate()olay işlemenin gerçekleştirildiği eylemde yapılmalıdır . Bundan emin olmanın bir yolu runOnUiThread () kullanmak , diğeri Handlers kullanmaktır.

ProgressBar.setProgress() her zaman ana iş parçacığında yürütülecek bir mekanizmaya sahiptir, bu yüzden işe yaradı.

Bkz. Ağrısız Diş Açma .


Bu bağlantıdaki Ağrısız İş Parçacığı makalesi şimdi 404. İşte Ağrısız İş Parçacığı üzerindeki (eski?) Bir blog parçasının bağlantısı - android-developers.blogspot.com/2009/05/painless-threading.html
Tony Adams

20

Bu durumda bulundum, ancak Handler Object ile bir çözüm buldum.

Benim durumumda, bir ProgressDialog'u gözlemci modeliyle güncellemek istiyorum . Benim görüşüm gözlemci uygular ve güncelleme yöntemini geçersiz kılar.

Yani, benim ana iş parçacığı görünümü oluşturmak ve başka bir iş parçacığı ProgressDialop ve .... güncelleştirme güncelleştirme yöntemini çağırır:

Yalnızca bir görünüm hiyerarşisi oluşturan orijinal iş parçacığı görünümlerine dokunabilir.

Sorunu İşleyici Nesnesi ile çözmek mümkündür.

Aşağıda, kodumun farklı bölümleri:

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

Bu açıklama bu sayfada bulunabilir ve "İkinci bir iş parçacığına sahip örnek ProgressDialog" okumalısınız.


10

Ana UI İş Parçasını rahatsız etmeden Görünümü Silmek için İşleyici'yi kullanabilirsiniz. İşte örnek kod

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

7

@ Providence'ın cevabını kabul ettiğinizi görüyorum. Her ihtimale karşı, işleyiciyi de kullanabilirsiniz! İlk olarak int alanlarını yapın.

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

Ardından, alan olarak bir işleyici örneği oluşturun.

    //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;
            }
            }
        }
    };

Bir yöntem yapın.

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

Son olarak, bunu onCreate()yönteme koyun .

showHandler(true);

7

Benzer bir sorunum vardı ve çözümüm çirkin, ama işe yarıyor:

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
}

2
@ R.jzadeh bunu duymak güzel. Bu cevabı yazdığım andan beri, muhtemelen şimdi daha iyisini yapabilirsiniz :)
Błażej

6

Kullandığım Handlerile Looper.getMainLooper(). Benim için iyi çalıştı.

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

5

Bu kodu kullanın ve çalışmanıza gerek yok runOnUiThread:

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

5

Bu açıkça bir hata veriyor. Hangi ileti dizisinin bir görünüm oluşturduğunu, yalnızca görünümlerine dokunabileceklerini belirtir. Bunun nedeni, oluşturulan görünümün iş parçacığının alanının içinde olmasıdır. Görünüm oluşturma (GUI) kullanıcı arabirimi (ana) iş parçacığında gerçekleşir. Bu nedenle, bu yöntemlere erişmek için her zaman UI iş parçacığını kullanırsınız.

Resim açıklamasını buraya girin

Yukarıdaki resimde, progress değişkeni UI iş parçacığının boşluğunun içindedir. Bu nedenle, yalnızca UI iş parçacığı bu değişkene erişebilir. Burada, yeni Thread () ile ilerlemeye erişiyorsunuz ve bu yüzden bir hata aldınız.


4

Bir gelen bir UI değişim için çağırdığında bu benim oldu doInBackgroundden Asynctaskkullanmak yerine onPostExecute.

UI ile uğraşmak onPostExecutesorunumu çözdü.


1
Teşekkürler Jonathan. Bu da benim sorunumdu ama burada ne demek istediğini anlamak için biraz daha okuma yapmak zorunda kaldım. Herkes için, onPostExecuteaynı zamanda bir yöntemdir AsyncTaskama UI iş parçacığında çalışır. Buraya bakın: blog.teamtreehouse.com/all-about-android-asynctasks
ciaranodc

4

Kotlin coroutines kodunuzu daha özlü ve okunabilir hale getirebilir:

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

Ya da tam tersi:

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

3

Bağlama referans içermeyen bir sınıfla çalışıyordum. Beni kullanmak mümkün değildi yüzden runOnUIThread();kullandığım view.post();ve çözüldü.

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

Bir benzetme nedir audioMessageve tvPlayDurationsorular koduna?
gotwo

audioMessage, metin görünümünün bir tutucu nesnesidir. tvPlayDurationUI olmayan bir iş parçacığından güncellemek istediğimiz metin görünümüdür. Yukarıdaki soruda currentTime, metin görünümüdür, ancak bir tutucu nesnesi yoktur.
Ifta

3

AsyncTask kullanırken onPostExecute yöntemindeki kullanıcı arayüzünü güncelleyin

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

     }

bu bana oldu. ben uyn asink görev doinbackground ui güncelleniyordu.
mehmoodnisar125

3

Benzer bir sorunla karşı karşıya kaldım ve yukarıda belirtilen yöntemlerin hiçbiri benim için işe yaramadı. Sonunda, bu benim için hile yaptı:

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

Bu mücevheri burada buldum .


2

Bu, belirtilen istisnanın yığın izidir

        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)

Eğer gidip kazarsan, o zaman öğrenmeye gelirsin

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

Nerede mThread aşağıda gibi yapıcı içinde ilklendir olduğunu

mThread = Thread.currentThread();

Söylemek istediğim, belirli bir görünüm oluşturduğumuzda, bunu UI İş Parçacığı üzerinde oluşturduk ve daha sonra bir İşçi İş Parçacığı'nda değişiklik yapmaya çalıştık.

Aşağıdaki kod snippet'i ile doğrulayabiliriz

Thread.currentThread().getName()

düzeni şişirdiğimizde ve daha sonra istisna aldığınız yer.


2

runOnUiThreadAPI'yı kullanmak istemiyorsanız , aslında AsynTasktamamlanması birkaç saniye süren işlemler için başvurabilirsiniz . Ancak bu durumda, çalışmanızı işledikten sonra doinBackground()da bitmiş görünümü geri vermeniz gerekir onPostExecute(). Android uygulaması, yalnızca ana kullanıcı arayüzü iş parçacığının görünümlerle etkileşime girmesine izin verir.


2

Kullanıcı Arabirimi olmayan İş Parçanızdan geçersiz kılmak (yeniden boyama / yeniden çizme işlevini çağırmak) istiyorsanız, postInvalidate () kullanın

myView.postInvalidate();

Bu, UI iş parçacığında geçersiz bir istek gönderir.

Daha fazla bilgi için: what-does-postinvalidate-do


1

Benim için sorun onProgressUpdate()açıkça kodumdan aradığını oldu . Bu yapılmamalı. Bunun publishProgress()yerine aradım ve bu hatayı çözdü.


1

Benim durumumda var EditText Adaptörde ve zaten UI iş parçacığında. Ancak, bu Etkinlik yüklendiğinde, bu hatayla kilitlenir.

Benim çözüm <requestFocus />XML'de EditText kaldırmak gerekir .


1

Kotlin'de mücadele eden insanlar için şu şekilde çalışır:

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

0

Çözüldü: Bu yöntemi doInBackround Class'a koyun ... ve mesajı iletin

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

    }

0

Benim durumumda, arayan kısa sürede çok fazla çağrı yapar bu hatayı alacak, ben sadece çok kısa ise hiçbir şey yapmak için kontrol geçen süre koymak, örneğin işlev 0.5 saniyeden daha az çağrılırsa görmezden:

    private long mLastClickTime = 0;

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

        //... do ui update
    }

Daha iyi bir çözüm, tıklandığında düğmeyi devre dışı bırakmak ve işlem tamamlandığında tekrar etkinleştirmek olacaktır.
lsrom

@lsrom Benim durumumda o kadar basit değil çünkü çağıran 3. parti kütüphanesi dahili ve benim kontrolüm dışında.
Meyve

0

Bir UIThread bulamadıysanız bu yolu kullanabilirsiniz.

şu anki bağlamınız, Geçerli Bağlamı ayrıştırmanız gerekir

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

0

Kotlin Cevap

İş için UI İş Parçasını doğru bir şekilde kullanmamız gerekiyor. Kotlin'de UI İş Parçasını kullanabiliriz:

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

@canerkaseler


0

In KOTLIN basitçe runOnUiThread etkinlik yönteminde kodunuzu koymak

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)
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.