AsyncTask, how to properly use WeakReference in all the UI methods - android

I want to use the WeakReference approach to not leak memory with my AsyncTask. I found examples online and on Stackoverflow, but they only get the reference in onPostExecute and I am unsure how to correctly use it in all 3 of the UI methods.
My current approach is like this, but I have no clue if I can get rid of some redundancy. Why can't I just call activityReference.get() in the constructor and then only check for null in each of the UI methods? Why do the examples online call get right before using the WeakReference?
private static class ExampleAsyncTask extends AsyncTask<Integer, Integer, String> {
private WeakReference<MainActivity> activityReference;
ExampleAsyncTask(MainActivity context) {
activityReference = new WeakReference<>(context);
}
#Override
protected void onPreExecute() {
super.onPreExecute();
MainActivity activity = activityReference.get();
if (activity == null || activity.isFinishing()) {
return;
}
activity.progressBar.setVisibility(View.VISIBLE);
}
#Override
protected String doInBackground(Integer... integers) {
for (int i = 1; i < integers[0]; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
publishProgress((i * 100) / integers[0]);
}
return "Finished";
}
#Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
MainActivity activity = activityReference.get();
if (activity == null || activity.isFinishing()) {
return;
}
activity.progressBar.setProgress(values[0]);
}
#Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
MainActivity activity = activityReference.get();
if (activity == null || activity.isFinishing()) {
return;
}
activity.progressBar.setProgress(0);
activity.progressBar.setVisibility(View.INVISIBLE);
Toast.makeText(activity, s, Toast.LENGTH_SHORT).show();
}
}

You can initialise it in the constructor and use it in all methods.
private static class ExampleAsyncTask extends AsyncTask<Integer, Integer, String>
{
private WeakReference<MainActivity> activityReference;
MainActivity activity;
ExampleAsyncTask(MainActivity context)
{
activityReference = new WeakReference<>(context);
activity = activityReference.get();
}
#Override
protected void onPreExecute()
{
super.onPreExecute();
if (activity == null || activity.isFinishing())
{
return;
}
activity.progressBar.setVisibility(View.VISIBLE);
}
#Override
protected String doInBackground(Integer... integers)
{
if (activity == null)
return null;
for (int i = 1; i < integers[0]; i++)
{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
publishProgress((i * 100) / integers[0]);
}
return "Finished";
}
#Override
protected void onProgressUpdate(Integer... values)
{
super.onProgressUpdate(values);
if (activity == null || activity.isFinishing())
{
return;
}
activity.progressBar.setProgress(values[0]);
}
#Override
protected void onPostExecute(String s)
{
super.onPostExecute(s);
if (activity == null || activity.isFinishing())
{
return;
}
activity.progressBar.setProgress(0);
activity.progressBar.setVisibility(View.INVISIBLE);
Toast.makeText(activity, s, Toast.LENGTH_SHORT).show();
}
}

The most correct and obvious way to use weak reference here is not to. Instead, you should cancel the task when Activity gets destroyed. This will prevent memory leak and stop async computation which became useless.
public final class SomeActivity extends Activity {
private AsyncTask<?, ?, ?> runningTask;
private void startTaskPlease() {
if (runningTask != null) runningTask.cancel(true);
runningTask = new ExampleAsyncTask(this);
runningTask.execute();
}
#Override protected void onDestroy() {
super.onDestroy();
if (runningTask != null) runningTask.cancel(true);
}
}
Now your AsyncTask#doInBackground should be interruption-friendly, but UI part should not mind weak references and cancellation.
public final class ExampleAsyncTask extends AsyncTask<whatever> {
private final SomeActivity activity; // now this is OK! Thanks to cancellation
public ExampleAsyncTask(SomeActivity activity) {
this.activity = activity;
}
}

Related

AsyncTask - Why can I access layout widgets in the doInBackground method?

