我在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在布局,得到它与findViewById()在onCreate(),并把这个在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。只有创建视图层次结构的原始线程才能触及它的视图。
然而,我在这里所做的事情基本上与我在SeekBar中所做的事情相同——在onCreate中创建视图,然后在run()中触摸它——它没有给我这个抱怨。
我曾经遇到过这种情况,但我用Handler对象找到了解决方案。
在我的例子中,我想用观察者模式更新一个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() {
@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);
}
}
这个解释可以在这个页面上找到,你必须阅读“示例ProgressDialog with a second thread”。
我看到你已经接受了@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);
这是上述异常的堆栈跟踪
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是活动上的一个方法。
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))
})
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);
}
});
在启动过程中。Init方法有ReactiveUI路由,这需要在主线程上调用。这个Invoke方法也比RunOnUIThread更好地接受async/await。
所以我需要在主线程上调用方法的任何地方都使用这个。
请评论这一点,如果有人知道一些我不知道的,可以帮助我提高我的申请。
对于runOnUiThread()方法的一行程序版本,您可以使用lambda函数,即:
runOnUiThread(() -> doStuff(Object, myValue));
其中doStuff()可以表示一些用于修改某些UI对象值的方法(设置文本,改变颜色等)。
我发现当尝试更新多个UI对象而不需要每个对象都有6行Runnable定义时,这要整洁得多,就像投票最多的答案中提到的那样,这绝不是不正确的,它只是占用了更多的空间,而且我发现可读性较差。
所以这个:
runOnUiThread(new Runnable() {
@Override
public void run() {
doStuff(myTextView, "myNewText");
}
});
可以变成这样:
runOnUiThread(() -> doStuff(myTextView, "myNewText"));
doStuff的定义在别处。
或者如果你不需要这么一般化,只需要设置TextView对象的文本:
runOnUiThread(() -> myTextView.setText("myNewText"));
“这是我的代码”
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
getActivity().runOnUiThread(new Runnable() {
@Override
public 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);