第四章 保存数据
许多app程序都要保存数据,即使是app处onPause状态也需要。
很多实用的app会保存用户的设置,以及其它的大量信息,有些还保存在数据库中。
这章介绍以下几个内容
保存键值对在shared preferences文件中
在android系统中保存任意的文件
使用SQLite进行保存数据
1. 使用SharedPreferences类保存小数据
一个SharePreferences对象含有一个键值对,提供简单的方法进行读写操作。
getSharedPreferences()
getPreferences()
函数获取相关的share实例
下面这个例子在一个fragment中执行,
通过R.string.preference_file_key获取一个一个sharedPref实例,这个实例采用MODE_PRIVATE方式,表示只有这个程序才能使用这个文件。
Context context = getActivity();
SharedPreferences sharedPref = context.getSharedPreferences(
getString(R.string.preference_file_key), Context.MODE_PRIVATE);
2. 往Shared Preferences中写入数据
需要调用Sharedpreferences.Editor函数
下面这里例子把newHighScore放进去
SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(getString(R.string.saved_high_score), newHighScore);
editor.commit();
3. 从SharedPreferences中读取数值
SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
int defaultValue = getResources().getInteger(R.string.saved_high_score_default);
long highScore = sharedPref.getInt(getString(R.string.saved_high_score), defaultValue);
第五章 保存文件
Android系统的文件系统和其它平台的系统基本上是一样的,主要通过File类完成。
这个类适用于一些比较大的文件,比如网络上交互的图片,以及保存其它比较大的文件。
1. 选择内或者外存储
内存储和外存储来源于android早期的时候,那时候有一个内置的存储器还有一个可移动的sd卡,后来有些手机没有sd卡,但是现在这个习惯还是保留下来。
内存储和外存储有些简单区别,用户可以自己去查资料。
如果需要写到外存储中,需要在manifest中申明以下的权限。
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
</manifest>
现在基本上所有的app都默认有读权限,在未来的版本中需要申明
READ_EXTERNAL_STORAGE
权限,所以现在也最好把它加上去。
2. 保存文件在内存储中
getFileDir()
getCacheDir();
这两个函数返回内置的文件目录,西药及时清楚不需要的文件,因为系统一旦处于低内存的时候,会可能删掉相关的数据。
可以通过以下的方法创建一个文件:
File file = new File(context.getFilesDir(), filename);
可以通过openFileOutput()函数得到一个文件输出流,然后写入相关的文件。
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();
}
如果你想通过Url获取的图片想要暂时保存,可以用下面的代码片段;
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;
}
3. 保存文件在外存储中
因为外存储可能不存在,所以你在使用之前需要确认它外存储是否能获取。通过调用以下函数确认
如果获取的状态是MEDIA_MOUTED,你就可以在外存储中进行文件的读取和写入操作。
比如以下的代码片段就是一个包裹函数,检出外存储的读写状态
/* 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;
}
可以保存为两种类型的文件,一种是public file其它app也可以获取到文件,比如相机拍的照片。
一种是private file,只有保存的app才能读取,在app卸载的时候相关的文件就会删除。
如果你想在外存储中得到public file
可以通过以下函数调用获取
getExternalStoragePublicDirectory()
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;
}
如果你想获取一个只有本身app能读取的文件的话,可以通过下面这个函数函数调用
getExternalFilesDir()
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;
}
指定文件的类型很重要,这可以让系统根据类型而把public file放到不同的文件目录中,比如歌曲会放到music文件夹。
4. 询问可用空间
需要提前知道保存文件的空间是否足够,可以提前调用getFreeSpace()或者getTotalSpace()函数,这些函数会返回当前系统的可用空间,这些信息也可以用来设置一个使用阈值,如果不够用的话会返回一个IOException的异常。
在保存文件的时候最好调用这个函数,然后一旦出现的话就可以捕捉IOException.比如你在将png文件转化为jpeg文件格式的时候,最好提前知道文件空间的大小。
5. 在你不需要文件的时候,你应该调用delete函数方法删除文件,最简单的方式
myFile.delete();
如果文件在内存储中,你可以通过Context获取文件的位置,然后调用deleteFile()函数。
myContext.deleteFile(fileName);
当卸载程序的时候,系统会删除所有的内置存储文件,以及所有通过getExternalFilesDir()获取的文件路径。
第六章 保存到SQL数据库中
最理想的保存结构体数据的方式保存到结构体中,比如联系人信息。这篇文章假设你已经熟悉SQL数据,android中表示数据库信息的在android.database.sqlite包中。
1. 定义一个数据库
首先是数据库的头,它定义了数据库怎么存储的结构。
你可能需要创建一个类似于Contact的类来表示所有的信息。
一个很好的定义方式就是把Contact类至于整个数据库的根部,这样所有的其它的类都可以访问。
内部类继承了一个_ID可以在cursor adaptors的时候使用,但是它不是不是必须的。
比如下面就定义了一个表格
public final class FeedReaderContract {
// To prevent someone from accidentally instantiating the contract class,
// make the constructor private.
private FeedReaderContract() {}
/* Inner class that defines the table contents */
public static class FeedEntry implements BaseColumns {
public static final String TABLE_NAME = "entry";
public static final String COLUMN_NAME_TITLE = "title";
public static final String COLUMN_NAME_SUBTITLE = "subtitle";
}
}
2. 使用SQL Helper创建一个数据库
一旦你定义你的数据的存储结构,就可以通过实现某些函数进行创建和维护数据库,这里有些常用的创建和删除表格的语句。
private static final String SQL_CREATE_ENTRIES =
"CREATE TABLE " + FeedEntry.TABLE_NAME + " (" +
FeedEntry._ID + " INTEGER PRIMARY KEY," +
FeedEntry.COLUMN_NAME_TITLE + " TEXT," +
FeedEntry.COLUMN_NAME_SUBTITLE + " TEXT)";
private static final String SQL_DELETE_ENTRIES =
"DROP TABLE IF EXISTS " + FeedEntry.TABLE_NAME;
android会将数据存储在私有的磁盘中,你的数据是安全的,因为这部分其它的程序不能获取。
SQLiteQpenHelper有很多有用的函数,当你使用这个类获取数据库的引用的时候,系统其实做了很多工作去创建,当有需要的时候,系统也会去更新数据库,所有的操作你只需要调用
getWritableDatabase() 和getReadableDatabase()函数。
因为数据库是长期运行的,需要确保在后台进程中也能够通过getWritableDatabase()和getReadableDatabase()访问。
为了使用SQLiteOpenHelper类,需要创建一个它的子类,这个类需要重写onCreate(), onUpgrade() 和onOpen()回调函数。如果需要你还可以重写onDowngrade()函数。
比如下面的例子:
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 = "FeedReader.db";
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);
}
}
为了获取数据库,下一步是实例话这个子类
FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext());
4. 往数据库中放数据
主要是使用ContentValues的insert方法实现
// 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(FeedEntry.COLUMN_NAME_TITLE, title);
values.put(FeedEntry.COLUMN_NAME_SUBTITLE, subtitle);
// Insert the new row, returning the primary key value of the new row
long newRowId = db.insert(FeedEntry.TABLE_NAME, null, values);
insert的第一参数是表格的名称。
第二个参数告诉数据库在ContentValues为空的时候需要怎么做,如果你指定了一行的名字,这个数据库就会在里面插入一列。
5. 从数据库中读取信息
为了从数据库中读取数据,这里需要使用query方法,传递你想要的数据的信息给这个函数,这个函数最终的结构会返回一个Cursor类对象。
SQLiteDatabase db = mDbHelper.getReadableDatabase();
// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
FeedEntry._ID,
FeedEntry.COLUMN_NAME_TITLE,
FeedEntry.COLUMN_NAME_SUBTITLE
};
// Filter results WHERE "title" = 'My Title'
String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?";
String[] selectionArgs = { "My Title" };
// How you want the results sorted in the resulting Cursor
String sortOrder =
FeedEntry.COLUMN_NAME_SUBTITLE + " DESC";
Cursor cursor = db.query(
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
);
为了查看一行的信息,可以调用moveToNext函数,因为cursor的起始位置总是-1,所以开始阅读的时候就是调用这个函数,直到返回最后一个数据。
对于每一行,都可以调用Cursor的getString和getLong()函数。对于每个get函数,都需要传递列的位置信息,通过调用getColumnIndex方法,或者getColumnIndexOrThrow方法。
当遍历完结果的时候,需要调用close方法释放资源。
List itemIds = new ArrayList<>();
while(cursor.moveToNext()) {
long itemId = cursor.getLong(
cursor.getColumnIndexOrThrow(FeedEntry._ID));
itemIds.add(itemId);
}
cursor.close();
6. 从数据库中删除信息
为了删除表格中的一行,需要提供有效的信息确认这一行在哪里。数据库接口提供了一个机制。这个机制把数据库的条目和选择的参数分开。条目定义了需要查阅的列,同时允许你查看行。如下示例
// Define 'where' part of query.
String selection = FeedEntry.COLUMN_NAME_TITLE + " LIKE ?";
// Specify arguments in placeholder order.
String[] selectionArgs = { "MyTitle" };
// Issue SQL statement.
db.delete(FeedEntry.TABLE_NAME, selection, selectionArgs);
7. 更新数据库
当你改变数据库的值的时候,需要调用update方法更新数据库。
update结合了insert函数很delete函数。
SQLiteDatabase db = mDbHelper.getWritableDatabase();
// New value for one column
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_TITLE, title);
// Which row to update, based on the title
String selection = FeedEntry.COLUMN_NAME_TITLE + " LIKE ?";
String[] selectionArgs = { "MyTitle" };
int count = db.update(
FeedReaderDbHelper.FeedEntry.TABLE_NAME,
values,
selection,
selectionArgs);
8. 断开与数据库的连接
因为getWritableDataBase 和getReadableDataBase对耗时会比较长,你需要断开数据库的连接一旦你不需要访问它的时候,通常的数据断开的写在调用Activity的destroy方法中。
@Override
protected void onDestroy() {
mDbHelper.close();
super.onDestroy();
}