From my understanding, the doInBackground method should not have access to the layout views. So why does the following code work? As you can see, I access the ProgressBar from doInBackground
private class TestAsyncTask extends AsyncTask<Integer, Integer, String> {
#Override
protected String doInBackground(Integer... integers) {
progressBar.setMax(integers[0]);
for (int i = 1; i <= integers[0]; i++) {
try {
Thread.sleep(1000);
publishProgress(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "Finished!";
}
#Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
Toast.makeText(MainActivity.this, s, Toast.LENGTH_SHORT).show();
progressBar.setProgress(0);
}
#Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
progressBar.setProgress(values[0]);
}
}
Because in the widget code it checks the thread, if not UI it creates a Runnable and posts it to the UI Thread :
private synchronized void refreshProgress(int id, int progress, boolean fromUser,
boolean animate) {
if (mUiThreadId == Thread.currentThread().getId()) {
doRefreshProgress(id, progress, fromUser, true, animate);
} else {
if (mRefreshProgressRunnable == null) {
mRefreshProgressRunnable = new RefreshProgressRunnable();
}
final RefreshData rd = RefreshData.obtain(id, progress, fromUser, animate);
mRefreshData.add(rd);
if (mAttached && !mRefreshIsPosted) {
post(mRefreshProgressRunnable);
mRefreshIsPosted = true;
}
}
}
RefreshProgressRunnable : Causes the Runnable to be added to the message queue. The runnable will be run on the user interface thread.

Can the strong reference returned from WeakReference's get method cause a memory leak in AsyncTask onProgressUpdate?

I want to use WeakReference to avoid a memory leak in AsyncTask. The examples I find online only use the get() method in onPostExecute, but I also want to use it to update the progress. Now I wonder if that very frequent call of the get method can in itself cause a memory leak?
private static class ExampleAsyncTask extends AsyncTask<Integer, Integer, String> {
private WeakReference<MainActivity> activityReference;
ExampleAsyncTask(MainActivity context) {
activityReference = new WeakReference<>(context);
}
#Override
protected void onPreExecute() {
super.onPreExecute();
MainActivity activity = activityReference.get();
if (activityReference.get() == null) {
return;
}
activity.progressBar.setVisibility(View.VISIBLE);
}
#Override
protected String doInBackground(Integer... integers) {
Log.i("TAG", "doInBackground started");
for (int i = 1; i < integers[0]; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
publishProgress((i * 100) / integers[0]);
}
return "Finished";
}
#Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
MainActivity activity = activityReference.get();
if (activity == null || activity.isFinishing()) {
return;
}
activity.progressBar.setProgress(values[0]);
}
#Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
MainActivity activity = activityReference.get();
if (activity == null || activity.isFinishing()) {
return;
}
activity.progressBar.setProgress(0);
activity.progressBar.setVisibility(View.INVISIBLE);
Toast.makeText(activity, s, Toast.LENGTH_SHORT).show();
}
}
Your code almost has no strong reference as far as the get and onProgressUpdate are concerned because you are using local variable which will go out of scope, the moment onProgressUpdate is finished.
So basically everytime your code is accessing the reference, retrieved from get() method so it is safe. Although it would have been an issue, if you would have kept a reference, declared outside onProgressUpdate method

Android AsyncTask json return value

