I've got an activity with a handler defined:
final Handler updateHandler = new Handler() {
public void handleMessage(Message msg) {
// Do stuff.
}
};
In my onCreate method, if I assign this handler to a static list outside of my Activity, will it leak (or rather, will that outside list keep a reference to my activity forever)? I.e,:
#Override
public void onCreate(Bundle savedInstanceState) {
SomeStaticClass.addHandler(updateHandler);
}
...
public class SomeStaticClass {
static List<Handler> handlers = new ArrayList<Handler>();
public static void addHandler(Handler handler) {
handlers.add(handler);
}
}
In my onCreate method, if I assign this handler to a static list outside of my Activity, will it leak (or rather, will that outside list keep a reference to my activity forever)?
Yes. Do not put Handlers in static data members.
Related
I try to get back a view I initialized before in my activity in onCreate(), but I don't know how to do this.
It's look like something like this :
public class activityA extends AppCompatActivity {
public ImageView ivImg1;
#Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.layoutA);
ivImg1 = (ImageView)findViewById(R.id.img1);
}
The image view is empty of any ressource. I create a Handler in an other class :
public class firstClass{
final private imgHandler mHandler = new imgHandler();
final int KEY = 1;
Message msg = mHandler.obtainMessage(KEY,R.drawable.face1,0,ivImg1);
mHandler.sendMessage(msg);
}
And now I would like to set the first argument R.drawable.face1 to the ImageView in my Handler :
public class imgHandler extends Handler {
#Override
public void handleMessage(Message msg){
super.handleMessage(msg);
ImageView viewToSet = (ImageView)msg.obj;
viewToSet.setImageResource(msg.arg1); //Here is the logcat error
}
}
I result on a java.lang.NullPointerException. I think it's because the object I send in the message is empty. In onCreate the view is inside, but when I call it in the message, the findViewByIdisn't in ivImg1.
Class FirstClass is in a new Thread, and class imgHandler is in the UI Thread.
I hope someone can help me.
Sorry for my english.
I have asyncTask class:
public class DownloadJar {
public DownloadJar(String serialnum)
{
if (serialnum!=null)
{
this.serialnum = serialnum;
new Download().execute();
}
}
private class Download extends AsyncTask<String, Integer, String>
{
#Override
protected String doInBackground(String... params) {
//Downloading stuff
return null;
}
#Override
protected void onProgressUpdate(Integer... values) {
//Need to notify main class
super.onProgressUpdate(values);
}
}
}
I activate it via main class:
public class FTPDownload extends Activity{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ftpdownload);
Bundle extras = getIntent().getExtras();
if (extras != null)
{
serialnum = extras.getString("serialnum");
}
if (serialnum!=null)
{
DownloadJar dj = new DownloadJar(serialnum);
}
}
}
How can I create callback from the DownloadJar to notify my main class about the progress?
If you need onProgressUpdate() inside your main class, override it there. like,
DownloadJar dj = new DownloadJar(serialnum){
#Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
};
Pass a handler created in the activity along with the serialnumString in the constructor of the DownloadJar Async and call it like this from your onProgressUpdate:
handler.sendEmptyMessage(0);
where you can pass data through messages from your AsyncTask, you can receive the events in your Activity like this
handler = new Handler() {
#Override
public void handleMessage(Message msg) {
//do something
}
};
For further info: http://www.vogella.com/articles/AndroidBackgroundProcessing/article.html
You can create interface ie:
interface UpdateProgress {
void progressUpdate(String s)
}
make your class FTPDownload (its your main class?) implement it. Then when creating DownloadJar, pass reference to your activity to it. Then you can call in onProgressUpdate from your DownloadJar.Download asynctask progressUpdate on main activity. You must remember to update this reference on configuration changes.
If on the other hand DownloadJar is your main class, then you already have access to it, your asynctask is internal to it.
you have make Interface so that you can set data in that interface method from onPostExecute method of AsyncTask. and pass interface method as Anonymous inner class as like onClickListner.
here you can find nice explanation.
I need the button to be static so I can enable it/ disable it form my services in case the activity is shown. Still I setOnClickListener and anyway static views are considered dangerous. Do I leak ? Can I avoid it ?
public class MonitorActivity extends FragmentActivity implements
OnClickListener {
private static Button updateButton; // static??
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_monitor);
// button
updateButton = (Button) findViewById(R.id.update_data_button);
updateButton.setOnClickListener(this); // oops ?
}
public static void onDataUpdated(Context ctx) {
if (updateButton != null) { //that's why I need it static
updateButton.setEnabled(true); // + set the text etc
}
}
public static void onUpdating() {
if (updateButton != null) {
updateButton.setEnabled(false);
}
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.update_data_button:
serviceIntent.putExtra(MANUAL_UPDATE_INTENT_KEY, true);
this.startService(serviceIntent);
}
}
#Override
protected void onResume() {
super.onResume();
Boolean isUpdating = AccessPreferences.get(this, updateInProgressKey,
false);
// set the button right
updateButton.setText((isUpdating) ? defaultUpdatingText
: getResources().getString(R.string.update_button_text));
updateButton.setEnabled(!isUpdating);
}
}
I think You can create BroadcastReceiver in MonitorActivity. And send extras message from Service to enable/disable button.
I suggest you use LocalBroadcastManager
In your Activity define a BroadcastReceiver and register the Broadcast in onStart()onResume() and unregister it in onStop()onPause().
From your Service send the Broadcast to the Activity if the Activity is active it will receive the Broadcast and update the UI, if not nothing will happen.
Define another BroadcastReceiver in your Service, Register the Broadcast in onCreate() and Unregister it in onDestroy().
When your Activity is started send a Broadcast to the Service and let the Service reply to the Activity using the first Broadcast to update the UI.
UPDATE
After doing some investigation I found you're correct "sticky broadcasts are discouraged", but if you check the date of that post it's on 2008 - before Google implemented the LocalBroadcastManager.
And I have checked the source code of LocalBroadcastManager, it's not a real Broadcast it's an interface, Singleton with a list of BroadcastReceivers (not global and no IPC communication).
I really hate public static and I always avoid them. every body should.
So yes - the static button would leak my activity. I came up with callback below but it is ugly. I finally solved it by making the Activity extend OnSharedPreferenceChangeListener
public final class MonitorActivity extends FragmentActivity implements
OnClickListener, OnSharedPreferenceChangeListener {
private Button updateButton;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
updateButton = (Button) findViewById(R.id.update_data_button);
updateButton.setOnClickListener(this); //no need to unregister methinks
}
#Override
public synchronized void onSharedPreferenceChanged(
SharedPreferences sharedPreferences, String key) {
if (updateInProgressKey.equals(key)) {
final Boolean isUpdating = AccessPreferences.get(this,
updateInProgressKey, false);
// set the button right
updateButton.setText((isUpdating) ? sDefaultUpdatingText
: sUpdateButtonTxt);
updateButton.setEnabled(!isUpdating);
}
}
#Override
protected void onStart() {
super.onStart();
AccessPreferences.registerListener(this, this);
AccessPreferences.callListener(this, this, updateInProgressKey);
}
#Override
protected void onStop() {
// may not be called (as onStop() is killable), but no leak,
// see: http://stackoverflow.com/a/20493608/281545
AccessPreferences.unregisterListener(this, this);
super.onStop();
}
}
Callback
onPause() is guaranteed to run - so I null the static fields there and populate them on onResume(). I only do a read from default shared preferences so it should not take long in the synchronized blocks (I have to synchronize cause the service might call onUpdating() or onDataUpdated() any odd time). Not sure about unregistering the listener though
public class MonitorActivity extends FragmentActivity implements
OnClickListener {
private static TextView dataTextView; //null this onPause() to avoid a leak
private static Button updateButton; // null this onPause() to avoid a leak
// ...
public static synchronized void onDataUpdated(Context ctx) {
if (updateButton != null) {
updateButton.setEnabled(true); // + set the text etc
}
}
public static synchronized void onUpdating() {
if (updateButton != null) {
updateButton.setEnabled(false);
}
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.update_data_button:
serviceIntent.putExtra(MANUAL_UPDATE_INTENT_KEY, true);
this.startService(serviceIntent);
}
}
#Override
protected void onResume() {
super.onResume();
synchronized (MonitorActivity.class) {
Boolean isUpdating = AccessPreferences.get(this,
updateInProgressKey, false);
updateButton = (Button) findViewById(R.id.update_data_button);
updateButton.setOnClickListener(this);
// set the button right
updateButton.setText((isUpdating) ? defaultUpdatingText
: getResources().getString(R.string.update_button_text));
updateButton.setEnabled(!isUpdating);
}
}
#Override
protected void onPause() {
synchronized (MonitorActivity.class) {
updateButton.setOnClickListener(null); // TODO : needed ??
dataTextView = updateButton = null; // to avoid leaking my activity
}
super.onPause();
}
}
There are 3 solutions for you:
Set button = null when context is destroyed(onStop);
Use WeakReference for button field, Example:
private static WeakReference<Button> updateButton;
Not creating static button. It's always hold the context.
My static handler has a WeakReference to my Activity (this is to prevent the well documented memory leak issue).
I post a long delayed message and I want this message delivered to my activity (which should be in the foreground).
My concern is that on orientation change, my activity is destroyed and the handler has a reference to the old activity which should have been destroyed.
In order to get around this in my onCreate for the activity I do this.
if(mHandler == null)
mHandler = new LoginHandler(this);
else {
mHandler.setTarget(this);
}
And my handler is declared as a static global variable:
private static LoginHandler mHandler = null;
and the implementing class is also static as below:
private static class LoginHandler extends Handler {
private WeakReference<LoginActivity> mTarget;
LoginHandler(LoginActivity target) {
mTarget = new WeakReference<LoginActivity>(target);
}
public void setTarget(LoginActivity target) {
mTarget = new WeakReference<LoginActivity>(target);
}
#Override
public void handleMessage(Message msg) {
// process incoming messages here
LoginActivity activity = mTarget.get();
switch (msg.what) {
case Constants.SUCCESS:
activity.doSomething();
break;
default:
activity.setStatusMessage("failed " + msg.obj, STATUS_TYPE_DONE);
}
}
}
What I want to know is if there is something wrong with changing the WeakReference on onCreate or is there anything else wrong with this approach?
Thanks,
So I wrote the following test to figure out whether I had the right idea or not and it seems that m approach is correct. In onCreate we change the WeakReference and the posted message will always get delivered to the activity that is in the foreground. If you change this code to always create a new Handler in onCreate you'll notice the update messages do not get delivered.
public class MainActivity extends Activity {
private static int COUNT = 0;
static LoginHandler mHandler;
private static class LoginHandler extends Handler {
private WeakReference<MainActivity> mTarget;
LoginHandler(MainActivity target) {
mTarget = new WeakReference<MainActivity>(target);
}
public void setTarget(MainActivity target) {
mTarget.clear();
mTarget = new WeakReference<MainActivity>(target);
}
#Override
public void handleMessage(Message msg) {
// int duration = Toast.LENGTH_LONG;
// process incoming messages here
MainActivity activity = mTarget.get();
activity.update(msg.arg1);
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mHandler == null)
mHandler = new LoginHandler(this);
else
mHandler.setTarget(this);
((Button)findViewById(R.id.button)).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Message msg = new Message();
msg.arg1 = COUNT++;
mHandler.sendMessageDelayed(msg, 3000);
}
});
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
private void update(int count) {
((TextView) findViewById(R.id.hello_world)).setText("Hello World # "+ count);
}
}
A solution in getting away with activity's destroy-and-create life cycle, if you want to retain the active objects is to make use of the "Retent Fragments".
The idea is simple, you are telling the Android system to " retain" your fragment, when it's associated activity is being destroyed and re created. And make sure you grab the current activity's context in the fragment's onAttach() callable, so you are always updating the correct activity.
Below link has more details:
http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
In my Main Activity, I have a Thread that is doing alot of stuff, including adding some records to a database. In my second activity, which inherit from the Main Activity, I want to do a query to my database. But I need to check if the first thread in the Main Activity is finished, what I've done so far is:
public class History extends Main {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(!(MainThread.isAlive())) {
getFromDatabase();
}
}
}
This is my getFromDatabase() method
pd = ProgressDialog.show(this, "Please Wait",
"Getting cases from database", false);
Thread t = new Thread(this);
t.start();
which will call this run method:
#Override
public void run() {
ArrayList<Case> c = db1.getAllCases();
Message msg = handler.obtainMessage();
msg.obj = c;
handler.sendMessage(msg);
}
private Handler handler = new Handler() {
#SuppressWarnings("unchecked")
#Override
public void handleMessage(Message m) {
pd.dismiss();
list = (ArrayList<Case>) m.obj;
tempList = getCaseNumberToTempList(list);
tempCaseList = createTempList(list);
lv.setAdapter(new CustomAdapter(History.this, list));
lv.setTextFilterEnabled(true);
}
};
But if I do so, the following line of code will crash my application, it will give a NullPointerException:
if(!(MainThread.isAlive())) {
getFromDatabase();
}
How can I be sure that that the first thread is finished with all the work before I query the database from my history activity?
You can make the Thread in the getFromDatabase() method a static class level variable, write a static get method for it in your Activity, and check for isAlive() in your child Activity.
How about simply using a semaphore variable that you modify from the thread once it has reached a certain state?