Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Persistent Data Storage Android allows applications to permanently store data on the mobile device for later use and protects it from accidental or malicious access by other programs. Saving and loading data is an essential requirement for most applications. At a minimum, activities should save their UI state each time they move out of the foreground. This ensures that the same UI state is presented when it’s next seen, even if the process has been killed and restarted before that happens. It’s also likely that you’ll need to save preferences, to let users customize the application, and persist data entered or recorded. Just as important is the ability to load data from files, databases, or Content Providers. An application can store data using several techniques depending on the size of the data, its structure, its lifetime, and whether it will be shared with other programs. 1. Preferences are a simple, lightweight key/value pair mechanism for saving primitive application data, most commonly UI state, user preferences, or application settings. 2. Android also provides access to the local file system, both through specialized methods and the normal Java.IO classes. 3. For a more robust persistence layer, Android provides the SQLite database library. The SQLite relational database offers a powerful native SQL database over which you have total control. 4. Content Providers offer a generic interface to any data source. They effectively decouple the underlying data storage technique from the application layer. They let you expose a well-defined interface for using and sharing private data. By default, access to all files, databases, and preferences is restricted to the application that created them. Content Providers offer a managed way for your applications to share private data with other applications. As a result, your applications can use the Content Providers offered by others, including native providers. There are two lightweight techniques for saving simple application data for Android applications: (1) Shared Preferences and (2) a pair of event handlers used for saving the details of an Activity instance. Both mechanisms use a name-value pair mechanism to store simple primitive values. 1. Shared Preferences support the primitive types boolean, float, long, int, and String making them an ideal way to quickly store default values, class instance variables, the current UI state, and user preferences. They are most commonly used to persist data across user sessions and to share settings between application components. 2. Alternatively, Activities offer the onSaveInstanceState handler. It’s designed specifically to persist the UI state when the Activity becomes eligible for termination by a resource-hungry run time. The handler works like the Shared Preference mechanism. It offers a Bundle parameter that represents a key/value map of primitive types that can be used to save the Activity’s instance values. This Bundle is then made available as a parameter passed in to the onCreate() and onRestoreInstanceState() method handlers. This UI state Bundle is used to record the values needed for an Activity to provide an identical UI following unexpected restarts. 1. Shared Preferences Shared preferences are preferences that are shared between all activities of an application and can be managed with the help of the getSharedPreferences() method of the Context class. This method takes two arguments, the name of the set of shared preferences and the operating mode. MODE_PRIVATE is the default operating mode and means the created set of preferences can be accessed only by the application that created it. Basically, the shared preferences are shared across the application’s components but aren’t available to other applications. The getSharedPreferences() method returns a SharedPreferences object that holds the contents of the specified preference set through which you can retrieve or modify the preference values. public static final String PREF_SET_NAME = "PrefSet"; SharedPreferences preferences = getSharedPreferences(PREF_SET_NAME,MODE_PRIVATE); Remark: Note that if several application components call the getSharedPreferences() method with the same preference set name, then the same SharedPreferences object is returned to all callers (meaning they will see each other's edits as soon as they are made). To edit/modify a shared preference, use the SharedPreferences.Editor class. Get the Editor object by calling edit() on the SharedPreferences object you want to change and then use mutators/setters put<type> to save value of specific types. To save edits, call commit() on the Editor, as illustrated in the following generic example. SharedPreferences.Editor editor = preferences.edit(); editor.putInt("key1", int_value); editor.putString("key2", string_value); editor.commit(); To access/retrieve saved shared preferences use the type-safe get<type> methods. Each getter takes a key and a default value (used when no value is available for that key). int value1 = preferences.getInt("key1", 0); String value2 = preferences.getString("key2", "abc"); If a preference does not need to be shared across multiple components of an application, but is needed only by one Activity for example, then you can use the getPreferences() method of the Activity class (instead of the getSharedPreferences() method of the Context class). The getPreferences() method invokes the getSharedPreferences() method with the name of the activity class as the name for that set of preferences. The code to modify and access these preferences (called activity preferences) is the same as for shared preferences. 1.1. Implementing the Continue Game option using Preferences We will illustrate the use of activity preferences by implementing the option to continue an old game in our Sudoku application. A player can decide to quit playing a Sudoku game at any point in time. To allow the player to come back later and continue where they left off, we first need to save the current state of the puzzle (i.e., the numbers in each of the 9x9 = 81 tiles) whenever the game is paused. Therefore, in the onPause() method of the Game activity, we first concatenate the numbers from the 81 tiles into a string, using method toPuzzleString() and then store the value of this string paired with a key into the shared preferences. In our code below we use the constant "puzzle" as the key. private static final String PREF_PUZZLE = "puzzle" ; @Override protected void onPause() { super.onPause(); // Save the current puzzle String s = toPuzzleString(puzzle); getPreferences(MODE_PRIVATE).edit().putString(PREF_PUZZLE, s).commit(); } Now that the puzzle is saved, in order to read it when needed, we will just modify method getPuzzle(), which loads a puzzle (according to the difficulty level selected by the player). All we need to do is use a flag, for example -1, to indicate that the puzzle saved in the preferences should be loaded (instead of starting a new puzzle of a certain difficulty level). /** Given a difficulty level, come up with a new puzzle */ private int[] getPuzzle(int diff) { String puz; switch (diff) { case -1: puz = getPreferences(MODE_PRIVATE).getString(PREF_PUZZLE, easyPuzzle); break; case 0: puz = hardPuzzle; break; case 1: puz = mediumPuzzle; break; case 2: default: puz = easyPuzzle; break; } return fromPuzzleString(puz); } Now when player chooses the option to continue an old game, we can call the same method used for starting a new game, startGame(), but passing it -1 instead of a difficulty level (0, 1 or 2) to indicate that the puzzle to be loaded is the one stored in preferences. public class Sudoku extends Activity implements OnClickListener { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Set up click listeners for all the buttons View continueButton = this.findViewById(R.id.continue_button); continueButton.setOnClickListener(this); View newButton = this.findViewById(R.id.new_button); newButton.setOnClickListener(this); View aboutButton = this.findViewById(R.id.about_button); aboutButton.setOnClickListener(this); View exitButton = this.findViewById(R.id.exit_button); exitButton.setOnClickListener(this); } public void onClick(View v) { switch (v.getId()) { case R.id.continue_button: startGame(-1); break; case R.id.about_button: Intent i = new Intent(this, About.class); startActivity(i); break; case R.id.new_button: openNewGameDialog(); break; case R.id.exit_button: finish(); break; } } /** Start a new game with the given difficulty level */ private void startGame(int i) { Intent intent = new Intent(Sudoku.this, Game.class); intent.putExtra(Game.KEY_DIFFICULTY, i); startActivity(intent); } } 2. Storing the State of an Activity Instance To save Activity instance variables, Android offers a specialized alternative to preferences. By overriding an Activity’s onSaveInstanceState event handler, you can use its Bundle parameter to save instance values. Values are stored/retrieved to/from this Bundle object using the same get and put methods as shown for preferences. This handler will be triggered whenever an Activity completes its active life cycle, but only when it’s not being explicitly finished. As a result, it’s used to ensure a consistent Activity state between active life cycles of a single user session. The saved Bundle is passed in to the onRestoreInstanceState() and onCreate() methods if the application is forced to restart during a session. 2.1. Remembering the Selected Tile when Screen Orientation Changes We will illustrate how to use the onSaveInstanceState event handler to remember the current position of the selected tile when the player changes the screen orientation while Sudoku is running. In this case, we need to store the values of data fields selX and selY (indicating the selected tile) from the PuzzleView class. Thus, as for shared preferences we first define two constants "selX" and "selY" as keys for the values to be stored. Note that normally, Android views save their state automatically, but since we made our own view, we don’t get that for free. Notice that the PuzzleView class is a View (not an Activity). The View class also has a onSaveInstanceState() method that is invoked automatically by the Activity.onSaveInstanceState() method for every hosted view that has an ID. Normally, this ID would come from XML, but since PuzzleView was created in code, we need to set it ourselves. Thus, we make up an arbitrary number (any value will work as long as it is positive) and then use the setId() method to assign it to our view in the constructor. In the onSaveInstanceState() method we call the superclass to get its state, and then save it plus the additional value-key pairs in a Bundle object. Later, onRestoreInstanceState() will be called to read the information we saved. We get our own x and y positions from the Bundle, and then we call the superclass to let it get whatever it needs. public class PuzzleView private int selX; private int selY; … private static final private static final private static final private static final extends View { // X index of selection // Y index of selection String String String int ID SELX = "selX"; SELY = "selY"; VIEW_STATE = "viewState"; = 42; public PuzzleView(Context context) { ... setId(ID); } @Override protected Parcelable onSaveInstanceState() { Parcelable p = super.onSaveInstanceState(); Bundle bundle = new Bundle(); bundle.putInt(SELX, selX); bundle.putInt(SELY, selY); bundle.putParcelable(VIEW_STATE, p); return bundle; } @Override protected void onRestoreInstanceState(Parcelable state) { Bundle bundle = (Bundle) state; select(bundle.getInt(SELX), bundle.getInt(SELY)); super.onRestoreInstanceState(bundle.getParcelable(VIEW_STATE)); return; } } 3. Describing Application Preferences/Settings using XML files Beginning with the 0.9 SDK, Android has introduced a framework for managing application preferences/settings. This framework does not change anything shown above. Instead, the framework is more for presenting a consistent set of preference-setting options for users, so different applications do not have to "reinvent the wheel". Basically, the framework allows you to describe an application's preferences/settings in an XML file stored in your project's res/xml/ directory. Given that, Android can present a pleasant user UI for manipulating those preferences, which are then stored in a SharedPreferences object, which can be obtained through the getDefaultSharedPreferences() method of the PreferencesManager class. This method is similar to getPreferences() and getSharedPreferences(), in the sense that it returns a SharedPreferences object which offers a series of type-safe getters to access preference values. We illustrate an XML file containing application preferences with our Sudoku example. In Sudoku the user has the option to have music playing while he is playing the game. This application setting is described in the following XML file. res/xml/settings.xml <?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <CheckBoxPreference android:key="music" android:title="@string/music_title" android:summary="@string/music_summary" android:defaultValue="true" /> </PreferenceScreen> Note that music_title and music_summary are defined in the res/values/strings.xml ... <string name="music_title">Music</string> <string name="music_summary">Play background music</string> ... The root of the preference XML document is a PreferenceScreen element. Inside a PreferenceScreen element you can have preference definitions. These are subclasses of Preference, such as CheckBoxPreference as shown above, which allows you to check a checkbox. Once you have set up your preference XML, you can use a nearly-built-in activity for allowing your users to set their preferences. The activity is "nearly-built-in" because you need to subclass it to point it to your preference XML, plus hook it into the rest of your application. For our Sudoku example, let’s call this activity Settings. public class Settings extends PreferenceActivity { // Options names and default values private static final String OPT_MUSIC = "music"; private static final boolean OPT_MUSIC_DEF = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.settings); } /** Get the current value of the music option */ public static boolean getMusic(Context context) { return PreferenceManager.getDefaultSharedPreferences(context) .getBoolean(OPT_MUSIC, OPT_MUSIC_DEF); } } As you can see, all you need to do is extend the PreferenceActivity class and, in the onCreate() method, call the addPreferencesFromResource() method specifying the XML resource containing your preferences (in our example, R.xml.settings). Note: Do not forget to add this activity to your AndroidManifest.xml file. Now we have to link this activity with the rest of the Sudoku application. Specifically, we have to invoke this activity (through an Intent) when the user presses the Menu button. public class Sudoku extends Activity implements OnClickListener { @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.settings: startActivity(new Intent(this, Settings.class)); return true; } return false; } } If you have a lot of preferences for users to set, having them all in one big list may become troublesome. Android's preference framework gives you a few ways to impose a bit of structure on your bag of preferences, including categories and screens. Categories are added via a PreferenceCategory element in your preference XML and are used to group together related preferences. Rather than have your preferences all as children of the root PreferenceScreen, you can put a few PreferenceCategory elements in the PreferenceScreen, and then put your preferences in their appropriate categories. Visually, this adds a divider with the category title between groups of preferences. If you have lots and lots of preferences – more than is convenient for users to scroll through – you can also put them on separate "screens" by introducing the PreferenceScreen element. Any children of PreferenceScreen go on their own screen. If you nest PreferenceScreens, the parent screen displays the screen as a placeholder entry – tapping that entry brings up the child screen. For example, here is a preference XML file that contains both PreferenceCategory and nested PreferenceScreen elements. <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceCategory android:title="Simple Preferences"> <CheckBoxPreference android:key="@string/checkbox" android:title="Checkbox Preference" android:summary="Check it on, check it off" /> <RingtonePreference android:key="@string/ringtone" android:title="Ringtone Preference" android:showDefault="true" android:showSilent="true" android:summary="Pick a tone, any tone" /> </PreferenceCategory> <PreferenceCategory android:title="Detail Screens"> <PreferenceScreen android:key="detail" android:title="Detail Screen" android:summary="Additional preferences held in another page"> <CheckBoxPreference android:key="@string/checkbox2" android:title="Another Checkbox" android:summary="On. Off. It really doesn't matter." /> </PreferenceScreen> </PreferenceCategory> </PreferenceScreen> If you tap on the Detail Screen entry, you are taken to the child preference screen: