我在 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 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()
中:
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()
中触摸它 - 它并没有给我这个抱怨。
您必须将更新 UI 的后台任务部分移动到主线程上。为此有一段简单的代码:
runOnUiThread(new Runnable() {
@Override
public void run() {
// Stuff that updates the UI
}
});
只需将其嵌套在后台运行的方法中,然后将实现任何更新的代码复制粘贴到块的中间。尽可能只包含最少量的代码,否则你会开始破坏后台线程的目的。
我通过将 runOnUiThread( new Runnable(){ ..
放入 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();
wait(5000);
不在 Runnable 中,否则您的 UI 将在等待期间冻结。对于此类操作,您应该考虑使用 AsyncTask
而不是 Thread。
我对此的解决方案:
private void setText(final TextView text,final String value){
runOnUiThread(new Runnable() {
@Override
public void run() {
text.setText(value);
}
});
}
在后台线程上调用此方法。
runOnUiThread
替换为 runTestOnUiThread
。谢谢
Kotlin 协程可以让你的代码更简洁易读,如下所示:
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 线程中完成,即执行 onCreate()
和事件处理的线程。一种确定方法是使用 runOnUiThread(),另一种方法是使用处理程序。
ProgressBar.setProgress()
有一种机制,它总是在主线程上执行,所以这就是它起作用的原因。
我一直处于这种情况,但我找到了处理程序对象的解决方案。
就我而言,我想用 observer pattern 更新 ProgressDialog。我的视图实现了观察者并覆盖了更新方法。
因此,我的主线程创建视图,另一个线程调用更新 ProgressDialop 的 update 方法和....:
只有创建视图层次结构的原始线程才能接触其视图。
处理程序对象可以解决问题。
下面,我的代码的不同部分:
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 page 上找到,您必须阅读“带有第二个线程的示例 ProgressDialog”。
您可以使用 Handler 来删除视图,而不会干扰主 UI 线程。这是示例代码
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
//do stuff like remove view etc
adapter.remove(selecteditem);
}
});
科特林答案
我们必须以真正的方式使用 UI Thread 来完成这项工作。我们可以在 Kotlin 中使用 UI Thread,例如:
runOnUiThread(Runnable {
//TODO: Your job is here..!
})
我看到你已经接受了@providence 的回答。以防万一,您也可以使用处理程序!首先,做 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);
使用此代码,无需 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();
}
我有一个类似的问题,我的解决方案很丑,但它有效:
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
}
我遇到了类似的问题,上面提到的方法都不适合我。最后,这对我有用:
Device.BeginInvokeOnMainThread(() =>
{
myMethod();
});
我找到了这颗宝石 here。
我将 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);
这是明确抛出错误。它说无论哪个线程创建了一个视图,只有那个可以触及它的视图。这是因为创建的视图位于该线程的空间内。视图创建 (GUI) 发生在 UI(主)线程中。因此,您总是使用 UI 线程来访问这些方法。
https://i.stack.imgur.com/rTJZq.png
在上图中,进度变量在 UI 线程的空间内。所以,只有 UI 线程可以访问这个变量。在这里,您正在通过 new Thread() 访问进度,这就是您遇到错误的原因。
当我从 Asynctask
而不是使用 onPostExecute
调用从 doInBackground
更改 UI 时,这发生在我身上。
处理 onPostExecute
中的 UI 解决了我的问题。
onPostExecute
也是 AsyncTask
的一种方法,但它在 UI 线程上运行。见这里:blog.teamtreehouse.com/all-about-android-asynctasks
我正在使用一个不包含对上下文的引用的类。所以我无法使用 runOnUIThread();
我使用了 view.post();
并解决了。
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);
audioMessage
和 tvPlayDuration
与问题代码有何相似之处?
audioMessage
是文本视图的持有者对象。 tvPlayDuration
是我们要从非 UI 线程更新的文本视图。在上面的问题中,currentTime
是文本视图,但它没有持有者对象。
使用 AsyncTask 时更新 onPostExecute 方法中的 UI
@Override
protected void onPostExecute(String s) {
// Update UI here
}
这是提到的异常的堆栈跟踪
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 线程上创建它,然后尝试在工作线程中进行修改。
我们可以通过下面的代码片段来验证它
Thread.currentThread().getName()
当我们膨胀布局时,稍后您会遇到异常。
如果您不想使用 runOnUiThread
API,您实际上可以为需要几秒钟才能完成的操作实现 AsynTask
。但在这种情况下,同样在 doinBackground()
中处理完您的工作后,您需要在 onPostExecute()
中返回完成的视图。 Android 实现只允许主 UI 线程与视图交互。
对于 runOnUiThread()
方法的单行版本,您可以使用 lambda 函数,即:
runOnUiThread(() -> doStuff(Object, myValue));
其中 doStuff()
可以表示一些用于修改某些 UI 对象的值的方法(设置文本、更改颜色等)。
我发现这在尝试更新多个 UI 对象时更简洁,而不需要在每个 the most upvoted answer 中提到的 6 行 Runnable 定义,这绝不是不正确的,它只是占用更多空间,我发现可读性差。
所以这:
runOnUiThread(new Runnable() {
@Override
public void run() {
doStuff(myTextView, "myNewText");
}
});
可以变成这样:
runOnUiThread(() -> doStuff(myTextView, "myNewText"));
doStuff 的定义在别处。
或者如果你不需要这么通用,只需要设置一个 TextView 对象的文本:
runOnUiThread(() -> myTextView.setText("myNewText"));
如果您只是想从非 UI 线程中无效(调用重绘/重绘函数),请使用 postInvalidate()
myView.postInvalidate();
这将在 UI 线程上发布一个无效请求。
欲了解更多信息:what-does-postinvalidate-do
好吧,你可以这样做。
https://developer.android.com/reference/android/view/View#post(java.lang.Runnable)
一个简单的方法
currentTime.post(new Runnable(){
@Override
public void run() {
currentTime.setText(time);
}
}
它还提供延迟
https://developer.android.com/reference/android/view/View#postDelayed(java.lang.Runnable,%20long)
对我来说,问题是我从我的代码中显式调用了 onProgressUpdate()
。不应该这样做。我改为调用 publishProgress()
并解决了错误。
在我的例子中,我在 Adaptor 中有 EditText
,它已经在 UI 线程中。但是,当这个 Activity 加载时,它会因为这个错误而崩溃。
我的解决方案是我需要从 XML 中的 EditText 中删除 <requestFocus />
。
对于在 Kotlin 中苦苦挣扎的人来说,它是这样工作的:
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)
}
如果你找不到 UIThread 你可以使用这种方式。
yourcurrentcontext 的意思是,你需要解析当前上下文
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();
如果您在一个片段内,那么您还需要获取活动对象,因为 runOnUIThread 是活动上的一个方法。
Kotlin 中的一个示例,其中包含一些周围的上下文,以使其更清晰 - 这个示例是从相机片段导航到画廊片段:
// Setup image capture listener which is triggered after photo has been taken
imageCapture.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 thread
activity?.runOnUiThread( {
Navigation.findNavController(
requireActivity(), R.id.fragment_container
).navigate(CameraFragmentDirections
.actionCameraToGallery(outputDirectory.absolutePath))
})
对于任何使用片段的人:
(context as Activity).runOnUiThread {
//TODO
}
已解决:只需将此方法放在 doInBackround 类中...并传递消息
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.5 秒,则忽略:
private long mLastClickTime = 0;
public boolean foo() {
if ( (SystemClock.elapsedRealtime() - mLastClickTime) < 500) {
return false;
}
mLastClickTime = SystemClock.elapsedRealtime();
//... do ui update
}
不定期副业成功案例分享
error.setText(res.toString());
,但我不能使用 res 因为它不是最终的......太糟糕了myActivityObject.runOnUiThread(etc)
之类的事情runOnUiThread()
是 Activity 的一种方法。我在一个片段中运行我的代码。我最终做了getActivity().runOnUiThread(etc)
,它奏效了。极好的!;