I have called an async task from my button click.In the doInBackground I have called an API and It is returning me a Json object.I want to pass the Json object to another activity on the button click.How can I can get the return Json object value so that I can send it to other activity.
Thanks.
Create Interface
public interface Listener {
void success(BaseModel baseModel);
void fail(String message);
}
Create Base model class
public class BaseModel implements Serializable {
private static final long serialVersionUID = 1L;
}
Call below method inside your onClick mehtod.
protected void userLoginData(final String userName) {
// if you want to pass multiple data to server like string or json you can pass in this constructor
UserLoginLoader userLoginLoader = new UserLoginLoader(LoginActivity.this, userName, "1234567899", new Listener() {
#Override
public void success(BaseModel baseModel) {
// here you got response in object you can use in your activity
UserLoginModel userLoginModel = (UserLoginModel) baseModel;
// you can get data from user login model
}catch(Exception exception){
exception.printStackTrace();
Utils.showAlertDialog(LoginActivity.this, "Server is not responding! Try Later.");
}
}
#Override
public void fail(String message) {
}
});
userLoginLoader.execute();
}
:- User Login Loader class
public class UserLoginLoader extends AsyncTask<String, Void, Boolean> {
private Dialog dialog;
private Listener listner;
private String deviceId;
Activity activity;
String message;
String userName;
boolean checkLoginStatus;
public UserLoginLoader(Activity activity,String userName, String deviceId, Listener listener) {
this.listner = listener;
this.userName =userName;
this.activity = activity;
this.deviceId = deviceId;
}
#Override
protected Boolean doInBackground(String... arg0) {
//User login web service is only for making connection to your API return data into message string
message = new UserLoginWebService().getUserId(userName, deviceId);
if (message != "null" && !message.equals("false")) {
return true;
}
return false;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
dialog = new Dialog(activity, R.style.CustomDialogTheme);
dialog.setContentView(R.layout.progress);
dialog.setCancelable(false);
dialog.show();
}
#Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
BaseModel baseModel = null;
if (!message.equals("null") && (!message.equals("false")) )
baseModel = parseData(message, result);
if (dialog.isShowing()) {
dialog.dismiss();
dialog.cancel();
dialog = null;
}
if (listner != null) {
if (result && baseModel != null)
listner.success(baseModel);
else
listner.fail("Server not responding! Try agian.");
} else
listner.fail("Server not responding! Try agian.");
}
//call parser for parsing data return data from the parser
private BaseModel parseData(String responseData, Boolean success) {
if (success == true && responseData != null
&& responseData.length() != 0) {
UserLoginParser loginParser = new UserLoginParser(responseData);
loginParser.parse();
return loginParser.getResult();
}
return null;
}
}
This is you Login parser class
public class UserLoginParser {
JSONObject jsonObject;
UserLoginModel userLoginModel;
/*stored data into json object*/
public UserLoginParser(String data) {
try {
jsonObject = new JSONObject(data);
} catch (JSONException e) {
Log.d("TAG MSG", e.getMessage());
e.printStackTrace();
}
}
public void parse() {
userLoginModel = new UserLoginModel();
try {
if (jsonObject != null) {
userLoginModel.setUser_name(jsonObject.getString("user_name")== null ? "": jsonObject.getString("user_name"));
userLoginModel.setUser_id(jsonObject.getString("user_id") == null ? "" : jsonObject.getString("user_id"));
userLoginModel.setFlag_type(jsonObject.getString("flag_type") == null ? "" : jsonObject.getString("flag_type"));
} else {
return;
}
} catch (Exception exception) {
exception.printStackTrace();
}
}
/*return ship name list which is stored into model */
public UserLoginModel getResult() {
return userLoginModel;
}
}
Write a callback method in the Activity that takes in the argument that you wish to pass from AsyncTask to that Activity. Send reference to the Activity to AysncTask while creating it. From doInBackground() method make a call to this callback method with the data your API returns.
Code would be something like -
public class TestAsyncTask extends AsyncTask<Integer, Integer, String[]> {
Activity myActivity;
public TestAsyncTask(Activity activity) {
this.myActivity = activity;
}
#Override
protected String[] doInBackground(Integer... params) {
String data = yourApi();
myActivity.callback(data);
}
}
public class MyActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
new TestAsyncTask(this).execute(someId);
}
public void callback(String data) {
//process data
}
}
Just for the record you can directly get return value from doInBackground() method by calling get() on it.
String data = new TestAsyncTask(this).execute(someId).get();
But note this may block your UI thread as it will wait for the doInBackground() method to complete it's execution.

Android - Async validation from Parse

