我一直在尝试在Android中写一个节拍器,但是我发现使用Handler postdelayed方法很难准确地同步节拍,我使用ScheduledThreadPoolExecutor实现了准确的定时,但是使用ScheduledThreadPoolExecutor,我无法在运行方法中控制定时。我使用ScheduledThreadPoolExecutor实现了精确的定时,但问题是,使用ScheduledThreadPoolExecutor,我不能在运行方法中控制定时,因此我被迫停止和启动预定任务,这并不理想。有没有办法让Handler postdelayed更准确? 或者有办法重新安排ScheduledThreadPoolExecutor的时间,而不用停止和启动线程?
我目前的代码如下。
public class Metronome extends Service implements Runnable
{
private Handler handler = new Handler();
private SoundPool soundPool;
private long interval;
private void initSoundPool()
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
{
soundPool = new SoundPool.Builder()
.setMaxStreams(1)
.setAudioAttributes(new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build())
.build();
} else
{
soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
}
soundId = soundPool.load(context, R.raw.secondary_clave, 1);
}
@Override
public void run()
{
handler.postDelayed(this, interval);
soundPool.play(soundId, 1, 1, 0, 0, 1);
}
public void start()
{
handler.post(this);
}
@Override
public IBinder onBind(Intent intent)
{
return null;
}
}
有了ScheduledThreadPoolExecutor,它是超级准确的,但是,我不能通过运行循环中的 "interval "标志来控制,所以如果我改变了间隔,我就必须终止执行器,并且每次需要重新安排的时候都要启动一个新的执行器,这太可怕了。
public class Metronome extends Service implements Runnable
{
private SoundPool soundPool;
private long interval;
private ScheduledThreadPoolExecutor beatsPerBarExec;
private ScheduledFuture<?> futureThread;
private void initSoundPool()
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
{
soundPool = new SoundPool.Builder()
.setMaxStreams(1)
.setAudioAttributes(new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build())
.build();
} else
{
soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
}
soundId = soundPool.load(context, R.raw.secondary_clave, 1);
}
@Override
public void run()
{
soundPool.play(soundId, 1, 1, 0, 0, 1);
}
public void start()
{
beatsPerBarExec = new ScheduledThreadPoolExecutor(1);
futureThread = beatsPerBarExec.scheduleAtFixedRate(this, 0, interval, TimeUnit.MILLISECONDS);
}
public void pause()
{
futureThread.cancel(false);
beatsPerBarExec.purge();
beatsPerBarExec = null;
}
@Override
public IBinder onBind(Intent intent)
{
return null;
}
}
你可能会看到漂移的影响。
例如:你希望你的 Runnable
每200毫秒运行一次。您可以重新安排您的 Runnable
在 run()
办法 postDelayed()
并将其传递200msec作为延迟。当 run()
方法时,它可能不会被调用。恰恰 200msec,从上一次。也许是210msec。现在你重新安排你的 Runnable
再以200毫秒的速度运行。这一次的 run()
方法可能会在210毫秒后再次被调用,这意味着你的声音从第一次开始播放420毫秒,等等。
为了消除漂移,你需要确定你想要的准确的时钟时间。Runnable
运行的时间,减去当前时间,并将其用于调用 postDelayed()
. 这将考虑到线程时序的任何潜在变化。
请注意,当你调用 postDelayed()
你发的是 Runnable
来运行在主线程(UI)上。这个线程负责处理所有的UI更新,当你的 Runnable
准备好了,就可以运行了。排队 到主(UI)线程处理程序,并且可能不会立即运行。
您可以通过调度您的 Runnable
在后台线程上运行,而不是在主线程(UI)上运行。然而这意味着你的 Runnable
会在后台线程上被调用,我不知道你的其他代码(播放声音)是否需要在主(UI)线程上运行。