Updating Destroyed Activity UI from Handler Runnable - java

The following code updates a TextView till a certain condition evaluates to false and then the Handler postDelayed is not called further.
However if the activity is destroyed, it'll try to update a null TextView, what is the right way to handle this? I know I could put a defensive null check on TextView but that is still not truly thread safe.
#Override
protected void onCreate(Bundle savedInstanceState) {
Handler durationHandler = new Handler();
durationHandler.postDelayed(updateSeekBarTime, 50);
}
private Runnable updateSeekBarTime = new Runnable() {
public void run() {
timeElapsed = mediaPlayer.getCurrentPosition();
double timeRemaining = finalTime - timeElapsed;
timeLeft.setText(String.format("%d", TimeUnit.MILLISECONDS.toSeconds((long) timeRemaining)));
if (timeRemaining >= 1000)
durationHandler.postDelayed(this, 200);
}
};
In other words the updateSeekBarTime can at any execution point try to access data members of an already destroyed activity, how to prevent that?

Start your handler in onResume().
In onPause() stop the handler with
handler.removeCallbacksAndMessages(null); //null removes everything

I'm building my own Music Player app and I use
durationHandler.removeCallbacks(updateSeekBarTime);
in onStop(). It works for me.
EDIT:
The above line is helped by the fact that I prevent the Activity from being destroyed by using
#Override
public void onBackPressed() {
moveTaskToBack(true);
}
This ensures that the Activity is minimized instead of destroyed so that when opened again, it's very snappy.
EDIT 2:
private Runnable updateSeekBarTime = new MyRunnable();
private class MyRunnable extends Runnable {
private boolean dontWriteText = false;
#Override
public void run() {
timeElapsed = mediaPlayer.getCurrentPosition();
double timeRemaining = finalTime - timeElapsed;
if(!dontWriteText)
timeLeft.setText(String.format("%d", TimeUnit.MILLISECONDS.toSeconds((long) timeRemaining)));
if (timeRemaining >= 1000)
durationHandler.postDelayed(this, 200);
}
public void dontWriteText() {
dontWriteText = true;
}
};
Then call updateSeekBarTime.dontWriteText() in onDestroy() or onStop(). I'd prefer onStop().

So after some code searching and reading blogs I found the answer in sample code of Communicating with the UI Thread
Even though you can and should be removing callbacks from handler:
handler.removeCallbacksAndMessages(null)
But the above does not prevent the existing running Thread from accessing a destroyed Activity or its views.
The answer I was looking for is WeakReference.
Create a weak reference to the TextView or UI element that you'll
access. The weak reference prevents memory leaks and crashes, because
it automatically tracks the "state" of the variable it backs. If the
reference becomes invalid, the weak reference is garbage-collected.
This technique is important for referring to objects that are part of
a component lifecycle. Using a hard reference may cause memory leaks
as the value continues to change; even worse, it can cause crashes if
the underlying component is destroyed. Using a weak reference to a
View ensures that the reference is more transitory in nature.
You should still check for null reference but now the view will be set to null by the active thread/runnable so you will not face a race-condition.

Related

LinearLayout.getwidth is zero when outside Runnable

