Avoid memory leak with WeakReference Android - android

I have this code example below and i want to make sure that no memory will happen. By passing the whole activity to an async task will lead to a memory leak if the task itself will have a bigger lifecycle than activity's. But if i declare a weakReference, means that if a rotation is about to happen, the activity will be destroyed without memory leak. Am right guys? Do i have to set something to null also?
Main .java
public class Main extends Activity {
private TextView mMessageView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView();
new Background(this /*getBaseContext()*/ ).execute();
}
}
Background.java
public class Background extends AsyncTask<String, Void, String>
{
private WeakReference activity;
public void Background(Activity act)
{
activity = new WeakReference(act);
}
#Override
protected String doInBackground(String... params)
{
}
#Override
protected void onPostExecute(String result)
{
if(activity.get()!=null)
//ToDo
}
}

There is no need for explicit nulling when using WeakReference. Regarding the use of AsyncTask inside an Activity, just be careful not to create an anonymous instance because it will have an implicit reference to the enclosing class which can lead to memory leaks.
The code you posted seems ok, don't forget to terminate the AsyncTask gracefully when the activity is recreated.

Related

AsyncTask questions

I was reading things about AsyncTask. It was understandable to me, but immediately two concerns came up to my mind:
Let say I have a class and an interface:
public MyInterface<T>
{
void done(T result);
}
public MyActivity extends Activity implements MyInterface<String>
{
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
R.getViewByID(R.....);
//...
beginTask(urls);
}
private void beginTask(URL[] urls)
{
ATask task = new ATask(this);
task.execute(urls);
}
#Override
void done()
{
System.out.println("done!");
}
}
And the AsyncTask class:
public ATask extends AsyncTask<URL, Void, String>
{
MyInterface<String> handler = null;
private StringBuilder fetchedResult = new StringBuilder();
ATask(MyInterface<T> handler)
{
this.handler = handler;
}
#Override
protected String doInBackGround(URL urls...)
{
URL url = urls[0];
HttpClient client = new DefaultHttpClient();
HttpGet request = new HttpGet(url.toString());
ResponseHandler<String> handler = new BasicResponseHandler<String>();
String result = null;
try
{
result = client.execute(request, handler);
fetchResult.append(result);
}
catch(IOException e)
{
e.printStackTrace();
}
catch(ClientProtocolException e)
{
e.printStackTrace();
}
finally
{
client.getConnectionManager().shutdown();
}
return fetchResult.toString();
}
#Override
protected void onPreExecute()
{
super.onPreExecute();
}
#Override
protected void onPostExecute(String result)
{
super.onPostExecute(result);
handler.done(result);
}
}
All is well. But the concerns that came to me were:
1.)
What if for some reasons the Activity (MyActivity) that calls the AsyncTask
(ATask) gets destroyed and when ATask is finished and then calls done(); in the destroyed Activity? How could we prevent
this from happening or is there a way to re-create the destroyed
Activity? What would be the best practice here?
2.)
Another scenario: What if the background task takes a long time (intended, even though
the App is no longer in view) in doInBackground? Would it then keep holding onto a
reference of this Activity (MyActivity) for as long when the Activity (MyActivity) should be destroyed for memory's sake (i.e., when onStop() needs to be called)?
In both cases you have to override application onDestroy or activity onStop methods and call task.cancel(). So you need to keep reference to your task object.
1.) As per the documentation :
onStop() -> Called when the activity is no longer visible to the user, because another activity has been resumed and is covering this one.
ondestroy() -> The final call you receive before your activity is destroyed. This can happen either because the activity is finishing (someone called finish() on it, or because the system is temporarily destroying this instance of the activity to save space
So you need to decide when do you want to call your task.cancel(), it may depend on your business logic.
2.) As per the documentation AsyncTask should be used for operations running only for a very few seconds. Whenever an AsyncTask/Thread is running and your activity gets destroyed then memory is leaked because they still hold the reference of your activity. To avoid any leak declare the AsyncTask/Thread as a private static inner class and in onDestroy() close your thread by t.close and in case of AsyncTask set your activity to null mActivity = null so in both the cases your activity is now eligible for garbage collection.

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.

How to check if an activity is continuing

In my app, I am trying to run multiple photos through a photo editor (one at a time). I have it set up in a for-loop at the moment, but I feel like it is overloading the photo editor and not actually waiting until the current edit session is over, so I wanted to put a control statement in my for-loop to check if the session was still active.
Is this possible?
Easier to tell when the current is over than polling to see if it is still active. If you start it with startActivityForResult, your calling activity will be notified when the invoked activity ends.
Have a look at Starting Activities and Getting Results in the Activity docs for an example.
You might also want to consider running this in an async task. This will pull your heavy processing away from the UI thread. Async task let's you do progress updates as well.
http://developer.android.com/reference/android/os/AsyncTask.html
public class MainActivity extends ListActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.logo_page);
// Call async task.
my_async as = new my_async(this);
as.execute();
}
--
my_async:
public class my_async extends AsyncTask<Object, Integer, String> {
private parentClass activity;
public my_async (parentClass activity) {
this.activity = activity;
}
#Override
protected String doInBackground(Object... arg0) {
// Do stuff
return "MyString";
}
protected void onPostExecute(String contents) {
activity.contents = contents;
}

What happens to an AsyncTask when the launching activity is stopped/destroyed while it is still running?

I've seen few questions nearly identical to mine, but I couldn't find a complete answer that satisfies all my doubts.. so here I am.. Suppose that you have an activity with an inner class that extends the AsyncTask class like this:
public class MyActivity extends Activity {
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
protected Bitmap doInBackground(String... urls) {
return DownloadImage(urls[0]);
}
protected void onPostExecute(Bitmap result) {
ImageView img = (ImageView) findViewById(R.id.img);
img.setImageBitmap(result);
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new DownloadImageTask().execute("http://mysite.com/image.png")
}
}
Suppose that the activity is paused or destroyed (maybe the two cases are different) while the DownloadImageTask is still running in background.. then, the DownloadImageTask's methods that run on the activity UI thread can be triggered and the DownloadImageTask may try to access Activity's methods (it is an inner class, so it can access the methods and instance variables of the outer class) with a paused or destroyed Activity, like the call to findViewByID in the example below.. what happens then? Does it silently fail? Does it produce any exception? Will the user be notified that something has gone wrong?
If we should take care that the launching thread (the Activity in this case) is still alive when running-on-UI methods are invoked, how can we accomplish that from within the AsyncTask?
I'm sorry if you find this as a duplicate question, but maybe this question is a bit more articulated and someone can answer with greater detail
Consider this Task (where R.id.test refers to a valid view in my activity's layout):
public class LongTaskTest extends AsyncTask<Void, Void, Void>{
private WeakReference<Activity> mActivity;
public LongTaskTest(Activity a){
mActivity = new WeakReference<Activity>(a);
}
#Override protected Void doInBackground(Void... params) {
LogUtil.d("LongTaskTest.doInBackground()");
SystemClock.sleep(5*60*1000);
LogUtil.d("mActivity.get()==null " + (mActivity.get()==null));
LogUtil.d("mActivity.get().findViewById(R.id.frame)==null " + (mActivity.get().findViewById(R.id.test)==null));
return null;
}
}
If I run this task from an Activity's onCreate like so:
public class Main extends Activity {
#Override
public void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.testlayout);
new LongTaskTest(this).execute();
finish();
}
}
No matter how long I sleep the background thread, my log always shows:
LongTaskTest.doInBackground()
mActivity.get()==null false
mActivity.get().findViewById(R.id.frame)==null false
Which is to say that the activity and its views appear to stay alive (even if I manually issue GCs via DDMS). If I had more time I'd look at a memory dump, but otherwise I don't really know why this is the case ... but in answer to your questions it appears that:
Does it silently fail? No
Does it produce any exception? No
Will the user be notified that something has gone wrong? No
The doInBackground() will keep on running even if your Activity gets destroyed(i,e your main thread gets destroyed) because the doInBackground() method runs on the worker's/background thread. There will be a 'problem' in running the onPostExecute() method as it runs on the main/UI thread and you may experience running into unrelated data but there will be no exception shown to the user. Thus, it is always better to cancel your AsyncTask when your activity gets destroyed as there is no reason to run AsyncTask when the Activity is no longer present. Use android Service if you continuously want to download something from the network even when your Component/Activity gets destroyed. Thanks.

Is it possible to access an object of an activity fom other place?

This maybe a stupid idea, but does anyone know is it possible to access one activity's object form other places?
To be specific, lets say if you have an activity A (with a textView t) and you create a normal java class B.
At onCreate, you start to run B for some calculation like below,
public class MyActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
t = (TextView)findViewById(R.id.outputtext);
Somejava B = new Somejava();
B.run();
}
}
Is there a way for B to update the textView?
I know the simple way (maybe the correct way) is to return the result from B class and use t.setText(result) in MyActivity, but I'm just want to know is it possible to update the textview in B?
Use Intent or public static variables
can simply pass activity refernce to b in constructor and create the method in your acitivty to update textview. if you using another thread not forgot to use handler or other ways to update UI thread.
Yes, it is possible if the Activity's field is public and post the UI changes in a public Handler created on the first Activity but in facts, it's really ugly to do that...
You can use startActivityForResult(...) to notify an other activity how the process has passed with some serialiezable data in the Bundle extras of the Intent and catch the result in the overrided method onActivityResult(...)...
For a "normal java class" B I would work with interfaces
public interface SomejavaListener{
void onSomejavaFinish(Object result);
}
public class MyActivity implements SomejaveFinish extends Activity {
TextView t;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
t = (TextView)findViewById(R.id.outputtext);
Somejava B = new Somejava();
B.run(MyActivity.this); //notice the extra argument!
}
public void onSomejavaFinish(Object result){
t.setText("updated! ^,^");
}
}
public class Somejava {
//...
public void run(SomejavaListener callback){
//working working
callback.onSomejavaFinish( new Object() );
}
}
However in respect to the android environment the question is sitting in I got the feeling maybe an AsyncTask would be the right thing for you. It has an doInBackground method to do work and not spoiling your UI Thread (resulting in ANR Errors.)
Another advantage is the onPreExecute and onPostExecute methods are running in the UI Thread itself again, so it just takes a blink to update your TextView
public class MyActivity extends Activity {
TextView t;
private class MyAsyncTask extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... void) {
//do your stuff
return null
}
protected void onPostExecute(Void void) {
MyActivity.this.t.setText("updated ^^v");
}
}
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
t = (TextView) findViewById(R.id.outputtext);
MyAsyncTask myAsyncTask = new MyAsyncTask();
myAsyncTask.execute();
}
}

Categories

Resources