Accessing data of destroyed activity means I have a memory leak? - java

I've created an interface which holds a reference to an interfaces instantiated from an activity.
This is the interface:
public interface Calback {
void fun();
}
This is the activity which instantiates the calback and binds it to asincktask.
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView txt = findViewById(R.id.helloTxtv);
txt.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Calback call = new Calback() {
#Override
public void fun() {
Log.d("tag","text of destroyed activity: "+((TextView)findViewById(R.id.helloTxtv)).getText());
}
};
Worker worker = new Worker(call);
worker.execute();
}
});
}
}
What's strange is that using that calback I can access textview even if the activity was destroyed.
This is the code from asyncktask:
public class Worker extends AsyncTask<Void, Void, Void> {
private final Calback call;
public Worker(Calback call) {
this.call = call;
}
#Override
protected Void doInBackground(Void... voids) {
try {
sleep(5000);
Log.d("tag","done");
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
call.fun();
}
}
To ensure that the activity it's destroyed I've just rotated the screen.(But I've got the same result after starting another activity and finish the current one)
And here is the log result.
PS: I've used Android Studio 3.0

If you are able to access the text of the TextView after the parent Activity has been destroyed, then you have a memory leak.
However, I'm not convinced that is what is going on here. I think it is more likely that either the activity has not been destroyed, or the activity's state was persistent and you are now looking at the state in the new (reincarnated) activity.
Why? Because, it seems that the callback is being called via an onClick listener for the text view. And that can only occur if the specific text view is still visible. It can't be visible if it is a component of a destroyed activity.

Related

Android: How to get the activity calling the class

I have Activity1 and Activity2. Both can call a class named "fetchData.java". Is there any way inside fetchData.java wherein I can get which activity called it?
Here is my fetchData.java:
public class fetchData extends AsyncTask<String,String,String> {
#Override
protected String doInBackground(String... voids) {
//do something
}
#Override
protected void onPostExecute(String aVoid) {
super.onPostExecute(aVoid);
}
}
Activity1 and Activity2 code:
public class Activity1 extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Call fetchData.java
fetchData getValues = new fetchData();
getValues.execute();
}
The only way I can see for you to do this is by passing the Activity in the constructor:
public class FetchData extends AsyncTask<String, String, String> { //rename fetchData to FetchData to follow Java coding practices
private Activity activity;
public FetchData(Activity activity) {
this.activity = activity;
}
#Override
protected String doInBackground(String... strings) { //use the proper variable names
//...
}
#Override
protected void onPostExecute(String string) { //use the proper variable names
//use instanceof to check the identity of the activity instance
if (activity instanceof Activity1) {
//whatever
} else if (activity instanceof Activity2) {
//whatever else
}
//super call isn't needed
}
}
And to instantiate it:
FetchData getValues = new FetchData(this);
getValues.execute();
Take note of my comments. I made a few changes to your code to improve readability and conform better to Java's coding standards.
In other cases, you might be able to read the stacktrace and find the calling class, but since you're using an AsyncTask, which runs on another Thread, the stacktrace only goes back to when the Thread was created, so it wouldn't work here.

AsyncTask: invalidating view does not take effect

Update
My small showcase is stored on Bitbucket
https://bitbucket.org/solvapps/animationtest
I have an Activity with a view in it. Contentview is set to this view.
public class MainActivity extends AppCompatActivity {
private MyView myView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myView = new MyView(this);
setContentView(myView);
startMovie();
}
public void startMovie(){
MovieTask movieTask = new MovieTask(myView, this);
movieTask.doInBackground(null);
}
}
A MovieTask is an Asynctask and refreshes the view periodically.
But invalidate() doesn't refresh the view.
public class MovieTask extends AsyncTask<String, String, String> {
MyView drawingView;
MainActivity mainActivity;
public MovieTask(MyView view, MainActivity mainActivity){
this.mainActivity = mainActivity;
this.drawingView =view;
}
#Override
protected String doInBackground(String... strings) {
for(int i=20;i<100;i++){
drawingView.myBall.goTo(i,i);
publishProgress();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
#Override
protected void onProgressUpdate(String... values) {
super.onProgressUpdate(values);
mainActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
Log.v("DEBUG_DRAW","in onProgressUpdate()");
drawingView.invalidate();
}
});
}
}
Can someone help ?
See how you are launching the AsyncTask:
public void startMovie() {
MovieTask movieTask = new MovieTask(myView, this);
movieTask.doInBackground(null);
}
You are manually calling a method inside some class called MovieTask, thus you are running a code on the same thread. Obviously, that is not your intention, you intended to run the computation code on a background thread.
Correct way to launch AsyncTask is using execute(Params...):
public void startMovie() {
MovieTask movieTask = new MovieTask(myView, this);
movieTask.execute("");
}
Now you will get the desired effect.
P.S.
Please, do not use that code: you do not need to launch a background thread in order to do that kind of stuff. As an alternative consider Animators API.
Declare setBall(int pos) method inside MyBall class:
public class MyView extends View {
...
public void setBall(int pos) {
myBall.setX(pos);
myBall.setY(pos);
invalidate();
}
}
Then change startMovie() to following:
public void startMovie() {
// "ball" means, that Animators API will search for `public setBall(int)` method inside MyView.java and call that method
ObjectAnimator ball = ObjectAnimator.ofInt(myView, "ball", 20, 100);
ball.setDuration(1000);
ball.start();
}
You'll get the same animation without a nasty code.
There is two possible case, first as described in documents:
void invalidate ()
Invalidate the whole view. If the view is visible,
onDraw(android.graphics.Canvas) will be called at some point in the
future.
So try to run your code in onResume, there is a chance that View is not visible yet.
Secondly View#invalidate tells the system to redraw the view as soon as the main UI thread goes idle. That is, calling invalidate schedules your view to be redrawn after all other immediate work has finished.
If you'd like to have your view updated periodically use Handler#postDelay or run it in a separate thread and use View#postInvalidate to update the View and trigger the call to onDraw.