I tried to get the width of a LinearLayout.
Here is the code of the MainActivity.java:
public class MainActivity extends AppCompatActivity {
BoardClass board;
private int widthareagame;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final LinearLayout gamearea;
ImageView im1 ;
Button abutton;
abutton = (Button) findViewById(R.id.buttonnew);
gamearea = ( LinearLayout) findViewById(R.id.boardarea);
gamearea.post(new Runnable(){
public void run(){
widthareagame = gamearea.getWidth();
}
});
board = new BoardClass(this,widthareagame);
gamearea.addView(board);
}
The value of widthareagame at new BoardClass(this,widthareagame); is still Zero.
Thanks
Here is what documentation says about View#post():
Causes the Runnable to be added to the message queue. The runnable
will be run on the user interface thread.
Your task, of modifying the value of widthareagame variable, has been pushed to the message queue of the view. It doesn't guarantee that it will get executed at the very same instance. The control then proceeds to the next line, where you still get the unmodified value.
You can try something like this, to ensure that you are able to use the modified value:
gamearea.post(new Runnable(){
public void run(){
widthareagame = gamearea.getWidth();
board = new BoardClass(this,widthareagame);
gamearea.addView(board);
}
});
This is because post method call queued the setting of widthareagame where as your view is rendering.You didn't guarantee the order of execution.
You have to make sure the statements inside the run method execute first and then new Board(.. is invoked.For that you can do something like this
final AtomicBoolean done = new AtomicBoolean(false);
run(){
//inside run method
done.set(true);
notify();
}
then do something like this
synchronized(task) {
while(!done.get()) {
task.wait();
}
new Board(..
}
where task is your runnable task defined something like this
final Runnable task = new Runnable() {
#Override
public void run() {
The reason is zero is because within the onCreate the LinearLayout has not been measured yet.
And the reason it only works when within the Runnable is because since this one has been posted then it will run on the next execution cycle, which is after the onCreate and the rest of the Activity lifecycle methods (onStart, onResume, etc.) and even onAttachedToWindow have been called, at which point will be already measured and give the correct size.
Said all that, a safer way to get your layout metrics with certainty would be to listen when the layout state changes.
gamearea.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// Remove the listener here unless you want to get this callback for
// "every" layout pass, which can get you into an infinite loop if you
// modify the layout from within this method
gamearea.getViewTreeObserver().removeGlobalOnLayoutListener(this);
// A this point you can get the width and height
widthareagame = gamearea.getWidth();
}
});

Configure Back Button to Skip First View

I have an app with a title screen. When the app first starts, I have an onCreate method that contains the following code:
setContentView(R.layout.title_screen);
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
setContentView(R.layout.main_screen);
}
}, 2000);
When I run my app and press the back button while on the main_screen layout, it closes the app (as it should). However, when I reopen the app, it displays the title_screen layout for two seconds again even though the app is already running. How can I prevent this?
This will prevent the delay appearing again when resumed:
private static boolean flag = false;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(!flag){
setContentView(R.layout.title_screen);
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
setContentView(R.layout.main_screen);
}
}, 2000);
flag = true;
} else {
setContentView(R.layout.main_screen);
}
}
Btw, if your app was on background and it is calling onCreate again while being resumed, it means that it is killed by the OS. Therefore it is normal to have the initial delay again.
What I would do is to implement two different activities first one showing title_screen and the second which is started after 2s should show your main screen.
After looking at your code I can see that you ALWAYS start with title_screen then after 2s, you change to main_screen. Therefore, when you press back, that means you finish your activity. When you re-open your app, onCreated is called again, and it run every line of code as the previous opening.Of course, there's no difference in 2 times you open your app. To overcome it, I recommend to use SharedPreference to store the flag to check main_screen or title_screen.

Custom view isn't being restored after adding it to a layout from AsyncTask's onPostExecute()

