我在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()中触摸它——它没有给我这个抱怨。
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。
所以我需要在主线程上调用方法的任何地方都使用这个。
请评论这一点,如果有人知道一些我不知道的,可以帮助我提高我的申请。
我曾经遇到过这种情况,但我用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”。
对于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"));