Android系统提供4种基本的数据存储方式,分别是SharedPreferences存储方式,文件存储方式,SQLite数据库存储方式和ContentProvider存储方式。
-
SharedPreferences:一个轻量级数据存储类,适用于保存软件配置参数。使用SharedPreferences保存数据,最终使用xml格式文件存放数据,文件存放在/data/data/<package name>/shared_prefs目录下。
-
SQLite:一个轻量级数据库,支持基本SQL语法,是常被常用的一种数据存储方式。Android为此数据库提供了一个名为SQLiteDatabase的类,封装了一些操作数据库的API。
-
File:通用的文件(I/O)存储方法,常用于存储大数量的数据,缺点是更新数据比较麻烦。
-
ContentProvider:Android系统中所有应用实现数据共享的一种数据存储方式,由于数据通常在各应用间的是相对私密的,为了数据安全,应用相互之间不能直接实现共享,特别是音频,视频,图片或者通信录,一般采用这种方式存储。每个ContentProvider都会对外提供一个公共的URI(包装为URI对象),如果应用程序有数据需要共享,就需要使用ContentProvider为这些数据定义一个URI,然后其他应用程序就可以通过ContentProvider传入的这个URI来对数据进行操作。
SharedPreferences的使用
将数据存储到SharedPreferences中:
a. 要想使用SharedPreferences存储数据,首先需要获取到SharedPreferences对象:
- Context类中的getSharedPreferences方法:
/**
* 参数一用于指定SharedPreferences文件的名称
* 参数二指定操作,MODE_APPEND为默认操作,其余操作已经在Android6之后废弃
*/
SharedPreferences sharedPreferences = getSharedPreferences("data",MODE_APPEND);
- Activity类的getPreferences方法:
//传入一个默认的指定操作
//这里不需要指定文件名,因为它默认将当前活动的类名作为文件名
SharedPreferences sharedPreferences = getPreferences(MODE_APPEND);
- PreferenceManager类中的getDefaultSharedPreferences方法:
//这是一个静态方法,它接收一个Context参数,并自动使用当前应用程序的包名作为文件名
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
建议:如果文件为整个应用程序所共用,则使用getSharedPreferences方法或getDefaultSharedPreferences方法,若文件只是针对某个活动使用,则可以使用getPreferences方法。
b. 获取到SharedPreferences对象后,再通过下面三步实现:
//获取Editor对象
SharedPreferences.Editor editor = sharedPreferences.edit();
//通过Editor对象添加数据
editor.putString("name","Tom");
editor.putInt("age",18);
editor.putBoolean("married",false);
//通过Editor提交数据
editor.apply();
读取SharedPreferences中的数据
//获取SharedPreferences对象
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
//通过get方法和key获取到对应的value
String name = sharedPreferences.getString("name","");
int age = sharedPreferences.getInt("age",0);
boolean isMarried = sharedPreferences.getBoolean("married",false);
SharedPreferences的优化
SharedPreferences实际上是对一个XML文件存储key-value键值对,每一次的commit和apply操作都是一次I/O写操作。我们知道I/O操作时最慢的操作之一,在主线程中操作会导致主线程缓慢。SharedPreferences性能优化主要是两个方面:
- IO性能
- 同步锁问题
IO性能:SharedPreferences上的IO分别为读取数据到内存和数据写入磁盘
- 当SharedPreferences文件还没被加载到内存时,调用SharedPreferences方法会初始化文件并读入内存,这容易导致耗时更长。
- Editor的commit或apply方法每次执行时,写入磁盘时会耗时。
建议:
- 在application初始化时,声明SharedPreferences为全局变量并对其进行初始化操作。
- 对于提交数据时,若不需要返回结果,则使用apply方法,apply为异步写入,commit为同步写入,因此apply方法可以提高性能。
同步锁:SharedPreferences类中的commitToMemory方法会锁定SharedPreference对象,put和getEditor方法会锁定Editor对象,在写入磁盘时会锁定一个写入锁。
建议:最好的办法是避免频繁读写SharedPreferences,减少无谓的调用。对于SharedPreferences的批量操作,最好先获取一个Editor,进行批量操作,然后调用apply方法。
文件存储
文件存储是Android中最基本的一种数据存储方式,它不对存储的内容进行任何形式的格式化处理,所有数据都是原封不动地保存到文件中,因此它比较适合用于存储一些简单的文本数据或二进制数据。
将数据存储到文件中
Context类提供了一个openFileOutput方法,可以用于将数据存储到指定的文件中。该方法接收两个参数,第一个参数为文件名,注意这里指定的文件是不包含路径的,因为所有的文件都默认存储到/data/data/<packagename>/files/目录下。第二个参数为文件的操作模式:MODE_PRIVATE和MODE_APPEND。MODE_PRIVATE表示如果该文件已经存在,则所写入的内容会覆盖原文件的内容;MODE_APPEND表示如果该文件存在,则将内容追加到文件末尾。
public void save(String inputText){
FileOutputStream fos = null;
BufferedWriter writer = null;
try{
fos = openFileOutput("data",MODE_PRIVATE);
writer = new BufferedWriter(new OutputStreamWriter(fos));
writer.write(inputText);
} catch (IOException e){
e.printStackTrace();
} finally {
if (writer != null){
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
如上,我们通过BufferedWritter来写入数据,但要注意的一点是在最后需要关闭写入流。
读取文件数据
Context类提供了一个openFileInput方法用来从文件中读取数据。它只接收一个参数,即要读取的文件名称。然后系统会从默认路径/data/data/<packagename>/files/下去查找,若查找成功则加载该文件。
public void load(){
FileInputStream fis = null;
BufferedReader reader = null;
StringBuilder content = new StringBuilder();
try{
fis = openFileInput("data");
reader = new BufferedReader(new InputStreamReader(fis));
String line = "";
while ((line = reader.readLine()) != null)
content.append(line);
}catch (IOException e){
e.printStackTrace();
} finally {
if (reader != null){
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
如上,我们通过BufferedReader来实现读取,同样在最后需要关闭文件流。
存储方式:内部存储和外部存储
内部存储:
- 总是可用的
- 内部存储空间的文件默认只有你的App可以访问
- 当用户卸载了你的App,系统从内部存储空间中移除所有你的App相关的文件。
建议:当你希望用户和其他App都不能访问你的文件时,内部存储是最好的选择
外部存储
- 并非总是可用的,比如外部存储空间连接到PC或SD卡被移除时,所以最好在访问它的时候检查它的可用性。
- 外部存储是公用的,因此存储在这里的文件可以被其他应用程序访问。
- 当用户卸载你的App时,系统仅仅会移除存储在通过getExternalFilesDir()获取到的路径中的该App相关的文件。
- 使用前,需要在Manifest中获取到其相关权限。
建议:当你的文件不需要访问限制,或者你想把文件分享给其他的App,或者允许用户通过电脑来访问它,那么外部存储是最好的选择。
保存文件到内部存储空间
File file = new File(getFilesDir(),"data");
File file1 = new File(getCacheDir(),"data");
FileOutputStream fos = openFileOutput("data",MODE_PRIVATE);
如上,有三种存储方式:
- getFilesDir获取到一个合适的目录作为File的目录。
- getCacheDir返回一个代表内部临时缓存文件目录的File对象。需要对该文件目录设置一个合理的大小。如果系统存储空间不足时,可能会在没有警告的情况下删除缓存的文件。
保存文件到外部存储空间
- 检查是否可读写:
//Check if external storage is available for read and write
public boolean isExternalStorage(){
return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
}
//Check if external storage is available for read
public boolean isExternalStorageReadable(){
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state)
|| Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
}
- 获取到外部存储空间文件:
public File getPublicStorageFile(String fileName){
File file = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), fileName);
if (!file.mkdirs()){
Log.e(LOG_TAG,"Directory not created");
}
return file;
}
public File getPrivateStorageFile(String fileName){
File file = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES),fileName);
if (!file.mkdirs()){
Log.e(LOG_TAG,"Directory not created");
}
return file;
}
如上,getExternalStoragePublicDirectory方法获取一个代表外部存储合适目录的File对象,用于保存公有文件。而getExternalFilesDir所创建的文件目录将被添加到封装了该App所有外部存储文件的目录下,并且会在用户卸载App时被系统删除。
SQLite数据库存储
SQLite数据库是一款轻量级的关系型数据库,它的运算速度非常快,占用资源少,通常只需要几百kb即可,因而特别适用于移动设备中。
SQLite的使用
- 创建数据库:通过在onCreate方法中执行数据库创建操作
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "create table Book(" + "id integer primary key autoincrement,"
+ "author text," + "price real," + "pages integer," + "name text)";
private Context mContext;
public MyDatabaseHelper(Context context, String name,
SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
使用:
public class MainActivity extends AppCompatActivity {
private MyDatabaseHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dbHelper = new MyDatabaseHelper(this,"BookStore.db",null,1);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dbHelper.getWritableDatabase();
}
});
}
}
- 升级数据库:通过增加版本号,来调用onUpgrade方法来实现升级。
a. 实现为表Book添加一个出版社字段:
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < 2){
db.execSQL("alter table Book add column press text");
}
}
b. 实现增加表Category来记录图书的分类:
public static final String CREATE_CATEGORY = "create table Category("
+ "id integer primary key autoincrement," + "category_name text,"
+ "category_code integer)";
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < 2){
db.execSQL(CREATE_CATEGORY);
}
}
c. 实现删除表中的某个字段:例如删除表Book中的pages字段(通过建立一个临时表来传递数据,以保留原来的数据)
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < 2){
db.beginTransaction();
try{
db.execSQL("create temporary table tmp_book(id,author,price,name);");
db.execSQL("insert into tmp_book select id,author,price,name from Book;");
db.execSQL("drop table Book;");
db.execSQL("create table Book(id integer primary key autoincrement,author text," +
"price real, name text);");
db.execSQL("insert into Book select * from tmp_book;");
db.execSQL("drop table tmp_book;");
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
}
d. 调用方法:升级版本号即可。
dbHelper = new MyDatabaseHelper(this,"BookStore.db",null,2);
- 插入数据:使用ContentValues+insert方法或使用execSQL方法
SQLiteDatabase db = dbHelper.getWritableDatabase();
try{
db.beginTransaction();
ContentValues values = new ContentValues();
values.put("name","The Da Vinci Code");
values.put("author","Dan Brown");
values.put("pages",454);
values.put("prices",16.96);
db.insert("Book",null,values);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
try{
db.beginTransaction();
db.execSQL("insert into Book(name,author,pages,prices) values(" + "'The Da Vinci Code',"
+ "'Dan Brown',454,16.96);");
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
- 更新数据:例如我们将价格修改为11.99
SQLiteDatabase db = dbHelper.getWritableDatabase();
try{
db.beginTransaction();
ContentValues values = new ContentValues();
values.put("prices",11.99);
//参数一为表名
//参数二为更新的数据
//参数三为判断的条件
//参数四为所需加入的条件
db.update("Book",values,"name = ?",new String[]{"The Da Vinci Code"});
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
try{
db.beginTransaction();
db.execSQL("update Book set prices = 11.99 where name = 'The Da Vinci Code';");
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
- 删除数据:
SQLiteDatabase db = dbHelper.getWritableDatabase();
try{
db.beginTransaction();
db.delete("Book","pages > ?",new String[]{"500"});
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
try{
db.beginTransaction();
db.execSQL("delete from Book where pages > 500;");
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
- 查询数据:
SQLiteDatabase db = dbHelper.getWritableDatabase();
Cursor cursor = null;
try{
db.beginTransaction();
//参数一:table(from table_name) 指定查询的表名
//参数二:column (select from column1,column2) 指定查询的列名
//参数三:selection (where column = value) 指定where的约束条件
//参数四:selectionArgs 为where中的占位符提供具体的值
//参数五:groupBy (group by column) 指定需要group by的列
//参数六:having (having column = value) 对gourp by 后的结果进一步约束
//参数七:orderBy (order by column1,column2) 指定查询结果的排序方式
cursor = db.query("Book",new String[]{"name","prices"},"pages > ?",
new String[]{"500"},"author","'Dan Brown'","id");
if (cursor.moveToFirst()){
do {
String name = cursor.getString(cursor.getColumnIndex("name"));
String prices = cursor.getString(cursor.getColumnIndex("prices"));
Log.d("tag",name);
Log.d("tag",prices);
}while (cursor.moveToNext());
}
db.setTransactionSuccessful();
} finally {
if (cursor != null) {
cursor.close();
}
db.endTransaction();
}
try{
db.beginTransaction();
db.execSQL("select name,prices from Book where pages > 500 group by author having author = 'Dan Brown' orderBy id;");
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
数据库的优化
- 我们通过一个简单联系人管理模块来实现数据库的优化,如下为各个类的功能:
ContactInfo类:描述联系人的信息
public class ContactInfo {
private long contactId = -1;
private String contactName, contactNum;
public ContactInfo(long contactId, String contactName, String contactNum) {
this.contactId = contactId;
this.contactName = contactName;
this.contactNum = contactNum;
}
}
BaseTable类:所有表的基类
public abstract class BaseTable {
private Context mContext;
public BaseTable(Context context){
mContext = context;
}
public Context getContext(){
return mContext;
}
public void setContext(Context context){
mContext = context;
}
public ContentResolver getContentResolver(){
return (mContext == null) ? null : mContext.getContentResolver();
}
public SQLiteDatabase getSqliteDB(){
return DBManager.getWriteDB(mContext.getApplicationContext());
}
public SQLiteDatabase getSqliteReadDB(){
return DBManager.getReadDB(mContext.getApplicationContext());
}
public abstract SQLiteStatement getSQLiteStatement();
public abstract String getTableName();
public abstract String[] getAllKey();
public int getTotalCount(){
Cursor cursor = null;
try{
SQLiteDatabase sqLiteDb = getSqliteDB();
if (sqLiteDb != null){
cursor = sqLiteDb.rawQuery("select count(*) from " + getTableName(),null);
if (cursor != null && cursor.moveToFirst()){
return cursor.getInt(0);
}
}
} catch (Exception e){
e.printStackTrace();
} finally {
if (cursor != null){
cursor.close();
}
}
return -1;
}
protected static String kv(String key,String value){
return key + "=" + value;
}
protected static String kv(String key, long value){
return key + "=" + value;
}
protected static String kv(String key, int value){
return key + "=" + value;
}
}
DBConfig接口:描述表字段
public interface DBConfig {
public static final String DATABASE_FILE = "data";
public static final int DB_VER = 1;
}
DBManager:数据库管理类,提供应用唯一的数据库SQLiteOpenHelper实例。
public class DBManager implements DBConfig{
private static final String TAG = "DBManager";
private static SQLiteDatabase mDB = null;
private static DataBaseHelper mDatabaseHelper = null;
public static void close(){
if (mDB != null){
mDB.close();
}
mDB = null;
}
private static DataBaseHelper getDatabaseHelper(Context AppContext){
if (mDatabaseHelper == null){
mDatabaseHelper = new DataBaseHelper(AppContext);
}
return mDatabaseHelper;
}
public static synchronized void InitDB(Context AppContext){
getWriteDB(AppContext);
getReadDB(AppContext);
}
public static synchronized SQLiteDatabase getWriteDB(Context AppContext){
if (mDB == null || !mDB.isOpen()){
mDB = getDatabaseHelper(AppContext).getWritableDatabase();
}
return mDB;
}
public static synchronized SQLiteDatabase getReadDB(Context AppContext){
if (mDB == null || !mDB.isOpen()){
mDB = getDatabaseHelper(AppContext).getReadableDatabase();
}
return mDB;
}
private static class DataBaseHelper extends SQLiteOpenHelper{
private Context mContext;
public DataBaseHelper(Context context) {
super(context, DATABASE_FILE, null, DB_VER);
mContext = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
createTable(db);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
private void createTable(SQLiteDatabase db){
db.execSQL("DROP TABLE IF EXISTS " + ContactInfoTable.CONTACT_INFO_TABLE);
db.execSQL(ContactInfoTable.TABLE_CREATE);
}
}
}
ContactManager类:具体业务,为一个单例,实现相关操作的接口方法。
public class ContactInfoTable extends BaseTable {
private static String TAG = "ContactInfoTable";
public static final String CONTACT_INFO_TABLE = "ContactInfo_table";
private static final String KEY_CONTACT_ID = "user_num";
private static final String KEY_CONTACT_NAME = "user_name";
private static final String KEY_USER_PHONE_HOME = "user_phonenumber";
private SQLiteStatement mSQLiteStatement = null;
public static final String TABLE_CREATE = "create table if not exists " + CONTACT_INFO_TABLE
+ "(" + KEY_CONTACT_ID + " long primary key, " + KEY_CONTACT_NAME + " text, "
+ KEY_USER_PHONE_HOME + " long);";
public final String STR_INSERT_STATEMENT_CONTACTS = "insert into " + CONTACT_INFO_TABLE
+ "(" + KEY_CONTACT_ID + "," + KEY_CONTACT_NAME + "," + KEY_USER_PHONE_HOME + ")values(?,?,?)";
public ContactInfoTable(Context context) {
super(context);
}
@Override
public SQLiteStatement getSQLiteStatement() {
if (mSQLiteStatement == null){
mSQLiteStatement = getSqliteDB().compileStatement(STR_INSERT_STATEMENT_CONTACTS);
}
return mSQLiteStatement;
}
@Override
public String getTableName() {
return CONTACT_INFO_TABLE;
}
@Override
public String[] getAllKey() {
return new String[]{CONTACT_INFO_TABLE + "." + KEY_CONTACT_ID,
CONTACT_INFO_TABLE + "." + KEY_CONTACT_NAME, CONTACT_INFO_TABLE + "." + KEY_USER_PHONE_HOME};
}
public boolean insertContactInfo(ContactInfo info){
return getSqliteDB().insert(CONTACT_INFO_TABLE,null,transContactInfo(info)) > 0;
}
public boolean insertContactInfoForStat(ContactInfo info){
getSQLiteStatement().clearBindings();
getSQLiteStatement().bindLong(1,info.getContactId());
getSQLiteStatement().bindString(2,info.getContactName());
getSQLiteStatement().bindString(3,info.getContactNum());
return getSQLiteStatement().executeInsert() > 0;
}
public boolean updateContactInfo(ContactInfo info){
return getSqliteDB().update(CONTACT_INFO_TABLE,transContactInfo(info),kv(KEY_CONTACT_ID,info.getContactId()),null) > 0;
}
public boolean deleteContactInfo(ContactInfo info){
return getSqliteDB().delete(CONTACT_INFO_TABLE,kv(KEY_CONTACT_ID,info.getContactId()),null) > 0;
}
private ContentValues transContactInfo(ContactInfo info){
ContentValues value = new ContentValues();
value.put(KEY_CONTACT_ID,info.getContactId());
value.put(KEY_CONTACT_NAME,info.getContactName());
value.put(KEY_USER_PHONE_HOME,info.getContactNum());
return value;
}
}
建议:
- 使用Application的Context创建数据库,在Application生命周期结束时再关闭。
- 在应用启动过程中最先初始化数据库,避免进入应用后再初始化导致相关操作时间变长。
因此,在结束应用时,记得关闭数据库,并且不能使用某个Activity的Context,否则会导致这个Activity的资源都不会释放,出现内存泄漏。
接下来,我们进行测试插入10000条数据所消耗的时间:
long start = System.currentTimeMillis();
Log.d("tag","start...");
for (int i = 1; i <= 10000; i ++){
ContactInfo info = new ContactInfo(i, "姓名" + i, "1389832" + (0000+i));
boolean insert = mContactInfoTable.insertContactInfo(info);
}
long end = System.currentTimeMillis();
Log.d("tag","it takes " + (end - start) + "ms");
这样的运行时间大概要花44s左右,接下来我们进行优化。
- 使用SQLiteStatement:Android系统提供的SQLiteStatement类来将数据插入数据库,在性能上有一定的提供,并且也解决了SQL注入的问题。
public final String STR_INSERT_STATEMENT_CONTACTS = "insert into " + CONTACT_INFO_TABLE
+ "(" + KEY_CONTACT_ID + "," + KEY_CONTACT_NAME + "," + KEY_USER_PHONE_HOME + ")values(?,?,?)";
@Override
public SQLiteStatement getSQLiteStatement() {
if (mSQLiteStatement == null){
mSQLiteStatement = getSqliteDB().compileStatement(STR_INSERT_STATEMENT_CONTACTS);
}
return mSQLiteStatement;
}
public boolean insertContactInfoForStat(ContactInfo info){
getSQLiteStatement().clearBindings();
getSQLiteStatement().bindLong(1,info.getContactId());
getSQLiteStatement().bindString(2,info.getContactName());
getSQLiteStatement().bindString(3,info.getContactNum());
return getSQLiteStatement().executeInsert() > 0;
}
重新运行代码,发现其实性能并没有提升多少,时间降到了39s左右。
- 使用事务:若没有显示创建任何事务,每执行一次插入操作,系统都会自动创建一个事务,在插入后立即提交,这样会出现一个问题,如果插入非常频繁,就会频繁创建事务,影响插入的效率。
事务有两个基本特性:原子提交和性能更好。
事务的使用方法如下:
beginTransaction():开启一个事务
setTransactionSuccessful():设置事务标志成功
endTransaction():检查事务的标志是否成功,若成功则所有操作都会被提交,否则回滚事务
具体使用如下:
mContactInfoTable.getSqliteDB().beginTransaction();
long start = System.currentTimeMillis();
Log.d("tag","start...");
try{
for (int i = 1; i <= 10000; i ++){
ContactInfo info = new ContactInfo(i, "姓名" + i, "1389832" + (0000+i));
boolean insert = mContactInfoTable.insertContactInfoForStat(info);
//Log.d("tag","insert Contact info : " + insert);
}
mContactInfoTable.getSqliteDB().setTransactionSuccessful();
} catch (Exception e){
e.printStackTrace();
} finally {
mContactInfoTable.getSqliteDB().endTransaction();
}
long end = System.currentTimeMillis();
Log.d("tag","it takes " + (end - start) + "ms");
Log.d("tag","end...");
使用事务提交,插入10000条数据只需要1秒多,可见事务对性能的极大作用。
- 使用索引:索引维护一个表中或表中某一列或某几列的顺序,这样就可以快速定位到一组值,而不用扫遍全表。
索引虽然可以提高查询的速度,但也有两个明显的缺点:
- 数据库的插入、更新和删除使用索引反而更慢,因为删除、更新字典中的一个字,也要删除这个字在拼音索引和部首索引中的信息。
- 建立索引会增加数据库的大小。
虽然索引的目的是提高数据库的性能,但在以下场合不建议使用索引:
- 在较小的表中
- 在有频繁的大批量更新或插入操作的表上
- 在含有NULL值的列上
- 在频繁操作的列上
- 异步线程,写数据库统一管理:虽然对数据库优化后性能提高了很多,但相对来说还是一个耗时的操作,因此有必要放到异步系统中操作。同时为了保证数据的同步和避免一些死锁等待情况,可以考虑双缓存机制(把一些常用的数据放到内存缓存中,再异步更新到数据库中)。
内容提供器
如果一个应用通过内容提供器对数据提供了外部访问的接口,那么任何其他的应用程序就都可以对这部分数据进行访问。Android系统中自带的电话簿、短信、媒体库等程序都提供了类似的访问接口,这就使得第三方程序可以充分地利用这部分数据来实现更好的功能。
- ContentResolver的使用:对于每个应用程序来说,如果想要访问内容提供器中共享的数据,就要借助ContentResolver类,通过Context中的getContextResolver方法来获取到该类的实例。
内容URI
内容URI给内容提供器中的数据建立了一个唯一的标识符,它主要由两部分组成:authority和path。authority是对不同的应用程序做区分的,一般采用包名形式;path则是用于对同一应用程序中不同的表做区分的。
比如某个程序的包名为com.example.app,程序内含有表table1和table2,那么其内容URI如下:
content://com.example.app.provider/table1
content://com.example.app.provider/table2
CRUD
- 获取到内容URI后,我们还需要将其解析成Uri对象作为参数传入:
Uri uri = Uri.parse("content://com.example.app.provider/table1");
- 获取到Uri对象后,就可以对其进行CRUD操作:
//projection:指定查询某个应用程序下的一张表
//selection:指定where的约束条件
//selectionArgs:为where中的占位符提供具体的值
//orderBy:指定查询结果的排序方式
Cursor cursor = getContentResolver().query(uri, projection,selection,selectionArgs,sortOrder);
//插入操作:
ContentValues values = new ContentValues();
values.put("column1","text");
values.put("column2",1);
getContentResolver().insert(uri,values);
//更新操作:
ContentValues values = new ContentValues();
values.put("column1","");
getContentResolver().update(uri,values,"column1 = ? and column2 = ?",new String[]{"text",1});
//删除操作:
getContentResolver().delete(uri,"column1 = ?", new String[]{"text"});
如下为读取联系人列表的例子:
public class MainActivity extends AppCompatActivity {
ArrayAdapter<String> adapter;
List<String> contactList = new ArrayList<>();
Button button;
ListView listView;
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.list);
button = (Button) findViewById(R.id.button);
adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,contactList);
listView.setAdapter(adapter);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_CONTACTS},1);
}
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
readContacts();
}
});
}
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode){
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
readContacts();
}else {
Toast.makeText(this,"You denied the permission",Toast.LENGTH_SHORT).show();
}
}
}
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
private void readContacts(){
Cursor cursor = null;
try{
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,null,null,null,null);
if (cursor != null){
while (cursor.moveToNext()){
String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
contactList.add(displayName + "\n" + number);
}
adapter.notifyDataSetChanged();
}
} catch (Exception e){
e.printStackTrace();
} finally {
if (cursor != null){
cursor.close();
}
}
}
}
网友评论