Before WorkManager was introduced many applications used implementations of AsyncTask
and
AsyncTaskLoader
. The implementation of AsyncTask
is now deprecated.
AsyncTaskLoader
on the other hand is still in use in some cases. Fore more information see
AsyncTaskLoader documentation.
It is recommended to use the WorkManager
and / or Kotlin Coroutines (if Kotlin used) in most
cases.
Activity.runOnUIThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
Note: Using the above alternatives can become hard to maintain in a more complex application.
Below is an example for loading an image in background using a Thread
(not recommended in
Android, see next slides):
fun onClick(v: View?) {
Thread {
val bitmap: Bitmap = loadImageFromNetwork("http://example.com/image.png")
mImageView.post(Runnable { mImageView.setImageBitmap(bitmap) })
}.start()
}
The Gradle import used for AsyncTaskLoader
:
implementation("androidx.loader:loader:1.1.0")
Example implementation for AsyncTaskLoader
:
import android.os.Bundle;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
class MainActivity : AppCompatActivity(), LoaderManager.LoaderCallbacks<String?> {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val loader: Loader<String?> = supportLoaderManager.initLoader(LOADER_ID, null, this)
if (loader != null) {
loader.forceLoad()
}
}
override fun onCreateLoader(id: Int, @Nullable args: Bundle?): Loader<String> {
return MyDataLoader(this@MainActivity)
}
override fun onLoadFinished(loader: Loader<String?>, data: String?) {
val textView = findViewById<TextView>(R.id.myTextView)
textView.text = data
}
override fun onLoaderReset(loader: Loader<String?>) {
val textView = findViewById<TextView>(R.id.myTextView)
textView.text = ""
}
companion object {
private const val LOADER_ID = 42
}
}
import android.content.Context;
import android.os.SystemClock;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.loader.content.AsyncTaskLoader;
class MyDataLoader(context: Context) :
AsyncTaskLoader<String?>(context) {
override fun loadInBackground(): String? {
//do your long running task here
SystemClock.sleep(2000)
return "My loaded data"
}
}
With Android Jetpack Google introduced the WorkManager for asynchronous task processing (aka. persistent work).
A detailed guide for WorkManager
can be found here.
Dependencies necessary for using the WorkManager
:
dependencies {
def work_version = "2.7.1"
// (Java only)
implementation "androidx.work:work-runtime:$work_version"
// optional - Test helpers
androidTestImplementation "androidx.work:work-testing:$work_version"
// optional - Multiprocess support
implementation "androidx.work:work-multiprocess:$work_version"
}
The WorkManager
brings a few benefits over the other options:
WorkManager
individual work tasks can be chained togetherWorkManager
works well with Kotlin Coroutines and RxJava (see Threading in WorkManager)The WorkManager
differentiates between 3 types of persistent work:
Work constraints allow the execution of work under optimal conditions. There are 5 work constraints provided:
NetworkType
BatteryNotLow
RequiresCharging
DeviceIdle
StorageNotLow
For more information see Work Constraints.
To create a set of constraints you can use the Constraints.Builder()
:
Constraints constraints = new Constraints.Builder()
// require to be connected to a WiFi
.setRequiredNetworkType(NetworkType.UNMETERED)
// require to be in charging mode
.setRequiresCharging(true)
.build();
The work will be executed only when all defined constraints are met.
When scheduling work for execution WorkRequest
s are created. There are two derived classes of
WorkRequest
:
OneTimeRequest
for scheduling non-repeating workPeriodicWorkRequest
for scheduling work that repeats on some intervalWorkRequest myWorkRequest = OneTimeWorkRequest.from(MyWork.class);
// or for complex work
WorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class)
// Additional configuration like constraints and retry policies
.build();
Retry policies allow work to be re-executed on failure. For that, a backoff delay and backoff policy
can be defined in setBackoffCriteria()
:
backoffPolicy
can be either LINEAR
or EXPONENTIAL
backoffDelay
is a number (cannot be less than 10 seconds)timeUnit
is the TimeUnit
of backoffDelay
Once a worker returns Result.retry()
the defined policy applies.
Example for adding a retry and backoff policy:
WorkRequest myWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
).build();
public class WeatherDataWorker extends Worker {
public static final String KEY_LATITUDE = "lat";
public static final String KEY_LONGITUDE = "long";
private final Double latitude;
private final Double longitude;
public WeatherDataWorker(
@NonNull Context context,
@NonNull WorkerParameters params
) {
super(context, params);
// Get data from params
latitude = params.getInputData().getDouble(KEY_LATITUDE, 0.0);
longitude = params.getInputData().getDouble(KEY_LONGITUDE, 0.0);
// Optionally check if data is valid
// ...
}
@Override
public Result doWork() {
// Do the work here, e.g. fetch weather data
try {
Map<String, Object> weatherData = getWeatherData(latitude, longitude);
if (weatherData == null) {
// Weather data could not be fetched, retry
return Result.retry();
}
// Prepare data result (limited to 10KB)
Data data = new Data.Builder()
.putAll(weatherData)
.build();
// Weather data fetched successfully.
return Result.success(data);
} catch (Exception exception) {
// Some error occurred, abort
return Result.failure();
}
}
// ...
}
Call the worker then from your activity as follow:
// method in activity class
private void loadWeatherData(Double latitude, Double longitude) {
// Prepare input data
Data inputData = new Data.Builder()
.putDouble(KEY_LATITUDE, latitude)
.putDouble(KEY_LONGITUDE, longitude)
.build();
// Define Constraints (require network connectivity)
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
// Create work request
WorkRequest weatherWorkRequest = new OneTimeWorkRequest.Builder(WeatherDataWorker.class)
.setInputData(inputData)
.setConstraints(constraints)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS)
.build();
// Get WorkManager
WorkManager workManager = WorkManager.getInstance(this);
// Enqueue request and get result
workManager.enqueue(weatherWorkRequest);
// Listen with live data to result (listening should happen in ViewModel)
workManager.getWorkInfoByIdLiveData(weatherWorkRequest.getId())
.observe(this, info -> {
if (info != null && info.getState().isFinished()) {
// Result data is stored in info.getOutputData()
}
});
}
Other options that are easily maintained are Kotlin Coroutines and lifecylce-aware components. These options are not handled in this lecture but very important to know when developing an Android application.
You can find more about Coroutines in the official Kotlin documentation as well in the Android documentation.
For lifecyle-aware components you can read Handling Lifecycles with Lifecycle-Aware Components.
WorkManager
WorkManager
differentiates between 3 persistent work types, depending of when and how long
a task should be executedWorkManager
provide? What do these options configure?