Android“只有创建视图层次结构的原始线程才能触摸其视图。”

我在Android中构建了一个简单的音乐播放器。每首歌曲的视图都包含一个SeekBar,实现如下:

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

这工作得很好。现在我想要一个计时器来计算歌曲进度的秒/分钟。所以我在布局中放了一个TextView,在onCreate()中放了findViewById(),在progress.setProgress(pos)之后放了run()

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

但最后一行给了我一个例外:

android.view.ViewRoot$CalledFromWrongThreadException:只有创建视图层次结构的原始线程才能触摸其视图。

然而,我在这里做的基本上和我在SeekBar中做的一样——在onCreate中创建视图,然后在run()中触摸它——它没有给我这个抱怨。

750849 次浏览

你必须将更新UI的后台任务部分移动到主线程上。有一段简单的代码可以做到这一点:

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

#0的文档。

只需将其嵌套在后台运行的方法中,然后复制粘贴在块中间实现任何更新的代码。只包含尽可能少的代码,否则你开始破坏后台线程的目的。

通常,任何涉及用户交互界面的操作都必须在主线程或UI线程中完成,即执行onCreate()和事件处理的线程。一种方法是使用runOnUiThread()方法名,另一种方法是使用处理程序。

ProgressBar.setProgress()有一个机制,它将始终在主线程上执行,这就是它工作的原因。

无痛螺纹

我遇到过这种情况,但我找到了处理程序对象的解决方案。

在我的情况下,我想用观察者模式更新ProgressDialog。我的视图实现了观察者并覆盖了更新方法。

所以,我的主线程创建视图,另一个线程调用更新ProgressDialop和……的更新方法:

只有创建视图层次结构的原始线程才能触摸其评论。

可以使用Handler对象解决问题。

下面是我的代码的不同部分:

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() {
@Overridepublic void onClick(View v) {showDialog(PROGRESS_DIALOG);}});}
@Overrideprotected 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;}}
@Overrideprotected 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 progressfinal 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)@Overridepublic void update(Observable obs, Object arg1) {
Message msg = handler.obtainMessage();msg.arg1 = ++currentPluginNumber;handler.sendMessage(msg);}}

这个解释可以在此页面上找到,你必须阅读“带有第二个线程的示例ProgressDialog”。

