Android“只有创build视图层次结构的原始线程才能触及其视图。”
我已经在Android中构build了一个简单的音乐播放器。 每首歌曲的视图都包含一个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 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); } }
这工作正常。 现在我想要一个计时器来计算歌曲进度的秒/分钟。 所以我把一个TextView
放在布局中,用onCreate()
findViewById()
来获得它,然后在progress.setProgress(pos)
之后把它放在run()
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);
但最后一行给我例外:
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
然而,我在这里做的基本上是一样的事情,因为我在做SeekBar
– 在onCreate
创build视图,然后在run()
触摸它 – 并没有给我这个抱怨。 那么这里是否有明显的错误? 我对Android和Java都是新手,所以如果它是愚蠢的,我不会感到惊讶。
您必须将更新ui的后台任务部分移到主线程上。 这里有一个简单的代码:
runOnUiThread(new Runnable() { @Override public void run() { //stuff that updates ui } });
Activity.runOnUiThread
文档
只要在后台运行的方法中嵌套这个,然后将实现任何更新的代码复制粘贴到块的中间。 只包括尽可能less的代码量,否则就开始挫败后台线程的目的。
我解决了这个问题,把runOnUiThread( new Runnable(){ ..
inside 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();
我的解决scheme是:
private void setText(final TextView text,final String value){ runOnUiThread(new Runnable() { @Override public void run() { text.setText(value); } }); }
在后台线程上调用这个方法
通常,涉及用户界面的任何操作都必须在主线程或UI线程中完成,也就是执行onCreate()
和事件处理的那个线程。 一种确定的方法是使用runOnUiThread() ,另一种方法是使用Handlers。
ProgressBar.setProgress()
有一个机制,它总是在主线程上执行,所以这就是为什么它工作。
看无痛线程 。
我一直在这种情况下,但我find了处理程序对象的解决scheme。
在我的情况下,我想用观察者模式更新ProgressDialog。 我的视图实现观察者并重写更新方法。
所以,我的主线程创build视图,另一个线程调用更新ProgressDialop和….的方法:
Only the original thread that created a view hierarchy can touch its views.
处理器对象可以解决这个问题。
下面,我的代码的不同部分:
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); } }
这个解释可以在这个页面上find,你必须阅读“第二个线程的例子ProgressDialog”。
我看到你已经接受了@普罗斯的答案。 以防万一,你也可以使用处理程序! 首先,做int字段。
private static final int SHOW_LOG = 1; private static final int HIDE_LOG = 0;
接下来,将一个处理程序实例作为一个字段。
//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; } } } };
做一个方法。
//TODO __________[ Callbacks ]__________ @Override public void showHandler(boolean show) { handler.sendEmptyMessage(show ? SHOW_LOG : HIDE_LOG); }
最后,把这个onCreate()
方法。
showHandler(true);
我有类似的问题,我的解决scheme是丑陋的,但它
void showCode() { hideRegisterMessage(); // Hides view final Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { showRegisterMessage(); //Shows view } }, 3000); // after 3 sec }
这是明确地抛出一个错误。 它说任何线索创造了一个观点,只有这个观点可以触及它的观点。 这是因为创build的视图是在该线程的空间内。 视图创build(GUI)发生在UI(主)线程中。 所以,你总是使用UI线程来访问这些方法。
在上图中,进度variables在UI Thread的空间内。 所以,只有UI线程可以访问这个variables。 在这里,你通过新的线程()访问进度,这就是为什么你有错误。
使用这个代码,并且不需要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(); }
我使用Handler
与Looper.getMainLooper()
它为我工作得很好
Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { // Any UI task, example textView.setText("your text"); } }; handler.sendEmptyMessage(1);
这是提到的exception的堆栈跟踪
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();
我的意思是说,当我们创build特定的视图时,我们在UI线程上创build了它,然后尝试在工作线程中进行修改。
我们可以通过下面的代码片段来validation它
Thread.currentThread().getName()
当我们膨胀布局,然后在哪里得到例外。
如果你不想使用runOnUiThread
API,你可以AsynTask
实现AsynTask
来完成需要几秒钟的操作。但是在这种情况下,在doinBackground()
处理完你的工作之后,你需要返回onPostExecute()
的成品视图。 Android实现只允许主UI线程与视图交互。
谢谢
对我来说,问题是我从代码中明确地调用了onProgressUpdate()
。 这不应该做。 我调用了publishProgress()
来解决这个错误。
在我的情况下,我在适配器中有EditText
,并且它已经在UI线程中,但是当这个活动加载时,这个错误会崩溃。
我的解决scheme是我需要从XML中的EditText中删除<requestFocus />
我知道我迟到了派对,但是这发生在我从Asynctask
的doInBackground
调用UI更改,而不是使用onPostExecute
。
处理onPostExecute
的用户界面解决了我的问题。