简体   繁体   English

准确的 android 线程处理程序 postDelayed

[英]Accurate android Thread Handler postDelayed

I have been trying to write a metronome in Android however I am finding really hard to sync the beats accurately using the Handler postdelayed method.我一直在尝试在 Android 中编写节拍器,但是我发现使用 Handler postdelayed 方法很难准确地同步节拍。 I manage to achieve an accurate timing using a ScheduledThreadPoolExecutor, but the issue is that with the ScheduledThreadPoolExecutor I can't control the timing from within the run method and therefore I am forced to stop and start the scheduled job, which is not ideal.我设法使用 ScheduledThreadPoolExecutor 实现了准确的计时,但问题是使用 ScheduledThreadPoolExecutor 我无法从 run 方法中控制计时,因此我被迫停止并启动计划的作业,这并不理想。 Is there a way to make the Handler postdelayed more accurate?有没有办法让 Handler postdelayed 更准确? or a way to reschedule the ScheduledThreadPoolExecutor without having to stop and start the thread?或者一种重新调度 ScheduledThreadPoolExecutor 而不必停止和启动线程的方法?

My current code is as below:我当前的代码如下:

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

With the ScheduledThreadPoolExecutor it's super accurate, however, I don't have control via the "interval" flag inside the run loop, so if I change the interval I have to terminate the executor and start a new one everytime I need to rechedule which is horrible.使用 ScheduledThreadPoolExecutor 它非常准确,但是,我无法通过运行循环中的“间隔”标志进行控制,所以如果我更改间隔,我必须终止执行器并在每次需要重新调度时启动一个新的执行器可怕。

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

You may be seeing the effects of drift.您可能会看到漂移的影响。

Example: you want your Runnable to run every 200msec.示例:您希望Runnable每 200 毫秒运行一次。 You reschedule your Runnable in the run() method using postDelayed() and pass it 200msec as the delay.您使用postDelayed()run()方法中重新安排Runnable并将其传递 200 毫秒作为延迟。 When the run() method is called the next time, it may not be exactly 200msec since the previous time.下次调用run()方法时,距离上一次可能不正好是200 毫秒。 Perhaps it is 210msec.也许是 210 毫秒。 Now you reschedule your Runnable to run in another 200msec.现在你重新安排你的Runnable再运行 200 毫秒。 This time the run() method may be called again after 210 msec, which means your sound plays 420msec since the first one, etc.这次可能会在 210 毫秒后再次调用run()方法,这意味着您的声音自第一次播放以来播放了 420 毫秒,依此类推。

To eliminate drift, you need to determine the exact clock time you want the Runnable to run at, subtract the current time and use that in the call to postDelayed() .为了消除漂移,您需要确定您希望Runnable运行的确切时钟时间,减去当前时间并在对postDelayed()的调用中使用该时间。 This will take into account any potential variance in the thread timing.这将考虑线程计时中的任何潜在差异。

Be aware that when you call postDelayed() you are posting a Runnable to run on the main (UI) thread.请注意,当您调用postDelayed()时,您将发布一个Runnable以在主 (UI) 线程上运行。 This is the thread that handles all the UI updates and when your Runnable is ready to run it will just be queued to the main (UI) thread handler, and may not run immediately.这是处理所有 UI 更新的线程,当您的Runnable准备好运行时,它只会排队到主 (UI) 线程处理程序,并且可能不会立即运行。

You can mitigate this problem by scheduling your Runnable to run on a background thread instead of the main (UI) thread.您可以通过安排Runnable在后台线程而不是主 (UI) 线程上运行来缓解此问题。 However this means your Runnable will be called on the background thread and I don't know if your other code (that plays the sound) needs to run on the main (UI) thread or not.但是,这意味着您的Runnable将在后台线程上被调用,我不知道您的其他代码(播放声音)是否需要在主(UI)线程上运行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM