Android 'leaking context' error - android

I'm running an AsyncTask to retrieve some info via HTTP.
I'm calling this method from my main activity (do_get_from_url() is triggered by a button click):
private void do_get_from_url() {
new getFromURL(this).execute();
}
The class that this calls is in the same .java file and starts as follows:
class getFromURL extends AsyncTask<Void, Void, String> {
private MainActivity activity;
getFromURL(MainActivity activity){
this.activity = activity;
}
...other code here...
String linkURL = activity.link_url.getText().toString();
String getFromURLrequestURL = activity.getString(R.string.short_url_request_url_base);
...other code here...
}
It seems that I need to use activity in order to access both the string resources and the UI element link_url from the main activity BUT the line this.activity = activity displays a warning in Android Studio that it is leaking a context.
Is there any way to avoid this?

Root cause: You are passing an activity as a context to a background thread. So the thread will keep a reference to the activity. As long as the thread running, the activity cannot be destroyed (by calling finish() method or users press back key to finish the activity). The term leak simply mean when an object or instance no longer used but the system cannot reclaim memory where they live on.
Solution: In Android you can use an API called WeakReference, Simply it will keep a weak reference to your object (in this case your activity), so when the activity get destroyed, it will not keep the reference anymore.
getFromURL.java
class getFromURL extends AsyncTask<Void, Void, String> {
private WeakReference<MainActivity> mainActivityWeakReference;
getFromURL(MainActivity activity){
mainActivityWeakReference = new WeakReference<>(activity);
}
...other code here...
MainActivity activity = mainActivityWeakReference.get();
// Always check this to make sure the activity haven't got destroyed yet.
if (activity != null) {
String linkURL = activity.link_url.getText().toString();
String getFromURLrequestURL = activity.getString(R.string.short_url_request_url_base);
}
...other code here...
}

Yes, there is. Correct way is to use a WeakRefrence like this
WeakReference<MainActivity> parent;
GetFromURL(MainActivity activity){
parent = new WeakReference(activity);
}
...other code here...
String linkURL = parent.get().link_url.getText().toString();
String getFromURLrequestURL = parent.get().getString(R.string.short_url_request_url_base);
...other code here...

Related

How to get the current savedInstanceState in an AsyncTask callback?