I've written a pretty large custom view which overrides onSaveInstanceState() and onRestoreInstanceState(Parcelable state).
I wanted to populate a LinearLayout with my custom view, so I wrote the following code:
public class MainActivity extends Activity {
private LinearLayout mRootLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRootLayout = (LinearLayout) findViewById(R.id.root_layout);
int i;
// Test: adding 10 instances of MyCustomView.
for (i = 0; i < 10; ++i) {
MyCustomView cv = new MyCustomView(this);
// I set an ID for this view so that onSaveInstanceState() and
// onRestoreInstanceState(Parcelable state) will be called
// automatically.
cv.setId(++i);
mRootLayout.addView(cv);
}
}
// ...
}
It works fine - mRootLayout is indeed being populated with 10 instances of MyCustomView, and each instance of MyCustomView is being properly restored after, for example, screen rotation.
I've noticed that due to the fact that MyCustomView is pretty large, my code is being heavy on the UI thread.
To solve the issue and take some effort off of the UI thread, I decided to use a custom AsyncTask, which will create an instance of MyCustomView in doInBackground() and add it to the the main layout ( mRootLayout ) in onPostExecute().
The following code is my custom AsyncTask:
private class LoadMyCustomViewTask extends AsyncTask<Void, Void, MyCustomView> {
private Context mContext;
private LinearLayout mLayoutToPopulate;
private int mId;
public LoadMyCustomViewTask(Context context, LinearLayout layout, int id) {
mContext = context;
mLayoutToPopulate = layout;
mId = id;
}
#Override
protected MyCustomView doInBackground(Void... params) {
MyCustomView cv = new MyCustomView(mContext);
return cv;
}
#Override
protected void onPostExecute(MyCustomView result) {
result.setId(mId);
mLayoutToPopulate.addView(result);
}
}
In MainActivity I use it as follows:
public class MainActivity extends Activity {
private LinearLayout mRootLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRootLayout = (LinearLayout) findViewById(R.id.root_layout);
int i;
for (i = 0; i < 10; ++i) {
new LoadMyCustomViewTask(this, mRootLayout, ++i).execute();
}
}
// ...
}
This code works too, but there is only one problem - MyCustomView is not being restored at all.
For debug purposes I put a Log.d(...) in MyCustomView's onSaveInstanceState() and in onRestoreInstanceState(Parcelable state), and I've noticed that onRestoreInstanceState(Parcelable state) isn't being called.
Do you have any idea why onRestoreInstanceState(Parcelable state) isn't being called when I use an AsyncTask to populate mRootLayout, but it is indeed being called when I create MyCustomView completely on the UI thread?
Thank you.
Edit: I'm posting the methods onSaveInstanceState() and onRestoreInstanceState() of MyCustomView
#Override
protected Parcelable onSaveInstanceState() {
debug("onSaveInstanceState()");
Bundle state = new Bundle();
state.putParcelable(_BUNDLE_KEY_PARENT_STATE, super.onSaveInstanceState());
state.putBooleanArray(_BUNDLE_KEY_CLICKED_VIEWS, mClickedViews);
return state;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
debug("onRestoreInstanceState(Parcelable state)");
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
mClickedViews = bundle.getBooleanArray(_BUNDLE_KEY_CLICKED_VIEWS);
state = bundle.getParcelable(_BUNDLE_KEY_PARENT_STATE);
}
super.onRestoreInstanceState(state);
}
View state restoration begins at the root view and moves down to all of the child views attached at that time. This can be seen in the ViewGroup.dispatchRestoreInstanceState method. This means that Android can only restore your views if they are part of the view hierarchy at the time Activity.onRestoreInstanceState is called.
Using the AsyncTask, you are creating your views asynchronously and then scheduling them to be added some time later when the main looper is idle. Considering the lifecycle, Android only lets your AsyncTask.onPostExecute run after Activity.onStart, Activity.onRestoreInstanceState, Activity.onResume, etc. are called. Your views are being added to the layout too late for automatic restoration to take place.
If you add log statements to those methods mentioned above, as well as to your AsyncTask.onPostExecute, you will be able to see how the ordering/timing plays out in reality. The following code runs after Activity.onRestoreInstanceState even though it all happens on the main thread, simply because of the scheduling:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
new Handler().post(new Runnable() {
#Override
public void run() {
Log.i("TAG", "when does this run?");
}
});
...
}
Smells like a false observation... creating a view on a background thread should not affect your activity lifecycle.
That said, doing anything at all with View objects on background threads is a no-no and I'm surprised you got this far with such an approach. All View code should be quick and avoid blocking. If you have long-running work to do then separate that work into the background thread, post the results of that complex computation to the main thread, and keep all the actual View/presentation stuff on the main thread where it belongs.
I remember having read about how onSaveInstanceState()/onRestoreInstanceState() work a time ago and it was not a "fixed-situation-rules" thing. As I can't find the references to it at this moment, I'll try to explain it with my own words:
Basically, a factor on which depends the calling of those methods is the resources left that the device has. Both methods will get in action when a second Activity gets focused and the first one gets killed due to lack of resources. Concretely, onRestoreInstanceState should be triggered when that Activity was killed and restarted, so it gets the previous state.
Although you've not posted your MyCustomView implementation, my guess is that when you do that entirely on the main UI Thread, you're involving some action that makes the MainActivity lose its focus and once the MyCustomView is created, it needs to restore its state. Doing this in a separate thread (as AsyncTask does) makes creating those Views in paralell, so your main Activity doesn't lose its focus and thus it doesn't get killed, so those methods are not called.
Concluding this, don't worry if those methods are not always called as they don't have to be called everytime, just when needed, and that doesn't mean there's something going wrong.
I recommend you to connect SDK sources to your IDE and walk there with debugger through the whole process related with the View class onRestoreInstanceState(). Since I don't have your code, looking at sources I can only guess what might have gone wrong, but from what I see, that might be related to problem:
1) try to set an Id to every view you generate
2) try to use Fragment as a host to your views (instead of MainActivity).

Android Thread Allocation - growing heap?

Hi everyone out there,
i am developing an android application against API 7 at the moment in which i use an activity which need to be restarted. Lets say my activity looks like this:
public class AllocActivity extends Activity implements OnClickListener{
Button but;
private Handler hand = new Handler();
#Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_alloc);
but = (Button) findViewById(R.id.button);
but.setText("RELOAD");
but.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0){
Intent intent = getIntent();
startActivity(intent);
finish();
}
});
}
#Override
protected void onDestroy(){
super.onDestroy();
System.gc();
}
/****** THREADS AND RUNNABLES ******/
final Runnable fullAnim = new Thread(new Runnable(){
#Override
public void run(){
try{
hand.post(anim1);
Thread.sleep(2000);
hand.post(anim2);
Thread.sleep(1000);
// and so on
}catch(InterruptedException ie){ie.printStackTrace();}
}
});
final Runnable anim1 = new Runnable() {
#Override
public void run(){
// non-static method findViewById
ImageView sky = (ImageView)findViewById(R.id.sky);
}
};
}
The problem is that the gc doesnt seem to free the fullAnim thread so that the heap is growing by ~100K at every restart - till it slows down and crashes. Declaring fullAnim as static does solve this problem - but as i use non static references this doesnt work out for me.
So at this point i am kindof lost - and i hope u can advice me where to go next. Is there something i might be doing wrong or is there a tool i can use to manage threads to drop and free heap after restart.
kindly regards
UPDATE
thanks to everyone who answered - helped alot. using TimerTask did the trick in the end. i did the following change:
/****** THREADS AND RUNNABLES ******/
final TimerTask fullAnim = new TimerTask(){
#Override
public void run(){
try{
hand.post(anim1);
Thread.sleep(2000);
hand.post(anim2);
Thread.sleep(1000);
// and so on
}catch(InterruptedException ie){ie.printStackTrace();}
}
};
as the activity was more than 6k loc long this was a pretty decent solution without facing bigger impacts. KUDOS!
i dont use a Timer to shedule the task - dont know if its bad practice but
the animation is called like this:
Thread t = new Thread(fullAnim);
t.start();
A running Thread is never garbage collected.
A Thread is not stopped automatically if your Activity stops or is destroyed. It could run forever.
Every non-static inner class keeps a reference to the enclosing instance. E.g. hand.post(anim1); works inside that inner class because it has an implicit reference to AllocActivity.this.
So what you effectively do is to keep a reference to your Activity alive for longer than it is supposed to be alive, i.e. until after onDestroy.
Make sure to stop threads manually if you don't want them anymore.
Because final variable have low priority for GC. So you need to explicitly release the runneable objects in onPause() method because there is not ensurence onDestory() will call immediate after finish() call .
#Override
protected void onPause(){
super.onPause();
//cancel timer to stop animations
if(t!=null){
t.cancel();
}
System.gc();
}
UPDATE
use timer to achieve this
boolean isFirstAnim=true;
Timer t = new Timer();
t.schedule(new TimerTask() {
#Override
public void run() {
if(isFirstAnim){
// play your first animation at every
}else{
// play your second animation at every
}
}
}, 0, 3000);
What happens when all activities of an application finishes?
"When you call finish() this doesn't mean the Activity instance is
garbage collected. You're telling Android you want to close the
Activity (do not show it anymore). It will still be present until
Android decides to kill the process (and thus terminate the DVM) or
the instance is garbage-collected."
You need to implement your own stop method to stop the running thread, you can make a call to it in onDestroy
refer this Stopping a runnable
Alternatively
you can perform your operation in an asynctask and use onProgressUpdate() to publish progress on UI thread and use cancel(true) in combination with check in doInBackground() whether cancel has been called to stop the task.

