【Android開發基礎系列】數據持久化專題

1 Android四種數據持久化方式

????????Android有四種數據持久化方式:

? ??SharePreference

????????輕量級鍵-值方式存儲,以XML文件方式保存。

? ??文件

????????采用java.io.*庫所提供有I/O接口,讀寫文件。

? ??SQLit數據

????????SQLite是輕量級嵌入式內置數據庫。

? ??ContentProvider

????????ContentProvider可為數據封裝,為多個應用共享


2 SharePreference

Android--sharepreference總結

http://blog.csdn.net/wulianghuan/article/details/8501063

2.1 簡介

????????SharedPreferences類,它是一個輕量級的存儲類,特別適合用于保存軟件配置參數。SharedPreferences保存數據,其背后是用xml文件存放數據,文件存放在/data/data//shared_prefs目錄下:

????????一個簡單的存儲代碼如下:

SharedPreferences sharedPreferences = getSharedPreferences("wujay", Context.MODE_PRIVATE); //私有數據

Editor editor = sharedPreferences.edit(); ????//獲取編輯器

editor.putString("name", "wujaycode");

editor.putInt("age", 4);

editor.commit();????//提交修改


生成的wujay.xml文件內容如下:

2.2 使用方法

????????分析以下幾個方法:

2.2.1 一、getSharedPreferences(name, mode)

????????方法的第一個參數用于指定該文件的名稱,名稱不用帶后綴,后綴會由Android自動加上;方法的第二個參數指定文件的操作模式,共有四種操作模式。

????????四種操作模式分別為:

????1. MODE_APPEND: 追加方式存儲

????2. MODE_PRIVATE: 私有方式存儲,其他應用無法訪問

????3. MODE_WORLD_READABLE: 表示當前文件可以被其他應用讀取

????4. MODE_WORLD_WRITEABLE: 表示當前文件可以被其他應用寫入

2.2.2 二、edit()方法獲取editor對象

Editor editor = sharedPreferences.edit();

????????editor存儲對象采用key-value鍵值對進行存放,

editor.putString("name", "wujaycode");

????????通過commit()方法提交數據,與之對應的獲取數據的方法:

SharedPreferences share = getSharedPreferences("Acitivity", Activity.MODE_WORLD_READABLE);

int i = share.getInt("i", 0);

String str = share.getString("str", "");

boolean flag = share.getBoolean("flag", false);

????????getString()第二個參數為缺省值,如果preference中不存在該key,將返回缺省值。如果你想要刪除通過SharedPreferences產生的文件,可以通過以下方法:

File file = newFile("/data/data/"+getPackageName().toString()+"/shared_prefs", "Activity.xml");

if(file.exists()){

????file.delete();

????Toast.makeText(TestActivity.this, "刪除成功", Toast.LENGTH_LONG).show();

}


2.2.3 三、訪問其他應用中的Preference

????????如果要訪問其他應用中的Preference,必須滿足的條件是,要訪問的應用的Preference創建時指定了Context.MODE_WORLD_READABLE或者Context.MODE_WORLD_WRITEABLE權限。

????????舉例,假如有個name>為com.wujay.action下面的應用使用了下面語句創建了Preference,getSharedPreferences("wujay", Context.MODE_WORLD_READABLE),

????????現在要訪問該Preferences:

????????首先,需要創建上面的Context,然后通過Context訪問Preferences,訪問preference時會在應用所在包下的shared_prefs目錄找到preference:

Context otherAppsContext = createPackageContext("com.wujay.action", Context.CONTEXT_IGNORE_SECURITY);

SharedPreferences sharedPreferences = otherAppsContext.getSharedPreferences("wujay", Context.MODE_WORLD_READABLE);

String name = sharedPreferences.getString("name", "");

int age = sharedPreferences.getInt("age", 0);

??? ????如果不通過創建Context訪問其他應用的preference,可以以讀取xml文件方式直接訪問其他應用preference對應的xml文件,如:

File xmlFile = new File(“/data/data/name>/shared_prefs/itcast.xml”); //應替換成應用的包名。


????????創建SharePreference

SharedPreferences settings = this.getSharedPreferences("TestXML", 0);

SharedPreferences.Editor localEditor = settings.edit();

//A_MAP03類必需要繼承了Activity的子類 才會有getSharedPreferences方法.


????????以鍵值<String Key, String Value> 方式加入數據:

localEditor.putBoolean("ShowNote", false);

IocalEditor.commit();


????????以 String Key 為索引來取出數據:

String str =settings.getString("ShowNote", "");


????????清除數據

localEditor.clear().commit();

SharedPreferences數據保存在:

