docs: android training - Data Storage

Change-Id: I9579a7eb04cb39d3b457dde390b6b2a7b240a429
This commit is contained in:
Scott Main
2012-09-07 15:26:39 -07:00
parent 94b94fc5b3
commit 04c63a4098
6 changed files with 906 additions and 1 deletions

View File

@ -232,7 +232,12 @@ save files. This can be a removable storage media (such as an SD card) or an int
(non-removable) storage. Files saved to the external storage are world-readable and can (non-removable) storage. Files saved to the external storage are world-readable and can
be modified by the user when they enable USB mass storage to transfer files on a computer.</p> be modified by the user when they enable USB mass storage to transfer files on a computer.</p>
<p class="caution"><strong>Caution:</strong> External files can disappear if the user mounts the <p>It's possible that a device using a partition of the
internal storage for the external storage may also offer an SD card slot. In this case,
the SD card is <em>not</em> part of the external storage and your app cannot access it (the extra
storage is intended only for user-provided media that the system scans).</p>
<p class="caution"><strong>Caution:</strong> External storage can become unavailable if the user mounts the
external storage on a computer or removes the media, and there's no security enforced upon files you external storage on a computer or removes the media, and there's no security enforced upon files you
save to the external storage. All applications can read and write files placed on the external save to the external storage. All applications can read and write files placed on the external
storage and the user can remove them.</p> storage and the user can remove them.</p>

View File