Android Activity takes too long to show content

My Activity in onCreate() performs long computations that take some time.
In the same onCreate() I call setContentView() to set the appearance of the activity.
The point is that, since it takes a while to performs the above mentioned computations the screen of the Activity loads only after long time.
Please, any suggestion on how to avoid this?
I have tried to call setContentView() in onCreate() and start the computations in onResume(), but again the Activity screen is loaded only at the end.
There is no other way than to use e.g. an AsyncTask. The reason is that the actual rendering does not take place asynchronously; in other words, setContentView will only set some data but nothing will be displayed at that point in time.
AsyncTask, however, is not necessarily meant for "long" computations. But if your app relies on the result, and no other computations take place in parallel, it may still be the simplest way for you to achieve what you want. If not, you may have to use a Thread even.
Update Since everybody keeps bombarding the original poster with more use AsyncTask answers of various quality, I'd like to stress one more time that AsyncTask is intended for short operations (to quote the reference: a few seconds at the most) while the OP has given no indication on how long his computations really take. Also, an AsyncTask is a one-shot-only object which can only run once.
One more very important point to consider is the following. Android assigns AsyncTask a background task priority. This means that, besides the lower scheduling priority, the computations in AsyncTask will take ten times as long as if they were performed in the foreground, because Android runs all tasks which have background priority with an artificial limit of 10% CPU cycles. However, AsyncTasks can be lifted out of this group by raising its priority "just a little bit". For an AsyncTask, it would be done like so:
public R doInBackground(I... is) {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND +
Process.THREAD_PRIORITY_MORE_FAVORABLE);
...
}
You have to implement AsyncTask
public class AsyncTaskActivity extends Activity implements OnClickListener {
Button btn;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btn = (Button) findViewById(R.id.button1);.
//because we implement OnClickListener we only have to pass "this" (much easier)
btn.setOnClickListener(this);
}
public void onClick(View view){
//detect the view that was "clicked"
switch(view.getId())
{
case R.id.button1:
new LongOperation().execute("");
break;
}
}
private class LongOperation extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... params) {
for(int i=0;i<5;i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return "Executed";
}
#Override
protected void onPostExecute(String result) {
TextView txt = (TextView) findViewById(R.id.output);
txt.setText("Executed"); // txt.setText(result);
//might want to change "executed" for the returned string passed into onPostExecute() but that is upto you
}
#Override
protected void onPreExecute() {
}
#Override
protected void onProgressUpdate(Void... values) {
}
}
If all computations take long time to execute, your UI is 'locked' and not updated.
You need to do all long work in an AsyncTask
AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.
call setContentView(layoutID) in onCreate method before you start initializing views, create a AsyncTask and start AsyncTask thread after you called setContentView in onCreate method only. Something like given below
onCreate(....){
--
--
setContentView(layoutID);
---
--
new asynchTask(); // load your ui in AsyncTask by creating an inner class in your activity by extending AsyncTask class
}
here is a tutorial of how to implement AsyncTask

Categories