本文来自对Android Training的总结。
SharedPreferences对象
SharedPreferences通过键值集合的方式存储数据,通常用来存储应用程序的偏好设置(设置信息)。SharedPreferences 对象指向包含键值对的文件并提供读写这些文件的简单方法。每个 SharedPreferences 文件由框架进行管理并且可以专用或共享。
注:SharedPreferences API 仅用于读写键值对,注意不能将其与 Preference API 混淆,后者用于应用设置构建用户界面(尽管它们使用 SharedPreferences 作为其实现以保存应用设置)。
读取
getSharedPreferences()
: 如果有多个首选项文件,第一个参数需传入指定的文件名称,第二个参数为文件标识符。Context.MODE_PRIVATE
、MODE_WORLD_READABLE
或MODE_WORLD_WRITEABLE
。getPreferences()
:如只需使用 Activity 的一个共享首选项,那用该方法。 因为此方法会检索属于该 Activity 的默认共享首选项文件,无需提供名称。- 调用Context对象的getSharedPreferences()方法获得的SharedPreferences对象可以被同一应用程序下的其他组件共享.
调用Activity对象的getPreferences()方法获得的SharedPreferences对象只能在该Activity中使用.
//首先获取到SharedPreferences的实例
Context context = getActivity();
SharedPreferences sharedPref = context.getSharedPreferences(
getString(R.string.preference_file_key), Context.MODE_PRIVATE);
/*命名共享首选项文件时,应使用对于应用而言唯一可识别的名称,比如 "com.example.myapp.PREFERENCE_FILE_KEY"
如果只需 Activity 的一个共享首选项文件,可以使用 getPreferences() 方法:SharedPreferences sharedPref =getActivity().getPreferences(Context.MODE_PRIVATE);*/
//读取值
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);
写入
SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(getString(R.string.saved_high_score), newHighScore);
editor.commit();
文件存储
所有 Android 设备都有两个文件存储区域:“内部”和“外部”存储。这些名称在 Android 早期产生,当时大多数设备都提供内置的非易失性内存(内部存储),以及移动存储介质,比如微型 SD 卡(外部存储)。一些设备将永久性存储空间划分为“内部”和“外部”分区,即便没有移动存储介质,也始终有两个存储空间,并且无论外部存储设备是否可移动,API 的行为均一致。以下列表汇总了关于各个存储空间的实际信息。
内部存储 | 外部存储 |
---|---|
它始终可用。 | 它并非始终可用,因为用户可采用 USB 存储设备的形式装载外部存储,并在某些情况下会从设备中将其移除。 |
只有你的应用可以访问此处保存的文件。 | 它是全局可读的,因此此处保存的文件可能不受控制地被读取。 |
当用户卸载应用时,系统会从内部存储中移除应用的所有文件。 | 当用户卸载您的应用时,只有在您通过 getExternalFilesDir() 将你的应用的文件保存在目录中时,系统才会从此处移除您的应用的文件。 |
当你希望确保用户或其他应用均无法访问你的文件时,内部存储是最佳选择。 | 对于无需访问限制以及你希望与其他应用共享或允许用户使用计算机访问的文件,外部存储是最佳位置。 |
尽管应用默认安装在内部存储中,但你可在你的清单文件中指定 android:installLocation
属性,这样你的应用便可安装在在外部存储中。当 APK
非常大且它们的外部存储空间大于内部存储时,用户更青睐这个选择。
关于权限
要向外部存储写入信息,必须在清单文件中请求 WRITE_EXTERNAL_STORAGE 权限。
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
...
</manifest>
注意:
- 目前,所有应用都可以读取外部存储,而无需特别的权限。 但这在将来版本中会进行更改。如果应用需要读取外部存储(但不向其写入信息),那么需要声明 READ_EXTERNAL_STORAGE 权限。要确保应用继续正常工作,应在更改生效前声明此权限。
- 如果你的应用使用 WRITE_EXTERNAL_STORAGE 权限,那么它也隐含读取外部存储的权限。
- 无需任何权限,即可在内部存储中保存文件。 你的应用始终具有在其内部存储目录中进行读写的权限。
内部存储
在内部存储中保存文件时,您可以通过调用以下两种方法之一获取作为 File 的相应目录:
1. getFilesDir()
返回表示您的应用的内部目录的 File 。
2. getCacheDir()
返回表示您的应用临时缓存文件的内部目录的 File。 务必删除所有不再需要的文件并对在指定时间您使用的内存量实现合理大小限制,比如,1MB。 如果在系统即将耗尽存储,它会在不进行警告的情况下删除您的缓存文件。
然后通过调用标准的JavaIO的API进行操作。如:
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();
}
如果需要缓存文件,应改用 createTempFile()
。例如,以下方法从 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;
}
注:应用的内部存储目录由应用在 Android 文件系统特定位置中的软件包名称指定。从技术上讲,如果您将文件模
式设置为可读,那么,另一应用也可以读取您的内部文件。
但是,此应用也需要知道您的应用的软件包名称和文件名。
其他应用无法浏览您的内部目录并且没有读写权限,除非您明确将文件设置为可读或可写。
只要您为内部存储上的文件使用 MODE_PRIVATE,其他应用便从不会访问它们。
外部存储
由于外部存储可能不可用—比如,当用户已将存储装载到电脑或已移除提供外部存储的 SD 卡时。因此,在访问它之前,应该进行状态判断。 可以通过调用 getExternalStorageState()
查询外部存储状态。 如果返回的状态为 MEDIA_MOUNTED
,那么您可以对您的文件进行读写。 例如,以下方法对于确定存储可用性非常有用:
//检查外部存储是否可读写
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
//检查外部存储是否可读
public boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
尽管外部存储可被用户和其他应用进行修改,但在此处可保存两类文件:
公共文件:应供其他应用和用户自由使用的文件。 当用户卸载您的应用时,用户应仍可以使用这些文件。
例如,您的应用拍摄的照片或其他已下载的文件。如Music、Pictures等目录(通常位于根目录)。File Environment.getExternalStoragePublicDirectory (String type)
获取外部存储的共有文件。得到一个顶级共享/外部存储目录将特定类型的文件。这就是用户通常会放置和管理自己的文件,所以在这里进行操作需要小心。- 与多个用户设备上(如被UserManager),每个用户有自己的独立的共享存储。应用程序只能访问正在运行的用户共享存储。
- type有:
DIRECTORY_MUSIC
,DIRECTORY_PICTURES
,DIRECTORY_MOVIES
,DIRECTORY_DOWNLOADS
,DIRECTORY_DCIM
等。
私有文件:属于您的应用且在用户卸载您的应用时应予删除的文件。 尽管这些文件在技术上可被用户和其他应用访问(因为它们存储在外部存储中), 但它们实际上不向您的应用之外的用户提供任何输出值。 当用户卸载您的应用时,系统会删除应用外部私有目录中的所有文件。
getExternalFilesDir(String type)
返回程序在外部存储中的私有目录。通常路径为:SDCard/Android/data/packageName/files。在私有目录中依然可以指定特定的类型目录,type
值同上。getExternalCacheDir()
返回程序在外部存储中的私有缓存目录。通常路径为:SDCard/Android/data/packageName/cache。
关于各API获取的路径,调试信息如下(7.1版本):
查询可用空间
long getFreeSpace ()
返回分区中未分配的字节数(剩余容量大小)。
long getTotalSpace ()
返回分区的总字节数(容量大小)
通常在保持文件之前,无需检查可用空间量。 可以尝试立刻写入文件,然后在 IOException 出现时将其捕获。
如果您不知道所需的确切空间量,你可能需要这样做。
删除文件
- 调用File的方法。
myFile.delete();
- 调用Context的方法。
myContext.deleteFile(fileName);
注:当用户卸载应用时,Android 系统会删除以下各项:
- 保存在内部存储中的所有文件
- 外部存储中的私有文件。(使用 getExternalFilesDir() 保存在外部存储中的私有文件)
而且,应该定期删除使用 getCacheDir() 创建的所有缓存文件并且和不再需要的其他文件。
使用SQLite数据库
SQLite是Android系统内置的轻量级关系型数据库,运算速度快、占用内存小。SQLite支持标准的SQL语法,还遵循了数据库的ACID(原子性Atomicity、一致性Consistency、隔离性Isolation、持久性Durability)事务。
数据文件存储在内置存储中(data/data/<packagename>databases下),因此数据是安全的,因为在默认情况下,其他应用无法访问此区域。
BaseColumns接口
这个接口提供了自动增长的ID
和COUNT
这两个字段,不是必须的操作。用于规范格式。
创建数据库
为了更方便地创建、管理、升级数据库,Android提供了一个帮助类SQLiteOpenHelper
。使用这个类可以很方便地创建和升级数据库。
创建一个继承SQLiteOpenHelper的类
public class FeedReaderDbHelper extends SQLiteOpenHelper { 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); } }
创建数据库
获取Helper的实例,调用
getReadableDatabase()
或getWritableDatabase()
便可以创建或打开一个数据库。(数据库已存在就打开,否则创建)当数据库不可写入的时候(如磁盘空间已满),
getWritableDatabase()
方法返回的对象将以只读的方式打开数据库,而getReadableDatabase()
将会产生异常。FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext()); // 以可写模式读取数据库 SQLiteDatabase db = mDbHelper.getWritableDatabase();
插入数据
// 创建一个ContentValue对象将插入的数据作为键值对存入,该对象有系列重载的put方法 ContentValues values = new ContentValues(); //第一个参数为key(字段名),第二个为插入的值 values.put("name", "liu"); // 插入一条记录,并返回该记录id,第一个参数为表名,第二个为nullColumnHacki,第三个为ContentValues对象。 long newRowId = db.insert(FeedEntry.TABLE_NAME, null, values);
查询数据
使用 query() 方法进行查询,以游标的方式(Cursor)返回。
query()方法参数 对应SQL部分 描述 table from table_name 指定查询的表名 columns select column1,column2 指定查询的列名 selection where column=value 指定where的约束条件 selectionArgs - 为where中的占位符提供具体的值 groupBy group by column 指定需要group by的列 having having column=value 对group by后的结果进一步约束 orderBy order by column1, column2 指定查询结果的排序方式 query()方法:
Cursor query (boolean distinct,
String table,
String[] columns,
String selection,
String[] selectionArgs,
String groupBy,
String having,
String orderBy,
String limit)删除数据
int delete (String table, String whereClause, String[] whereArgs)
- table:表名
- whereClause:条件,为null时将删除所有记录
- 为whereClause中的占位符提供具体的值
例:
db.delete("book", "page > ?", new String[]{"500"})
删除page字段大于500的所有记录。更新数据
int update (String table,
ContentValues values,
String whereClause,
String[] whereArgs)
此处不再累述。使用标准SQL语句操作数据库
上面的方法都是通过使用Android提供的API对数据库进行操作,我们也可以使用标准的SQL语句操作数据库。
例:
//同样值需要使用占位符 db.execSQL("insert into Book(name, author, page, price) values(?, ?, ?, ?)", new String[]{"Thinking in Java", "Bruce", "500", "213"});
使用事务
事务的特性可以保证让一系列操作要么全部完成,要么一个都不能完成。
同时,利用事务批量提交语句,可以提高性能和效率。例:
myDB.beginTransaction(); //需要批量执行的操作 for (int i = 0; i< 2000;i++){ myDB.execSQL("insert into meetings (id , mainid) values ( '1','1')"); } myDB.setTransactionSuccessful(); myDB.endTransaction();