Ich habe einen einfachen Musikplayer in Android gebaut. Die Ansicht für jedes Lied enthält eine SeekBar, die folgendermaßen implementiert ist:
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);
}
}
Das funktioniert gut. Jetzt möchte ich einen Timer, der die Sekunden/Minuten des Fortschritts des Songs zählt. Also setze ich eine TextView
in das Layout, besorge es mit findViewById()
in onCreate()
und stelle dies in run()
nach 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);
Aber diese letzte Zeile gibt mir die Ausnahme:
Android.view.ViewRoot $ CalledFromWrongThreadException: Nur der ursprüngliche Thread, der eine Ansichtshierarchie erstellt hat, kann seine Ansichten berühren.
Aber ich mache hier im Grunde dasselbe wie mit SeekBar
- die Ansicht in onCreate
erstellen, dann in run()
anfassen - und das gibt mir diese Beschwerde nicht.
Sie müssen den Teil der Hintergrundaufgabe, der die Benutzeroberfläche aktualisiert, in den Haupt-Thread verschieben. Dafür gibt es einen einfachen Code:
runOnUiThread(new Runnable() {
@Override
public void run() {
// Stuff that updates the UI
}
});
Dokumentation für Activity.runOnUiThread
.
Verschachteln Sie dies einfach in der Methode, die im Hintergrund ausgeführt wird, und fügen Sie den Code ein, der alle Aktualisierungen in der Mitte des Blocks implementiert. Fügen Sie nur die kleinstmögliche Menge an Code ein, andernfalls beginnen Sie, den Zweck des Hintergrundthreads zu umgehen.
Ich habe das Problem gelöst, indem ich runOnUiThread( new Runnable(){ ..
in run()
gesteckt habe:
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();
Meine Lösung dazu:
private void setText(final TextView text,final String value){
runOnUiThread(new Runnable() {
@Override
public void run() {
text.setText(value);
}
});
}
Rufen Sie diese Methode in einem Hintergrundthread auf.
Normalerweise muss jede Aktion, die die Benutzeroberfläche betrifft, im Haupt- oder UI-Thread ausgeführt werden. Dies ist die Aktion, in der onCreate()
und die Ereignisbehandlung ausgeführt werden. Eine Möglichkeit, um sicher zu gehen, ist die Verwendung von runOnUiThread () . Eine andere Möglichkeit ist die Verwendung von Handlers.
ProgressBar.setProgress()
hat einen Mechanismus, für den es immer im Haupt-Thread ausgeführt wird. Deshalb hat es funktioniert.
Siehe schmerzloses Einfädeln .
Ich war in dieser Situation, habe aber mit dem Handler-Objekt eine Lösung gefunden.
In meinem Fall möchte ich einen ProgressDialog mit dem Observer-Muster ..__ aktualisieren. Meine Ansicht implementiert Observer und überschreibt die Update-Methode.
Also, mein Haupt-Thread erstellt die Ansicht und ein anderer Thread ruft die Aktualisierungsmethode auf, mit der ProgressDialop und ... aktualisiert werden:
Nur der ursprüngliche Thread, der eine Ansichtshierarchie erstellt hat, kann seine .__ berühren. Ansichten.
Es ist möglich, das Problem mit dem Handler-Objekt zu lösen.
Nachfolgend verschiedene Teile meines Codes:
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);
}
}
Diese Erklärung finden Sie auf dieser Seite und Sie müssen den "Beispiel-ProgressDialog mit einem zweiten Thread" lesen.
Ich sehe, dass Sie die Antwort von @ Provence akzeptiert haben. Für den Fall, dass Sie auch den Handler benutzen können! Zuerst machen Sie die int Felder.
private static final int SHOW_LOG = 1;
private static final int HIDE_LOG = 0;
Als Nächstes erstellen Sie eine Handlerinstanz als Feld.
//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;
}
}
}
};
Machen Sie eine Methode.
//TODO __________[ Callbacks ]__________
@Override
public void showHandler(boolean show)
{
handler.sendEmptyMessage(show ? SHOW_LOG : HIDE_LOG);
}
Zum Schluss setzen Sie diese auf onCreate()
-Methode.
showHandler(true);
Ich hatte ein ähnliches Problem und meine Lösung ist hässlich, aber es funktioniert:
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
}
Ich verwende Handler
mit Looper.getMainLooper()
. Es hat gut für mich funktioniert.
Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// Any UI task, example
textView.setText("your text");
}
};
handler.sendEmptyMessage(1);
Mit Handler können Sie die Ansicht löschen, ohne den Haupt-UI-Thread zu stören. Hier sehen Sie Beispielcode
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
//do stuff like remove view etc
adapter.remove(selecteditem);
}
});
Dies löst explizit einen Fehler aus. Es sagt, welcher Thread eine Ansicht erstellt hat, nur die Ansichten können berührt werden. Dies liegt daran, dass sich die erstellte Ansicht innerhalb des Bereichs des Threads befindet. Die Ansichtserstellung (GUI) erfolgt im UI-Hauptthread. Daher verwenden Sie immer den UI-Thread, um auf diese Methoden zuzugreifen.
Im obigen Bild befindet sich die Fortschrittsvariable innerhalb des Bereichs des UI-Threads. Daher kann nur der UI-Thread auf diese Variable zugreifen. Hier greifen Sie über einen neuen Thread () auf den Fortschritt zu, weshalb Sie einen Fehler erhalten haben.
Verwenden Sie diesen Code und keine runOnUiThread
-Funktion:
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();
}
Bei Verwendung von AsyncTask Aktualisieren Sie die Benutzeroberfläche in der onPostExecute -Methode
@Override
protected void onPostExecute(String s) {
// Update UI here
}
Dies geschah bei mir, als ich eine Änderung der Benutzeroberfläche von einer doInBackground
von Asynctask
forderte, anstatt onPostExecute
zu verwenden.
Der Umgang mit der Benutzeroberfläche in onPostExecute
hat mein Problem gelöst.
Wenn Sie die runOnUiThread
-API nicht verwenden möchten, können Sie AsynTask
für die Vorgänge implementieren, deren Ausführung einige Sekunden in Anspruch nimmt. In diesem Fall müssen Sie jedoch auch nach der Bearbeitung Ihrer Arbeit in doinBackground()
die fertige Ansicht in onPostExecute()
zurückgeben. Bei der Android-Implementierung kann nur der Haupt-UI-Thread mit Ansichten interagieren.
Dies ist der Stack-Trace der erwähnten Ausnahme
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)
Wenn Sie also Dig besuchen, werden Sie wissen
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
Wo mThread wird im Konstruktor wie folgt initialisiert
mThread = Thread.currentThread();
Alles, was ich damit sagen will, als wir eine bestimmte Ansicht erstellt haben, haben wir sie in UI-Thread erstellt und versuchen später, sie in einem Worker-Thread zu ändern.
Wir können es über den folgenden Code-Ausschnitt überprüfen
Thread.currentThread().getName()
wenn wir das Layout aufblasen und später, wo Sie eine Ausnahme bekommen.
Ich arbeitete mit einer Klasse, die keinen Hinweis auf den Kontext enthielt. Daher war es mir nicht möglich, runOnUIThread();
zu verwenden, ich verwendete view.post();
und es wurde gelöst.
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);
In meinem Fall ruft der Anrufer zu oft in kurzer Zeit diesen Fehler auf, ich setze einfach die elpased-Zeitprüfung ein, um nichts zu tun, wenn er zu kurz ist, z.B. ignorieren, wenn die Funktion weniger als 0,5 Sekunden aufgerufen wird:
private long mLastClickTime = 0;
public boolean foo() {
if ( (SystemClock.elapsedRealtime() - mLastClickTime) < 500) {
return false;
}
mLastClickTime = SystemClock.elapsedRealtime();
//... do ui update
}
Für die Leute, die in Kotlin kämpfen, funktioniert das so:
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)
}
Für mich bestand das Problem darin, dass ich onProgressUpdate()
explizit von meinem Code aus anrief. Das sollte nicht gemacht werden. Ich rief stattdessen publishProgress()
an und das hat den Fehler behoben.
Gelöst: Setzen Sie diese Methode einfach in doInBackround Class ... und übergeben Sie die Nachricht
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);
}
Wenn Sie einfach die Aufhebung der Funktion (Aufruf der Funktion "Repaint/Redraw") von Ihrem Thread außerhalb der Benutzeroberfläche aus durchführen möchten, verwenden Sie postInvalidate ().
myView.postInvalidate();
Dadurch wird eine Ungültigkeitsanforderung im UI-Thread bereitgestellt.
Weitere Informationen: what-do-postinvalidate-do
Ich hatte ein ähnliches Problem und keine der oben genannten Methoden funktionierte für mich. Am Ende war das der Trick für mich:
Device.BeginInvokeOnMainThread(() =>
{
myMethod();
});
Ich habe dieses Juwel gefunden hier .
In meinem Fall Ich habe EditText
im Adapter und ist bereits im UI-Thread. Wenn diese Aktivität geladen wird, stürzt sie jedoch mit diesem Fehler ab.
Meine Lösung ist, dass ich <requestFocus />
aus EditText in XML entfernen muss.