我通过将runOnUiThread( new Runnable(){ ..放在run()中解决了这个问题:

thread = new Thread(){@Overridepublic void run() {try {synchronized (this) {wait(5000);
runOnUiThread(new Runnable() {@Overridepublic 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();

我看到你已经接受了@普罗维登斯的答案。以防万一,你也可以使用处理程序!首先,执行int字段。

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

接下来,将处理程序实例作为字段。

    //TODO __________[ Handler ]__________@SuppressLint("HandlerLeak")protected Handler handler = new Handler(){@Overridepublic 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;}}}};

做一个方法。

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

最后,将其放在onCreate()方法中。

showHandler(true);

对我来说,问题是我从代码中显式调用onProgressUpdate()。这不应该这样做。我调用了publishProgress(),这解决了错误。

我对此的解决方案:

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

在后台线程上调用此方法。

这是上述异常的堆栈跟踪

        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)

所以如果你去挖掘,你就会知道

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

其中mThread在构造函数中初始化,如下所示

mThread = Thread.currentThread();

我的意思是说,当我们创建特定视图时,我们在UI线程上创建了它,然后尝试在Worker线程中进行修改。

我们可以通过下面的代码片段进行验证

Thread.currentThread().getName()

当我们膨胀布局,后来你得到异常。

如果你不想使用runOnUiThread API,你实际上可以为需要几秒钟才能完成的操作实现AsynTask。但在这种情况下,在处理了doinBackground()中的工作后,你需要在onPostExecute()中返回完成的视图。Android实现只允许主UI线程与视图交互。

我有一个类似的问题,我的解决方案很丑陋,但它有效:

void showCode() {hideRegisterMessage(); // Hides viewfinal Handler handler = new Handler();handler.postDelayed(new Runnable() {@Overridepublic void run() {showRegisterMessage(); // Shows view}}, 3000); // After 3 seconds}

在我看来,我在Adaptor中有EditText,它已经在UI线程中。但是,当此活动加载时,它会因此错误而崩溃。

我的解决方案是我需要从EditText in XML中删除<requestFocus />

使用此代码,无需runOnUiThread函数:

private Handler handler;private Runnable handlerTask;
void StartTimer(){handler = new Handler();handlerTask = new Runnable(){@Overridepublic void run() {// do somethingtextView.setText("some text");handler.postDelayed(handlerTask, 1000);}};handlerTask.run();}

我使用HandlerLooper.getMainLooper()。它对我很有效。

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

这明确抛出了一个错误。它说无论哪个线程创建了一个视图,只有那个线程可以触摸它的视图。这是因为创建的视图在该线程的空间内。视图创建(GUI)发生在UI(主)线程中。所以,你总是使用UI线程来访问这些方法。

在此输入图片描述

在上图中,进度变量位于UI线程的空间内。因此,只有UI线程可以访问此变量。在这里,你正在通过new Thread()访问进度,这就是为什么你得到了错误。

当我从Asynctask调用doInBackground而不是使用onPostExecute时,发生了这种情况。

onPostExecute中处理UI解决了我的问题。

我正在使用一个不包含上下文引用的类。所以我不可能使用runOnUIThread();我使用了view.post();,它被解决了。

timer.scheduleAtFixedRate(new TimerTask() {
@Overridepublic void run() {final int currentPosition = mediaPlayer.getCurrentPosition();audioMessage.seekBar.setProgress(currentPosition / 1000);audioMessage.tvPlayDuration.post(new Runnable() {@Overridepublic void run() {audioMessage.tvPlayDuration.setText(ChatDateTimeFormatter.getDuration(currentPosition));}});}}, 0, 1000);

使用异步任务时更新onPostExecute方法中的UI

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

在我的情况下,调用者在短时间内调用太多次会得到这个错误,如果太短,我只是把经过的时间检查什么也不做,例如,如果函数get调用小于0.5秒,则忽略:

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

您可以使用Handler删除视图而不会干扰主UI线程。以下是示例代码

new Handler(Looper.getMainLooper()).post(new Runnable() {@Overridepublic void run() {//do stuff like remove view etcadapter.remove(selecteditem);}});

解决:只需将此方法放入doInBackround Class…并传递消息

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

我遇到了类似的问题,上面提到的方法都不适合我。最后,这对我起了作用:

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

我找到了这个宝石这里

如果你只是想从你的非UI线程中使无效(调用重绘/重绘函数),请使用postInval的()

myView.postInvalidate();

这将在UI线程上发布一个无效请求。

更多信息:What-do-post无效-做什么

对于那些在静态编程语言中苦苦挣扎的人来说,它是这样工作的:

lateinit var runnable: Runnable //global variable
runOnUiThread { //Lambdarunnable = Runnable {
//do something here
runDelayedHandler(5000)}}
runnable.run()
//you need to keep the handler outside the runnable body to work in kotlinfun runDelayedHandler(timeToWait: Long) {
//Keep it runningval handler = Handler()handler.postDelayed(runnable, timeToWait)}

如果您找不到UIThread,您可以使用这种方式。

你当前的上下文意味着,您需要解析当前上下文

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

静态编程语言协程可以使您的代码更加简洁易读,如下所示:

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

反之亦然:

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

静态编程语言

我们必须以真实的方式使用UI线程。我们可以在静态编程语言中使用UI线程,例如:

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

静态编程语言中,只需将您的代码放在runOnUiThread活动方法中

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

如果您在一个片段中,那么您还需要获取活动对象,因为runOnUIThread是活动上的一个方法。

静态编程语言中的一个示例,其中包含一些周围的上下文以使其更清晰-此示例是从相机片段导航到库片段:

// Setup image capture listener which is triggered after photo has been takenimageCapture.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 threadactivity?.runOnUiThread( {Navigation.findNavController(requireActivity(), R.id.fragment_container).navigate(CameraFragmentDirections.actionCameraToGallery(outputDirectory.absolutePath))})

RunOnUIThread似乎对我不起作用,但以下内容最终解决了我的问题。

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

在Startup. Init方法中有ReactiveUI路由,需要在主线程上调用。此Invoke方法也比RunOnUIThread更好地接受async/wait。

因此,无论我需要在主线程上调用方法,我都使用this。

如果有人知道我不知道的事情,可以帮助我改进我的申请,请对此发表评论。

嗯,你可以这样做。

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

一个简单的方法

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

它还提供延迟

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

对于runOnUiThread()方法的单行版本,您可以使用lambda函数,即:

runOnUiThread(() -> doStuff(Object, myValue));

其中doStuff()可以表示用于修改某些UI对象的值(设置文本、更改颜色等)的某些方法。

我发现这在尝试更新多个UI对象时更加整洁,而不需要像投票最多的答案中提到的每个6行Runnable定义,这绝不是不正确的,它只是占用了更多的空间,我发现可读性较差。

所以这个:

runOnUiThread(new Runnable() {@Overridepublic void run() {doStuff(myTextView, "myNewText");}});

可以变成这样:

runOnUiThread(() -> doStuff(myTextView, "myNewText"));

doStuff的定义在别处。

或者,如果您不需要如此通用化,只需要设置TextView对象的文本:

runOnUiThread(() -> myTextView.setText("myNewText"));

对于任何使用片段的人:

(context as Activity).runOnUiThread {//TODO}

'Esseémeu codigo'

Timer timer = new Timer();timer.scheduleAtFixedRate(new TimerTask() {@Overridepublic void run() {getActivity().runOnUiThread(new Runnable() {@Overridepublic void run() {KeyValuePair<String, String> keyValuePair = getCalculateDifferenceBetweenTwoTimes();
CharSequence charSequence = getPresenterUtil().contact(getPresenterUtil().messageStyle("Check-in", Color.GRAY, Typeface.NORMAL, 0.9f),UtilPresenter.LINE,getPresenterUtil().messageStyle(keyValuePair.getKey(), Color.BLACK, Typeface.NORMAL, 1.3f),UtilPresenter.LINE,UtilPresenter.LINE,getPresenterUtil().messageStyle("Tempo de execução", Color.GRAY, Typeface.NORMAL, 0.9f),UtilPresenter.LINE,getPresenterUtil().messageStyle(keyValuePair.getValue(), Color.BLACK, Typeface.NORMAL, 1.3f));
if (materialDialog.getContentView() != null) {materialDialog.getContentView().setText(charSequence);}}});}}, 0, 1000);