????????存入XML后的內容目錄:/data/data/<包>/shared_prefs/***.xml

3 SQLite

Android中SQLite應用詳解

http://blog.csdn.net/liuhe688/article/details/6715983/

最受歡迎的5個Android ORM框架

http://www.codeceo.com/article/5-android-orm-framework.html

3.1 簡介

????????現在的主流移動設備像Android、iPhone等都使用SQLite作為復雜數據的存儲引擎,在我們為移動設備開發應用程序時,也許就要使用到SQLite來存儲我們大量的數據,所以我們就需要掌握移動設備上的SQLite開發技巧。對于Android平臺來說,系統內置了豐富的API來供開發人員操作SQLite,我們可以輕松的完成對數據的存取。

????????下面就向大家介紹一下SQLite常用的操作方法,為了方便,我將代碼寫在了Activity的onCreate中:

3.2 SQLite常用操作方法

3.2.1 Db創建

????@Override

????protected?void?onCreate(Bundle?savedInstanceState)?{

????????super.onCreate(savedInstanceState);

????????//打開或創建test.db數據庫

????????SQLiteDatabase?db?=?openOrCreateDatabase("test.db",?Context.MODE_PRIVATE,?null);

????????db.execSQL("DROP?TABLE?IF?EXISTS?person");

????????//創建person表

????????db.execSQL("CREATE?TABLE?person?(_id?INTEGER?PRIMARY?KEY?AUTOINCREMENT,?name?VARCHAR,?age?SMALLINT)");

????????Person?person?=?new?Person();

????????person.name?=?"john";

????????person.age?=?30;

????????//插入數據

????????db.execSQL("INSERT?INTO?person?VALUES?(NULL,??,??)",?new?Object[]{person.name,?person.age});

????????person.name?=?"david";

????????person.age?=?33;

????????//ContentValues以鍵值對的形式存放數據

????????ContentValues?cv?=?new?ContentValues();

????????cv.put("name",?person.name);

????????cv.put("age",?person.age);

????????//插入ContentValues中的數據

????????db.insert("person",?null,?cv);

????????cv?=?new?ContentValues();

????????cv.put("age",?35);

????????//更新數據

????????db.update("person",?cv,?"name?=??",?new?String[]{"john"});

????????Cursor?c?=?db.rawQuery("SELECT?*?FROM?person?WHERE?age?>=??",?new?String[]{"33"});

????????while(c.moveToNext())?{

????????????int?_id?=?c.getInt(c.getColumnIndex("_id"));

????????????String?name?=?c.getString(c.getColumnIndex("name"));

????????????int?age?=?c.getInt(c.getColumnIndex("age"));

????????????Log.i("db",?"_id=>"?+?_id?+?",?name=>"?+?name?+?",?age=>"+?age);

? ? }

? ? c.close();

? ? //刪除數據

? ? db.delete("person",?"age?<??",?new?String[]{"35"});

? ? //關閉當前數據庫

????db.close();

? ? //刪除test.db數據庫

????//??????deleteDatabase("test.db");

}?


????????在執行完上面的代碼后,系統就會在/data/data/[PACKAGE_NAME]/databases目錄下生成一個“test.db”的數據庫文件,如圖:

????????上面的代碼中基本上囊括了大部分的數據庫操作;對于添加、更新和刪除來說,我們都可以使用。

3.2.2 executeSQL

db.executeSQL(String?sql);

db.executeSQL(String?sql,?Object[]?bindArgs);//sql語句中使用占位符,然后第二個參數是實際的參數集

????????除了統一的形式之外,他們還有各自的操作方法:

3.2.3 增刪改

db.insert(String?table,?String?nullColumnHack,?ContentValues?values);

db.update(String?table,?Contentvalues?values,?String?whereClause,?String?whereArgs);

db.delete(String?table,?String?whereClause,?String?whereArgs);

????????以上三個方法的第一個參數都是表示要操作的表名;insert中的第二個參數表示如果插入的數據每一列都為空的話,需要指定此行中某一列的名稱,系統將此列設置為NULL,不至于出現錯誤;insert中的第三個參數是ContentValues類型的變量,是鍵值對組成的Map,key代表列名,value代表該列要插入的值;update的第二個參數也很類似,只不過它是更新該字段key為最新的value值,第三個參數whereClause表示WHERE表達式,比如“age> ? and age < ?”等,最后的whereArgs參數是占位符的實際參數值;delete方法的參數也是一樣。

3.2.4 查詢

????????下面來說說查詢操作。查詢操作相對于上面的幾種操作要復雜些,因為我們經常要面對著各種各樣的查詢條件,所以系統也考慮到這種復雜性,為我們提供了較為豐富的查詢形式:

db.rawQuery(String?sql,?String[]?selectionArgs);

db.query(String?table,?String[]?columns,?String?selection,?String[]?selectionArgs,?String?groupBy,?String?having,?String?orderBy);

db.query(String?table,?String[]?columns,?String?selection,?String[]?selectionArgs,?String?groupBy,?String?having,?String?orderBy,?String?limit);

db.query(String?distinct,?String?table,?String[]?columns,?String?selection,?String[]?selectionArgs,?String?groupBy,?String?having,?String?orderBy,?String?limit);

????????上面幾種都是常用的查詢方法,第一種最為簡單,將所有的SQL語句都組織到一個字符串中,使用占位符代替實際參數,selectionArgs就是占位符實際參數集;下面的幾種參數都很類似,columns表示要查詢的列所有名稱集,selection表示WHERE之后的條件語句,可以使用占位符,groupBy指定分組的列名,having指定分組條件,配合groupBy使用,orderBy指定排序的列名,limit指定分頁參數,distinct可以指定“true”或“false”表示要不要過濾重復值。需要注意的是,selection、groupBy、having、orderBy、limit這幾個參數中不包括“WHERE”、“GROUPBY”、“HAVING”、“ORDER BY”、“LIMIT”等SQL關鍵字。

????????最后,他們同時返回一個Cursor對象,代表數據集的游標,有點類似于JavaSE中的ResultSet。

????????下面是Cursor對象的常用方法:

c.move(int?offset);?//以當前位置為參考,移動到指定行

c.moveToFirst();????//移動到第一行

c.moveToLast();?????//移動到最后一行

c.moveToPosition(int?position);?//移動到指定行

c.moveToPrevious();?//移動到前一行

c.moveToNext();?????//移動到下一行

c.isFirst();????????//是否指向第一條

c.isLast();?????//是否指向最后一條

c.isBeforeFirst();??//是否指向第一條之前

c.isAfterLast();????//是否指向最后一條之后

c.isNull(int?columnIndex);??//指定列是否為空(列基數為0)

c.isClosed();???????//游標是否已關閉

c.getCount();???????//總數據項數

c.getPosition();????//返回當前游標所指向的行數

c.getColumnIndex(String?columnName);//返回某列名對應的列索引值

c.getString(int?columnIndex);???//返回當前行指定列的值

????????在上面的代碼示例中,已經用到了這幾個常用方法中的一些,關于更多的信息,大家可以參考官方文檔中的說明。

????????最后當我們完成了對數據庫的操作后,記得調用SQLiteDatabase的close()方法釋放數據庫連接,否則容易出現SQLiteException。

????????上面就是SQLite的基本應用,但在實際開發中,為了能夠更好的管理和維護數據庫,我們會封裝一個繼承自SQLiteOpenHelper類的數據庫操作類,然后以這個類為基礎,再封裝我們的業務邏輯方法。

3.2.5 封裝操作類DBHelper

? ? ? 下面,我們就以一個實例來講解具體的用法,我們新建一個名為db的項目,結構如下:

?????? 其中DBHelper繼承了SQLiteOpenHelper,作為維護和管理數據庫的基類,DBManager是建立在DBHelper之上,封裝了常用的業務方法,Person是我們的person表對應的JavaBean,MainActivity就是我們顯示的界面。

????????下面我們先來看一下DBHelper:

package?com.scott.db;

import?android.content.Context;

import?android.database.sqlite.SQLiteDatabase;

import?android.database.sqlite.SQLiteOpenHelper;

public?class?DBHelper?extends?SQLiteOpenHelper?{

????private?static?final?String?DATABASE_NAME?=?"test.db";

????private?static?final?int?DATABASE_VERSION?=?1;

????public?DBHelper(Context?context)?{

????????//CursorFactory設置為null,使用默認值

????????super(context,?DATABASE_NAME,?null,?DATABASE_VERSION);

}

????//數據庫第一次被創建時onCreate會被調用

????@Override

????public?void?onCreate(SQLiteDatabase?db)?{

????????db.execSQL("CREATE?TABLE?IF?NOT?EXISTS?person" + "(_id?INTEGER?PRIMARY KEY AUTOINCREMENT, name?VARCHAR,?age?INTEGER,?info?TEXT)");

}

????//如果DATABASE_VERSION值被改為2,系統發現現有數據庫版本不同,即會調用onUpgrade

????@Override

????public?void?onUpgrade(SQLiteDatabase?db,?int?oldVersion,?intnewVersion)?{

????????db.execSQL("ALTER?TABLE?person?ADD?COLUMN?other?STRING");

????}

}

??????? 正如上面所述,數據庫第一次創建時onCreate方法會被調用,我們可以執行創建表的語句,當系統發現版本變化之后,會調用onUpgrade方法,我們可以執行修改表結構等語句。

??????? 為了方便我們面向對象的使用數據,我們建一個Person類,對應person表中的字段,如下:

package?com.scott.db;

public?class?Person?{

????public?int_id;

????public?String?name;

????public?int?age;

????public?String?info;

????public?Person()?{

????}

????public?Person(String?name,?int?age,?String?info)?{

????????this.name?=?name;

????????this.age?=?age;

????????this.info?=?info;

????}

}

??????? 然后,我們需要一個DBManager,來封裝我們所有的業務方法,代碼如下:

package?com.scott.db;

import?java.util.ArrayList;

import?java.util.List;

import?android.content.ContentValues;

import?android.content.Context;

import?android.database.Cursor;

import?android.database.sqlite.SQLiteDatabase;

public?class?DBManager?{

????private?DBHelper?helper;

????private?SQLiteDatabase?db;

????public?DBManager(Context?context)?{

????????helper?=?new?DBHelper(context);

????????//因為getWritableDatabase內部調用了mContext.openOrCreateDatabase(mName,?0,?mFactory);

????????//所以要確保context已初始化,我們可以把實例化DBManager的步驟放在Activity的onCreate里

????????db?=?helper.getWritableDatabase();

????}

????/**

?????*?add?persons

?????*?@param?persons

?????*/

