Android Chỉ có chủ đề ban đầu tạo phân cấp chế độ xem mới có thể chạm vào chế độ xem của nó.


940

Tôi đã xây dựng một trình phát nhạc đơn giản trong Android. Chế độ xem cho mỗi bài hát chứa SeekBar, được triển khai như sau:

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

Điều này hoạt động tốt. Bây giờ tôi muốn một bộ đếm thời gian đếm giây / phút tiến trình của bài hát. Vì vậy, tôi đặt một TextViewbố cục, đưa nó findViewById()vào onCreate()và đặt nó vào run()sau 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);

Nhưng dòng cuối cùng cho tôi ngoại lệ:

android.view.ViewRoot $ CalledFromWrongThreadException: Chỉ chủ đề ban đầu tạo phân cấp chế độ xem mới có thể chạm vào chế độ xem của nó.

Tuy nhiên, về cơ bản, tôi đang làm điều tương tự như tôi đang làm với SeekBar- tạo ra chế độ xem onCreate, sau đó chạm vào nó run()- và nó không cho tôi khiếu nại này.

Câu trả lời:


1894

Bạn phải di chuyển phần của tác vụ nền cập nhật giao diện người dùng lên luồng chính. Có một đoạn mã đơn giản cho việc này:

runOnUiThread(new Runnable() {

    @Override
    public void run() {

        // Stuff that updates the UI

    }
});

Tài liệu cho Activity.runOnUiThread.

Chỉ cần lồng cái này vào bên trong phương thức đang chạy trong nền, sau đó sao chép và dán mã thực hiện bất kỳ cập nhật nào ở giữa khối. Chỉ bao gồm số lượng mã nhỏ nhất có thể, nếu không bạn bắt đầu đánh bại mục đích của luồng nền.


5
làm việc như người ở. Đối với tôi, vấn đề duy nhất ở đây là tôi muốn thực hiện một error.setText(res.toString());phương thức bên trong run (), nhưng tôi không thể sử dụng độ phân giải vì nó không phải là cuối cùng .. quá tệ
noloman

64
Một nhận xét ngắn gọn về điều này. Tôi đã có một luồng riêng đang cố gắng sửa đổi giao diện người dùng và đoạn mã trên đã hoạt động, nhưng tôi đã gọi runOnUiThread từ đối tượng Activity. Tôi đã phải làm một cái gì đó như myActivityObject.runOnUiThread(etc)
Kirby

1
@Kirby Cảm ơn bạn đã tham khảo. Bạn chỉ có thể thực hiện 'MainActivity.this' và nó cũng hoạt động tốt để bạn không phải tiếp tục tham khảo lớp hoạt động của mình.
JRomero

24
Phải mất một thời gian tôi mới nhận ra đó runOnUiThread()là một phương pháp Hoạt động. Tôi đã chạy mã của tôi trong một đoạn. Tôi đã kết thúc việc làm getActivity().runOnUiThread(etc)và nó đã làm việc. Tuyệt diệu!;
lejonl

Chúng ta có thể dừng nhiệm vụ đang được thực hiện được viết trong phần thân của phương thức 'runOnUiThread' không?
Karan Sharma

143

