Monday, May 28, 2018

Android thread coordination and network retry

While we have no shortage of API's and data-structures on a moderne Android stack and generally should favor those, occasionally we need to resort to low-level Java constructs to get work done - this is particularly true for thread and concurrency stuff.

Main thread waiting for background thread

On Android, if on background/worker thread, while you need to schedule work for main/UI thread and wait for it to complete before doing other work on background/worker thread:


class SomeBackgroundWorker{
    

    final CountDownLatch latch = new CountDownLatch(1); 
    
    private void someBackgroundWorkerMethod(){ 
    
        // 1) Post work to UI thread 
        activity.runOnUiThread(new Runnable() { 
                @Override public void run() { 
                    try { 
                        // 1) Do some where here on UiThread 
                    } finally { 
                        // 2) Once done on UiThread, instruct worker to continue 
                        latch.countDown(); 
                    
                }
        }); 
        // Worker thread will now wait for the work on UiThread to complete 

        try { 
            latch.await(); 
        } catch (InterruptedException e) { 
            throw new RuntimeException(msg, e); 
        
        // 3) Worker thread continues here after UiThread completed

    
}

Background thread waiting for main thread

On Android, if on a background/worker thread, while you need to schedule work for the main/UI thread and wait for it to complete before doing other work on background/worker thread:

public class SomeBackgroundWorker { 
    private final Object lock = new Object();


    // Handler for UiThread to signal continuation 

    public void carryOn() { 
        // Inform the waiting thread that we are ready to try again
        synchronized(lock) { 
            lock.notifyAll(); 
        
    

    private void someBackgroundWorkerMethod() { 
        // Make the threat wait until UiThread calls carryOn 
        try { 
            synchronized(lock) { 
                lock.wait(); 
            
        } catch (InterruptedException interruptedException) {
            Thread.currentThread().interrupt(); 
        
    } 
}



Practical concerns and usages

Doing these kind of thread coordination tasks at such a low-level and should generally be avoided in favor of higher abstractions such as using the dedicated data-structures and architectural API's (LiveData) but occasionally this becomes extremely handy.

One example where I used this recently, was with an OkHttp Interceptor, where I wanted a general purpose "Retry mechanism" within the app whenever my client API for some web-service resultet in Socket Timeout, TLS handshake, DNS lookup and all the other IOException related stuff. With less than 100 lines of code, your app could gain a consistant network error fault tolerence:

/**
* OkHttp interceptor which allows for manual retry of the last network request 

*/
public class NetworkRetryInterceptor implements Interceptor { 
    private final Object lock = new Object(); 

    private Chain chain; 

    @Override public Response intercept(Chain chain) throws IOException { 
        // Store the chain so we may issue a retry on it later
        this.chain = chain; 

        // Execute the retry automatically the first time 
        return proceed(); 
    

    public void retry() { 
        
        // Inform the waiting thread that we are ready to try again
        synchronized(lock) { 
            lock.notifyAll(); 
        
    

    private Response proceed() { 
        final SingleLiveEvent> networkEventBus = EventBus.getNetwork(); 

        final Request request = chain.request(); 

        // Potentially we retry forever and ever 
        for(;;){ 
            // A request is going out, lets make sure we let any subscriber know about this 
            networkEventBus.postValue(new NetworkProgressEvent(0, 0, false)); 

             try{ 
                // Attempt to continue request chain 
                return chain.proceed(request); }
            catch(IOException e){ 
                // We consider all IOException types candidate for manual retry attempts 
                networkEventBus.postValue(new RetryNetworkEvent(this)); 

                // Make the threat wait until we call retry 
                try { 
                    synchronized(lock) { 
                        lock.wait(); 
                    
                } catch (InterruptedException interruptedException) {
                    Thread.currentThread().interrupt(); 
                
            
            // All other exception types bubble except IOException's 
        
    
}

// UI class in Kotlin subscribing to processRetryNetworkEvent
class SearchLocationActivity : AppCompatActivity(){

    private fun processRetryNetworkEvent(event: RetryNetworkEvent<*>, view: View) { 

        val retryInterceptor = event.payload as NetworkRetryInterceptor Snackbar                    .make(view, "Kommunikationsfejl", Snackbar.LENGTH_INDEFINITE)                    .setActionTextColor(ContextCompat.getColor(this,                                    R.color.colorAttentionNegative)) .setAction("Fors√łg igen", {                                AsyncTask.execute({ retryInterceptor.retry() }) })
                .show()
                hideIndeterminateProgressBar() 
    }
}


That's it - as long as you make sure to add the NetworkRetryInterceptor from above to your Http Client al IOException related stuff is handled automatically from now on using one error handler in your application.



Post a Comment