Anyone who has ever worked with Android Studio knows that it has a very helpful code linting capacity that helps programmers avoid common anti-patterns and mistakes.
Such a system is sometimes annoying however, and in this particular case I think it's just being that.
I have a AsyncTask like the follwing
class MyAsyncTask extends AsyncTask<Void, Void, MyDataType> {
private Context context;
MyAsyncTask(Context _context) {
context = _context;
}
#Override
protected void onPreExecute() {
// Show a progress dialog or something
// to indicate that we're doing some work
// here.
}
#Override
protected MyDataType doInBackground(Void... args) {
return generateData(); // returns `MyDataType` of course
}
#Override
protected void onPostExecute(MyDataType data) {
// Deliver the data and then
context = null;
}
}
And of course, Android Studio is kindly telling me that the context field leaks a Context object.
My questions is,
is this really leaking the Context object? or,
is context = null; guaranteed to run and thus there is no leak?
This is different than Warning: This AsyncTask class should be static or leaks might occur because this is NOT A non-static inner class of a Context like in that case.
note: I am not a Java programmer, and I don't understand garbage collection very well. I have mostly worked with c and there I know how memory is managed. Garbage collected languages make me uncomfortable because I don't know how this "garbage collection" occurs.
Yes it is leaking of Context, because your Context object can't be garbage collected before your AsyncTask wasn't terminated.
doInBackground() method can proceed some large operation and before terminating your Context will be still alive.
Good practice to wrap your Context object into WeakReference so if this object can be accessed only by WeakReference's it will be collected.
Related
I'm using an ASyncTask in my app to get some data (a short URL) via a REST API from a web service (Bitly).
When the ASyncTask completes I want to pass the result back to my MainActivity.
Getting the data back to the MainActivity is acheievd by using the onPostExecute method of the AsyncTask.
I've read and read and read about how to do this and there seem to be two general approaches.
Originally I was using a 'WeakReference' approach whereby at the start of the AsyncTask class you create a weak reference to your MainActivity as follows:
private class getShortURL extends AsyncTask<String, Void, String> {
private WeakReference<MainActivity> mainActivityWeakReference;
myASyncTask(MainActivity activity) {
mainActivityWeakReference = new WeakReference<>(activity);
}
{etc etc}
With this approach your AsyncTask class sits outside of your MainActivity class and so a lot of things need to be referenced via the weak reference.
This worked fine (except I suspected - possibly incorrectly - that this weak reference may have been the cause of occassional NPEs), but I then found another way of doing things.
This second approach involved moving the ASyncTask class inside of the MainActivity class.
This way I was able to access everything that was accessible in the MainActivity class directly, inlcuding UI elements and methods defined in the MainActivity. It also means that I can access resources such as strings etc and can generate toasts to advise the user what is happening.
In this case the whole of the WeakReference code above can be removed and the AsyncTask class can be made private.
I am also then able to do things like this directly in onPostExecute or to keep this in a method within the MainActivity that I can call directly from onPostExecute:
shorten_progress_bar.setIndeterminate(false);
shorten_progress_bar.setVisibility(View.INVISIBLE);
if (!shortURL.equals("")) {
// Set the link URL to the new short URL
short_link_url.setText(shortURL);
} else {
CommonFuncs.showMessage(getApplicationContext(), getString(R.string.unable_to_shorten_link));
short_link_url.setHint(R.string.unable_to_shorten_link);
}
(note that CommonFuncs.showMessage() is my own wrapper around the toast function to make it easier to call).
BUT, Android Studio then gives a warning that "the AsyncTask class should be static or leaks might occur".
If I make the method static I then get a warning that the method from the MainActivity that I want to call from onPostExecute cannot be called as it is non-static.
If I make that method from MainActivity a static method, then it cannot access string resources and any other methods that are non static - and down the rabbit hole I go!
The same is true, as you would expect, if I just move the code from the method in the MainActivity into the onPostExecute method.
So...
Is having an AsyncTask as a non-static method really a bad thing? (My
app seems to work fine with this warning in AS, but I obviously don't
want to be creating a memory leak in my app.
Is the WeakReference appraoch actually a more correct and safer approach?
If I use the WeakReference approach, how can I create things like toasts which need to be run on the UI thread and access string
resources etc from the MainActivity?
I read somewhere about creating an interface but got a bit lost and couldn't find that again. Also would this not have the same kind of reliance on the MainActivity that a WeakReference does and is that a bad thing?
I'm really looking for best practice guidance on how to get some data back to the MainActivity and the UI thread from an AsyncTask that is safe and doesn't risk memory leaks.
Is having an AsyncTask as a non-static method really a bad thing? (My app seems to work fine with this warning in AS, but I obviously don't want to be creating a memory leak in my app.
Yes, your Views and your Context will leak.
Enough rotations and your app will crash.
Is the WeakReference approach actually a more correct and safer approach?
It's lipstick on a dead pig, WeakReference in this scenario is more-so a hack than a solution, definitely not the correct solution.
What you're looking for is a form of event bus from something that outlives the Activity.
You can use either retained fragments* or Android Architecture Component ViewModel for that.
And you'll probably need to introduce Observer pattern (but not necessarily LiveData).
If I use the WeakReference approach, how can I create things like toasts which need to be run on the UI thread and access string resources etc from the MainActivity?
Don't run that sort of thing in doInBackground().
I'm really looking for best practice guidance on how to get some data back to the MainActivity and the UI thread from an AsyncTask that is safe and doesn't risk memory leaks.
The simplest way to do that would be to use this library (or write something that does the same thing yourself, up to you), put the EventEmitter into a ViewModel, then subscribe/unsubscribe to this EventEmitter inside your Activity.
public class MyViewModel: ViewModel() {
private final EventEmitter<String> testFullUrlReachableEmitter = new EventEmitter<>();
public final EventSource<String> getTestFullUrlReachable() {
return testFullUrlReachableEmitter;
}
public void checkReachable() {
new testFullURLreachable().execute()
}
private class testFullURLreachable extends AsyncTask<Void, Void, String> {
...
#Override
public void onPostExecute(String result) {
testFullUrlReachableEmitter.emit(result);
}
}
}
And in your Activity/Fragment
private MyViewModel viewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
// ...
}
private EventSource.NotificationToken subscription;
#Override
protected void onStart() {
super.onStart();
subscription = viewModel.getTestFullUrlReachable().startListening((result) -> {
// do `onPostExecute` things here
});
}
#Override
protected void onStop() {
super.onStop();
if(subscription != null) {
subscription.stopListening();
subscription = null;
}
}
In my app I used the singleton pattern for objects which serve other classes and not more than one instance of them is needed. LocalstorageManager for example:
public class LocalStorage {
private Context context;
private static LocalStorage instance = null;
protected LocalStorage() {
// Exists only to defeat instantiation.
}
public synchronized static LocalStorage getInstance() {
if (instance == null) {
instance = new LocalStorage();
}
return instance;
}
public void setContext(Context _context) {
instance.context = _context;
}
...
In this app I am using a BroadcastReceiver that responds to GSM notifications:
...
#Override
protected void onPushReceive(Context context, Intent intent) {
LocalStorage localStorage = LocalStorage.getInstance();
...
Should I check whether my singletons state is set before using them? Is there a chance than on cases of low memory or an incoming GSM when the app is not running I will have to reinstansiate my singletons with their state? If so, should I do it in the custom app class? Would it always be called?
Should I check whether my singletons state is set before using them?
Singleton if properly implemented would return only one instance
which is common throughout the app life-cycle. Your singleton's state
is ensured until unless the Singleton object gets destroyed say as described in point "2" below.
Is there a chance than on cases of low memory or an incoming GSM when the app is not running I will have to reinstansiate my singletons with their state?
Java Garbage Collector (GC) will collect all the objects that are not used/not referenced. Which means if no active object of your app has a "reference"/"is using" this object then the GC will clear it. In such case you may have to instantiate your object. Java GC gets triggered irrespective of "..low memory or an incoming GSM.." it could also be System discretion.
If so, should I do it in the custom app class? Would it always be called?
No, not in custom app class do it in the Singleton class implementation. The method "getInstance()" will take care of when to instantiate a new object Vs when to serve an existing object. If there is any custom state/parameter to instantiate you may want to pass them in getInstance() method.
I have to communicate between an Android Activity and another Java class. In a very, very stripped down version of what I want, I want the Java Class to run, and when it's done, set some information. To get a more specific idea of what I want to happen:
Activity {
CallJavaClass(); // Doesn't return anything, would be very bad form
GetInfoFromJavaClass() // Once the JavaClass has finished what needs to be done
}
What I could do:
Set a global variable in JavaClass that my Activity can access. I'd rather not do it this way, as I would have to implement some kind of OnChangedListener on that object in the JavaClass.
Use an Interface with Setters/ Getters. The problem with this is my JavaClass is a Singleton, and most of its methods are static, which is a no-go when working with an Interface.
Create a separate class that handles these variables. I would rather keep it simple and not have to use a bunch of different classes to do this, though.
So what do you think would be the best solution? If needed (and probably will be), I can provide more information about what exactly I want done. Thanks for your help in advance.
Sounds like something you could use AsyncTask for http://developer.android.com/reference/android/os/AsyncTask.html
then again, it depends on the specifics of what you're going for
AsyncTask should resolve your problem:
private class myTask extends AsyncTask<Void, Void, Boolean> {
protected void onPreExecute() {
super.onPreExecute();
// do something before starting the AsyncTask
}
#Override
protected Boolean doInBackground(Void... params) {
// do what you want to do
return false;
}
protected void onPostExecute(Boolean success)
{
super.onPostExecute(success);
// do something right after you finish the "doInBackground"
}
}
Here is a sample code which make me a little missing:
package com.leak;
import android.app.Activity;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
public class WindowLeakActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new LeakThread().execute();
}
class LeakThread extends AsyncTask<Void, Void,Void>{
ProgressDialog dialog;
#Override
protected void onPreExecute() {
dialog=new ProgressDialog(WindowLeakActivity.this);
dialog.show();
}
#Override
protected Void doInBackground(Void... params) {
try {
Thread.sleep(2000);
finish();
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void result) {
//that would be ok
if(WindowLeakActivity.this!=null && !WindowLeakActivity.this.isFinishing())
dialog.dismiss();
}
}}
As you see,I create a LeakThread and finish the WindowLeakActivity at doInBackground()method.
In order to prevent window leak error,I have to check if the Activity has been finished at onPostExecute()method.That make me a little missing.I have following questions:
Is do Activity instance isFinish() check at onPostExecute safe?If my Thread class is not a inner class of Activity.Do I have to check Activity instance is not null at first?
When would Activity instance die?As Activity's lifecycle description,it will terminal when callback call onDestroy().But however,the Activity's Thread is still going.Though it's window been not visible,I can also get it's instance.
If I call the System.gc().Will it collect Activity's instance?
Sorry for my bad description.Thank you for reading my question very much.
1) Generally as a rule, avoid using any reference to the activity inside doInBackground().
Managing AsyncTask along with the life cycle of an Activity is tricky at best. Look at this StackOverflow thread for a good discussion on AsyncTask and its pitfalls.
2) You are not in control of when the instance of the activity will die, so don't go about depending on it. The destruction of the instance of the activity depends on several factors, which are determined by the system. So try and ensure that you don't use a reference to the Activity anywhere outside the scope of the activity object itself. You however, do receive a callback when your Activity's execution is about to stop, so make sure you clean up memory there.
3) System.gc() is more like a request to the JVM, asking it to run the garbage collector as soon as it is conveniently possible. Take a look at this thread.
From personal experience, I can tell you this, try and avoid using ProgressDialog when using AsyncTask. It is painful to manage, can leak your Window object pretty easily, crash the application as soon as your devices configuration changes and pretty much give you hell trying to debug it. I've not even seen the Google apps on Android utilize ProgressDialog perfectly (uptil Gingerbread i.e. ). That's just my experience however.
I'm using a custom Database class in my code to manage my database and handle transactions. Whenever I instantiate it, I pass the application context to it's constructor. Reading up on the articles at the Android developer site makes me wonder if I'm doing something that could cause a huge memory leak in my application. Simplified, my code looks like this, first off an activity:
public class MyActivity extends Activity
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.somelayout);
Database db = new Database(getApplicationContext());
}
}
And my database class (in a seperate file) looks like this:
public class Database
{
Context context;
public Database(Context context)
{
this.context = context;
}
public DatabaseHelper extends SQLiteOpenHelper
{
// Pass the context to the constructor etc etc.
}
}
The code might have bugs, I wrote it quickly in notepad. Anyway, this got me worried that the db object keeps the context when the user navigates away from the activity, thus uneccesarily spending a huge amount of resources. If this is indeed the case, how can I avoid this? Is there a way to destroy and object when it is no longer needed?
The object referenced by db is eligible for garbage collection as soon as onCreate finishes. So there is no problem here.
If you made db or Database.context into a static field, that's when you should start to worry.
If the Database object holds resources and is not closed properly you might get into trouble.
If at all possible stay at pure SQL level, and use JDBC pooling to get standard way of handling these things.