Sending a message from background thread to UI Thread in real time

205 views Asked by At

I am building a metronome in android with sound and a visual blink and of course both things need to be in sync. The problem is that the audio processing happens inside a background thread and as we all very well know "only the thread that created the thread hierarchy can change it's views" therefore I needed a way to get the UI thread to change the veiew. So how can I update the ui thread in sync with the beat I am producing? Both things don't seem synchronized. Is there a better way to achieve this than the method I depict below?

This is the current code I have:

public class MetronomeService extends Service implements Runnable
{
    //Thread
    private HandlerThread handlerThread;
    private Handler backgroundHandler;
    private Handler uiHandler
    
    //Interface to the Fragment that owns the views
    @Setter
    private Listener listener;
    
    //Colour view
    private ColourViewManager colour = null;    


    @Override
    public void onCreate()
    {
        handlerThread = new HandlerThread("PlayerHandlerThread", Process.THREAD_PRIORITY_AUDIO);
        handlerThread.start();
        
        //For packages of work in the background
        this.backgroundHandler = new Handler(handlerThread.getLooper());
        
        //To send messages to the UI
        this.uiHandler = new Handler(Looper.getMainLooper())
        {
            @Override
            public void handleMessage(@NonNull Message msg)
            {
                switch (msg.what)
                {
                    case 0:
                    {
                        listener.resetState();
                        break;
                    }
                    case 1:
                    {
                        colour = listener.blink();
                        break;
                    }
                }
            }
        };
        

        super.onCreate();
    }
    
    @Override
    public void run()
    {
        uiHandler.sendEmptyMessage(0);
        audioGenerator.playTrack(bpm);

        while (playingState.get() == TimerState.PLAYING)
        {                
            uiHandler.sendEmptyMessage(1);               

            if (colour == PulsingColourToggleButton.PulsingColour.YELLOW)
            {
                audioGenerator.writeSound(AudioGenerator.TICKING);

            } else if (colour == PulsingColourToggleButton.PulsingColour.RED)
            {
                audioGenerator.writeSound(AudioGenerator.TOCKING);
            }

            audioGenerator.writeSound(AudioGenerator.SILENCE);
        }

        audioGenerator.destroy();
        colour = null;
    }   
    
    
    public void start()
    {
        backgroundHandler.post(this);       
    }   
}

Then on the Fragment class I call the service with a simple

service.start()
1

There are 1 answers

3
Mohammad Khair On

I don't know what you are doing exactly but to change the view from service you need to use LocalBroadcast so check this

In your service create the LocalBradcastManager

val broadcaster = LocalBroadcastManager.getInstance(baseContext)

val intent = Intent("update")
intent.putExtra("orderId", orderId)
intent.putExtra("status", status)
intent.putExtra("data", date)
broadcaster.sendBroadcast(intent)

And in your view

val receiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        try {
            val orderId = intent.getStringExtra("orderId")
            val status = intent.getStringExtra("status")
            val date = intent.getStringExtra("date")

            if (model.details.order_information[0].id.toString() == orderId) {
                if ("Request Order".contains(status!!)) 
                    binding.requestTime.text = date

            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}

LocalBroadcastManager.getInstance(App.instance).registerReceiver(receiver, IntentFilter("update"))

This is an example of the logic you have to do