csdnceshi69
YaoRaoLov
采纳率50%
2011-03-02 00:07

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

已采纳

I've built a simple music player in Android. The view for each song contains a SeekBar, implemented like this:

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

This works fine. Now I want a timer counting the seconds/minutes of the progress of the song. So I put a TextView in the layout, get it with findViewById() in onCreate(), and put this in run() after 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);

But that last line gives me the exception:

android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

Yet I'm doing basically the same thing here as I'm doing with the SeekBar - creating the view in onCreate, then touching it in run() - and it doesn't give me this complaint.

转载于:https://stackoverflow.com/questions/5161951/android-only-the-original-thread-that-created-a-view-hierarchy-can-touch-its-vi

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答

20条回答

  • csdnceshi74 7*4 10年前

    You have to move the portion of the background task that updates the UI onto the main thread. There is a simple piece of code for this:

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

    Documentation for Activity.runOnUiThread.

    Just nest this inside the method that is running in the background, and then copy paste the code that implements any updates in the middle of the block. Include only the smallest amount of code possible, otherwise you start to defeat the purpose of the background thread.

    点赞 22 评论 复制链接分享
  • csdnceshi76 斗士狗 8年前

    I solved this by putting 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();
    
    点赞 21 评论 复制链接分享
  • csdnceshi58 Didn"t forge 3年前

    You can use Handler to Delete View without disturbing the main UI Thread. Here is example code

    new Handler(Looper.getMainLooper()).post(new Runnable() {
                                                            @Override
                                                            public void run() {
                                                               //do stuff like remove view etc
                                                                adapter.remove(selecteditem);
                                                            }
                                                        });
    
    点赞 9 评论 复制链接分享
  • csdnceshi65 larry*wei 6年前

    This is the stack trace of mentioned 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)
    

    So if you go and dig then you come to know

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

    Where mThread is initialize in constructor like below

    mThread = Thread.currentThread();
    

    All I mean to say that when we created particular view we created it on UI Thread and later try to modifying in a Worker Thread.

    We can verify it via below code snippet

    Thread.currentThread().getName()
    

    when we inflate layout and later where you are getting exception.

    点赞 8 评论 复制链接分享
  • csdnceshi51 旧行李 4年前

    This is explicitly throwing an error. It says whichever thread created a view, only that can touch its views. It is because the created view is inside that thread's space. The view creation (GUI) happens in the UI (main) thread. So, you always use the UI thread to access those methods.

    Enter image description here

    In the above picture, the progress variable is inside the space of the UI thread. So, only the UI thread can access this variable. Here, you're accessing progress via new Thread(), and that's why you got an error.

    点赞 8 评论 复制链接分享
  • csdnceshi60 ℡Wang Yan 4年前

    I use Handler with Looper.getMainLooper(). It worked fine for me.

        Handler handler = new Handler(Looper.getMainLooper()) {
            @Override
            public void handleMessage(Message msg) {
                  // Any UI task, example
                  textView.setText("your text");
            }
        };
        handler.sendEmptyMessage(1);
    
    点赞 7 评论 复制链接分享
  • csdnceshi76 斗士狗 10年前

    I've been in this situation, but I found a solution with the Handler Object.

    In my case, I want to update a ProgressDialog with the observer pattern. My view implements observer and overrides the update method.

    So, my main thread create the view and another thread call the update method that update the ProgressDialop and....:

    Only the original thread that created a view hierarchy can touch its views.

    It's possible to solve the problem with the Handler Object.

    Below, different parts of my 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);
        }
    }
    

    This explanation can be found on this page, and you must read the "Example ProgressDialog with a second thread".

    点赞 7 评论 复制链接分享
  • csdnceshi69 YaoRaoLov 5年前

    Use this code, and no need to runOnUiThread function:

    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();
    }
    
    点赞 6 评论 复制链接分享
  • csdnceshi57 perhaps? 8年前

    I see that you have accepted @providence's answer. Just in case, you can also use the handler too! First, do the int fields.

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

    Next, make a handler instance as a field.

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

    Make a method.

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

    Finally, put this at onCreate() method.

    showHandler(true);
    
    点赞 5 评论 复制链接分享
  • csdnceshi57 perhaps? 4年前

    This happened to my when I called for an UI change from a doInBackground from Asynctask instead of using onPostExecute.

    Dealing with the UI in onPostExecute solved my problem.

    点赞 4 评论 复制链接分享
  • csdnceshi65 larry*wei 7年前

    My solution to this:

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

    Call this method on a background thread.

    点赞 3 评论 复制链接分享
  • csdnceshi59 ℙℕℤℝ 3年前

    I was working with a class that did not contain a reference to the context. So it was not possible for me to use runOnUIThread(); I used view.post(); and it was solved.

    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);
    
    点赞 2 评论 复制链接分享
  • csdnceshi63 elliott.david 6年前

    I had a similar issue, and my solution is ugly, but it works:

    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
    }
    
    点赞 1 评论 复制链接分享
  • csdnceshi68 local-host 10年前

    Usually, any action involving the user interface must be done in the main or UI thread, that is the one in which onCreate() and event handling are executed. One way to be sure of that is using runOnUiThread(), another is using Handlers.

    ProgressBar.setProgress() has a mechanism for which it will always execute on the main thread, so that's why it worked.

    See Painless Threading.

    点赞 1 评论 复制链接分享
  • csdnceshi61 derek5. 6年前

    If you do not want to use runOnUiThread API, you can in fact implement AsynTask for the operations that takes some seconds to complete. But in that case, also after processing your work in doinBackground(), you need to return the finished view in onPostExecute(). The Android implementation allows only main UI thread to interact with views.

    点赞 1 评论 复制链接分享
  • weixin_41568174 from.. 7年前

    For me the issue was that I was calling onProgressUpdate() explicitly from my code. This shouldn't be done. I called publishProgress() instead and that resolved the error.

    点赞 评论 复制链接分享
  • csdnceshi79 python小菜 3年前

    In my case, the caller calls too many times in short time will get this error, I simply put elpased time checking to do nothing if too short, e.g. ignore if function get called less than 0.5 second:

        private long mLastClickTime = 0;
    
        public boolean foo() {
            if ( (SystemClock.elapsedRealtime() - mLastClickTime) < 500) {
                return false;
            }
            mLastClickTime = SystemClock.elapsedRealtime();
    
            //... do ui update
        }
    
    点赞 评论 复制链接分享
  • weixin_41568131 10.24 5年前

    In my case, I have EditText in Adaptor, and it's already in the UI thread. However, when this Activity loads, it's crashes with this error.

    My solution is I need to remove <requestFocus /> out from EditText in XML.

    点赞 评论 复制链接分享
  • csdnceshi57 perhaps? 3年前

    When using AsyncTask Update the UI in onPostExecute method

        @Override
        protected void onPostExecute(String s) {
       // Update UI here
    
         }
    
    点赞 评论 复制链接分享
  • csdnceshi59 ℙℕℤℝ 3年前

    Solved : Just put this method in doInBackround Class... and pass the message

    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);
    
        }
    
    点赞 评论 复制链接分享