I ran into an interesting problem, and I'm not sure how to go about fixing it. Consider the following code:
public class MainActivity extends AppCompatActivity {
private Bundle savedState;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
savedState = savedInstanceState;
Log.d("ON CREATE", "savedState is null: "+(savedState==null));
new CustomTask().execute();
}
public class CustomTask extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... voids) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
protected void onPostExecute(Void v) {
Log.d("POST EXECUTE", "savedState is null: "+(savedState==null));
}
}
}
This code saves a reference to the savedInstanceState then runs an AsyncTask, which tries to access that variable 5 seconds later. If the user changes the orientation of the device before the AsyncTask finishes its work, the following output is produced:
ON CREATE: savedState is null: true //initial run
ON CREATE: savedState is null: false //re-created after orientation change
POST EXECUTE: savedState is null: true //first instance
POST EXECUTE: savedState is null: false //second instance
Both of the onPostExecute() methods fire after the orientation change, but they are seemingly accessing the same savedState variable, which I expected to be non-null in both cases since it was being accessed after the orientation change.
Apparently the first AsyncTask, which was started before the orientation change still references the savedState variable from before the change as well. So my questions are:
Why does it do this? After the app state is restored, I would expect the AsyncTask to simply access all class members in their current states, which means savedState would be non-null.
And how can I access the current savedInstanceState variable from the callback of an AsyncTask that was started before that variable was changed?
After the orientation change, there are two instances of MainActivity. The old one has gone through the tear-down lifecycle events (onStop(), onDestroy(), etc.) but has not been garbage collected because the inner-class CustomTask thread is still running and holds a reference to it. That instance of CustomTask sees the null savedState. It knows nothing of the new instance of MainActivity, created after the restart, and its non-null savedState.
Do you really want the original instance of CustomTask to continue running after restart? Maybe you should cancel it when the activity is destroyed. If you really need it to continue running and have access to state data that you now declare in the activity, you will need to move that state data out of the activity to somewhere else, such as a singleton object, subclass of Application or persistent storage.
Using a retained fragment might be another option for retaining state and background processing across restarts.
After a month of dealing with this issue, I finally found the solution to this. The key concept I was struggling with was how to update an (already running) AsyncTask with a reference to the current instance of the Activity it's working with. Here's how to do it.
The first step is to separate the AsyncTask into its own class file, and pass a reference to the Activity it's working with in through a constructor or a setter. So to use the same example I used in my original question, it would look something like this:
public class CustomTask extends AsyncTask<Void, Void, Void> {
//This could also be a reference to a callback interface
//implemented in an Activity
private Activity activity;
public CustomTask(Activity activity) {
this.activity = activity;
}
protected Void doInBackground(Void... voids) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
protected void onPostExecute(Void v) {
Log.d("POST EXECUTE", "savedState is null: "+(savedState==null));
}
//newly added method
public void setActivity(Activity activity) {
this.activity = activity;
}
//newly added method
public void detachFromActivity() {
activity = null;
}
}
The next step is to save a reference to the running AsyncTask as a data member of the Activity. That's just a matter of creating a private variable private CustomTask customTask; in the Activity.
Next, and this is the important part, you need to override onRetainCustomNonConfigurationInstance() in your Activity, and in this method, detach the AsyncTask from its old Activity (which is destroyed on a screen rotation) and retain the reference to the task so we can work with it in the new instance of the Activity that will be re-created when the screen finishes rotating. So this method would look like this:
#Override
public Object onRetainCustomNonConfigurationInstance() {
if(customTask != null) {
customTask.detachFromActivity();
}
return customTask;
}
Now, after the screen rotation has completed and the Activity is re-created, we need to get the reference to our task that was passed through. So we call getLastCustomNonConfigurationInstance() and cast the Object it returns to our specific AsyncTask class type. We can do a null check here to see if we have a task that was passed through. If there is one, we set its listener to our CURRENT Activity reference, so that the callbacks come to the right Activity instance (and hence avoided NullPointerExceptions, IllegalStateExceptions, and other nasties). This bit should look like this:
customTask = (CustomTask) getLastCustomNonConfigurationInstance();
if(customTask != null) {
customTask.setListener(this);
}
Now our running AsyncTask has the correct reference to the current instance of our Activity, and it will properly deliver its callbacks to an up-to-date Activity.
For more information on the uses and limitations of this solution, please see the Android documentation here: http://developer.android.com/reference/android/app/Activity.html#onRetainNonConfigurationInstance()

Is it a good practice to pass an object of Android Activity to a Thread class' constructor?

While writing an Android activity that submits input queries to a web server, I was thinking instead of having an anonymous inner class to define the networking thread, why can't we use a separate class that extends Thread.
While this works as expected, I would like to know whether this belongs any good or bad practice.
public class GreetActivity extends Activity{
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_greet_activity);
}
public void onClickBtn(View v){
Thread t = new WorkerThread("http://10.0.2.2:8080",this);
t.start();
}
}
class WorkerThread extends Thread{
private String targetURL;
private Activity activity;
public WorkerThread(String url, Activity act){
this.activity = act;
this.targetURL = url;
}
public void run(){
TextView tv = (TextView) activity.findViewById(R.id.textview1);
. . . . . .
}
}
Passing an Activity reference to a thread has some caveats. Activity lifecycle is separate from thread lifecycle. Activities can be destroyed and recreated e.g. by orientation change events. If the activity reference is hold in a thread, the resources held by the activity (lots of bitmap assets for example, taking a lot of memory) are not garbage collectible.
An non-static inner class also has the same problem since the reference to the parent is implicit.
A working solution is to clear the activity reference when the activity is destroyed, and supply a new activity reference when the activity is recreated.
You can only touch your UI widgets in the UI thread as mentioned by blackbelt.
For what it's worth, an AsyncTask is easier to work with than a bare-bones Thread.
In your case, no it is not, since only the UI Thread can touch the UI, your code will make your application crashes with
android.view.ViewRoot$CalledFromWrongThreadException

How activity leak happen in this code