Tôi đã giải quyết điều này bằng cách đặt runOnUiThread( new Runnable(){ ..vào bên trong 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();

2
Cái này làm rung chuyển. Cảm ơn thông tin rằng, điều này cũng có thể được sử dụng bên trong bất kỳ chủ đề khác.
Nabin

Cảm ơn bạn, thật buồn khi tạo một chủ đề để quay lại Giao diện người dùng nhưng chỉ có giải pháp này mới cứu được trường hợp của tôi.
Pierre Maoui 04/05/2015

2
Một khía cạnh quan trọng wait(5000);là không nằm trong Runnable, nếu không UI của bạn sẽ đóng băng trong thời gian chờ. Bạn nên cân nhắc sử dụng AsyncTaskthay vì Thread cho các hoạt động như thế này.
Martin

điều này thật tệ cho việc rò rỉ bộ nhớ
Rafael Lima

Tại sao phải bận tâm với khối đồng bộ? Mã bên trong nó trông có vẻ hợp lý an toàn (mặc dù tôi đã chuẩn bị đầy đủ để ăn lời của tôi).
David

69

Giải pháp của tôi cho việc này:

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

Gọi phương thức này trên một chủ đề nền.


Lỗi: (73, 67) lỗi: Tập phương thức không tĩnh (Chuỗi) không thể được tham chiếu từ ngữ cảnh tĩnh

1
Tôi có cùng một vấn đề với các lớp kiểm tra của tôi. Điều này làm việc như một cơ duyên đối với tôi. Tuy nhiên, thay thế runOnUiThreadbằng runTestOnUiThread. Cảm ơn
DaddyMoe

28

Thông thường, bất kỳ hành động nào liên quan đến giao diện người dùng phải được thực hiện trong luồng chính hoặc giao diện người dùng, đó là hành động trong đó onCreate()và xử lý sự kiện được thực thi. Một cách để chắc chắn đó là sử dụng runOnUiThread () , một cách khác là sử dụng Trình xử lý.

ProgressBar.setProgress() có một cơ chế mà nó sẽ luôn luôn thực thi trên luồng chính, vì vậy đó là lý do tại sao nó hoạt động.

Xem luồng không đau .


Bài viết Threading không đau tại liên kết đó hiện là 404. Đây là liên kết đến một bài viết trên blog (cũ hơn?) Về Threading không đau - android-developers.blogspot.com/2009/05/p
Tony Adams

20

Tôi đã ở trong tình huống này, nhưng tôi đã tìm thấy một giải pháp với Đối tượng xử lý.

Trong trường hợp của tôi, tôi muốn cập nhật một ProgressDialog với mẫu quan sát viên . Quan điểm của tôi thực hiện quan sát và ghi đè phương thức cập nhật.

Vì vậy, luồng chính của tôi tạo ra khung nhìn và một luồng khác gọi phương thức cập nhật cập nhật ProgressDialop và ....:

Chỉ có chủ đề ban đầu tạo ra một hệ thống phân cấp khung nhìn có thể chạm vào các khung nhìn của nó.

Có thể giải quyết vấn đề với Đối tượng xử lý.

Dưới đây, các phần khác nhau của mã của tôi:

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

Có thể tìm thấy lời giải thích này trên trang này và bạn phải đọc "Ví dụ ProgressDialog với một luồng thứ hai".


10

Bạn có thể sử dụng Trình xử lý để xóa Chế độ xem mà không làm phiền Chủ đề giao diện người dùng chính. Đây là mã ví dụ

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

7

Tôi thấy rằng bạn đã chấp nhận câu trả lời của @ providence. Chỉ trong trường hợp, bạn cũng có thể sử dụng xử lý quá! Đầu tiên, làm các trường int.

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

Tiếp theo, tạo một ví dụ xử lý như một trường.

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

Thực hiện một phương pháp.

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

Cuối cùng, đặt điều này tại onCreate()phương thức.

showHandler(true);

7

Tôi đã có một vấn đề tương tự, và giải pháp của tôi là xấu, nhưng nó hoạt động:

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
}

2
@ R.jzadeh thật tuyệt khi nghe điều đó. Kể từ giây phút tôi viết câu trả lời đó, có lẽ bây giờ bạn có thể làm điều đó tốt hơn :)
Błażej

6

Tôi sử dụng Handlervới Looper.getMainLooper(). Nó làm việc tốt cho tôi.

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

5

Sử dụng mã này và không cần phải runOnUiThreadhoạt động:

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

5

Đây rõ ràng là một lỗi ném. Nó nói bất cứ chủ đề nào tạo ra một khung nhìn, chỉ có điều đó có thể chạm vào các khung nhìn của nó. Đó là bởi vì khung nhìn được tạo nằm trong không gian của luồng đó. Việc tạo chế độ xem (GUI) xảy ra trong luồng UI (chính). Vì vậy, bạn luôn sử dụng luồng UI để truy cập các phương thức đó.

Nhập mô tả hình ảnh ở đây

Trong hình trên, biến tiến trình nằm trong khoảng trống của luồng UI. Vì vậy, chỉ có giao diện người dùng có thể truy cập biến này. Tại đây, bạn đang truy cập tiến trình thông qua Thread () mới và đó là lý do tại sao bạn gặp lỗi.


4

Điều này đã xảy ra với tôi khi tôi kêu gọi thay đổi giao diện người dùng doInBackgroundtừ Asynctaskthay vì sử dụng onPostExecute.

Xử lý UI trong việc onPostExecutegiải quyết vấn đề của tôi.


1
Cảm ơn Jonathan. Đây cũng là vấn đề của tôi nhưng tôi phải đọc thêm một chút để hiểu ý của bạn ở đây. Đối với bất kỳ ai khác, onPostExecutecũng là một phương thức AsyncTasknhưng nó chạy trên luồng UI. Xem tại đây: blog.teamtreehouse.com/all-about-android-asynct nhiệm vụ
ciaranodc

4

Kotlin coroutines có thể làm cho mã của bạn ngắn gọn và dễ đọc hơn như thế này:

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

Hoặc ngược lại:

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

3

Tôi đã làm việc với một lớp không chứa tài liệu tham khảo đến bối cảnh. Vì vậy, tôi không thể sử dụng runOnUIThread();tôi đã sử dụng view.post();và nó đã được giải quyết.

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