Asynctask onpostexecute is not updating views if the screen is rotated while doinbackground is excuted

I had a simple program where i need to update the list and text based on the server response ...
But Asynctask onpostexecute is not updating views if the screen is rotated while doinbackground is executed .
I came to know the reason that , as the activity is recreated , onpostexecute wont update its views (Same problem..here is the link : Chek this link)
But i was not satisfied with the answer as it just suggesting to restricting to recreate the activity (i want recreating the activity as in my project i had some extra layout in landscape mode).
Please dont suggest setretaininstance(true) by taking fragments as it doesnt call oncreateview(), which is not suitable for my project.
May be as lastoption i can restrict orientation programatically in onpreexecute and release it in onpostexecute. But still it will not be good practice i think.
public class MainActivity extends ActionBarActivity {
TextView textView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView=(TextView) findViewById(R.id.textview);
if(savedInstanceState==null)
{
new myAsync().execute();
}
}
public class myAsync extends AsyncTask<Void, String, Void>
{
#Override
protected void onPreExecute() {
// TODO Auto-generated method stub
super.onPreExecute();
textView.setText("started");
Log.e("started", "started");
}
#Override
protected Void doInBackground(Void... params) {
// TODO Auto-generated method stub
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
#SuppressWarnings("deprecation")
#Override
protected void onPostExecute(Void result) {
// TODO Auto-generated method stub
super.onPostExecute(result);
Log.e("executed", "executed");
}
}
}
This is my sample program . textview is not updating if screen is rotated.
Please suggest . Thanks in advance .
You could provide myAsyncTask with a TextView member with a setter and store the current task as static member of the activity.
class MyActivity extends Activity {
private static AsyncTask myTask = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = (TextView) findViewById(R.id.textview);
if (myTask == null) {
new myAsync(textView).execute();
} else if(myTask.getStatus() != AsyncTask.Status.FINISHED) {
myTask.set(textView);
}
}
private class myAsync extends AsyncTask<Void, String, Void>
{
TextView textView;
myAsync(TextView textView) {
this.textView = textView;
}
synchronized void setTextView(TextView textView) {
this.textView = textView;
}
...
}
}
You would still have to deal with race conditions. E.g. you would probably want to impelemnt a mechanism to pause/resume your task, whenever the activity pauses/resumes.
You'd also have to make sure that the tasks textView is still valid and that you cleanup your static task in onPostExecute.
You can use the concept of bundle to put some string in it. When the activity is recreated after rotation check if saved instance state is null or not in the oncreate method. If not null retrieve the string using the bundle and update the textview. For more information on this rotation thing check out the videos of slidenerd on YouTube in the asynctask and threads playlist. Hope it helps.

AsyncTask is restarting when i press back button

