I'm going round in circles trying different techniques but every time hitting a different snag.
Ideally I want my code to look something like;
public class MyActivity extends AppCompatActivity {
SomeMethod();
new SomeAsyncTask().execute();
SomeOtherMethod();
new SomeOtherAsyncTask().execute();
}
But I need each method to be executed in order (and to wait for the previous to complete). Let's imagine the first AsyncTask authenticates and stores an authentication token in a static somewhere- this is then needed for the calls which follow.
My AsyncTasks have to be Async as they are communicating with an API.
I know I could use the onPostExecute() methods of the AsyncTasks but I don't like the mess this creates (having to jump around the code).
I know I could also create an interface and pass a class in to my AsyncTask but this doesn't help a great deal either (code still jumps around).
I thought I had come up with the perfect solution, calling SomeAsyncTask.execute().get() to wait until the task completes before continuing with the next line of code BUT I've hit a few issues with that today too.
Are there any other techniques I might be able to use to achieve clean code with a mix of foreground and background thread activity?
EDIT At the moment I am considering whether I can make all of my method non-async but then call them from an async- more like;
public class MyActivity extends AppCompatActivity {
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground( final Void ... params ) {
SomeMethod();
SomeMethod2();
SomeOtherMethod();
SomeOtherMethod2();
return null;
}
#Override
protected void onPostExecute( final Void result ) {
// any ui stuff
}
}.execute();
}
EDIT2 Current favoured solution is;
Define a "PostExecute" interface in my AsyncTask;
public class GetMerchantDataAsync extends AsyncTask<Void, Void, Void> {
private Context mContext;
private PostExecute mDelegate;
public interface PostExecute {
void Callback();
}
public GetMerchantDataAsync(Context context, PostExecute delegate) {
mContext = context;
mDelegate = delegate;
}
#Override
protected Void doInBackground(Void... params) {
}
#Override
protected void onPostExecute(Void v){
mDelegate.Callback();
}
}
Then define an instance of the interface and pass it on the constructor;
public class StartActivity extends Activity {
private final Context context = this;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new GetMerchantDataAsync(context, getMerchantDataPostExecute).execute();
}
private GetMerchantDataAsync.PostExecute getMerchantDataPostExecute = new GetMerchantDataAsync.PostExecute() {
#Override
public void Callback() {
DoSomethingElse();
}
};
}
It's a little bit messy (not a single block of code) but it almost reads as a single block and hopefully with sensible/consistent naming will be easily readable.
There are many ways to skin this cat and a few nice async await libraries you can use, similar to Promise.
Or you can also simply do onPostExecute call backs inline if that fits your needs and call the next step onPostExecute, assuming life cycle is still valid.
However, if you want some nice clean code, you should consider Kotlin and Coroutines.
Example with Timeout, cancellation and error handling:
Notice the withTimeout and the withContext calls. Those allow you to await on the contents within before moving on. Also as an added bonus, the method here is a suspendable coroutine which means the caller can wait on it as well. You resume the caller with c.resume(returnValueType).
If you find this too complicated, then I would stick with your onPostExecute, but what most developers forget on AsyncTasks are.
AsyncTasks should be canceled if you exit the Activity typically
AsyncTasks can complete out of order if you are allowing threadpool
management
Object locks must be wrapped to ensure concurrent
modification of variables does not become a problem.
Callbacks should handle errors and not just the positive route
Timeouts have to be managed outside of the Async task adding much more bloat.
So you see, simply doing a myAsyncTask.execute{onPostExecute(value){//dostuff}}
may look like simple fast code, it is definitely prone to errors if you don't handle all the niche cases that can occur.
The Coroutines have provided nice wrappers around all of this in easy to read brackets.
private suspend fun updateConfigurationWithChanges() : Boolean = suspendCoroutine { c ->
A35Log.v(mClassTag, "updateConfigurationWithChanges")
setIsActionInProgress(true)
mSaveOrUpdateJob = launch(UI) {
try{
withTimeout(TIMEOUT_FOR_DB_INTERACTION_MS){
showProgressDialog(mClassTag, "Saving", false)
mSelectedConfiguration!!.setLastSavedDateStr(DateTimeHelper.getNowTimeStamp())
val updatedRecordCount = withContext(DefaultDispatcher){ SSDBHelper.updateConfiguration(mSelectedConfiguration!!) }
if(updatedRecordCount > 0){
showFancyToast("Successfully updated", true, FancyToast.SUCCESS)
c.resume(true)
}else{
showFancyToast("Error while updating, please try again or press back", true, FancyToast.SUCCESS)
c.resume(false)
}
}
}catch(ex: JobCancellationException){
showFancyToast("Save canceled", true, FancyToast.ERROR, "Save canceled: ${ex.message}")
c.resume(false)
}catch (ex: TimeoutCancellationException) {
showFancyToast("Timed out updating, please try again or press back", true, FancyToast.ERROR, "Timed out updating database: ${ex.message}")
c.resume(false)
}catch(ex: Exception){
showFancyToast("Error updating database, please try again or press back", true, FancyToast.ERROR, "Error updating database: ${ex.message}")
c.resume(false)
}
}
}
Of course it is still good practice to cancel if the user leaves your screen if it doesn't hurt anything.
override fun onPause() {
super.onPause()
if(mSaveOrUpdateJob != null && mSaveOrUpdateJob!!.isActive) {
A35Log.v(mClassTag, "canceling saveOrUpdate job")
mSaveOrUpdateJob?.cancel()
}
}
But at the end of the day, do what's best for your situation, if managing the asyncTask and onPostExecute works for your needs and you have all your i's dotted and your t's crossed you should be fine going that route as well.
Also just for completeness if you are wondering how to call and await this above method it would look like this.
fun myButtonClick(){
launch(UI) {
if(mIsNewConfiguration){
//save first if new
if(withContext(DefaultDispatcher){ isNewOrUnchangedName() }) {
if (withContext(DefaultDispatcher) { saveNewConfigurationToDatabase() }) {
refreshCurrentNamesList()
launchConnectAndSendToDeviceFlow()
}
}
}else {
//update first if changes made
if(withContext(DefaultDispatcher){ isNewOrUnchangedName() }) {
if(DeviceAndConfigurationHelper.hasConfigurationModelChanged(mOriginalCopyConfiguration!!, mSelectedConfiguration!!)){
if(withContext(DefaultDispatcher) { updateConfigurationWithChanges() }){
refreshCurrentNamesList()
launchConnectAndSendToDeviceFlow()
}
}else{
launchConnectAndSendToDeviceFlow()
}
}
}
}
}
Happy Coding.
I can think of two approaches, depending on your constraints you can pick any of them, one using custom async tasks:
Create a custom AsyncTask which will be inherited by all your async tasks (or it can be done inline too):
public abstract class BaseAsyncTask<T, U, V> extends AsyncTask<T, U, V> {
public interface Callback<X> {
void onComplete(X param);
}
private Callback mainCallback;
public void execute(Callback<V> callback, T... params) {
mainCallback = callback;
executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
}
#Override
protected void onPostExecute(V v) {
super.onPostExecute(v);
if (mainCallback != null) {
mainCallback.onComplete(v);
}
}
}
This can be used like this for your case:
public static class TestThinghy {
BaseAsyncTask<String, String, String> task = new BaseAsyncTask<String, String, String>() {
#Override
protected String doInBackground(String... strings) {
return null;
}
};
void firstFunOnMainThread() {
//....do whatever...///
}
void runEverything() {
firstFunOnMainThread();
new BaseAsyncTask<String, String, String>() {
#Override
protected String doInBackground(String... strings) {
return "testing thinghy";
}
}.execute(new Callback<String>() {
#Override
public void onComplete(String param) {
secondRunOnMainThread();
//you can start new async here, or better start it from a different methond
}
}, "the other string param");
}
void secondRunOnMainThread() {
///...whatever here
}
}
The other approach is using RxJava, it's a powerful approach that gives you a ton of ways to chain tasks like this and decide how to run them, for this approach I would let you to do some research.
Related
I'm using Google Architecture Components, especially Room.
In my Dao i have this method:
#Query("SELECT COUNT(*) FROM photos")
int getPersistedPhotosSize();
And i need to execute it in my Repository to check if persisted photos size is 0.
So i gotta execute this method on background and get the value from it.
Now i perform this operation like this:
public int getNumRowsFromFeed() {
final int[] rows = new int[1];
Completable.fromAction(() -> rows[0] = photosDao.getPersistedPhotosSize())
.subscribeOn(Schedulers.io())
.blockingAwait();
return rows[0];
}
But i guess it's not the best way to do it.
So how can i get the value the right way? Especially i want to do it without RX
In your DAO the function to get the photo count doesn't use either LiveData nor RX. So instead of wrapping the code afterwards in a Completable, you can basically use any Android Async technology, like AsyncTask.
public class LoadTask extends AsyncTask<Void, Void, Integer> {
public interface Callback {
void onPhotoCount(int count);
}
private final Callback callback;
public LoadTask(Callback callback) {
this.callback = callback;
}
protected Integer doInBackground(Void... params) {
return photosDao.getPersistedPhotosSize();
}
protected void onPostExecute(Integer result) {
callback.onPhotoCount(result);
}
}
...
new LoadTask(photoCount -> {
// Do stuff with value,e.g. update ui.
}).execute();
This is basically just a proposal, of course you can use Threads, Handler as well.
P.S: From my point of view, this example shows one advantage of the Rx development. You get the callback stuff for free, without defining anything. And you can cancel the Rx chain for example due to lifecycle events. This is not implemented in this example.
I have implemented a way of getting the result from onPostExecute back to my main activity. I wanted to know if this is the right thing I did, is there any more chance of improving it, and if it's not the best way, what is the best way?
This is what I have done:
public class MainClass implements AsyncResponse {
private MyAsyncTask asyncTask;
public MainClass() {
asyncTask = new MyAsyncTask();
asyncTask.asyncResponse = this;
}
public void startTask( {
asyncTask.execute("string");
}
#Override
public void processDone(String res) {
// got response in MainClass from onPostExecute
}
private class MyAsyncTask extends AsyncTask<String, String, String> {
protected AsyncResponse asyncResponse = null;
#Override
protected String doInBackground(String... urls) {
return "some processed string";
}
#Override
protected void onPostExecute(String res) {
this.asyncResponse.processDone(res);
}
}
}
Here's the AsyncResponse interface:
public interface AsyncResponse {
void processDone(String res);
}
I want to know in terms of processing speed that on an average android mobile device, would this be a good approach and if not, how do I improve it to make it a good approach?
Thanks.
I always done this way and never had any issues. I would say it is the best way.
in one Line without any callback
String s= new MyAsyncTask().execute().get();
You added an unnecessary interface - and perhaps it makes your code less usable.
First, if you create the AsyncTask as a class within your Activity there is no need for the interface. You can simply do this:
#Override
protected void onPostExecute(String res) {
processDone(res);
}
The AsyncTask will execute onPostExecute on the UI thread and you can call the Activity method without the interface.
Second, if you create AsyncTask outside the Activity class (for example, in its own java file) then you can use this method, except it is not a good idea because it will hold a reference to the Activity on another thread - it's a memory leak risk.
To avoid that, your interface should be implemented in a separate class, like AsyncTaskResponse.java that is passed to the AsyncTask class.
Last, AsyncTask provides the response in the form of a String if that is sufficient. You should look at the docs on AsyncTask:
https://developer.android.com/reference/android/os/AsyncTask.html
You are wrapping the AsyncTask inside another POJO class; doing this doesn't hurt, but provides little benefit.
Consider that when the task is completed, you will want a callback notification somewhere. Your MainClass will get a callback in processDone(), but something will need to be listening to MainClass to get that notification.
Here is a pattern I always use with my AsyncTask subclasses:
public class GetDataRemoteTask extends AsyncTask<String, Void, Data> {
private static final String TAG = "GetDataRemoteTask ";
private WeakReference<GetDataResultListener> mListenerRef;
private Exception mExc;
#Override
protected Data doInBackground(String... params) {
Data result = null;
try {
result = mService.getData(params[0], params[1], params[2]);
} catch (Exception e) {
Log.e(TAG, "Error occurred getting data", e);
mExc = e;
}
return result;
}
#Override
protected void onPostExecute(Data result) {
if (mListenerRef != null) {
GetDataResultListener listener = mListenerRef.get();
if (listener != null) {
if (mExc == null) {
listener.dataReceived(result);
} else {
listener.dataException(mExc);
}
}
}
}
public void setGetDataResultListener(GetDataResultListener listener) {
if (listener == null) {
this.mListenerRef = null;
} else {
this.mListenerRef = new WeakReference<GetDataResultListener >(listener);
}
}
public static interface GetDataResultListener {
public void dataReceived(Data data);
public void dataException(Exception exc);
}
}
So to start off, here I have an interface, like you do, for connecting to the AsyncTask. But I don't wrap my AsyncTask with an implementation, I expect that I will have an Activity or a Fragment that will implement this interface. (That's why I use a WeakReference; if the Activity finishes, my AsyncTask won't keep holding on to the Activity. But that also means I can't use an anonymous class listener unless the client holds the reference for it.)
My client code will look like this:
GetDataRemoteTask task = new GetDataRemoteTask();
task.setListener(this);
task.execute(param1, param2, param3);
I also have a way to find out if there was an exception that occurred in the background task. Any background exceptions should always be reported to the client, which can decide how best to deal with the exception - for example pop up a dialog for the user, so they know the request failed.
I think that a big drawback of AsyncTask is that it doesn't have more structure around handling exceptions that occur in the background thread.
My task holds a reference to the exception, but I have also used Pair<Data, Exception> as a type parameter for return result so I don't need the exception property.
Using this pattern has helped me avoid some typical problems that occur when coding AsyncTask subclasses.
I'm a bit new to Android, Java, and Azure, and I'm using the Azure MobileServiceClient class to try to call various APIs in my backend. The problem I'm having is that MobileServiceClient's methods all seem to be async, using ListenableFutures.
This would be just fine, except that I want to use these methods in a helper class that also does other stuff that ought to be async (so it extends AsyncTask). However, since the MobileServiceClient call is async, it causes the AsyncTask to return prematurely. I want the AsyncTask not to call its onPostExecute method until the MobileServiceClient method has returned.
How do I avoid this issue? Do I need to change my architecture? Is it possible to put a MobileServiceClient call inside another async task and have it block it?
#Override
protected Boolean doInBackground(Void... params) {
Log.i(TAG, "Doing background task");
if(mTaskType==tTaskType.LOGIN_TASK){
login();
//do other stuff here that should be async
}
return true;
}
private void login(){
Log.i(TAG, "Doing login task...");
ListenableFuture<JsonElement> result = mClient.invokeApi("login", mJSONHelper.makeLoginObject(thisUser));
Futures.addCallback(result, new FutureCallback<JsonElement>() {
#Override
public void onFailure(Throwable exc) {
error.setError(ErrorHelper.Error.NETWORK_ERROR);
}
#Override
public void onSuccess(JsonElement result) {
}
});
}
I'm going to preface this with the caveat that I'm not very familiar with Android either. However from my experience on other platforms and a quick search of the APIs this is the approach I think you should take. I'm also not promising that code code snippets will compile as I haven't checked that, but they should be close to doing so.
Your login method should return a ListenableFuture<T> and then the doInBackground method can then add it's own callback which is executed when the login completes.
If you want something else to be able to wait for the doInBackground task to complete then that should also return a ListenableFuture<T> which can be done by using the Futures.transform method to chain together a series of async calls.
Here is what I think it should look like:
protected void doInBackground(Void... params) {
Log.i(TAG, "Doing background task");
if(mTaskType==tTaskType.LOGIN_TASK){
var loginFuture = ListenableFuture<UserDetail> login();
Futures.addCallback(loginFuture, new FutureCallback<UserDetail>() {
#Override
public void onSuccess(UserDetail userDetail)
{
// do other stuff here that should be async
// also optionally you could implement this as a transform
// style thing to and return another future from this `doInBackground`
// method so other parts of your code could know when it is completed.
}
#Override
public void onFailure(Throwable exc) {
// I'd quite likely move the error handling from the login method here
// as that way it can also handle any exceptions caused by the transform
// from json to user detail as well.
}
})
}
}
private ListenableFuture<UserDetail> login(){
Log.i(TAG, "Doing login task...");
ListenableFuture<JsonElement> loginFutureResult = mClient.invokeApi("login", mJSONHelper.makeLoginObject(thisUser));
Futures.addCallback(loginFutureResult, new FutureCallback<JsonElement>() {
#Override
public void onFailure(Throwable exc) {
// This is just to keep with what your style is, for recording the error
// I think you might be better off handling it at a higher level and
// also you might want to check `exc` to see if it was an actual network
// error and not for example just failed credentials or something.
error.setError(ErrorHelper.Error.NETWORK_ERROR);
}
#Override
public void onSuccess(JsonElement result) {
Log.i(TAG, "The login was successful");
}
});
// lets pretend that instead of returning the JSON response
// you wanted to map it to a user detail before returning, just to show how to do that.
AsyncFunction<JsonElement, UserDetail> transformUserJsonFunction =
new AsyncFunction<JsonElement, UserDetail>() {
public ListenableFuture<UserDetail> apply(JsonElement userJson) {
// some code to map the json element to user detail
UserDetail userDetail = new UserDetail(userJson);
return Futures.immediateFuture(userDetail);
}
};
return Futures.transform(loginFutureResult, transformUserJsonFunction);
}
I hope that points you in the right direction.
Well, I did it with a flag - it's not pretty though. I'm still interested in any ways to do this that are more elegant or correct. Or is this actually the right way to do it?
private void login(){
Log.i(TAG, "Doing login task...");
isLoginFinished= false;
ListenableFuture<JsonElement> result = mClient.invokeApi("login", mJSONHelper.makeLoginObject(thisUser));
Futures.addCallback(result, new FutureCallback<JsonElement>() {
#Override
public void onFailure(Throwable exc) {
error.setError(ErrorHelper.Error.NETWORK_ERROR);
isLoginFinished= true;
}
#Override
public void onSuccess(JsonElement result) {
Log.i(TAG, "Login call was successful, parsing result:" + result.toString());
isLoginFinished= true;
}
});
while(!isLoginFinished);
}
I'm new to android and very used to web developing. in javascript when you want to perform an asynchronous task you pass a function as an argument (a callback):
http.get('www.example.com' , function(response){
//some code to handle response
});
I was wondering if we can do the same with android's AsyncTask , pass a function reference to the onPostExecute() method , and it will run it.
any suggestions ?
Yes the concept of callbacks also very much exists in Java. In Java you define a callback like this:
public interface TaskListener {
public void onFinished(String result);
}
One would often nest these kind of listener definitions inside the AsyncTask like this:
public class ExampleTask extends AsyncTask<Void, Void, String> {
public interface TaskListener {
public void onFinished(String result);
}
...
}
And a complete implementation of the callback in the AsyncTask would look like this:
public class ExampleTask extends AsyncTask<Void, Void, String> {
public interface TaskListener {
public void onFinished(String result);
}
// This is the reference to the associated listener
private final TaskListener taskListener;
public ExampleTask(TaskListener listener) {
// The listener reference is passed in through the constructor
this.taskListener = listener;
}
#Override
protected String doInBackground(Void... params) {
return doSomething();
}
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
// In onPostExecute we check if the listener is valid
if(this.taskListener != null) {
// And if it is we call the callback function on it.
this.taskListener.onFinished(result);
}
}
}
onPostExecute() is called as soon as the background task finishes. You can use the whole thing like this:
ExampleTask task = new ExampleTask(new ExampleTask.TaskListener() {
#Override
public void onFinished(String result) {
// Do Something after the task has finished
}
});
task.execute();
Or you can define the TaskListener completely separately like this:
ExampleTask.TaskListener listener = new ExampleTask.TaskListener() {
#Override
public void onFinished(String result) {
// Do Something after the task has finished
}
};
ExampleTask task = new ExampleTask(listener);
task.execute();
Or you can subclass TaskListener like this:
public class ExampleTaskListener implements TaskListener {
#Override
public void onFinished(String result) {
}
}
And then use it like this:
ExampleTask task = new ExampleTask(new ExampleTaskListener());
task.execute();
You can of course just override the onPostExecute() method of the AsyncTask, but that is not recommended and in most cases actually pretty bad practice. For example you could do this:
ExampleTask task = new ExampleTask() {
#Override
public void onPostExecute(String result) {
super.onPostExecute(result);
// Your code goes here
}
};
This will work just as well as the implementation above with a separate listener interface, but there are a few problems with this:
First and foremost you can actually break the ExampleTask all together. It all comes down to the super.onPostExecute() call above. If you as a developer override onPostExecute() like above and forget to include the super call or simply delete it for whatever reason that the original onPostExecute() method in the ExampleTask will not be called anymore. For example the whole listener implementation with the TaskListener would suddenly not work anymore since the call to the callback is implemented in onPostExecute(). You can also break the TaskListener in many other ways by unknowingly or unwittingly influencing the state of the ExampleTask so it won't work anymore.
If you look at what's actually happening when you override a method like this than it becomes much more clear what's going on. By overriding onPostExecute() you are creating a new subclass of ExampleTask. It would be the exact same thing as doing this:
public class AnotherExampleTask extends ExampleTask {
#Override
public void onPostExecute(String result) {
super.onPostExecute(result);
// Your code goes here
}
}
All this is just hidden behind a language feature called anonymous classes. Suddenly overriding a method like this doesn't seem so clean and quick anymore does it?
To summarise:
Overriding a method like this actually creates a new subclass. You are not just adding a callback, you are modifying how this class works and can unknowingly break oh so many things.
Debugging errors like this can be much more than just a pain in the a**. Because suddenly ExampleTask could throw Exceptions or simply not work anymore for no apparent reason, because you never actually modified its code.
Each class has to provide listener implementations at places where it is appropriate and intended. Sure you can just add them later on by overriding onPostExecute() but that is always very dangerous. Even #flup with his 13k reputation has forgotten to include the super.onPostExecute() call in his answer, imagine what some other not as experienced developer might do!
A little abstraction never hurt anybody. Writing specific listeners might be slightly more code, but it is a much better solution. The code will be cleaner, more readable and a lot more maintainable. Using shortcuts like overriding onPostExecute() essentially sacrifices code quality for a little bit convenience. That is never a good idea an will just cause problems in the long run.
In Java, functions are less of a first class citizen than in JavaScript. The AsyncTask provides the callback as a method in the class, which you should override.
See Make an HTTP request with android for a subclass of AsyncTask with an implementation of the doInBackground which makes a web request.
If you want to do multiple HTTP requests with different callbacks, you can override RequestTask and implement onPostExecute with the different callback implementations.
You can use an anonymous class to simulate the closure a JavaScript callback commonly uses:
new RequestTask(){
#Override
public void onPostExecute(String result) {
// Implementation has read only access to
// final variables in calling scope.
}
}.execute("http://stackoverflow.com");
As Xaver shows, you can also create a full-blown interface for the listener. This seems only useful to me if you wish to implement a couple default onPostExecute functions and pick one of these default implementations for a particular call.
in Kotlin
Firstly, create class AsyncTaskHelper as below.
class AsyncTaskHelper() : AsyncTask<Callable<Void>, Void, Boolean>() {
var taskListener: AsyncListener? = null
override fun doInBackground(vararg params: Callable<Void>?): Boolean {
params.forEach {
it?.call()
}
return true
}
override fun onPreExecute() {
super.onPreExecute()
}
override fun onPostExecute(result: Boolean?) {
super.onPostExecute(result)
taskListener?.onFinished(result as Any)
}
}
interface AsyncListener {
fun onFinished(obj: Any)
}
the code below you can use when you want to use async task.
AsyncTaskHelper().let {
it.execute(Callable<Void> {
//this area is the process do you want to do it in background
// dosomething()
}
}
null
})
it.taskListener = object : AsyncListener{
override fun onFinished(obj: Any) {
// this area is for the process will want do after finish dosomething() from Callable<Void> callback
}
}
From the code above. if you want to separate your process to several task. you can do as this code below.
AsyncTaskHelper().let {
it.execute(Callable<Void> {
// task 1 do in background
null
},Callable<Void>{
// task 2 do in background
null
},Callable<Void>{
// task 3 do in background
null
})
it.taskListener = object : AsyncListener {
override fun onFinished(obj: Any) {
// when task1,task2,task3 has been finished . it will do in this area
}
}
}
I also want to contribute to Sup.la's Kotlin solution by making it a little more generic in case you're only interested in .execute()ing a function asnychronously and .get()ting a result from an AsyncTask.
I split up the explanation in order to make it better understandable.
If you only want to copy paste, just skip to Step2 :)
Note: Keep in mind that AsyncTask is deprecated now. However, I think it'll stay around for while due to compatibility and stuff.
Step 1
This is how the generic class can look like:
import android.os.AsyncTask
#Suppress("DEPRECATION") // As mentioned, AsyncTask is deprecated now
class GenericAsyncTask() : AsyncTask<Callable<Any>, Void, Any?>() {
override fun doInBackground(vararg params: Callable<Any>): Any? {
// params receives vararg, meaning its an Array.
// In this example we only want pass one function to easen up the example (-> [0]).
return params[0]?.call()
}
}
As can be seen, the AsyncTask now takes any function passed to it (Callable<Any>), whereby Any and null can be the result of the function. Thus, here nobody cares about the types the AsyncTask has to handle or return or how the passed function looks like.
You can now use it like this:
// In this example lets assume we have a method which returns a list of Strings
var res: List<String>? = GenericAsyncTask().let {
it.execute(
Callable<Any> { your_random_function() }
).get() as? List<String>?
}
As you can see, we handle the Any dilemma just by casting it. This works since all Types are deriving from Any except null. To handle the latter, we're doing a safe cast using as?. Thus, also null as return-type is handled.
Step2
However, ultimatively we can even make this more generic:
// The same example result as shown above
// The casting will be inferred directly from your_random_function() return-type
val res: List<String>? = GenericAsyncTask().async(::your_random_function)
// For the sake of completeness, this is valid as well
val res: List<String>? = GenericAsyncTask().async<List<String>?> { your_random_function() }
class GenericAsyncTask() : AsyncTask<Callable<Any>, Void, Any?>() {
// This function takes any method you provide and even infers the result-type, thus, spares you the casting
public fun<T> async(method: () -> T): T? {
return this.let {
it.execute(Callable<Any> { method() }).get() as? T?
}
}
override fun doInBackground(vararg params: Callable<Any>): Any? { ... }
}
Let's say you're designing the threading architecture for a an app -> the primary purpose is that your app will have a lot of tasks that need something done on the background thread, and sometimes a result task on UI thread, or something not (though more times, the result needs to be run on UI thread). For simplicity, let's say the tasks will be stuff like: download a file and display a pop-up, log a user in and go to a different page, process an image and store the result in a database (popular tasks that a lot of apps do)
I've researched a lot about the nuances, but would really like a deep-dive explanation/knowledge on what kind of architecture is better, and what are the considerations.
here are the three models in consideration:
AsyncTask model: each operation (like downloading a file and displaying a pop-up) is an AsyncTask, or some derivative of a parent class that abstracts out the common functionalities.
Thread/handler model: i always create a new Handler(Looper.getMainLooper()); and each time i need to do a task, i use a thread factory to spin off the task, with the handler on UI thread (or whatever custom handler).
Service/Thread model: i use a general Service class that is in charge of operations based on some operation code. there's a bunch of ServiceTask derivative objects that do certain things, but the Service class communicates with each ServiceTask when tasks are started/done.
I'm slightly leaning towards going the whole service/threading model, just because i've read some really awkward nuances with AsyncTask/Threads:
AsyncTask has a private static handler, and if the classloader calls it at the wrong time (such as including a library that uses it before your application does) then all of your onPostExecute will happen at the wrong time since your handler was not the main handler
it's easy to forget to check a bunch of things in the onPostExecute such as if there was a config change, or your activity was destroyed, or application was backgrounded/paused when the onPostExecute is called (leading to crashes)
AsyncTask changed its serial/parallel execution behavior on different APIs
If you went with the Thread/Handler model, on older devices, thread priority is actually incredibly low. i've heard something like there was a priority scale of 1-15 such that your threads automatically get a low priority and if the system was low on resources, your threads would stop running (whereas since services are running independently of your activity the thread priority there is higher?)
What is the best way to design a robust threading architecture that doesn't easily lead to crashes/unexpected behavior while also maintaining good performance ??
Please also let me know in the comments if this question is too vague and if you need actual code (i'm afraid to post code because it would super overbloat the question length more than it already is).
I don't think you will find a one-size fits all approach here.
Downloading a file? Use DownloadManager
Logging a user in and go to next screen? Probably an AsyncTask would be best.
Process an image and store it? A Service might be a good choice here since you don't want the action to be attached to any particular Activity.
Handlers are more tricky, if they are attached to a Looper running on a background thread you need to call quit() on the Looper when you are done with it. Handlers are good when you need to delay an action, postDelayed() is great for that. They are also good when you need to communicate back to the UI thread from a background thread.
But yes you are correct that each one has pitfalls as you mentioned. Android is a complex beast and it seems they could have a done a better job preventing developers from shooting themselves in the foot, especially in regards to AsyncTask being called after an Activity is destroyed!
I was using Java's old school approach by creating a class (I called it ThreadRunner) derived from Java's Thread. A constructor looked like:
public ThreadRunner (Object [] params, AbstractCallback callBack) {...}
AbstractCallback was a class that was implemnting a single 'onCall' method and was mostly used to notify a calling party about an event such as "execution of a task is completed".
I've used it to get content from Internet and run other time consuming operations. It didn't cause any problems and worked as expected.
However, I've heard many times that AsyncTask is an Android-ish way of doing things like that. I don't know why and do not have any intention to change, since I'm preaching "don't fix it if it's not broken" approach.
I've seen also comments that you'll need to write less code with AsyncTask, but in my approach with traditional Java's Threat the amount of coding was small as well, so I queses it's just a matter of your personal preferences and experience.
In regard of your 3-rd approach - I think you should use it when write a service that runs all the time, listens on requests and never stops. When you just need to execute a single task asynchronously Java Threads or AsyncTask should be used.
I think AsyncTask is a good tool for listed purposes. But it needs to wrap AsyncTask for an easy using. My variant of such wrapping (with a progress indicator) is a following:
Main class AsyncActivity for extending it in application activities:
public abstract class AsyncActivity extends Activity{
// Поле нужно обязательно объявить как статическое!
private static AsyncConnect asyncConnect = null;
protected void runBackgroundTask(String progressInscription, RequestTask task){
asyncConnect = new AsyncConnect(this, responseListener, progressInscription, task);
asyncConnect.execute();
}
protected abstract void onBackgroundTaskEnd(boolean result);
#Override
protected void onResume(){
super.onResume();
// Перерегистрируем текущий контекст этой формы
// для корректной работы слушателя ответа с сервера
responseListener.registerCurrentContext( this );
if (asyncConnect != null){
asyncConnect.onResume(this);
}
}
#Override
protected void onPause(){
super.onPause();
if (asyncConnect != null){
asyncConnect.onPause();
}
}
/**
* Чтобы диалоги не вызывались из устаревшего контекста
* и по этой причине не исчезали при повороте экрана,
* слушателя ответа с сервера необходимо сделать статическим полем класса,
* в котором должен быть зарегистрирован текущий контекст
*/
private static final OnServerResponseListener responseListener = new OnServerResponseListener(){
private AsyncActivity context = null;
#Override
public void registerCurrentContext(AsyncActivity context){this.context = context; }
#Override
public void onResponse(boolean result){
// Если никакой контекст не был зарегистрирован, ничего не делаем
if (context == null) return;
// Освождаем статическое поле для сборщика мусора (но делать это не обязательно!)
asyncConnect = null;
// Вызываем колбэк о завершении фоновой задачи
context.onBackgroundTaskEnd(result);
}
};
}
Additional class and a pair of interfaces:
public class AsyncConnect {
private final Activity context;
private final RequestTask task;
private final String progressInscription;
private final OnServerResponseListener responseListener;
private boolean isDone = false;
private ProgressDialog progressDialog;
public AsyncConnect(Activity context, OnServerResponseListener responseListener,
String progressInscription, RequestTask task){
this.context = context;
this.task = task;
this.progressInscription = progressInscription;
this.responseListener = responseListener;
progressDialog = null;
isDone = false;
}
public void execute(){
if (isDone) return;
new ConnectTask().execute();
}
public void onPause(){
if (isDone) return;
if (progressDialog != null){
if (progressDialog.isShowing()){
progressDialog.dismiss();
progressDialog = null;
}
}
}
public void onResume(Activity context){
if (isDone) return;
progressDialog = ProgressDialog.show( context, null, (CharSequence)progressInscription,
true, false);
}
private class ConnectTask extends AsyncTask<Object, Void, Boolean> {
#Override
protected void onPreExecute( ) {
super.onPreExecute();
progressDialog = ProgressDialog.show( context, null,
(CharSequence)progressInscription, true, false);
}
#Override
protected Boolean doInBackground(Object... messages) {
return task.call();
}
#Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if (progressDialog != null){
if (progressDialog.isShowing()){
progressDialog.dismiss();
progressDialog = null;
}
}
// Делаем невозможным повторное использование этого объекта
isDone = true;
responseListener.onResponse(result);
}
}
}
public interface OnServerResponseListener {
public void registerCurrentContext(AsyncActivity context);
public void onResponse(boolean result);
}
public interface RequestTask {
public boolean call();
}
For using AsyncActivity we only need to call runBackgroundTask and implement onBackgroundTaskEnd in the target activity. It's possible to create different kinds of AsyncTask wrappings based on this idea.
You may also check out Needle; it's an open-source, simple but powerful multithreading library for Android. With it you can say things like:
Needle.onMainThread().execute(new Runnable() {
#Override
public void run() {
// e.g. change one of the views
}
});
or
Needle.onBackgroundThread().execute(new UiRelatedTask<Integer>() {
#Override
protected Integer doWork() {
int result = 1+2;
return result;
}
#Override
protected void thenDoUiRelatedWork(Integer result) {
mSomeTextView.setText("result: " + result);
}
});
very simple API
fixed thread pool size
customizable thread pool size
supports UI interaction ("do work and then use result on UI thread")
android 1.5+
behaves the same on all platform versions
Check it out on GitHub: https://github.com/ZsoltSafrany/needle