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{
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
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.
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.
Comments