Sự tương tự audioMessagetvPlayDurationmã câu hỏi là gì?
gotwo

audioMessagelà một đối tượng chủ của chế độ xem văn bản. tvPlayDurationlà chế độ xem văn bản mà chúng tôi muốn cập nhật từ một luồng không phải UI. Trong câu hỏi trên, currentTimelà chế độ xem văn bản nhưng nó không có đối tượng chủ sở hữu.
Ifta

3

Khi sử dụng AsyncTask Cập nhật giao diện người dùng theo phương thức onPostExecute

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

     }

điều này đã xảy ra với tôi tôi đã cập nhật ui trong doinbackground của nhiệm vụ asynk.
mehmoodnisar125

3

Tôi đã phải đối mặt với một vấn đề tương tự và không có phương pháp nào được đề cập ở trên có hiệu quả với tôi. Cuối cùng, điều này đã tạo ra mánh khóe cho tôi:

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

Tôi tìm thấy viên ngọc này ở đây .


2

Đây là dấu vết ngăn xếp của ngoại lệ được đề cập

        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)

Vì vậy, nếu bạn đi và đào thì bạn sẽ biết

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

Trong đó mThread được khởi tạo trong hàm tạo như bên dưới

mThread = Thread.currentThread();

Tất cả những gì tôi muốn nói là khi chúng tôi tạo chế độ xem cụ thể, chúng tôi đã tạo nó trên Giao diện người dùng và sau đó cố gắng sửa đổi trong Chủ đề Công nhân.

Chúng tôi có thể xác minh nó thông qua đoạn mã dưới đây

Thread.currentThread().getName()

khi chúng tôi tăng bố cục và sau này, nơi bạn đang nhận được ngoại lệ.


2

Nếu bạn không muốn sử dụng runOnUiThreadAPI, trên thực tế bạn có thể thực hiện AsynTaskcho các hoạt động mất vài giây để hoàn thành. Nhưng trong trường hợp đó, cũng sau khi xử lý công việc của bạn doinBackground(), bạn cần trả về chế độ xem hoàn thành onPostExecute(). Việc triển khai Android chỉ cho phép luồng UI chính tương tác với các khung nhìn.


2

Nếu bạn chỉ đơn giản muốn vô hiệu hóa (gọi hàm repaint / vẽ lại) từ Chủ đề không phải UI của bạn, hãy sử dụng postInvalidate ()

myView.postInvalidate();

Điều này sẽ gửi một yêu cầu không hợp lệ trên luồng UI.

Để biết thêm thông tin: what-does-postinvalidate-do


1

Đối với tôi vấn đề là tôi đã gọi onProgressUpdate()một cách rõ ràng từ mã của tôi. Điều này không nên được thực hiện. Tôi gọi publishProgress()thay và điều đó đã giải quyết lỗi.


1

Trong trường hợp của tôi, tôi có EditTexttrong Adaptor và nó đã có trong giao diện người dùng. Tuy nhiên, khi Hoạt động này tải, nó gặp sự cố với lỗi này.

Giải pháp của tôi là tôi cần xóa <requestFocus />khỏi EditText trong XML.


1

Đối với những người đấu tranh ở Kotlin, nó hoạt động như thế này:

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

0

Đã giải quyết: Chỉ cần đặt phương thức này vào lớp DoInBackround ... và truyền thông báo

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

    }

0

Trong trường hợp của tôi, người gọi gọi quá nhiều lần trong thời gian ngắn sẽ gặp lỗi này, tôi chỉ cần đặt thời gian trôi qua để kiểm tra không làm gì nếu quá ngắn, ví dụ bỏ qua nếu hàm được gọi dưới 0,5 giây:

    private long mLastClickTime = 0;

    public boolean foo() {
        if ( (SystemClock.elapsedRealtime() - mLastClickTime) < 500) {
            return false;
        }
        mLastClickTime = SystemClock.elapsedRealtime();

        //... do ui update
    }

Giải pháp tốt hơn sẽ là vô hiệu hóa nút bấm và kích hoạt lại khi hành động hoàn tất.
từ

@lsrom Trong trường hợp của tôi không đơn giản vì người gọi là thư viện bên thứ 3 nằm ngoài tầm kiểm soát của tôi.
Trái cây

0

Nếu bạn không thể tìm thấy UIThread, bạn có thể sử dụng cách này.

yourciversecontext có nghĩa là, bạn cần phân tích ngữ cảnh hiện tại

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

0

Câu trả lời của Kotlin

Chúng ta phải sử dụng UI Thread cho công việc một cách chân thực. Chúng tôi có thể sử dụng UI Thread trong Kotlin:

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

@canerkaseler


0

Trong Kotlin chỉ cần đặt mã của bạn vào phương thức hoạt động runOnUiThread

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)
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.