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();
}
}
}
}