准确的安卓线程处理程序postDelayed。

问题描述 投票:0回答:1

我一直在尝试在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;
    }
}
java android multithreading threadpool
1个回答
1
投票

你可能会看到漂移的影响。

例如:你希望你的 Runnable 每200毫秒运行一次。您可以重新安排您的 Runnablerun() 办法 postDelayed() 并将其传递200msec作为延迟。当 run() 方法时,它可能不会被调用。恰恰 200msec,从上一次。也许是210msec。现在你重新安排你的 Runnable 再以200毫秒的速度运行。这一次的 run() 方法可能会在210毫秒后再次被调用,这意味着你的声音从第一次开始播放420毫秒,等等。

为了消除漂移,你需要确定你想要的准确的时钟时间。Runnable 运行的时间,减去当前时间,并将其用于调用 postDelayed(). 这将考虑到线程时序的任何潜在变化。

请注意,当你调用 postDelayed() 你发的是 Runnable 来运行在主线程(UI)上。这个线程负责处理所有的UI更新,当你的 Runnable 准备好了,就可以运行了。排队 到主(UI)线程处理程序,并且可能不会立即运行。

您可以通过调度您的 Runnable 在后台线程上运行,而不是在主线程(UI)上运行。然而这意味着你的 Runnable 会在后台线程上被调用,我不知道你的其他代码(播放声音)是否需要在主(UI)线程上运行。

© www.soinside.com 2019 - 2024. All rights reserved.