Android “Alleen de originele draad die een view-hiërarchie heeft gemaakt, kan zijn standpunten aanraken.”

Ik heb een eenvoudige muziekspeler in Android gebouwd. De weergave voor elk nummer bevat een zoekbalk, zoals dit geïmplementeerd:

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

Dit werkt prima. Nu wil ik een timer die de seconden/minuten van de voortgang van het nummer telt. Dus plaatste ik een TextViewin de lay-out, haal het op met findViewById()in onCreate(), en plaats dit in run()na 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);

Maar die laatste regel geeft me de uitzondering:

android.view.ViewRoot$CalledFromWrongThreadException: alleen de originele thread die een weergavehiërarchie heeft gemaakt, kan de weergaven ervan raken.

Toch doe ik hier in wezen hetzelfde als met de SeekBar– de weergave maken in onCreateen deze vervolgens aanraken in run()– en ik krijg deze klacht niet.


Antwoord 1, autoriteit 100%

U moet het gedeelte van de achtergrondtaak dat de gebruikersinterface bijwerkt, verplaatsen naar de hoofdthread. Hier is een eenvoudig stukje code voor:

runOnUiThread(new Runnable() {
    @Override
    public void run() {
        // Stuff that updates the UI
    }
});

Documentatie voor Activity.runOnUiThread.

Nest dit in de methode die op de achtergrond draait, en kopieer en plak de code die updates implementeert in het midden van het blok. Voeg alleen de kleinst mogelijke hoeveelheid code toe, anders mis je het doel van de achtergrondthread.


Antwoord 2, autoriteit 7%

Ik heb dit opgelost door runOnUiThread( new Runnable(){ ..in run()te plaatsen:

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

Antwoord 3, autoriteit 4%

Mijn oplossing hiervoor:

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

Noem deze methode op een achtergrondthread.


Antwoord 4

Meestal moet elke actie met betrekking tot de gebruikersinterface worden uitgevoerd in de hoofd- of ui-draad, dat is degene waarin onCreate()en evenementafhandeling wordt uitgevoerd. Een manier om zeker te zijn van dat is het gebruik van runonuithread () , een ander gebruikt handlers.

ProgressBar.setProgress()heeft een mechanisme waarvoor het altijd op de hoofddraad zal uitvoeren, dus daarom werkte het.

Zie Pijnloze draad .


Antwoord 5

Kotlin Coroutines kan uw code meer beknopt maken en leesbaar zijn, zoals deze:

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

of vice versa:

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

Antwoord 6

Ik heb in deze situatie geweest, maar ik heb een oplossing gevonden met het handlerobject.

In mijn geval wil ik een Progressdialog bijwerken met de observer patroon .
Mijn weergave implementeert Observer en negeert de updatemethode.

Dus, mijn hoofddraad creëert de weergave en een andere thread, bel de updatemethode die de ProgressDialop bijwerken en …:

Alleen de originele draad die een bekijk hiërarchie heeft gemaakt, kan het aanraken
Bekeken.

Het is mogelijk om het probleem op te lossen met het Handler Object.

Hieronder, verschillende delen van mijn code:

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

Deze uitleg is te vinden op deze pagina, en jij moet de “Voorbeeld voortgangsdialoogvenster met een tweede draad” lezen.


Antwoord 7

U kunt Handler gebruiken om weergave te verwijderen zonder de hoofd-UI-thread te verstoren.
Hier is een voorbeeldcode

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

Antwoord 8

Ik zie dat je het antwoord van @providence hebt geaccepteerd. Voor het geval dat je ook de handler kunt gebruiken! Voer eerst de int-velden in.

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

Maak vervolgens een instantie van een handler als een veld.

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

Maak een methode.

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

Voeg dit ten slotte toe aan de methode onCreate().

showHandler(true);

Antwoord 9

Gebruik deze code, en het is niet nodig om de functie runOnUiThreadte gebruiken:

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

Antwoord 10

Ik had een soortgelijk probleem en mijn oplossing is lelijk, maar het werkt:

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
}

Antwoord 11

Ik gebruik Handlermet Looper.getMainLooper(). Het werkte prima voor mij.

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

Antwoord 12

Dit geeft expliciet een fout. Het zegt welke draad een weergave heeft gemaakt, alleen dat zijn standpunten kan aanraken. Het is omdat het gemaakte uitzicht zich in de ruimte van die draad bevindt. De weergave-creatie (GUI) gebeurt in de UI (hoofd) thread. Dus gebruik je altijd de UI-draad om toegang te krijgen tot die methoden.

In het bovenstaande beeld bevindt de voortgangsvariabele zich in de ruimte van de UI-draad. Dus alleen de UI-draad heeft toegang tot deze variabele. Hier heb je toegang tot vooruitgang via nieuwe thread (), en daarom heb je een fout opgetreden.


Antwoord 13

Ik kreeg een soortgelijk probleem en geen van de hierboven genoemde methoden werkte voor mij. Uiteindelijk heeft dit de truc voor mij gedaan:

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

Ik vond dit edelsteen hier .


Antwoord 14

Dit is gebeurd met mijn toen ik een UI-verandering van een doInBackgroundvan Asynctaskin plaats van met onPostExecute.

Omgaan met de UI in onPostExecuteLos mijn probleem op.


Antwoord 15

Kotlin Antwoord

We moeten met echte manier ui-thread gebruiken voor de taak. We kunnen UI-draad gebruiken in Kotlin:

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

Antwoord 16

Ik werkte met een klasse die geen verwijzing naar de context bevatte. Het was dus niet mogelijk voor mij om runOnUIThread();te gebruiken Ik gebruikte view.post();en het was opgelost.

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

Antwoord 17

Bij gebruik van AsyncTaskWerk de gebruikersinterface bij volgens de onPostExecute-methode

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

Antwoord 18

Als u de runOnUiThreadAPI niet wilt gebruiken, kunt u in feite AsynTaskimplementeren voor de bewerkingen die enkele seconden in beslag nemen. Maar in dat geval, ook na het verwerken van uw werk in doinBackground(), moet u de voltooide weergave retourneren in onPostExecute(). De Android-implementatie staat alleen de hoofd-UI-thread toe om te communiceren met weergaven.


Antwoord 19

Dit is de stacktracering van de genoemde uitzondering

       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)

Dus als u gaat en digeert, kent u het weten

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

Waar mthread initialiseert in constructeur zoals hieronder

mThread = Thread.currentThread();

Alles wat ik wil zeggen dat wanneer we specifieke weergave hebben gemaakt die we het hebben gemaakt op de UI-draad en later proberen te modificeren in een werkdraad.

We kunnen het verifiëren via onderstaande code fragment

Thread.currentThread().getName()

Wanneer we lay-out opblazen en later waar u uitzondering krijgt.


Antwoord 20

Als u eenvoudig wilt beleggen (Oproep Repaint / Redraw-functie) uit uw niet-UI-draad, gebruikt u Postinvalidate ()

myView.postInvalidate();

Hiermee wordt een ongeldigverzoek op de UI-draad geplaatst.

Voor meer informatie: what-does-postinvalidate-do


Antwoord 21

Nou, je kunt het zo doen.

https://developer.android. com/reference/android/view/View#post(java.lang.Runnable)

Een eenvoudige aanpak

currentTime.post(new Runnable(){
            @Override
            public void run() {
                 currentTime.setText(time);     
            }
        }

het zorgt ook voor vertraging

https://developer .android.com/reference/android/view/View#postDelayed(java.lang.Runnable,%20long)


Antwoord 22

Voor mij was het probleem dat ik onProgressUpdate()expliciet vanuit mijn code aanriep. Dit zou niet moeten gebeuren. Ik belde in plaats daarvan publishProgress()en dat loste de fout op.


Antwoord 23

In mijn geval,
Ik heb EditTextin Adapter, en het staat al in de UI-thread. Wanneer deze activiteit echter wordt geladen, crasht deze met deze fout.

Mijn oplossing is dat ik <requestFocus />uit EditText in XML moet verwijderen.


Antwoord 24

Voor de mensen die het moeilijk hebben in Kotlin, werkt het als volgt:

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

Antwoord 25

Als u zich in een fragment bevindt, moet u ook het activity object ophalen, aangezien runOnUIThread een methode is voor de activiteit.

Een voorbeeld in Kotlin met wat omringende context om het duidelijker te maken – dit voorbeeld is navigeren van een camerafragment naar een galerijfragment:

// Setup image capture listener which is triggered after photo has been taken
imageCapture.takePicture(
       outputOptions, cameraExecutor, object : ImageCapture.OnImageSavedCallback {
           override fun onError(exc: ImageCaptureException) {
           Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
        }
        override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                        val savedUri = output.savedUri ?: Uri.fromFile(photoFile)
                        Log.d(TAG, "Photo capture succeeded: $savedUri")
             //Do whatever work you do when image is saved         
             //Now ask navigator to move to new tab - as this
             //updates UI do on the UI thread           
             activity?.runOnUiThread( {
                 Navigation.findNavController(
                        requireActivity(), R.id.fragment_container
                 ).navigate(CameraFragmentDirections
                        .actionCameraToGallery(outputDirectory.absolutePath))
              })

Antwoord 26

Opgelost: plaats deze methode gewoon in doInBackround Class… en geef het bericht door

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

Antwoord 27

In mijn geval krijgt de beller die te vaak in korte tijd belt deze foutmelding. negeer als functie minder dan 0,5 seconde wordt aangeroepen:

   private long mLastClickTime = 0;
    public boolean foo() {
        if ( (SystemClock.elapsedRealtime() - mLastClickTime) < 500) {
            return false;
        }
        mLastClickTime = SystemClock.elapsedRealtime();
        //... do ui update
    }

Antwoord 28

Als je geen UIThread kunt vinden, kun je deze manier gebruiken.

uwhuidigecontextbetekent dat u de huidige context moet ontleden

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

Antwoord 29

In Kotlinplaats je gewoon je code in de runOnUiThread-activiteitsmethode

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

Antwoord 30

RunOnUIThread leek niet te werken voor mij, maar het volgende loste mijn problemen uiteindelijk op.

           _ = MainThread.InvokeOnMainThreadAsync(async () =>
           {
               this.LoadApplication(Startup.Init(this.ConfigureServices));
               var authenticationService = App.ServiceProvider.GetService<AuthenticationService>();
               if (authenticationService.AuthenticationResult == null)
               {
                   await authenticationService.AuthenticateAsync(AuthenticationUserFlow.SignUpSignIn, CrossCurrentActivity.Current.Activity).ConfigureAwait(false);
               }
           });

Binnen de Startup.Init-methode is er ReactiveUI-routering en deze moet worden aangeroepen op de hoofdthread. Deze Invoke-methode accepteert async/wait ook beter dan RunOnUIThread.

Dus overal waar ik methoden op de mainthread moet aanroepen, gebruik ik dit.

Reageer hier alsjeblieft op als iemand iets weet wat ik niet weet en me kan helpen mijn applicatie te verbeteren.

Other episodes