????public?void?add(List?persons)?{

????????db.beginTransaction();??//開始事務

????????try{

????????????for(Person?person?:?persons)?{

????????????????db.execSQL("INSERT?INTO?person?VALUES(null,??,??,??)",?new?Object[]{person.name,?person.age,?person.info});

????????????}

????????????db.setTransactionSuccessful();??//設置事務成功完成

????????}?finally{

????????????db.endTransaction();????//結束事務

????????}

????}

????/**

?????*?update?person's?age

?????*?@param?person

?????*/

????public?void?updateAge(Person?person)?{

????????ContentValues?cv?=?new?ContentValues();

????????cv.put("age",?person.age);

????????db.update("person",?cv,?"name?=??",?new?String[]{person.name});

????}

????/**

?????*?delete?old?person

?????*?@param?person

?????*/

????public?void?deleteOldPerson(Person?person)?{

????????db.delete("person",?"age?>=??",?new?String[]{String.valueOf(person.age)});

????}

????/**

?????*?query?all?persons,?return?list

?????*?@return?List

?????*/

????public?List?query()?{

????????ArrayList?persons?=?new?ArrayList();

????????Cursor?c?=?queryTheCursor();

????????while(c.moveToNext())?{

????????????Person?person?=?new?Person();

????????????person._id?=?c.getInt(c.getColumnIndex("_id"));

????????????person.name?=?c.getString(c.getColumnIndex("name"));

????????????person.age?=?c.getInt(c.getColumnIndex("age"));

????????????person.info?=?c.getString(c.getColumnIndex("info"));

????????????persons.add(person);

????????}

????????c.close();

????????return?persons;

????}