@ -0,0 +1,322 @@
page.title=Saving Data in SQL Databases
parent.title=Data Storage
parent.link=index.html
trainingnavtop=true
previous.title=Saving Data in Files
previous.link=files.html
@jd:body
<div id="tb-wrapper">
<div id="tb">
<h2>This lesson teaches you to</h2>
<ol>
<li><a href="#DefineContract">Define a Schema and Contract</a></li>
<li><a href="#DbHelper">Create a Database Using a SQL Helper</a></li>
<li><a href="#WriteDbRow">Put Information into a Database</a></li>
<li><a href="#ReadDbRow">Read Information from a Database</a></li>
<li><a href="#DeleteDbRow">Delete Information from a Database</a></li>
<li><a href="#UpdateDbRow">Update a Database</a></li>
</ol>
<h2>You should also read</h2>
<ul>
<li><a href="{@docRoot}guide/topics/data/data-storage.html#db">Using Databases</a></li>
</ul>
<!--
<h2>Try it out</h2>
<div class="download-box">
<a href="{@docRoot}shareables/training/Sample.zip" class="button">Download the sample</a>
<p class="filename">Sample.zip</p>
</div>
-->
</div>
</div>
<p>Saving data to a database is ideal for repeating or structured data,
such as contact information. This class assumes that you are
familiar with SQL databases in general and helps you get started with
SQLite databases on Android. The APIs you'll need to use a database
on Android are available in the {@link android.database.sqlite} package.</p>
<h2 id="DefineContract">Define a Schema and Contract</h2>
<p>One of the main principles of SQL databases is the schema: a formal
declaration of how the database is organized. The schema is reflected in the SQL
statements that you use to create your database. You may find it helpful to
create a companion class, known as a <em>contract</em> class, which explicitly specifies
the layout of your schema in a systematic and self-documenting way.</p>
<p>A contract class is a container for constants that define names for URIs,
tables, and columns. The contract class allows you to use the same constants
across all the other classes in the same package. This lets you change a column
name in one place and have it propagate throughout your code.</p>
<p>A good way to organize a contract class is to put definitions that are
global to your whole database in the root level of the class. Then create an inner
class for each table that enumerates its columns.</p>
<p class="note"><strong>Note:</strong> By implementing the {@link
android.provider.BaseColumns} interface, your inner class can inherit a primary
key field called {@code _ID} that some Android classes such as cursor adaptors
will expect it to have. It's not required, but this can help your database
work harmoniously with the Android framework.</p>
<p>For example, this snippet defines the table name and column names for a
single table:</p>
<pre>
public static abstract class FeedEntry implements BaseColumns {
public static final String TABLE_NAME = &quot;entry&quot;;
public static final String COLUMN_NAME_ENTRY_ID = &quot;entryid&quot;;
public static final String COLUMN_NAME_TITLE = &quot;title&quot;;
public static final String COLUMN_NAME_SUBTITLE = &quot;subtitle&quot;;
...
}
</pre>
<p>To prevent someone from accidentally instantiating the contract class, give
it an empty constructor. </p>
<pre>
// Prevents the FeedReaderContract class from being instantiated.
private FeedReaderContract() {}
</pre>
<h2 id="DbHelper">Create a Database Using a SQL Helper</h2>
<p>Once you have defined how your database looks, you should implement methods
that create and maintain the database and tables. Here are some typical
statements that create and delete a table:</P>
<pre>
private static final String TEXT_TYPE = &quot; TEXT&quot;;
private static final String COMMA_SEP = &quot;,&quot;;
private static final String SQL_CREATE_ENTRIES =
&quot;CREATE TABLE &quot; + FeedReaderContract.FeedEntry.TABLE_NAME + &quot; (&quot; +
FeedReaderContract.FeedEntry._ID + &quot; INTEGER PRIMARY KEY,&quot; +
FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP +
FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP +
... // Any other options for the CREATE command
&quot; )&quot;;
private static final String SQL_DELETE_ENTRIES =
&quot;DROP TABLE IF EXISTS &quot; + TABLE_NAME_ENTRIES;
</pre>
<p>Just like files that you save on the device's <a
href="{@docRoot}guide/topics/data/data-storage.html#filesInternal">internal
storage</a>, Android stores your database in private disk space that's associated
application. Your data is secure, because by default this area is not
accessible to other applications.</p>
<p>A useful set of APIs is available in the {@link
android.database.sqlite.SQLiteOpenHelper} class.
When you use this class to obtain references to your database, the system
performs the potentially
long-running operations of creating and updating the database only when
needed and <em>not during app startup</em>. All you need to do is call
{@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase} or
{@link android.database.sqlite.SQLiteOpenHelper#getReadableDatabase}.</p>
<p class="note"><strong>Note:</strong> Because they can be long-running,
be sure that you call {@link
android.database.sqlite.SQLiteOpenHelper#getWritableDatabase} or {@link
android.database.sqlite.SQLiteOpenHelper#getReadableDatabase} in a background thread,
such as with {@link android.os.AsyncTask} or {@link android.app.IntentService}.</p>
<p>To use {@link android.database.sqlite.SQLiteOpenHelper}, create a subclass that
overrides the {@link
android.database.sqlite.SQLiteOpenHelper#onCreate onCreate()}, {@link
android.database.sqlite.SQLiteOpenHelper#onUpgrade onUpgrade()} and {@link
android.database.sqlite.SQLiteOpenHelper#onOpen onOpen()} callback methods. You may also
want to implement {@link android.database.sqlite.SQLiteOpenHelper#onDowngrade onDowngrade()},
but it's not required.</p>
<p>For example, here's an implementation of {@link
android.database.sqlite.SQLiteOpenHelper} that uses some of the commands shown above:</p>
<pre>
public class FeedReaderDbHelper extends SQLiteOpenHelper {
// If you change the database schema, you must increment the database version.
public static final int DATABASE_VERSION = 1;
public static final String DATABASE_NAME = &quot;FeedReader.db&quot;;
public FeedReaderDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
public void onCreate(SQLiteDatabase db) {
db.execSQL(SQL_CREATE_ENTRIES);
}
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// This database is only a cache for online data, so its upgrade policy is
// to simply to discard the data and start over
db.execSQL(SQL_DELETE_ENTRIES);
onCreate(db);
}
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
onUpgrade(db, oldVersion, newVersion);
}
}
</pre>
<p>To access your database, instantiate your subclass of {@link
android.database.sqlite.SQLiteOpenHelper}:</p>
<pre>
FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext());
</pre>
<h2 id="WriteDbRow">Put Information into a Database</h2>
<p>Insert data into the database by passing a {@link android.content.ContentValues}
object to the {@link android.database.sqlite.SQLiteDatabase#insert insert()} method:</p>
<pre>
// Gets the data repository in write mode
SQLiteDatabase db = mDbHelper.getWritableDatabase();
// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID, id);
values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, title);
values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_CONTENT, content);
// Insert the new row, returning the primary key value of the new row
long newRowId;
newRowId = db.insert(
FeedReaderContract.FeedEntry.TABLE_NAME,
FeedReaderContract.FeedEntry.COLUMN_NAME_NULLABLE,
values);
</pre>
<p>The first argument for {@link android.database.sqlite.SQLiteDatabase#insert insert()}
is simply the table name. The second argument provides
the name of a column in which the framework can insert NULL in the event that the
{@link android.content.ContentValues} is empty (if you instead set this to {@code "null"},
then the framework will not insert a row when there are no values).</p>
<h2 id="ReadDbRow">Read Information from a Database</h2>
<p>To read from a database, use the {@link android.database.sqlite.SQLiteDatabase#query query()}
method, passing it your selection criteria and desired columns.
The method combines elements of {@link android.database.sqlite.SQLiteDatabase#insert insert()}
and {@link android.database.sqlite.SQLiteDatabase#update update()}, except the column list
defines the data you want to fetch, rather than the data to insert. The results of the query
are returned to you in a {@link android.database.Cursor} object.</p>
<pre>
SQLiteDatabase db = mDbHelper.getReadableDatabase();
// Define a <em>projection</em> that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
FeedReaderContract.FeedEntry._ID,
FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE,
FeedReaderContract.FeedEntry.COLUMN_NAME_UPDATED,
...
};
// How you want the results sorted in the resulting Cursor
String sortOrder =
FeedReaderContract.FeedEntry.COLUMN_NAME_UPDATED + " DESC";
Cursor c = db.query(
FeedReaderContract.FeedEntry.TABLE_NAME, // The table to query
projection, // The columns to return
selection, // The columns for the WHERE clause
selectionArgs, // The values for the WHERE clause
null, // don't group the rows
null, // don't filter by row groups
sortOrder // The sort order
);
</pre>
<p>To look at a row in the cursor, use one of the {@link android.database.Cursor} move
methods, which you must always call before you begin reading values. Generally, you should start
by calling {@link android.database.Cursor#moveToFirst}, which places the "read position" on the
first entry in the results. For each row, you can read a column's value by calling one of the
{@link android.database.Cursor} get methods, such as {@link android.database.Cursor#getString
getString()} or {@link android.database.Cursor#getLong getLong()}. For each of the get methods,
you must pass the index position of the column you desire, which you can get by calling
{@link android.database.Cursor#getColumnIndex getColumnIndex()} or
{@link android.database.Cursor#getColumnIndexOrThrow getColumnIndexOrThrow()}.
For example:</p>
<pre>
cursor.moveToFirst();
long itemId = cursor.getLong(
cursor.getColumnIndexOrThrow(FeedReaderContract.FeedEntry._ID)
);
</pre>
<h2 id="DeleteDbRow">Delete Information from a Database</h2>
<p>To delete rows from a table, you need to provide selection criteria that
identify the rows. The database API provides a mechanism for creating selection
criteria that protects against SQL injection. The mechanism divides the
selection specification into a selection clause and selection arguments. The
clause defines the columns to look at, and also allows you to combine column
tests. The arguments are values to test against that are bound into the clause.
Because the result isn't handled the same as a regular SQL statement, it is
immune to SQL injection.</p>
<pre>
// Define 'where' part of query.
String selection = FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + &quot; LIKE ?&quot;;
// Specify arguments in placeholder order.
String[] selelectionArgs = { String.valueOf(rowId) };
// Issue SQL statement.
db.delete(table_name, mySelection, selectionArgs);
</pre>
<h2 id="UpdateDbRow">Update a Database</h2>
<p>When you need to modify a subset of your database values, use the {@link
android.database.sqlite.SQLiteDatabase#update update()} method.</p>
<p>Updating the table combines the content values syntax of {@link
android.database.sqlite.SQLiteDatabase#insert insert()} with the {@code where} syntax
of {@link android.database.sqlite.SQLiteDatabase#delete delete()}.</p>
<pre>
SQLiteDatabase db = mDbHelper.getReadableDatabase();
// New value for one column
ContentValues values = new ContentValues();
values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, title);
// Which row to update, based on the ID
String selection = FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + &quot; LIKE ?&quot;;
String[] selelectionArgs = { String.valueOf(rowId) };
int count = db.update(
FeedReaderDbHelper.FeedEntry.TABLE_NAME,
values,
selection,
selectionArgs);
</pre>

View File

@ -0,0 +1,382 @@
page.title=Saving Files
parent.title=Data Storage
parent.link=index.html
trainingnavtop=true
@jd:body
<div id="tb-wrapper">
<div id="tb">
<h2>This lesson teaches you to</h2>
<ol>
<li><a href="#InternalVsExternalStorage">Choose Internal or External Storage</a></li>
<li><a href="#GetWritePermission">Obtain Permissions for External Storage</a></li>
<li><a href="#WriteInternalStorage">Save a File on Internal Storage</a></li>
<li><a href="#WriteExternalStorage">Save a File on External Storage</a></li>
<li><a href="#GetFreeSpace">Query Free Space</a></li>
<li><a href="#DeleteFile">Delete a File</a></li>
</ol>
<h2>You should also read</h2>
<ul>
<li><a href="{@docRoot}guide/topics/data/data-storage.html#filesInternal">Using the Internal
Storage</a></li>
<li><a href="{@docRoot}guide/topics/data/data-storage.html#filesExternal">Using the External
Storage</a></li>
</ul>
</div>
</div>
<p>Android uses a file system that's
similar to disk-based file systems on other platforms. This lesson describes
how to work with the Android file system to read and write files with the {@link java.io.File}
APIs.</p>
<p>A {@link java.io.File} object is suited to reading or writing large amounts of data in
start-to-finish order without skipping around. For example, it's good for image files or
anything exchanged over a network.</p>
<p>This lesson shows how to perform basic file-related tasks in your app.
The lesson assumes that you are familiar with the basics of the Linux file system and the
standard file input/output APIs in {@link java.io}.</p>
<h2 id="InternalVsExternalStorage">Choose Internal or External Storage</h2>
<p>All Android devices have two file storage areas: "internal" and "external" storage. These names
come from the early days of Android, when most devices offered built-in non-volatile memory
(internal storage), plus a removable storage medium such as a micro SD card (external storage).
Some devices divide the permanent storage space into "internal" and "external" partitions, so even
without a removable storage medium, there are always two storage spaces and
the API behavior is the same whether the external storage is removable or not.
The following lists summarize the facts about each storage space.</p>
<div class="col-5" style="margin-left:0">
<p><b>Internal storage:</b></p>
<ul>
<li>It's always available.</li>
<li>Files saved here are accessible by only your app by default.</li>
<li>When the user uninstalls your app, the system removes all your app's files from
internal storage.</li>
</ul>
<p>Internal storage is best when you want to be sure that neither the user nor other apps can
access your files.</p>
</div>
<div class="col-7" style="margin-right:0">
<p><b>External storage:</b></p>
<ul>
<li>It's not always available, because the user can mount the external storage as USB storage
and in some cases remove it from the device.</li>
<li>It's world-readable, so
files saved here may be read outside of your control.</li>
<li>When the user uninstalls your app, the system removes your app's files from here
only if you save them in the directory from {@link android.content.Context#getExternalFilesDir
getExternalFilesDir()}.</li>
</ul>
<p>External storage is the best
place for files that don't require access restrictions and for files that you want to share
with other apps or allow the user to access with a computer.</p>
</div>
<p class="note" style="clear:both">
<strong>Tip:</strong> Although apps are installed onto the internal storage by
default, you can specify the <a
href="{@docRoot}guide/topics/manifest/manifest-element.html#install">{@code
android:installLocation}</a> attribute in your manifest so your app may
be installed on external storage. Users appreciate this option when the APK size is very large and
they have an external storage space that's larger than the internal storage. For more
information, see <a
href="{@docRoot}guide/topics/data/install-location.html">App Install Location</a>.</p>
<h2 id="GetWritePermission">Obtain Permissions for External Storage</h2>
<p>To write to the external storage, you must request the
{@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permission in your <a
href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest file</a>:</p>
<pre>
&lt;manifest ...>
&lt;uses-permission android:name=&quot;android.permission.WRITE_EXTERNAL_STORAGE&quot; /&gt;
...
&lt;/manifest>
</pre>
<div class="caution"><p><strong>Caution:</strong>
Currently, all apps have the ability to read the external storage
without a special permission. However, this will change in a future release. If your app needs
to read the external storage (but not write to it), then you will need to declare the {@link
android.Manifest.permission#READ_EXTERNAL_STORAGE} permission. To ensure that your app continues
to work as expected, you should declare this permission now, before the change takes effect.</p>
<pre>
&lt;manifest ...>
&lt;uses-permission android:name=&quot;android.permission.READ_EXTERNAL_STORAGE&quot; /&gt;
...
&lt;/manifest>
</pre>
<p>However, if your app uses the {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE}
permission, then it implicitly has permission to read the external storage as well.</p>
</div>
<p>You dont need any permissions to save files on the internal
storage. Your application always has permission to read and
write files in its internal storage directory.</p>
<h2 id="WriteInternalStorage">Save a File on Internal Storage</h2>
<p>When saving a file to internal storage, you can acquire the appropriate directory as a
{@link java.io.File} by calling one of two methods:</p>
<dl>
<dt>{@link android.content.Context#getFilesDir}</dt>
<dd>Returns a {@link java.io.File} representing an internal directory for your app.</dd>
<dt>{@link android.content.Context#getCacheDir}</dt>
<dd>Returns a {@link java.io.File} representing an internal directory for your app's temporary
cache files. Be sure to delete each file once it is no
longer needed and implement a reasonable size limit for the amount of memory you use at any given
time, such as 1MB. If the system begins running low on storage, it may delete your cache files
without warning.</dd>
</dl>
<p>To create a new file in one of these directories, you can use the {@link
java.io.File#File(File,String) File()} constructor, passing the {@link java.io.File} provided by one
of the above methods that specifies your internal storage directory. For example:</p>
<pre>
File file = new File(context.getFilesDir(), filename);
</pre>
<p>Alternatively, you can call {@link
android.content.Context#openFileOutput openFileOutput()} to get a {@link java.io.FileOutputStream}
that writes to a file in your internal directory. For example, here's
how to write some text to a file:</p>
<pre>
String filename = "myfile";
String string = "Hello world!";
FileOutputStream outputStream;
try {
outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
outputStream.write(string.getBytes());
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
</pre>
<p>Or, if you need to cache some files, you should instead use {@link
java.io.File#createTempFile createTempFile()}. For example, the following method extracts the
file name from a {@link java.net.URL} and creates a file with that name
in your app's internal cache directory:</p>
<pre>
public File getTempFile(Context context, String url) {
File file;
try {
String fileName = Uri.parse(url).getLastPathSegment();
file = File.createTempFile(fileName, null, context.getCacheDir());
catch (IOException e) {
// Error while creating file
}
return file;
}
</pre>
<p class="note"><strong>Note:</strong>
Your app's internal storage directory is specified
by your app's package name in a special location of the Android file system.
Technically, another app can read your internal files if you set
the file mode to be readable. However, the other app would also need to know your app package
name and file names. Other apps cannot browse your internal directories and do not have
read or write access unless you explicitly set the files to be readable or writable. So as long
as you use {@link android.content.Context#MODE_PRIVATE} for your files on the internal storage,
they are never accessible to other apps.</p>
<h2 id="WriteExternalStorage">Save a File on External Storage</h2>
<p>Because the external storage may be unavailable&mdash;such as when the user has mounted the
storage to a PC or has removed the SD card that provides the external storage&mdash;you
should always verify that the volume is available before accessing it. You can query the external
storage state by calling {@link android.os.Environment#getExternalStorageState}. If the returned
state is equal to {@link android.os.Environment#MEDIA_MOUNTED}, then you can read and
write your files. For example, the following methods are useful to determine the storage
availability:</p>
<pre>
/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
</pre>
<p>Although the external storage is modifiable by the user and other apps, there are two
categories of files you might save here:</p>
<dl>
<dt>Public files</dt>
<dd>Files that
should be freely available to other apps and to the user. When the user uninstalls your app,
these files should remain available to the user.
<p>For example, photos captured by your app or other downloaded files.</p>
</dd>
<dt>Private files</dt>
<dd>Files that rightfully belong to your app and should be deleted when the user uninstalls
your app. Although these files are technically accessible by the user and other apps because they
are on the external storage, they are files that realistically don't provide value to the user
outside your app. When the user uninstalls your app, the system deletes
all files in your app's external private directory.
<p>For example, additional resources downloaded by your app or temporary media files.</p>
</dd>
</dl>
<p>If you want to save public files on the external storage, use the
{@link android.os.Environment#getExternalStoragePublicDirectory
getExternalStoragePublicDirectory()} method to get a {@link java.io.File} representing
the appropriate directory on the external storage. The method takes an argument specifying
the type of file you want to save so that they can be logically organized with other public
files, such as {@link android.os.Environment#DIRECTORY_MUSIC} or {@link
android.os.Environment#DIRECTORY_PICTURES}. For example:</p>
<pre>
public File getAlbumStorageDir(String albumName) {
// Get the directory for the user's public pictures directory.
File file = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), albumName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
</pre>
<p>If you want to save files that are private to your app, you can acquire the
appropriate directory by calling {@link
android.content.Context#getExternalFilesDir getExternalFilesDir()} and passing it a name indicating
the type of directory you'd like. Each directory created this way is added to a parent
directory that encapsulates all your app's external storage files, which the system deletes when the
user uninstalls your app.</p>
<p>For example, here's a method you can use to create a directory for an individual photo album:</p>
<pre>
public File getAlbumStorageDir(Context context, String albumName) {
// Get the directory for the app's private pictures directory.
File file = new File(context.getExternalFilesDir(
Environment.DIRECTORY_PICTURES), albumName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
</pre>
<p>If none of the pre-defined sub-directory names suit your files, you can instead call {@link
android.content.Context#getExternalFilesDir getExternalFilesDir()} and pass {@code null}. This
returns the root directory for your app's private directory on the external storage.</p>
<p>Remember that {@link android.content.Context#getExternalFilesDir getExternalFilesDir()}
creates a directory inside a directory that is deleted when the user uninstalls your app.
If the files you're saving should remain available after the user uninstalls your
app&mdash;such as when your app is a camera and the user will want to keep the photos&mdash;you
should instead use {@link android.os.Environment#getExternalStoragePublicDirectory
getExternalStoragePublicDirectory()}.</p>
<p>Regardless of whether you use {@link
android.os.Environment#getExternalStoragePublicDirectory
getExternalStoragePublicDirectory()} for files that are shared or
{@link android.content.Context#getExternalFilesDir
getExternalFilesDir()} for files that are private to your app, it's important that you use
directory names provided by API constants like
{@link android.os.Environment#DIRECTORY_PICTURES}. These directory names ensure
that the files are treated properly by the system. For instance, files saved in {@link
android.os.Environment#DIRECTORY_RINGTONES} are categorized by the system media scanner as ringtones
instead of music.</p>
<h2 id="GetFreeSpace">Query Free Space</h2>
<p>If you know ahead of time how much data you're saving, you can find out
whether sufficient space is available without causing an {@link
java.io.IOException} by calling {@link java.io.File#getFreeSpace} or {@link
java.io.File#getTotalSpace}. These methods provide the current available space and the
total space in the storage volume, respectively. This information is also useful to avoid filling
the storage volume above a certain threshold.</p>
<p>However, the system does not guarantee that you can write as many bytes as are
indicated by {@link java.io.File#getFreeSpace}. If the number returned is a
few MB more than the size of the data you want to save, or if the file system
is less than 90% full, then it's probably safe to proceed.
Otherwise, you probably shouldn't write to storage.</p>
<p class="note"><strong>Note:</strong> You aren't required to check the amount of available space
before you save your file. You can instead try writing the file right away, then
catch an {@link java.io.IOException} if one occurs. You may need to do
this if you don't know exactly how much space you need. For example, if you
change the file's encoding before you save it by converting a PNG image to
JPEG, you won't know the file's size beforehand.</p>
<h2 id="DeleteFile">Delete a File</h2>
<p>You should always delete files that you no longer need. The most straightforward way to delete a
file is to have the opened file reference call {@link java.io.File#delete} on itself.</p>
<pre>
myFile.delete();
</pre>
<p>If the file is saved on internal storage, you can also ask the {@link android.content.Context} to locate and
delete a file by calling {@link android.content.Context#deleteFile deleteFile()}:</p>
<pre>
myContext.deleteFile(fileName);
</pre>
<div class="note">
<p><strong>Note:</strong> When the user uninstalls your app, the Android system deletes
the following:</p>
<ul>
<li>All files you saved on internal storage</li>
<li>All files you saved on external storage using {@link
android.content.Context#getExternalFilesDir getExternalFilesDir()}.</li>
</ul>
<p>However, you should manually delete all cached files created with
{@link android.content.Context#getCacheDir()} on a regular basis and also regularly delete
other files you no longer need.</p>
</div>

View File

@ -0,0 +1,55 @@
page.title=Saving Data
trainingnavtop=true
startpage=true
@jd:body
<div id="tb-wrapper">
<div id="tb">
<h2>Dependencies and prerequisites</h2>
<ul>
<li>Android 1.6 (API Level 4) or higher</li>
<li>Familiarity with Map key-value collections</li>
<li>Familiarity with the Java file I/O API</li>
<li>Familiarity with SQL databases</li>
</ul>
<h2>You should also read</h2>
<ul>
<li><a href="{@docRoot}guide/topics/data/data-storage.html">Storage Options</a></li>
</ul>
</div>
</div>
<p>Most Android apps need to save data, even if only to save information about the app state
during {@link android.app.Activity#onPause onPause()} so the user's progress is not lost. Most
non-trivial apps also need to save user settings, and some apps must manage large
amounts of information in files and databases. This class introduces you to the
principal data storage options in Android, including:</p>
<ul>
<li>Saving key-value pairs of simple data types in a shared preferences
file</li>
<li>Saving arbitrary files in Android's file system</li>
<li>Using databases managed by SQLite</li>
</ul>
<h2>Lessons</h2>
<dl>
<dt><b><a href="shared-preferences.html">Saving Data in Shared Preferences</a></b></dt>
<dd>Learn to use a shared preferences file for storing small amounts of information in
key-value pairs.</dd>
<dt><b><a href="files.html">Saving Data in Files</a></b></dt>
<dd>Learn to save a basic file, such as to store long sequences of data that
are generally read in order.</dd>
<dt><b><a href="databases.html">Saving Data in SQL Databases</a></b></dt>
<dd>Learn to use a SQLite database to read and write structured data.</dd>
</dl>

View File

@ -0,0 +1,121 @@
page.title=Saving Key-Value Sets
parent.title=Data Storage
parent.link=index.html
trainingnavtop=true
@jd:body
<div id="tb-wrapper">
<div id="tb">
<h2>This lesson teaches you to</h2>
<ol>
<li><a href="#GetSharedPreferences">Get a Handle to a SharedPreferences</a></li>
<li><a href="#WriteSharedPreference">Write to Shared Preferences</a></li>
<li><a href="#ReadSharedPreference">Read from Shared Preferences</a></li>
</ol>
<h2>You should also read</h2>
<ul>
<li><a href="{@docRoot}guide/topics/data/data-storage.html#pref">Using Shared Preferences</a></li>
</ul>
</div>
</div>
<p>If you have a relatively small collection of key-values that you'd like to save,
you should use the {@link android.content.SharedPreferences} APIs.
A {@link android.content.SharedPreferences} object points to a file containing
key-value pairs and provides simple methods to read and write them. Each
{@link android.content.SharedPreferences} file is
managed by the framework and can be private or shared.</p>
<p>This class shows you how to use the {@link android.content.SharedPreferences} APIs to store and
retrieve simple values.</p>
<p class="note"><strong>Note:</strong> The {@link android.content.SharedPreferences} APIs are
only for reading and writing key-value pairs and you should not confuse them with the
{@link android.preference.Preference} APIs, which help you build a user interface
for your app settings (although they use {@link android.content.SharedPreferences} as their
implementation to save the app settings). For information about using the {@link
android.preference.Preference} APIs, see the <a href="{@docRoot}guide/topics/ui/settings.html"
>Settings</a> guide.</p>
<h2 id="GetSharedPreferences">Get a Handle to a SharedPreferences</h2>
<p>You can create a new shared preference file or access an existing
one by calling one of two methods:</p>
<ul>
<li>{@link android.content.Context#getSharedPreferences(String,int)
getSharedPreferences()} &mdash; Use this if you need multiple shared preference files identified
by name, which you specify with the first parameter. You can call this from any
{@link android.content.Context} in your app.</li>
<li>{@link android.app.Activity#getPreferences(int) getPreferences()} &mdash; Use this from an
{@link android.app.Activity} if you need
to use only one shared preference file for the activity. Because this retrieves a default shared
preference file that belongs to the activity, you don't need to supply a name.</li>
</ul>
<p>For example, the following code is executed inside a {@link android.app.Fragment}.
It accesses the shared preferences file that's
identified by the resource string {@code R.string.preference_file_key} and opens it using
the private mode so the file is accessible by only your app.</p>
<pre>
Context context = getActivity();
SharedPreferences sharedPref = context.getSharedPreferences(
getString(R.string.preference_file_key), Context.MODE_PRIVATE);
</pre>
<p>When naming your shared preference files, you should use a name that's uniquely identifiable
to your app, such as {@code "com.example.myapp.PREFERENCE_FILE_KEY"}</p>
<p>Alternatively, if you need just one shared preference file for your activity, you can use the
{@link android.app.Activity#getPreferences(int) getPreferences()} method:</p>
<pre>
SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
</pre>
<p class="caution"><strong>Caution:</strong> If you create a shared preferences file
with {@link android.content.Context#MODE_WORLD_READABLE} or {@link
android.content.Context#MODE_WORLD_WRITEABLE}, then any other apps that know the file identifier
can access your data.</p>
<h2 id="WriteSharedPreference">Write to Shared Preferences</h2>
<p>To write to a shared preferences file, create a {@link
android.content.SharedPreferences.Editor} by calling {@link
android.content.SharedPreferences#edit} on your {@link android.content.SharedPreferences}.</p>
<p>Pass the keys and values you want to write with methods such as {@link
android.content.SharedPreferences.Editor#putInt putInt()} and {@link
android.content.SharedPreferences.Editor#putString putString()}. Then call {@link
android.content.SharedPreferences.Editor#commit} to save the changes. For example:</p>
<pre>
SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(getString(R.string.saved_high_score), newHighScore);
editor.commit();
</pre>
<h2 id="ReadSharedPreference">Read from Shared Preferences</h2>
<p>To retrieve values from a shared preferences file, call methods such as {@link
android.content.SharedPreferences#getInt getInt()} and {@link
android.content.SharedPreferences#getString getString()}, providing the key for the value
you want, and optionally a default value to return if the key isn't
present. For example:</p>
<pre>
SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
long default = getResources().getInteger(R.string.saved_high_score_default));
long highScore = sharedPref.getInt(getString(R.string.saved_high_score), default);
</pre>

View File

@ -103,6 +103,26 @@
</ul> </ul>
</li> </li>
<li class="nav-section">
<div class="nav-section-header"><a href="<?cs var:toroot?>training/basics/data-storage/index.html">
<span class="en">Saving Data</span>
</a></div>
<ul>
<li><a href="<?cs var:toroot ?>training/basics/data-storage/shared-preferences.html">
<span class="en">Saving Key-Value Sets</span>
</a>
</li>
<li><a href="<?cs var:toroot ?>training/basics/data-storage/files.html">
<span class="en">Saving Files</span>
</a>
</li>
<li><a href="<?cs var:toroot ?>training/basics/data-storage/databases.html">
<span class="en">Saving Data in SQL Databases</span>
</a>
</li>
</ul>
</li>
<li class="nav-section"> <li class="nav-section">
<div class="nav-section-header"><a href="<?cs var:toroot ?>training/basics/intents/index.html"> <div class="nav-section-header"><a href="<?cs var:toroot ?>training/basics/intents/index.html">
<span class="en">Interacting with Other Apps</span> <span class="en">Interacting with Other Apps</span>