Android - Hold Activity reference in application-level non-Activity class - java

I have an application class and it holds a reference of MyAdapter class:
public class MyApplication extends Application {
......
private static MyAdapter sMyAdapter;
public static MyAdapter getMyAdapter() {
if (sMyAdapter == null) {
sMyAdapter = new MyAdapter(this);
MyApplication.setMyAdapter(sMyAdapter);
}
return sMyAdapter;
}
public static void setMyAdapter(MyAdapter myAdapter) {
sMyAdapter = myAdapter;
}
......
}
MyAdapter class is a customized android adapter class, and the application Context is passed to the Adapter. The application holds a reference of it because it may be used anytime till the application is still running.
The problem is that now I need an Activity Context in the Adapter to start another Activity when some button is clicked because if I use application Context I need to add a Intent flag FLAG_ACTIVITY_NEW_TASK which I don't want to because that way the new Activity being started will be running in a new task. I tried a lot with changing launch mode and taskAffinity but either new issues came up or the Activity will be running in a new task.
So I am thinking to hold an Activity reference which shows the button in the Adapter class, and to avoid memory leak, I came up with the following:
public class MyActivity extends Activity {
......
#override
public void onResume() {
......
MyApplication.getMyAdapter().setActivity(this);
......
}
......
#override
public void onDestroy() {
......
MyApplication.getMyAdapter().setActivity(null);
......
}
}
Then in the Adapter class I will use the Activity reference to start another Activity. I tested and this worked fine but the question is would this avoid memory leak and is this a proper way to hold the Activity reference when onResume and release it when onDestroy? Is there any other decent way to achieve my purpose? Thanks.

would this avoid memory leak
Not really. You MyApplication object will still keep a reference to your adapter with all it's 'contents' (further references).
Yes, you've got rid of keeping the destroyed Activity around, and you might feel it's okay to keep the adapter because you're 'going to need it again anyways', still this whole construct is a horrible code smell and will with certainty introduce new memory leaks and other problems as you develop this further.
Logically, this adapter is part of an Activity, and as the Activity 'dies', so should the adapter.
I'm sure there's a reason why you felt you needed that adapter on your application, so I'd post another question asking 'how can I achieve soandso without my application knowing of my adapter'.

Related

Best way to return data to MainActivity from AsyncTask

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;
}
}

How can I get the context in the finish() method?

I'm just rtying to display an offerwall when someone is leaving the application, so I have placed the finish() method:
#Override
public void finish() {
super.finish();
MobileCore.init(this.getContext(), "xxx", MobileCore.LOG_TYPE.DEBUG, MobileCore.AD_UNITS.OFFERWALL);
MobileCore.setStickeezReadyListener(new OnReadyListener() {
#Override
public void onReady(MobileCore.AD_UNITS adUnit) {
if (adUnit == MobileCore.AD_UNITS.OFERWALL) {
MobileCore.showOferWall(getActivity());
}
}
});
}
But I have problems. First of all with this code this.getContext() and also with this getActivity()
I know that I can not access the activity this way, but I'm extremely confused at the moment. I know that I'm missing a very small part here. Can you give me a push?
Activity class extends Context so actually Activity is instance of Context, in onFinish the system is trying to destroy the Activity so there must be no jobs still working related to this Activity, if you still need a Context you can use this.getApplicationContext.
To call the outer class in a nested class (in your case an anonymous class) use the class name of the outer one:
YourOuterClass.this
Your activity's context will remain valid until you call super.finish(). So don't call it until you're done.
If you can't do that, use the application context.

use function from main activity in another java file

I’m trying to call a method in my main Activity from another file, but it crashes. I can easily do this the other way. Can you not invoke methods of the main Activity from another Activity?
public class MainActivity extends Activity {
….
……..
……
public void my_function(String a){
//do some stuff
}
}
package main;
public class stuff extends Activity {
….
….
…
MainActivity run = new MainActivity();
run.my_function(String a);
}
}
If you want a method to get called from different classes, create a seperate class
and encapsulate the logic. See an activity more as a view controller, the logic
should be somewhere else.
Sorry for my english, hope this helps.
Steve
The problem is this line
MainActivity run = new MainActivity();
you never instantiate Activities directly, you need to instantiate them through Intent objects.
Intent intent = new Intent(this, MainActivity.class);
If you want to instantiate a new activity use an Intent. If you want to call my_function in an activity that is already instantiated, you need first to get a pointer to that Activity, which in Android is a little bit tricky.
Additionally, the fact that you want to call a method from another activity is a sign of bad design, or that you don't fully understand the Android way of doing things.
Think if there's any other approach you can use for your purpose.

Static Context from Activity.onStart()

I am trying to generate a notification from a class, Utilities.java, outside of the subclass of Context. I've thought about providing a SingletonContext class and have looked at posts ike this. I'd like to be able to return != null Context object since the notification can be generated at any given time because it is generated from a messageReceived() callback.
What are there downsides to doing something like this:
public static Context c;
public class MainActivity extends Activity{
#Override
public void onStart()
super.onStart()
c = this.getApplicationContext();
}
//other method somewhere outside this class
public Context getContext(){
return MainActivity.c
}
I don't think it would be any different than putting this on the onCreate(), however, it guarantees that the context is up to date when the activity starts.
The Context keeps a reference to this activity in memory, which you might not want. Perhaps use
this.getApplicationContext();
instead. This will still let you do file IO and most other things a context requires. Without a specific reference to this activity.
Maybe you should overwrite the onResume Method.
If you open a new activity, and switch back, the onStart method will not getting invoked.
Android Lifecycle: doc
BTW: I read about problems with ApplicationContext using a dialog or toast, so if you use the context to create on of these you should use your Activity as context.

Could this code create a potential memory leak?

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.

Categories

Resources