????/**

?????*?query?all?persons,?return?cursor

?????*?@return??Cursor

?????*/

????public?Cursor?queryTheCursor()?{

????????Cursor?c?=?db.rawQuery("SELECT?*?FROM?person",?null);

????????return?c;

????}

????/**

?????*?close?database

?????*/

????public?void?closeDB()?{

????????db.close();

????}

}

?????? 我們在DBManager構造方法中實例化DBHelper并獲取一個SQLiteDatabase對象,作為整個應用的數據庫實例;在添加多個Person信息時,我們采用了事務處理,確保數據完整性;最后我們提供了一個closeDB方法,釋放數據庫資源,這一個步驟在我們整個應用關閉時執行,這個環節容易被忘記,所以朋友們要注意。

??????? 我們獲取數據庫實例時使用了getWritableDatabase()方法,也許朋友們會有疑問,在getWritableDatabase()和getReadableDatabase()中,你為什么選擇前者作為整個應用的數據庫實例呢?在這里我想和大家著重分析一下這一點。

????????我們來看一下SQLiteOpenHelper中的getReadableDatabase()方法:

public?synchronized?SQLiteDatabase?getReadableDatabase()?{

????if?(mDatabase?!=?null?&&?mDatabase.isOpen())?{

????????//?如果發現mDatabase不為空并且已經打開則直接返回

????????return?mDatabase;

????}

????if(mIsInitializing)?{

????????//?如果正在初始化則拋出異常

????????throw?new?IllegalStateException("getReadableDatabase?called?recursively");

????}

????//?開始實例化數據庫mDatabase

????try{

????????//?注意這里是調用了getWritableDatabase()方法

????????return?getWritableDatabase();

????}?catch(SQLiteException?e)?{

????????if?(mName?==?null)

????????????throw?e;?//?Can't?open?a?temp?database?read-only!

????????Log.e(TAG,?"Couldn't?open?"?+?mName?+?"?for?writing?(will?try?read-only):",?e);

????}

????//?如果無法以可讀寫模式打開數據庫?則以只讀方式打開

????SQLiteDatabase?db?=?null;

????try{

????????mIsInitializing?=?true;

????????String?path?=?mContext.getDatabasePath(mName).getPath();//?獲取數據庫路徑

????????//?以只讀方式打開數據庫

????????db?=?SQLiteDatabase.openDatabase(path,?mFactory,?SQLiteDatabase.OPEN_READONLY);

????????if(db.getVersion()?!=?mNewVersion)?{

????????????throw?new?SQLiteException("Can't?upgrade?read -only database?from?version " + db.getVersion() +?"?to?" +?mNewVersion?+?":?"+?path);

????????}

????????onOpen(db);

????????Log.w(TAG,?"Opened?"?+?mName?+?"?in?read-only?mode");

????????mDatabase?=?db;//?為mDatabase指定新打開的數據庫

????????return?mDatabase;//?返回打開的數據庫

????}?finally{

????????mIsInitializing?=?false;

????????if?(db?!=?null&&?db?!=?mDatabase)

????????????db.close();

????}

}

??????? 在getReadableDatabase()方法中,首先判斷是否已存在數據庫實例并且是打開狀態,如果是,則直接返回該實例,否則試圖獲取一個可讀寫模式的數據庫實例,如果遇到磁盤空間已滿等情況獲取失敗的話,再以只讀模式打開數據庫,獲取數據庫實例并返回,然后為mDatabase賦值為最新打開的數據庫實例。既然有可能調用到getWritableDatabase()方法,我們就要看一下了:

public?synchronized?SQLiteDatabase?getWritableDatabase()?{

????if?(mDatabase?!=?null?&&?mDatabase.isOpen()?&&?!mDatabase.isReadOnly())?{

????????//?如果mDatabase不為空已打開并且不是只讀模式?則返回該實例

????????return?mDatabase;

????}

????if(mIsInitializing)?{

????????throw?new?IllegalStateException("getWritableDatabase?called?recursively");

????}

????//?If?we?have?a?read-only?database?open,?someone?could?be?using?it

????//?(though?they?shouldn't),?which?would?cause?a?lock?to?be?held?on

????//?the?file,?and?our?attempts?to?open?the?database?read-write?would

????//?fail?waiting?for?the?file?lock.?To?prevent?that,?we?acquire?the

????//?lock?on?the?read-only?database,?which?shuts?out?other?users.

????boolean?success?=?false;

????SQLiteDatabase?db?=?null;

????//?如果mDatabase不為空則加鎖?阻止其他的操作

????if?(mDatabase?!=?null)

????????mDatabase.lock();

????try{

????????mIsInitializing?=?true;

????????if?(mName?==?null)?{

????????????db?=?SQLiteDatabase.create(null);

????????}?else{

????????????//?打開或創建數據庫

????????????db?=?mContext.openOrCreateDatabase(mName,?0,?mFactory);

????????}

????????//?獲取數據庫版本(如果剛創建的數據庫,版本為0)

????????int?version?=?db.getVersion();

????????//?比較版本(我們代碼中的版本mNewVersion為1)

????????if(version?!=?mNewVersion)?{

????????????db.beginTransaction();????//?開始事務

????????????try{

????????????????if?(version?==?0)?{

????????????????????//?執行我們的onCreate方法

????????????????????onCreate(db);

????????????????}?else{

????????????????????//?如果我們應用升級了mNewVersion為2,而原版本為1則執行onUpgrade方法

????????????????????onUpgrade(db,?version,?mNewVersion);

????????????????}

????????????????db.setVersion(mNewVersion);//?設置最新版本

????????????????db.setTransactionSuccessful();//?設置事務成功

????????????}?finally{

????????????????db.endTransaction();//?結束事務

????????????}

????????}

????????onOpen(db);

????????success?=?true;

????????return?db;//?返回可讀寫模式的數據庫實例

????}?finally{

????????mIsInitializing?=?false;

????????if(success)?{

????????????//?打開成功

????????????if?(mDatabase?!=?null)?{

????????????????//?如果mDatabase有值則先關閉

????????????????try{

????????????????????mDatabase.close();

????????????????}?catch(Exception?e)?{

????????????????}

????????????????mDatabase.unlock();//?解鎖

????????????}

????????????mDatabase?=?db;//?賦值給mDatabase

????????}?else{

????????????//?打開失敗的情況:解鎖、關閉

????????????if?(mDatabase?!=?null)

????????????????mDatabase.unlock();

????????????if?(db?!=?null)

????????????????db.close();

? ? ? ? }

????}

}

??????? 大家可以看到,幾個關鍵步驟是,首先判斷mDatabase如果不為空已打開并不是只讀模式則直接返回,否則如果mDatabase不為空則加鎖,然后開始打開或創建數據庫,比較版本,根據版本號來調用相應的方法,為數據庫設置新版本號,最后釋放舊的不為空的mDatabase并解鎖,把新打開的數據庫實例賦予mDatabase,并返回最新實例。

3.3 Realm



4 ContentProvider


Android ContentProvider的介紹(很詳細)

http://xiechengfa.iteye.com/blog/1415829


android四大組件--ContentProvider詳解

http://www.2cto.com/kf/201404/296974.html


ContentProvider總結(Android)

http://blog.csdn.net/chuyuqing/article/details/39995607


4.1 ContentProvider簡介

??? ????ContentProvider:為存儲和獲取數據提供統一的接口。可以在不同的應用程序之間共享數據。Android已經為常見的一些數據提供了默認的ContentProvider。

??? 1、ContentProvider使用表的形式來組織數據

??? ????????無論數據的來源是什么,ContentProvider都會認為是一種表,然后把數據組織成表格。

??? 2、ContentProvider提供的方法

?? ?????query:查詢

?????? ?insert:插入

?? ?????update:更新

?? ?????delete:刪除

?? ?????getType:得到數據類型

?? ?????onCreate:創建數據時調用的回調函數

?? 3、每個ContentProvider都有一個公共的URI,這個URI用于表示這個ContentProvider所提供的數據。Android所提供的ContentProvider都存放在android.provider包當中。


4.1.1 使用ContentProvider共享數據

????1)ContentProvider類主要方法的作用:

public boolean onCreate():

??????? 該方法在ContentProvider創建后就會被調用,Android開機后,ContentProvider在其它應用第一次訪問它時才會被創建。

public Uri insert(Uri uri, ContentValues values):

??????? 該方法用于供外部應用往ContentProvider添加數據。

public int delete(Uri uri, String selection, String[] selectionArgs):

??????? 該方法用于供外部應用從ContentProvider刪除數據。

public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs):

??????? 該方法用于供外部應用更新ContentProvider中的數據。

public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder):

??????? 該方法用于供外部應用從ContentProvider中獲取數據。

public String getType(Uri uri):

??????? 該方法用于返回當前Url所代表數據的MIME類型。

2)如果操作的數據屬于集合類型,那么MIME類型字符串應該以vnd.android.cursor.dir/開頭,

????????例如:要得到所有person記錄的Uri為content://com.bing.provider.personprovider/person,那么返回的MIME類型字符串應該為:"vnd.android.cursor.dir/person"。

3)如果要操作的數據屬于非集合類型數據,那么MIME類型字符串應該以vnd.android.cursor.item/開頭,

????????例如:得到id為10的person記錄,Uri為content://com.bing.provider.personprovider/person/10,那么返回的MIME類型字符串為:"vnd.android.cursor.item/person"。

4.1.2 ContentResolver操作ContentProvider中的數據

????????1)當外部應用需要對ContentProvider中的數據進行添加、刪除、修改和查詢操作時,可以使用ContentResolver 類來完成,要獲取ContentResolver 對象,可以使用Activity提供的getContentResolver()方法。

????????2)ContentResolver類提供了與ContentProvider類相同簽名的四個方法:

public Uri insert(Uri uri, ContentValues values):該方法用于往ContentProvider添加數據。

public int delete(Uri uri, String selection, String[] selectionArgs):該方法用于從ContentProvider刪除數據。

public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs):該方法用于更新ContentProvider中的數據。

public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder):該方法用于從ContentProvider中獲取數據。

??? ????這些方法的第一個參數為Uri,代表要操作的ContentProvider和對其中的什么數據進行操作,其實和contentprovider里面的方法是一樣的。他們所對應的數據,最終是會被傳到我們在之前程序里面定義的那個contentprovider類的方法,假設給定的是:Uri.parse("content://com.bing.providers.personprovider/person/10"),那么將會對主機名為com.bing.providers.personprovider的ContentProvider進行操作,操作的數據為person表中id為10的記錄。

??????? 使用ContentResolver對ContentProvider中的數據進行添加、刪除、修改和查詢操作:

ContentResolver resolver =? getContentResolver();

Uri uri = Uri.parse("content://com.bing.provider.personprovider/person");

//添加一條記錄

ContentValues values = newContentValues();

values.put("name", "bingxin");

values.put("age", 25);

resolver.insert(uri, values);?

//獲取person表中所有記錄

Cursor cursor = resolver.query(uri, null, null, null, "personid desc");

while(cursor.moveToNext()){

??????Log.i("ContentTest", "personid=" + cursor.getInt(0)+", name=" + cursor.getString(1));

}

//把id為1的記錄的name字段值更改新為zhangsan

ContentValues updateValues = newContentValues();

updateValues.put("name", "zhangsan");

Uri updateIdUri = ContentUris.withAppendedId(uri, 2);

resolver.update(updateIdUri, updateValues, null, null);

//刪除id為2的記錄

Uri deleteIdUri =ContentUris.withAppendedId(uri, 2);

resolver.delete(deleteIdUri, null, null);


4.1.3 監聽ContentProvider中數據的變化

??????? 如果ContentProvider的訪問者需要知道ContentProvider中的數據發生變化,可以在ContentProvider發生數據變化時調用getContentResolver().notifyChange(uri, null)來通知注冊在此URI上的訪問者,例子如下:

public class PersonContentProviderextends ContentProvider {

? ? public Uri insert(Uri uri, ContentValues values) {

? ? ? ? db.insert("person", "personid", values);

? ? ? ? getContext().getContentResolver().notifyChange(uri, null);

? ? }

}

??????? 如果ContentProvider的訪問者需要得到數據變化通知,必須使用ContentObserver對數據(數據采用uri描述)進行監聽,當監聽到數據變化通知時,系統就會調用ContentObserver的onChange()方法:

getContentResolver().registerContentObserver(Uri.parse("content://com.ljq.providers.personprovider/person"), true, new PersonObserver(new Handler()));

public class PersonObserver extends ContentObserver{

? ? public PersonObserver(Handler handler) {

? ? ? ? super(handler);

? ? }

? ? public void onChange(boolean selfChange) {

?????????//此處可以進行相應的業務處理

? ? ?}

}

4.2 Uri介紹

4.2.1 Uri簡介???

????????Uri為系統的每一個資源給其一個名字,比方說通話記錄。

????1)每一個ContentProvider都擁有一個公共的URI,這個URI用于表示這個ContentProvider所提供的數據。

????2)Android所提供的ContentProvider都存放在android.provider包中。 將其分為A,B,C,D 4個部分:

????????A:標準前綴,用來說明一個Content Provider控制這些數據,無法改變的"content://";

????????B:URI 的標識,用于唯一標識這個ContentProvider,外部調用者可以根據這個標識來找到它。它定義了是哪個Content Provider提供這些數據。對于第三方應用程序,為了保證URI標識的唯一性,它必須是一個完整的、小寫的類名。這個標識在 元素的 authorities屬性中說明:一般是定義該ContentProvider的包.類的名稱;

????????C:路徑(path),通俗的講就是你要操作的數據庫中表的名字,或者你也可以自己定義,記得在使用的時候保持一致就可以了;"content://com.bing.provider.myprovider/tablename";

????????D:如果URI中包含表示需要獲取的記錄的ID;則就返回該id對應的數據,如果沒有ID,就表示返回全部;"content://com.bing.provider.myprovider/tablename/#" #表示數據id。

? ??PS

???????路徑(path)可以用來表示我們要操作的數據,路徑的構建應根據業務而定,如下:

????1、要操作person表中id為10的記錄,可以構建這樣的路徑:/person/10

????2、要操作person表中id為10的記錄的name字段,person/10/name

????3、要操作person表中的所有記錄,可以構建這樣的路徑:/person

????4、要操作xxx表中的記錄,可以構建這樣的路徑:/xxx

????5、當然要操作的數據不一定來自數據庫,也可以是文件、xml或網絡等其他存儲方式,如下: 要操作xml文件中person節點下的name節點,可以構建這樣的路徑:/person/name

????6、如果要把一個字符串轉換成Uri,可以使用Uri類中的parse()方法,如下:Uri uri = Uri.parse("content://com.bing.provider.personprovider/person")

4.2.2 UriMatcher類使用介紹

??????? 因為Uri代表了要操作的數據,所以我們經常需要解析Uri,并從Uri中獲取數據。Android系統提供了兩個用于操作Uri的工具類,分別為UriMatcher和ContentUris 。掌握它們的使用,會便于我們的開發工作。

????????UriMatcher類用于匹配Uri,它的用法如下:

????????首先第一步把你需要匹配Uri路徑全部給注冊上,如下:

//常量UriMatcher.NO_MATCH表示不匹配任何路徑的返回碼

UriMatcher? sMatcher = newUriMatcher(UriMatcher.NO_MATCH);

//如果match()方法匹配content://com.bing.procvide.personprovider/person路徑,返回匹配碼為1

sMatcher.addURI("com.bing.procvide.personprovider", "person", 1);//添加需要匹配uri,如果匹配就會返回匹配碼

//如果match()方法匹配content://com.bing.provider.personprovider/person/230路徑,返回匹配碼為2

sMatcher.addURI("com.bing.provider.personprovider", "person/#", 2);//#號為通配符

switch(sMatcher.match(Uri.parse("content://com.ljq.provider.personprovider/person/10"))){

??????case 1

????????break;

??????case 2

????????break;

??????default://不匹配

????????break;

}

??????? 注冊完需要匹配的Uri后,就可以使用sMatcher.match(uri)方法對輸入的Uri進行匹配,如果匹配就返回匹配碼,匹配碼是調用addURI()方法傳入的第三個參數,假設匹配content://com.ljq.provider.personprovider/person路徑,返回的匹配碼為1

4.2.3 ContentUris類使用介紹

??????? ContentUris類用于操作Uri路徑后面的ID部分,它有兩個比較實用的方法:

????withAppendedId(uri, id)用于為路徑加上ID部分:

Uri uri = Uri.parse("content://com.bing.provider.personprovider/person");

Uri resultUri = ContentUris.withAppendedId(uri, 10);

//生成后的Uri為:content://com.bing.provider.personprovider/person/10

parseId(uri)方法用于從路徑中獲取ID部分:

Uri uri = Uri.parse("content://com.ljq.provider.personprovider/person/10");

long personid = ContentUris.parseId(uri);????//獲取的結果為:10

4.3 ContentProvider示例代碼

????自定義一個ContentProvider,來實現內部原理

?? 步驟:

?1、定義一個CONTENT_URI常量(里面的字符串必須是唯一)

?? Public static final Uri CONTENT_URI =Uri.parse("content://com.WangWeiDa.MyContentprovider");

?? 如果有子表,URI為:

?? Publicstatic final Uri CONTENT_URI = Uri.parse("content://com.WangWeiDa.MyContentProvider/users");

?2、定義一個類,繼承ContentProvider

?? Publicclass MyContentProvider extends ContentProvider

?3、實現ContentProvider的所有方法(query、insert、update、delete、getType、onCreate)

?? package com.WangWeiDa.cp;

?? import java.util.HashMap;

?? import com.WangWeiDa.cp.MyContentProviderMetaData.UserTableMetaData;

?? import com.WangWeiDa.data.DatabaseHelp;

?? import android.content.ContentProvider;

?? import android.content.ContentUris;

?? import android.content.ContentValues;

?? import android.content.UriMatcher;

?? import android.database.Cursor;

?? import android.database.sqlite.SQLiteDatabase;

?? import android.database.sqlite.SQLiteQueryBuilder;

?? import android.net.Uri;

?? import android.text.TextUtils;


?? public class MyContentProvider extends ContentProvider {

? ?????//訪問表的所有列

?? ?????public static final intINCOMING_USER_COLLECTION = 1;

?? ?????//訪問單獨的列

?? ?????public static final int INCOMING_USER_SINGLE =2;

?? ?????//操作URI的類

?? ?????public static final UriMatcher uriMatcher;

?? ?????//為UriMatcher添加自定義的URI

?? ?????static{

?? ?????????uriMatcher = newUriMatcher(UriMatcher.NO_MATCH);

?? ?????????uriMatcher.addURI(MyContentProviderMetaData.AUTHORITIES,"/user",

?? ?INCOMING_USER_COLLECTION);

?? ?????????uriMatcher.addURI(MyContentProviderMetaData.AUTHORITIES,"/user/#",

?? ?INCOMING_USER_SINGLE);

?? ?}

?? ?private DatabaseHelp dh;

?? ?//為數據庫表字段起別名

?? ?public static HashMap userProjectionMap;

?? ?static

?? ?{

?? ?????userProjectionMap = new HashMap();

?? ?????userProjectionMap.put(UserTableMetaData._ID, UserTableMetaData._ID);

?? ?????userProjectionMap.put(UserTableMetaData.USER_NAME, UserTableMetaData.USER_NAME);

?? ?}

?? ?/**

?? ?*刪除表數據

?? ?*/

?? ?@Override

?? ?public int delete(Uri uri, String selection, String[] selectionArgs) {

?? ?????System.out.println("delete");

?? ?????//得到一個可寫的數據庫

?? ?????SQLiteDatabase db = dh.getWritableDatabase();

?? ?????//執行刪除,得到刪除的行數

?? ?????int count = db.delete(UserTableMetaData.TABLE_NAME, selection, selectionArgs);

?? ?????return count;

?? ?}

?? ?/**

?? ?*數據庫訪問類型

?? ?*/

?? ?@Override

?? ?public String getType(Uri uri) {

?? ?????System.out.println("getType");

?? ?????//根據用戶請求,得到數據類型

?? ?????switch (uriMatcher.match(uri)) {

?? ?????????case INCOMING_USER_COLLECTION:

?? ?????????????return MyContentProviderMetaData.UserTableMetaData.CONTENT_TYPE;

?? ?????????case INCOMING_USER_SINGLE:

?? ?????????????return MyContentProviderMetaData.UserTableMetaData.CONTENT_TYPE_ITEM;

?? ?????????default:

?? ?????????????throw newIllegalArgumentException("UnKnown URI" + uri);

?? ?????}

?? ?}

?? ?/**

?? ?*插入數據

?? ?*/

?? ?@Override

?? ?public Uri insert(Uri uri, ContentValuesvalues) {

?? ?????//得到一個可寫的數據庫

?? ?????SQLiteDatabase db = dh.getWritableDatabase();

?? ?????//向指定的表插入數據,得到返回的Id

?? ?????long rowId = db.insert(UserTableMetaData.TABLE_NAME, null, values);

?? ?????if(rowId > 0){????//判斷插入是否執行成功

?? ?????????//如果添加成功,利用新添加的Id和

?? ?????????Uri insertedUserUri = ContentUris.withAppendedId(UserTableMetaData.CONTENT_URI, rowId);

?? ?????????//通知監聽器,數據已經改變

?? ?????????getContext().getContentResolver().notifyChange(insertedUserUri, null);

?? ?????????return insertedUserUri;

?? ?????}

?? ?????return uri;

?? ?}

?? ?/**

?? ?*創建ContentProvider時調用的回調函數

?? ?*/

?? ?@Override

?? ?public boolean onCreate() {

?? ?????System.out.println("onCreate");

?? ?????//得到數據庫幫助類

?? ?????dh = newDatabaseHelp(getContext(), MyContentProviderMetaData.DATABASE_NAME);

?? ?????return false;

?? ?}

?? ?/**

?? ?*查詢數據庫

?? ?*/

?? ?@Override

?? ?public Cursor query(Uri uri, String[]projection, String selection, String[] selectionArgs, String sortOrder) {

?? ?????//創建一個執行查詢的Sqlite

?? ?????SQLiteQueryBuilder qb = newSQLiteQueryBuilder();

?? ?????//判斷用戶請求,查詢所有還是單個

?? ?????switch(uriMatcher.match(uri)){

?? ?????????case INCOMING_USER_COLLECTION:

?? ?????????????//設置要查詢的表名

?? ?????????????qb.setTables(UserTableMetaData.TABLE_NAME);

?? ?????????????//設置表字段的別名

?? ?????????????qb.setProjectionMap(userProjectionMap);

?? ?????????????break;

?? ?????????case INCOMING_USER_SINGLE:

?? ?????????????qb.setTables(UserTableMetaData.TABLE_NAME);

?? ?????????????qb.setProjectionMap(userProjectionMap);

?? ?????????????//追加條件,getPathSegments()得到用戶請求的Uri地址截取的數組,get(1)得到去掉地址中/以后的第二個元素

?? ?????????????qb.appendWhere(UserTableMetaData._ID + "=" + uri.getPathSegments().get(1));

?? ?????????????break;

?? ?????????}

?? ?????????//設置排序

?? ?????????String orderBy;

?? ?????????if(TextUtils.isEmpty(sortOrder)){

?? ?????????????orderBy = UserTableMetaData.DEFAULT_SORT_ORDER;

?? ?????????}

?? ?????????else{

?? ?????????????orderBy = sortOrder;

?? ?????????}

?? ?????????//得到一個可讀的數據庫

?? ?????????SQLiteDatabase db = dh.getReadableDatabase();

?? ?????????//執行查詢,把輸入傳入

?? ?????????Cursor c = qb.query(db, projection, selection,selectionArgs, null, null, orderBy);

?? ?????????//設置監聽

?? ?????????c.setNotificationUri(getContext().getContentResolver(), uri);

?? ?????????return c;

?? ?????}

?? ?????/**

?? ?????*更新數據庫

?? ?????*/

?? ?????@Override

?? ?????public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {

?? ?????????System.out.println("update");

?? ?????????//得到一個可寫的數據庫

?? ?????????SQLiteDatabase db = dh.getWritableDatabase();

?? ?????????//執行更新語句,得到更新的條數

?? ?????????int count =db.update(UserTableMetaData.TABLE_NAME, values, selection, selectionArgs);

?? ?????????return count;

?? ?????}

?? }

?? 4、在AndroidMinifest.xml中進行聲明

<android:name = ".cp.MyContentProvider"

android:authorities = "com.WangWeiDa.cp.MyContentProvider" />

? ?//為ContentProvider提供一個常量類MyContentProviderMetaData.java

?? package com.WangWeiDa.cp;


?? import android.net.Uri;

?? import android.provider.BaseColumns;


?? public class MyContentProviderMetaData {

?? ????//URI的指定,此處的字符串必須和聲明的authorities一致

?? ?????public static final String AUTHORITIES = "com.wangweida.cp.MyContentProvider";

?? ?????//數據庫名稱

?? ?????public static final String DATABASE_NAME = "myContentProvider.db";

?? ?????//數據庫的版本

?? ?????public static final int DATABASE_VERSION = 1;

?? ?????//表名

?? ?????public static final String USERS_TABLE_NAME = "user";

?? ?????public static final class UserTableMetaData implements BaseColumns{

?? ?????????//表名

?? ?????????public static final String TABLE_NAME ="user";

?? ?????????//訪問該ContentProvider的URI

?? ?????????public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITIES + "/user");

?? ?????????//該ContentProvider所返回的數據類型的定義

?? ?????????public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.myprovider.user";

?? ?????????public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.myprovider.user";

?? ?????????//列名

?? ?????????public static final String USER_NAME = "name";

?? ?????????//默認的排序方法

?? ?????????public static final String DEFAULT_SORT_ORDER = "_id desc";

?? ?????}

?? }

5 參考鏈接

Android--sharepreference總結

http://blog.csdn.net/wulianghuan/article/details/8501063


Android中SharePreference的使用

http://www.cnblogs.com/wanqieddy/archive/2012/04/07/2436299.html


android--存儲之SharePreference

http://blog.csdn.net/jie1991liu/article/details/8665479


Android中SQLite應用詳解

http://blog.csdn.net/liuhe688/article/details/6715983/


最受歡迎的5個Android ORM框架

http://www.codeceo.com/article/5-android-orm-framework.html

Android ContentProvider的介紹(很詳細)

http://xiechengfa.iteye.com/blog/1415829


android四大組件--ContentProvider詳解

http://www.2cto.com/kf/201404/296974.html


ContentProvider總結(Android)

http://blog.csdn.net/chuyuqing/article/details/39995607

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 一.菜單Menu 1.OptionsMenu 選項菜單 也叫系統菜單,右上角的三點 (1)高版本的菜單 ...
    chaohx閱讀 1,052評論 0 7
  • 2017年5月17日 Kylin_Wu 標注(★☆)為考綱明確給出考點(必考) 常見手機系統(★☆) And...
    Azur_wxj閱讀 1,838評論 0 10
  • Android_7_數據存數方式 使用SharedPreferences存儲數據 獲取SharedPreferen...
    icechao閱讀 401評論 0 2
  • 抽象方法boolean test(T t); 該方法對傳入的參數進行驗證,滿足條件返回true,否則返回false...
    ted005閱讀 1,972評論 0 50
  • 《天龍八部》喬峰突然一聲怒喝:“滾出來!”聲震屋瓦,梁上灰塵簌簌而落。群雄均是耳中雷鳴,心跳加劇。喬峰一招打出,人...
    高大杰閱讀 235評論 0 0