I am building a game, that is step based, so I need to be able to create forward and back button.
Each step has an Id and the info for it is taken from and SQLite database. I am going to be saving maxLevel, the maximum reached step, as well as currentStep, the current step, in the shared preferences. Now I was wondering how do I start a new action that would take someone to the next/previous step, without clogging up the memory too much. I know I can start a new action and pass it data something like this:
final Context mContext=this;
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putInt("count", 2);
editor.commit();
Intent myIntent = new Intent(mContext, PlayGame.class);
mContext.startActivity(myIntent);
Now my main worry here is the memory usage. If I go forward 2 times using this, then back 2 times and then forward 2 times again, won't it create 6 instances of the activity? That could be a massive memory hog.
So the question here is:
How do I implement a forward/back system with 1 activity, that is relaunched and retrieves data from a database?
For more background info: The activity is a TabHost, which has 4 tabs, and each of these tabs has an activity inside it. Each activity retrieves the data it needs from the database. Therefore my concern about the memory usage.
Edit:
I seem to have found a solution. I am calling the finish() function before I start the new activity. I do believe, that this should help avoid any leaks. am I right in thinking this?
Just a thought
How about running a back ground thread which will update your UI, Such as AsynTask. back and forward method will trigger background thread which will update your UI and can update data from SQL as well, By this you don't have to change activity
Helpful Links,
http://developer.android.com/resources/articles/painless-threading.html
http://topandroid.net/threads-handlers-asynctask
Happy Coding!
After your edition,
If your second activity has different data structure than this is a good idea to finish() as you are going to create new object anyway. But if data structure is same than probably using same objects and update their value will be more efficient.
One more edition, After updating your variables from background thread you may have to call set methods on your views for your UI to show a recent change. This is logical but just wanted to share it :)
Enjoy!
Related
I have some code that works 98% of the time, and 100% during my own testing so I can not really reproduce the problem other than having user devices experience this issue.
What I do in onPostExecute() is set a parameter like this:
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences( AddProblemActivity.this);
prefs.edit().putString("recent_problem_id", result ).commit();
and then go to the next activity:
Intent myIntent = new Intent(AddProblemActivity.this, ProblemActivity.class);
AddProblemActivity.this.startActivity(myIntent);
and then try to get that parameter there like this:
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
ProblemActivity.this);
// display a loading message before problem loads.
String recent_problem_id = prefs.getString( "recent_problem_id" , null );
if ( recent_problem_id == null )
{
// Sometimes it is null!
}
Would anyone know why this happens?
Thanks!
If you are trying to pass the data to a new activity, why not put it as a String extra in the intent? Then, get that String from the intent in the new activity. If you still need to store it, you can do so in the new activity's onCreate() after it pulls it from the intent.
Intent myIntent = new Intent(AddProblemActivity.this, ProblemActivity.class);
//Add results here
myIntent.putExtra("RecentProblemId", result);
AddProblemActivity.this.startActivity(myIntent);
Then, in the onCreate of your new Activity, do:
String recentProblemId = getIntent().getStringExtra("RecentProblemId");
Now, if you still need this information stored, do:
if(recentProblemId != null){
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
ProblemActivity.this);
prefs.putString("recent_problem_id",recentProblemId).commit();
}
I know this doesn't exactly answer your question as to why the String isn't always being committed to the preferences in onPostExecute(). However, the best practice for passing information between activities is via Intents and extras.
My guess as to why it may not always work for some users, is that their devices are not done writing the data to the shared preferences file before the new Activity starts and tries to read from that same file. Hope this helps.
First of all, see Raghav Sood's answer.
There is one delicate moment. You might start executing your AsyncTask than rotate the device. Activity will be recreated and in PostExecute you'll have wrong context so your preferences won't be saved.
If it's true you should use onRetainNonConfigurationInstance() to save appropriate task's instance.
I'm not sure about this, but I think the problem might be due to the difference in the Context you're passing. You're using the Context of AddProblemActivity first, and then the Context of ProblemActivity. Try using a set preference, like a filename one:
SharedPreferences prefs = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE);
Note that getSharedPreferences() is a method from Context, so you'll need to have a reference to an Activity or maybe Application Context in your AsyncTask to be able to use it.
Seems like your code is fine and there's no reason why it shouldn't work, the only reason will be some defect which is probably device related. My thoughts are since the Shared prefs are saved on local storage something can go wrong in the process.
As advised in the comments, it will be a must to add a device type log,
I would recommend you use "ACRA" - http://code.google.com/p/acra/ that can give you detailed reports with minimum effort (pay attention you can send a report not necceserily only on app crash).
Take a look at this thread, he shows a problem that maybe you are experiencing as well:
SharedPreferences will not save/load in PreferenceActivity.
If it's the case, a solution will be to handle the saving of this persistent data manually on the local storage or use a DB solution. Good luck :)
I think the issue is due to the shared preference setting not being written to your file system yet when you try to access it from your second activity. You mention that you write the setting from the onPostExecute method (of an AsyncTask perhaps?). When you start an AsyncTask there is no guarantee that it will start immediately. The only guarantee there is, is that it will start in a background thread. The platform can and will decide when to actually run the background thread, depending on system load, file system blocks or so. It might very well be so that your AsyncTask hasn't been started yet when you switch to the second activity (and hence the onPostExecute method hasn't been called yet).
Saving shared preferences are tricky as they write to the file system. You might end up blocking the main thread if you aren't careful. The SharedPreferences.Editor object has an apply method as well which will update the in-memory cache of your shared preferences immediately (making the changes available at once) and kick of a background thread to save the actual value to the file system as well. So my tip would be that if you have the possibility, you should try to call the apply method (from your main thread) instead of the commit method from (what I assume) a AsyncTask. The apply method requires API level 9 or higher.
For your reference: http://developer.android.com/reference/android/content/SharedPreferences.Editor.html
Edit:
The commit method will return a boolean value depending on the result of the write operation. You could (should?) check that return-value in order to at least be able to take correct counter measures on failure (like show a "Couldn't save your setting, please try again" toast or something).
Cheers,
--dbm
I could be wrong. But I believe a friend of mine had a similar problem once. I was stuck on this problem for hours. The results for more like 30% of the time it wouldn't work. I believe that the onPostExecute() runs on a separate thread when the Intent is instantiated and the activity is called. This is because an AsyncTask is implemented on a separate thread. Depending on the device, this would be called more likely then not. Our tablet it rarely happened, on the smart phone it would occur more commonly.
You can test this by debugging the application and looking at the AsyncThread thread and see when the call is made.
Yes, it is better to send the variable via the putExtra().
I hope this helps you understand why this occurred.
The commit action of SharePreferences is synchroneos so I don't think the fact that you are starting the new intent should effect it, only thing is that the commit action isn't fail safe, it can fail.
http://developer.android.com/reference/android/content/SharedPreferences.Editor.html#commit()
Returns true if the new values were successfully written to persistent
storage.
Maybe you should check that return value to make sure you managed to save the result.
I've got four activities which the application cycles through. The first one fetches huge amount of data, so I don't wish to do that more than once. Thing is that if user presses back key on last activity, I want to return to the first one without reloading the activity. I'm currently thinking startActivityForResult methods and finish the two previous ones, but there may be a better solution?
Scenario:
Hasslarn,
The first thing that you must understand is that you have very little choice as to whether an Activity will be reloaded or not. That is determined by the system (largely). With that said, there are a number of things you can do to limit the system's desire to kill the Activity. Additionally, you may use a number of tools at your disposal to limit the impact of such a possible closure
Finish every child Activity as it becomes unimportant. This will free resources lowering the need to get rid of unused Activities (even temporarily).
Find a simple, but clever way to limit loads.
Based on the information provided, your proposed solution is a viable way to accomplish both. However, I would ask "Is Activity B required to be active while Activity C is open?" If not, you may want to do the following:
startActivityForResult(Activity B)
When Activity B is done, send result back to Activity A and startActivityForResult(Activity C)
Finally, when Activity C is done, you may startActivityForResult(Activity D) and back will work with no effort and you won't have to close the other activities.
Furthermore, if you press Back on Activity C, you can supply a cancelled result to Activity A forcing a restart of Activity B if you so desire. Depending on the required processing for Activity B, this may not be any hassle at all for your app.
Regardless of how you approach this, I recommend finding a way to cache this data such that onResume(), you may reload quickly if needed. This is because there is NO way to ensure that your original Activity will not be released to make way for the others.
Hope this helps,
FuzzicalLogic
Try using this:
Intent a = new Intent(this,A.class);
a.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(a);
return true;
Activity A will get reordered to front, without creating a new instance. If you want to pass extras through intent , you can get the intent extras in onNewIntent(intent) in Activity A.
At the moment I'm a little bit confused about the lifecycle management in Android. There are at least 4 possibilities to resume retained data after an Activity comes back to the foreground:
Android handling: If there is enough memory, Android stores and resumes the important data (checked radio buttons, text of EditText,-... and so on) after Activity restart, the user has the same state as before as the Activity went into background.
onPause, onResume: Overriding onPause and save the important data to a database or text file and resume it when onResume is executed next time.
onSavedInstance(Bundle), onRestoreInstance(Bundle): I can save the data as key-value-pair into bundles and restore them after onRestoreInstance is executed.
onRetainNonConfigurationInstance(), getLastNonConfigurationInstance(): I handle all my storage issues in one big object and read getLastNonConfigurationInstance() out when onCreate is executed.
Although it is confusing which approach is best, I guess it relies on development experience to know when to use which possibility. If you have some good examples for each I would be glad, but this is not my question. I wonder how to deal with all that when I have different Activities and one Activity will be killed by Android when it pauses in background:
In my case I have a MainActivity and a MessageActivity. The MessageActivity consists of a ViewSwitcher which consists of two states. State one is a radio button choice list. State two is an EditText with two buttons (send and abort). When I monkey test each state, hit the Android home button, and restart the application, the right Activity with the right state and the old data comes into foreground, when I leave the handling to Android. So that works.
But what happens when Android destroys the MessageActivity in background:
If I use the Android way, the data is lost and I guess MainActivity (instead of MessageActivity->state(1 or 2)) will start next time after I relaunch the application (is that correct?). So when I'd like to keep the data of MessageActivity, I have to use one of the other three possibilities.
How to do that neatly, when the application entry point (so the MainActivity) differs from the last active Activity. The problem is that I have to resume a special Activity with a special state of ViewSwitcher. I could start MessageActivity out of MainActivity with startActivity(Intent) in onStart() or onResume() method (because MainActivity is probably the entry point) but then I run into a lot of logical problems in Lifecycle management. Due to this fact I don't think that this is the right way to do that.
But, what's the right and best way to do that?
I guess MainActivity (instead of MessageActivity->state(1 or 2)) will start next time after I relaunch the application (is that correct?)
No, I don't believe this is correct, depending on what your code does in onCreate(). It certainly doesn't need to be correct if you go about things the right way. A simple way to test this is to rotate your screen, which recreates the running activities, unless you have overridden the default configuration change behaviour.
I recommend reading this section in the android docs carefully:
http://developer.android.com/guide/topics/fundamentals/activities.html#SavingActivityState
In particular:
even if you do nothing and do not implement onSaveInstanceState(), some of the activity state is restored by the Activity class's default implementation of onSaveInstanceState(). Specifically, the default implementation calls onSaveInstanceState() for every View in the layout, which allows each view to provide information about itself that should be saved. Almost every widget in the Android framework implements this method as appropriate, such that any visible changes to the UI are automatically saved and restored when your activity is recreated. For example, the EditText widget saves any text entered by the user and the CheckBox widget saves whether it's checked or not. The only work required by you is to provide a unique ID (with the android:id attribute) for each widget you want to save its state. If a widget does not have an ID, then it cannot save its state.
What this means is, that so long as you don't force any UI state in any onCreate() calls, your activity stack and UI state will be restored.
Personally, my preferred approach is to keep as little state as possible in member variables of my activities, saving and restoring it with onSave/RestoreInstanceState(), and relying on the default implementations to save the rest of the UI state (text box contents, etc). Data that should persist between sessions I commit straight to my DB or preferences as soon as it's changed (e.g. in the on-click handler). This means I don't need to worry about the activity lifecycle for that. As much as possible, my UI just presents a view of the data in my DB (using CursorAdapter etc.).
Edit:
Regarding restoration of the whole activity stack:
When the user leaves a task by pressing the HOME key, ... The system retains the state of every activity in the task. If the user later resumes the task by selecting the launcher icon that began the task, the task comes to the foreground and resumes the activity at the top of the stack.
(See http://developer.android.com/guide/topics/fundamentals/tasks-and-back-stack.html)
It's not my attempt for a best answer, but it's too long to get in the comments section.
First I will suggest not to rely on the "Android way" - this will result in inconsistent application behavior depending on the free memory of the device - bad practice.
My suggestion is to save your state-dependent data in key-value pairs in SharedPreferences, every time you go into onPause() in your MessageActivity. Store a flag in SharedPreferences, which indicates which was the Activity that was last opened (if you only have two Activities you can easily go 0/1 or true/false flags).
When you re-launch your application, it's normal to start the Activity marked in your AndroidManifest.xml as "entry point". So naturally you'll check the flag in onResume() in your MainActivity and start the other Activity if needed. In MessageActivity's onResume() check the values in SharedPreferences and fill in what's necessary...
If your application is "resumed" to the last Activity in the ActivityStack this will call onResume() in the last Activity in the ActivityStack.
The way I have handled an issue like this in the past, is to have a service running in the background, which handles the flow of information from different activities via either Intents and listeners (preferable, since they are the most easily decoupled solution), or if you are extremely careful, and the only viable solution for some reason is to store the data through direct property access or method calls, you can use static properties/methods on the service class as well. However, I would strongly recommend using the Intent/listener method as it is generally more flexible, thread safe, and decoupled. Additionally, it is wise to make sure that not much is happening at any point in time (in other words, only use this service for Intent handling) when it's not needed, otherwise the Service will tend to hog CPU time as well as RAM, when it's not really needed.
Some resources to look at when it comes to this approach would be IntentService and its related classes, including the superclass, Service. IntentService, however, it is worth noting handles a few more things about async Intent processing, etc that Service does not automatically come with.Hope this helps you!
login.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
String name=username.getText().toString();
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
SharedPreferences.Editor editor = settings.edit();
editor.putString("username", name);
if(name.equals("xxx")) {
Intent intent=new Intent(currentactivity.this,nextactivity.class);
intent.putExtras(bundle);
startActivityForResult(intent,0);
}
}
});
there is my situation. I have same activities which goes one by another, no matter what they are doing. Lets name them from the start of alphabet. When users uses my application, he goes through activities and makes his own path between them, so he could with back button go back in respecitve back order.
He starts with act. A - D - F and with back button he goes back as from F to D and A. Ok. Now, when Android system resolves the application is no longer in use or needs lot of RAM in some particular time, system kills it. My goal is to find, how to restore application to its former state including order of opened activities?
It might not be clear, so here is the example:
User has open activities A (login) - D - F - G, minimize it, after some time, app is killed. When he start this application again, he needs to login at activity A and than he has to see activity G (= he was there last time), and when he push back button, he will go do activity F, then activity D and so on... Is like revieving an row of activites. I know I have to persist all the information stored in my activities (D, F, G), but is it acutally possible to persist app state like that?
Thanks for any comment on this
Solution:
I am tracking flag, which identifies the state my application is in. If it is s 0, it means I am opening new activity normally. On start of each activity I put into shared preferences string, which contains all my activity history. Each activity has it's own id (again sharedPref). In another shared pref I am saving as a String formular data (or data with GUI), when onPause occurs. I set flag as a 1. When app starts and flag is 1, I revive application stack from sharedPref. Set data for each of them from another Shared Pref. And that's it, application state is revived :-)
You can persist anything you need to, it just a matter of how and what is going to be beneficial. There are multiple techniques that have been used to persist state over the years. Nearly all of them are available to you, but will require careful management on your part. Depending on what your application does, there may be special tricks available to you, as well.
Step 1
Determine what each Activity needs in order to run effectively. Determine what you can recalculate and what you absolutely should not recalculate. For instance, if one of your Activities is a Cursor Adapter of some kind and works according to a key to a table, you don't need to persist the entire Activity, you simply need to hold onto whichever _id relates to that particular Activity run.
Step 2
Since you are wanting to track Activity history, you will need some representation of that history. What you are proposing is a stack model, so you will want to write your own stack object and find an easy way to identify each activity in that stack. Do not try and save the actual Activity references as this will invariably lead to leaks. You can then save this stack to a database, shared preferences, file or even parcel it to a bundle. Integer constants that identify each Activity might be one way to accomplish this.
Step 3
Decide on your method of save, and build the appropriate save and load methods for your stack and each Activity.
Step 4
Override the Back button to retrieve the top Activity identifier and its appropriate data on the stack. (As a note: your stack might be better placed in an extended Application) Then start the next Activity with its required data.
Step 5
When your "login" Activity (or Application) starts, load the stack. When authentication completes, reload the top Activity on the stack, passing its required data through Intent Extras. You don't have to open ALL of the Activities at once, just the ones that the user is on.
Step 6
In your onCreate or onWindowAttached for each Activity, have it add itself to the stack. In your onDestroy for each Activity, have it remove itself from the stack. Since you are persisting your data, you can easily finish() to indicate that the Activity is complete.
Step 7
In your onPause for each Activity have it save the state that you feel is important. You can even save the scroll position and just have it rescroll when the Activity restarts. In your onCreate have it regain its state via the extras that you supplied.
It is really as simple as all of that. If you need some samples, I can gladly provide.
FuzzicalLogic
Assuming all you need to do is reconstruct the path of activities from A to Z (or whatever), you don't need to make things too complicated. if you want to do it the right way, do the suggestion by Fuzzical Logic. if you want to get it running quickly and complicate things after that, you can start with this simple method.
Basically, you map each activity to a code, and maintain a simple text file. Each time an activity is invoked, it should append it's code to the text file. So you're really just writing to a file exactly what you explained in your question. In your example, you'd have "ADFG" in a text file.
When you exit an activity and go back, just read the file, chop off the last letter, and write it out. In your example, if you had "ADFG", pressed back, the file would now contain "ADF".
When your app starts, simply read the file and for each character, create the associated activity as you would normally. Read the file once and pass the string to each activity as it is created. So the root activity would read "A" and start that activity (passing the string "DFG" in the bundle), then that activity would read the next character and start the D activity (passing "FG"), and so on until the last activity sees that there's no characters left in the string.
Once that's all working, you can worry about how to store state information for each activity. Fuzzy's solution is by far the most elegant, but elegant and ASAP don't usually mix, so it's your call. I'd separate the stack data from the state data for each item in the stack. It's just easier that way IMO.
Hope that helps!
I have app that connects to internet to get data. I can access data multi-level.
So let say I start at level 3 and on level 4 I decide to go back, whenever I press back the previous activity reloads the data from the internet.
Is there any possibility to prevent that?
I have tried to run the activity in single-top mode.
Move the data loading code to the single-exec event: onStart or
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState == null) {
// here
}
}
You have a lot of options here.
The simpler one is to avoid stopping your activity when you switch to another one. When you go to level 4 by means of startActivity(), your onPause() is called on the level 3 activity but it continues living in the background. Then when you come back from 3, onResume() is called.
So as long as you don't load data on resume nor finish() your level 3 activity, you should be fine. If the system happens to be very short on resources then level 3 activity might be killed (though this is very rare) and restarted when you get back to it, but if it happens it means the system needed memory for something important and it's probably fine to reload.
Now, there are other ways. It's usually much better to do it as I described above, but if for some reason you want to finish your level 3 activity, Here are your options.
As it has been noted, you may elect to dump your data somewhere. The saved instance state is an option - though if it's heavy data, more than a few kilobytes, it's not recommended. The idea is, you save your data in onSaveInstanceState() in the Bundle and restore it in onCreate().
If it's heavy data, you would be better off dumping it in a cache file.
If you have a data model, and want to use the same data across several activities, maybe a widget and possibly even a different app, you may want to consider building a ContentProvider to supply the data. It would live independently of the other parts of your application and manage access to the data. Other parts would query it for the data they would need.
The neat thing about that is, it abstracts the data away from the rest of the program. It can be accessed from anywhere and caching policies and everything is handled in a dedicated place. The drawback is, it's significantly more complicated.
One possible solution would be to work with states. You basically have a boolean which indicates if your activity has performed a certain action.
If the action was performed you it won't reach that code again. Of course that flag must be saved somewhere in the Application context or in SharedPreferences.
You can add this parameter to your intent to prevent reloading.
//intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(new Intent(this,My.class).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));