I have an activity with multiple AsyncTask's, but when i press back button, the Activity is reloaded and the AsyncTask's are executed again. what should i do to Back to the previous activity and not reload the activity and asynctask ? please help.
public class LugarActivity extends SherlockActivity {
CargarDatos cargarDatos;
CargarComentarios cargarComentarios;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lugar);
cargarDatos = new CargarDatos();
cargarCometarios = new CargarComentarios();
loadData();
}
public void loadData(){
cargarDatos.execute();
}
public void loadOtherData(){
cargarComentarios.execute();
}
public class CargarDatos extends AsyncTask<Integer, Integer, String>{
#Override
protected String doInBackground(Integer... params) {
// here download data
}
#Override
protected void onPostExecute(String html) {
loadOtherData();
}
}
public class CargarComentarios extends AsyncTask<Integer, Integer, String>{
#Override
protected String doInBackground(Integer... params) {
// here download data
}
}
}
FIXED!
i fixed the problem with Singleton class:
public class DataManager {
private static DataManager instance = null;
protected static boolean isShowingTheView = false;
protected DataManager() { }
public static synchronized DataManager getInstance() {
if (instance == null) {
instance = new DataManager();
}
return instance;
}
}
in the activity i add this code:
DataManager dataManager = new DataManager();
if(!dataManager.isShowingTheView){
loadData();
dataManager.isShowingTheView = true;
}else{
finish();
}
and finally i override the onDestroy() method
#Override
public void onDestroy(){
dataManager.isShowingTheView = false;
super.onDestroy();
}
Remove loadData() from onCreate and call somewhere else.
Use Fragments
http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
A fragment can stay in memory during a configuration change and therefore you can run your asynctask inside itself. You can then query the fragment for any state information you require from your tasks and update your Activity accordingly.
If your Activity is destroyed before the other activity starts, using the back button will call onCreate again, instead of onRestart or onResume.
See here for details.
As Kuffs already mentions, using Fragments is the way to go.
Uglier solution, you could also set a shared preference holding a boolean once your AsyncTask is launched (or on its onPostExecute) so that it won't launch again after checking for that preference on your Activity's onCreate.

Enabling Buttons after doInBackground finishes

I would like to enable a few buttons from my main activity once the stuffs from doInBackground() is finished! Can someone please let me know how to do that?
I can't use findViewByID() for making he button visible from the AsyncTask class as it's not an activity class! :/
Do Like this...
Define a method which enables the Buttons.
Then on PostExecute() on AsyncTask, call that method
there is one callback onPostExecution(...) { } of AsynTask class use this method to UI stuff,for enable,disable button just write this way in onPostExcustion(...)
runOnUiThread(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
}
});
also make sure this method only available in activity class
thank you
Follow this way:
[1] Create your AsyncTask :
public class performBackgroundTask extends AsyncTask<Void, Void, Void> {
ProgressDialog Dialog = new ProgressDialog(HotUsers.this);
protected void onPreExecute() {
Dialog.setMessage("Loading Hot Users...");
Dialog.show();
}
protected void onPostExecute(Void unused) {
if(Dialog.isShowing())
Dialog.dismiss();
set_details_on_screen();
}
#Override
protected Void doInBackground(Void... params) {
get_details_from_server(); // get data like userid,username,userdesc etc...
return null;
}
}
[2] That will call function to proceed for UI changes.
public void set_details_on_screen()
{
if(userid > 0 )
handler_default.sendEmptyMessage(0);
else
handler_default.sendEmptyMessage(1);
}
[3] At last your UI changes will be reflected on screen with this Handler.
private Handler handler_default = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case 0: {
textuserid = (TextView) findViewById(R.id.userid);
textusername = (TextView) findViewById(R.id.username);
textuserdesc = (TextView) findViewById(R.id.userdesc);
textuserid.setText(userid);
textusername.setText(username);
textuserdesc.setText(userdesc);
break;
}
case 1: {
Toast.makeText(getApplicationContext(),"Error",Toast.LENGTH_LONG).show();
break;
}
}
}
};
Thanks.
your class which extends AsyncTask you can push your context into it, when calling the execute().
private class RegisterUser extends AsyncTask<String,String,String> {
private ListActivity activity;
public RegisterUser(ListActivity activity) {
this.activity = activity;
}
protected void onPostExecute(JSONObject json) {
activity.editText = (EditText)activity.findViewById(R.id.editText1);
//or
activity.enableButton();
}
}
and call the execute from the Activity like this:
new RegisterUser(this).execute(new String[] {"param"});
or you can define the AsyncTask class inside your Activity class - where you can reach everything.
more info Lars Vogel - Android Threads, Handlers and AsyncTask

Categories