By passing activity object to the constructor of the AsyncTask. Is there any possibility of
Activity leaking. if so then how it happen, can anyone please explain this. and how to avoid the activity leaking if it happening.
public class RemoteLoader extends AsyncTask<Void, Void, Void> {
private Activity activity;
public RemoteLoader(Activity context){
this.activity = context;
}
#Override
protected Void doInBackground(Void... Pages) {
// do in bg
}
#Override
protected void onPostExecute(Void result) {
// Set title into TextView
TextView txttitle = (TextView)activity.findViewById(R.id.txtProtip);
txttitle.setText(protip);
}
}
The AsyncTask keeps a reference to the Activity, therefore the Activity instance cannot be garbage collected while the AsyncTask is alive/running.
In certain cases, such as orientation change, the "old" Activity instance is no longer required as another Activity instance is created by the framework. In such cases, if your AsyncTask is keeping a strong reference to the Activity instance, then there will be a memory leak.
To avoid this issue, use WeakReference as follows:
public class RemoteLoader extends AsyncTask<Void, Void, Void>{
private WeakReference<Activity> activity;
public RemoteLoader(Activity context){
this.activity = new WeakReference<Activity>(context);
}
...
...
}
When using WeakReference you can ensure that the Activity instance is garbage collectable if needed. You can read more about WeakReference here.
Try having a WeakReference to your Activity in the AsyncTask. And upon completion, check if the Activity still exists.

Get instance of current visible activity

How do I get the instance of the currently visible activity in Android? I've read that I can get the ComponentName of the activity by using ActivityManager to get a list of tasks and messing with that, but that's a recipe for disaster.
Is there a way to get an instance of the topmost activity, or if the activity isn't mine and I can't access it, just null?
TO get instance of currently visible activity, get its context as:
Context mContext = getApplicationContext();
or
mContext = this;
now use it for this activity related tasks.
OR to get instance of another activity; keep a static ActivityClass instance somewhere else and use getter setter to get set this instance like:
public void setActivity(MyActivity activity) {
myActivity = activity;
}
public MyActivity getMyActivity() {
return myActivity;
}

Share activity context among intents

I have 3 screens in my app, each of which are in their own classes. When the app launches, my Driver class sets up some GUI elements, and then launches the first Intent.
I have a separate GUI class (which Driver invokes) which handles everything from menu's to dialog boxes. Previously my app didn't use Intents so I could pass the activity/context from Driver to Gui in its constructor as an object of type Activity and as a result could define layouts etc like LinearLayout ll = new LinearLayout(activity) and everything would be operating in the same activity/context.
Since I've moved to using intents, each Activity/Class has its own context, thus the previous dialogs and popup boxes from the Gui class are in the background and not running. I get an error saying android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy#406629a0 is not valid; is your activity running? when I click on a button to launch a dialog.
To me, this indicates the new Intents have taken over the foreground and the objects from the previous context are out of scope.
So, is there a way I can still pass the same context through to the new Intents so I can still access these shared dialogs? Or will I have to bring the code into each class (duplicate code)?
In case thats a bit hard to understand, here is some basic source code:
public class Driver extends Activity
{
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Gui display = new Gui(this);
display.showScreen();
}
}
/////////////GUI.java///////////////////////
public class Gui
{
private Activity activity;
private Gui()
{}
public Gui(Activity _activity)//,Context _context)
{
this();
activity = _activity;
}
public void showScreen()
{
if(isLocationMode())
{
Intent i = new Intent(activity,LocationScreen.class);
//i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
activity.startActivity(i);
//locatScreen = new LocationScreen(activity);
//mainLayout.addView(locatScreen.getView());
}
else if (isManageMode())
{
Intent i = new Intent(activity,ManageScreen.class);
//i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
activity.startActivity(i);
//manageScreen = new ManageScreen(activity);
//mainLayout.addView(manageScreen.getView());
}
else if (isForwardMode())
{
Intent i = new Intent(activity,ForwardScreen.class);
//i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
activity.startActivity(i);
//forwardScreen = new ForwardScreen(activity);
//mainLayout.addView(forwardScreen.getView());
}
}
}
Have a setContext(Activity _activity) method in your Gui and call this in the onCreate of each activity?

Categories

Resources