I am using Parse in order to store my data. During the user 's registration, I create an AsyncTask to set the result in the calling activity if the user's email exists or not. Here is the code to trigger the validation
View.OnClickListener btnNextClick = new View.OnClickListener() {
#Override
public void onClick(View v) {
if (etEmail == null) {
return;
}
final String email = etEmail.getText().toString();
if (email == null || email.length() == 0) {
etEmail.setError(getResources().getString(
R.string.error_email_is_null)
);
etEmail.requestFocus();
valid = false;
} else {
if (!Common.isValidEmailAddress(email)) {
etEmail.setError(getResources().getString(R.string.error_email_not_valid));
etEmail.requestFocus();
valid = false;
} else {
// validate Email from back end
new CheckEmailAsyncTask(CreateAccountActivity.this, email).execute();
if (emailValid == false) {
etEmail.setError(getResources().getString(R.string.error_email_existed));
etEmail.requestFocus();
valid = false;
}
}
}
if (valid) {
// if valid then going to the next step
Intent intent = new Intent(CreateAccountActivity.this, UpdateUserActivity.class);
intent.putExtra(AppConstant.PARAM_EMAIL, email);
startActivity(intent);
}
}
boolean emailValid;
public void setEmailValid (boolean emailValid) {
this.emailValid = emailValid;
}
};
and this is the code for CheckEmailAysncTask
public class CheckEmailAsyncTask extends AsyncTask<String, Void, Void> {
ProgressDialog progressDialog;
Context context;
CreateAccountActivity createAccountActivity;
String email;
public CheckEmailAsyncTask(CreateAccountActivity createAccountActivity, String email){
this.createAccountActivity = createAccountActivity;
this.context = createAccountActivity;
this.email = email;
progressDialog = new ProgressDialog(context);
}
#Override
protected void onPreExecute() {
this.progressDialog.show();
super.onPreExecute();
}
#Override
protected Void doInBackground(String... params) {
UserDAO userDAO = new UserDAO(context);
try {
int count = userDAO.isUserExists(email);
if (count > 0) {
createAccountActivity.setEmailValid(false);
} else {
createAccountActivity.setEmailValid(true);
}
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void result) {
progressDialog.dismiss();
super.onPostExecute(result);
}
}
}
and in UserDAO
public int isUserExists(String email) throws ParseException {
ParseQuery query = new ParseQuery("User");
query.whereEqualTo("email", email);
return query.count();
}
However, in my setup, the code below the AsyncTask will be executed first before the result are returned back to from Parse. How can I just let the rest of the code wait for future return and then continue ? One of the solution that I come up with is to keep looping the calling to the AsyncTask and sleep for while until the result is back
Try this:
if (email == null || email.length() == 0) {
...
}
else if (email != null & email.length() != 0) {
...
}
One solution that I just came up with is sending a callback to the DAO layer function so when the done action is triggered, it will trigger back the callback to move on.
public interface NavCallback {
public void finish();
}
public class MainActivity {
// inside click listener
NavCallback navCallbackError = new NavCallback() {
#Override
public void finish() {
setError();
}
.....
}
and the DAO function will take the callback as the parameters
public void checkExists(String email, NavCallback callback) {
.....
if (callback != null) callback.finish();
}

How to get refference on object on reccursion?

I am starting asynctask from activity. Sometimes SearchAdd does not returns desired result ( due to TimeOuts etc) So, i call it recursivly up to 5 times. But I messed up, how to update refenece to SearchAdd in activity after selfcall.
SearchAdd s = new SearchAdd(callback);
s.execute();
Asyntask
public class SearchAdd extends AsyncTask<Void, Void, JSONObject> {
private SearchAddInterface callback;
private int trial = 0;
public SearchAdd(SearchAddInterface callback) {
this.callback = callback;
}
public SearchAdd(SearchAddInterface callback, int trial) {
this.callback = callback;
this.trial = trial;
}
#Override
protected JSONObject doInBackground(Void... arg0) {
return JSONParser.getJSONfromURL(RequestStringCreater
.concatenationStrings().replaceAll("\\s", "%20"));
}
#Override
protected void onPostExecute(JSONObject result) {
super.onPostExecute(result);
if (result == null){
if(trial <5){
SearchAdd sAdd = new SearchAdd(callback, trial);}
}
}else{
// do job
}
}
To remove the recursion and allow the request to be cancelled, you can do this:
public class SearchAdd extends AsyncTask<Void, Void, JSONObject> {
private SearchAddInterface callback;
private int trial = 0;
private boolean cancel = false;
public SearchAdd(SearchAddInterface callback) {
this.callback = callback;
}
public void cancel() {
cancel = true;
}
#Override
protected JSONObject doInBackground(Void... arg0) {
JSONObject result = null;
while (!cancel && (trial < 5) && (result == null)) {
trial++;
result = JSONParser.getJSONfromURL(RequestStringCreater.concatenationStrings().replaceAll("\\s", "%20"));
}
return result;
}
#Override
protected void onPostExecute(JSONObject result) {
super.onPostExecute(result);
if (cancel) {
// The request was cancelled
} else if (result == null) {
// All trials have failed.
} else {
// Do job
}
}
}

Categories

Resources