創(chuàng)建內(nèi)容提供器的步驟
通過新建一個類去繼承ContentProvider的方式來創(chuàng)建一個自己的內(nèi)容提供器,這個類中有6個抽象方法,我們在使用子類繼承它的時候,需要把這個6個方法全部重寫
- 新建MyProvider繼承自ContentProvider,代碼如下
public class MyProvider extends ContentProvider{
@Override
public boolean onCreate() {
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
return 0;
}
}
這6個方法大部分都是見過的
- onCreate()
- 初始化內(nèi)容提供器的時候調(diào)用,通常在這個里對數(shù)據(jù)庫的創(chuàng)建和升級操作,返回true表示內(nèi)容提供器初始化成功,返回false表示失敗,注意,只有ContentResolver嘗試訪問程序中的數(shù)據(jù)的時候,內(nèi)容提供器才會被初始化
- query()
- 從內(nèi)容提供器中查詢數(shù)據(jù)的,使用uri參數(shù)來確定有那張表,第二個參數(shù)是確定查詢那些列,第三個第四個參數(shù)用于約束查詢那些行,第五個參數(shù)用于對結(jié)果進行排序,查詢的結(jié)果放在Cursor對象中返回
- insert()
- 向內(nèi)容提供器中添加一條數(shù)據(jù),使用uri參數(shù)用于確定添加到的表,待添加的數(shù)據(jù)保存在values參數(shù)中,添加完成后,返回一個用于表示這天記錄的URI
- update()
- 更新內(nèi)容提供器中已有的數(shù)據(jù),使用uri參數(shù)來確定要更新那張表的數(shù)據(jù),新數(shù)據(jù)保存在第二個參數(shù)中,第三個第四個參數(shù)用于約束更新那些行,受影響的行數(shù)將作為返回值返回
- delete()
- 從內(nèi)容提供器中刪除數(shù)據(jù),使用uri參數(shù)來確定刪除那張表中的數(shù)據(jù),第二個第三個參數(shù)是用于約束刪除那些行,被刪除的行數(shù)作為返回值返回
- getType()
- 根據(jù)傳入的URI來返回相應的MIME類型
- 可以看到幾乎每一個方法中都會有這個uri參數(shù),這個參數(shù)也是調(diào)用ContentREsolver的增刪改查方法時傳遞過來的,現(xiàn)在會傳入的uri參數(shù)進行解析,從中分析出調(diào)用方期望訪問的表和數(shù)據(jù)
- 一個標準的uri是這樣寫的
content://com.example.app.provider/table1
這就表示期望訪問的是com.example.app這個應用的table1表中的數(shù)據(jù),還可以在內(nèi)容后面加一個id
content://com.example.app.provider/table1/1
這就表示期望訪問的是com.example.app應用的table1表中的id為1的數(shù)據(jù) - 內(nèi)容URI的格式主要就有以上的兩種,以路徑結(jié)尾的就表示期望訪問該表中的所有數(shù)據(jù),以id結(jié)尾表示訪問該表中擁有響應id的數(shù)據(jù),還可以使用通配符的方式分別來匹配這兩種格式的URI
*:表示匹配任意長度的任意字符
#:表示匹配任意長度的數(shù)字
所以一個能夠匹配任意表的內(nèi)容的URI就可以寫成
content"http://com.example.app.provider/*
而一個能夠匹配table1表中任意一行數(shù)據(jù)的內(nèi)容的URI就可以寫成
content"http://com.example.app.provider/table/#
- 接著借助這個UriMatcher這個類就可以輕松地實現(xiàn)匹配內(nèi)容URI的功能,UriMatcher中提供一個addURI()方法,這個方法接收3個參數(shù),分別把
authority、path和一個自定義代碼傳進去,這樣,當調(diào)用UriMatcher的match()方法時,就可以將一個Uri對象傳入,返回值就是某個能夠匹配這個Uri對象所對應的自定義代碼,利用這個代碼,就可以判斷出調(diào)用方期望訪問的是那張表中的數(shù)據(jù)了,修改MyProvider中的代碼
public class MyProvider extends ContentProvider{
// table1表中的所有數(shù)據(jù)
public static final int TABLE1_DIR = 0;
// table1表中的單條數(shù)據(jù)
public static final int TABLE1_ITEM = 1;
// table2表中的所有數(shù)據(jù)
public static final int TABLE2_DIR = 2;
// table2表中的單條數(shù)據(jù)
public static final int TABLE2_ITEM = 3;
private static UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.example.md.contacts","table1",TABLE1_DIR);
uriMatcher.addURI("com.example.md.contacts","table1/#",TABLE1_ITEM);
uriMatcher.addURI("com.example.md.contacts","table2",TABLE2_DIR);
uriMatcher.addURI("com.example.md.contacts","table2/#",TABLE2_ITEM);
}
@Override
public boolean onCreate() {
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
Cursor cursor = null;
switch (uriMatcher.match(uri)){
case TABLE1_DIR:
// 查詢table1表中的所有數(shù)據(jù)
break;
case TABLE1_ITEM:
// 查詢table1表中的單條數(shù)據(jù)
break;
case TABLE2_DIR:
// 查詢table2表中的所有數(shù)據(jù)
break;
case TABLE2_ITEM:
// 傳table2表中的單條數(shù)據(jù)
break;
default:
break;
}
return cursor;
}
// 剩下的和上面的一樣
...
}
- 新增了4個整型常量,接著在靜態(tài)代碼塊中創(chuàng)建了UriMatcher的實例,
并調(diào)用了addURI()方法,將期望匹配的內(nèi)容URI格式傳遞進入,注意這個傳入的路徑參數(shù)是可以使用通配符的 - 當query()方法被調(diào)用的時候,
就會通過UriMatcher的match()方法對傳入的Uri對象進行匹配,如果發(fā)現(xiàn)UriMatcher中的某個內(nèi)容URI格式成功匹配了該Uri對象,則會返回相應的自定義代碼,然然后就可以判斷出調(diào)用的期望訪問的到底是什么數(shù)據(jù)了 - 上面的query()方法只是一個實例,其實insert(),update(),delete()這幾個方法的實現(xiàn)都差不多,它們都會攜帶這個Uri參數(shù),然后利用UriMatcher的match()方法判斷出期望訪問的是那張表中的數(shù)據(jù),再該表中的數(shù)據(jù)進行相應的操作就行
- 此外還有一個方法getType()方法,它是所有內(nèi)容提供器都必須提供的一個方法,用于獲取uri對象所對應的MIME類型,一個內(nèi)容URI所對應的MIME字符串由3部分組成,Android對這3個部分做出了如下格式規(guī)定
1. 必須以vnd開頭
2. 如果內(nèi)容URI以路徑結(jié)尾,則后接android.cursor.dir/,如果內(nèi)容URI以id結(jié)尾,則后接android.cursor.item/
3. 最后接上vnd.<authority>.<path>
最后對于這個content://com.example.app.provider/table1URI,它所對應的MIME類型就可以寫成
vnd.android.cursor.dir/vnd.com.example.app.provider.table1
而這個content://com.example.app.provider/table1/1URI對應的MIME就可以寫成
vnd.android.cursor.item/vnd.com.example.app.provider.table1
- 繼續(xù)完善MyProvider中的內(nèi)容,這次來實現(xiàn)getType()方法中的邏輯,
public class MyProvider extends ContentProvider{
...
@Nullable
@Override
public String getType(@NonNull Uri uri) {
switch (uriMatcher.match(uri)){
case TABLE1_DIR:
return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1";
case TABLE1_ITEM:
return "vnd.android.cursor.item/vnd.com.example.app.provider.table1";
case TABLE2_DIR:
return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2";
case TABLE2_ITEM:
return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2";
default:
break;
}
return null;
}
}
- 到這里,一個完整的內(nèi)容提供器就創(chuàng)建完成了,現(xiàn)在任何一個應用程序都可以使用ContentResolver來訪問我們程序中的數(shù)據(jù),但是如何保證隱私數(shù)據(jù)不會泄漏?這個問題在不知不覺中就已經(jīng)解決了,因為所有的增刪改查操作都一定要匹配到相應的內(nèi)容URI格式才可以
實現(xiàn)跨程序數(shù)據(jù)共享
-
這里還是使用上一章中DatabaseTest項目的基礎上繼續(xù)開發(fā),通過內(nèi)容提供器來給它加入外部訪問接口,然后創(chuàng)建一個內(nèi)容提供器,右擊包名 new --> Other --> Content Provider,會彈出一個窗口
創(chuàng)建內(nèi)容提供器的窗口.png
這里將內(nèi)容提供器命名為DatabasesProvider,authority指定為com.example.md.d1,Exported屬性表示是否允許外部程序訪問我們的內(nèi)容提供器,Enabled屬性表示是否啟用這個內(nèi)容提供器,將兩個屬性勾選上,點擊Finish完成
- MyDatabaseHelper中的代碼,此時把Toast彈出創(chuàng)建成功提示去掉,因為跨程序中不能直接訪問Toast,別的保持不變就可以
public class MyDatabaseHelper extends SQLiteOpenHelper {
// 要創(chuàng)建的數(shù)據(jù)庫
public static final String CREATE_BOOK = "create table book(" +
"id integer primary key autoincrement," +
"author text," +
"price real," +
"pages integer," +
"name text)";
public static final String CREATE_CATEGORY = "create table Category(" +
"id integer primary key autoincrement," +
"category_name text," +
"category_code integer)";
private Context mContent;
// 重寫構(gòu)造方法,接收四個參數(shù)
public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContent = context;
}
// 創(chuàng)建數(shù)據(jù)庫
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
// 調(diào)用方法
sqLiteDatabase.execSQL(CREATE_BOOK);
// 再創(chuàng)建一個表
sqLiteDatabase.execSQL(CREATE_CATEGORY);
// Toast.makeText(mContent,"創(chuàng)建成功",Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
sqLiteDatabase.execSQL("drop table if exists Book");
sqLiteDatabase.execSQL("drop table if exists Category");
onCreate(sqLiteDatabase);
}
}
- 修改DatabaseProvider中的代碼
public class DatabaseProvider extends ContentProvider {
public static final int BOOK_DIR = 0;
public static final int BOOK_ITEM = 1;
public static final String AUTHORITY = "com.example.md.d1";
public static UriMatcher uriMatcher;
private MyDatabaseHelper databaseHelper;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY,"book",BOOK_DIR);
uriMatcher.addURI(AUTHORITY,"book/#",BOOK_ITEM);
}
public DatabaseProvider() {
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// 刪除數(shù)據(jù)
SQLiteDatabase db = databaseHelper.getWritableDatabase();
int deleteRows = 0;
switch (uriMatcher.match(uri)){
// 查詢?nèi)康臄?shù)據(jù)
case BOOK_DIR:
deleteRows = db.delete("book",selection,selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
deleteRows = db.delete("book","id = ?",new String[]{bookId});
break;
default:
break;
}
return deleteRows;
}
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)){
case BOOK_DIR:
return "vnd.android.cursor.dir/vnd.com.example.md.d1.book";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd.com.example.md.d1.book";
}
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
//添加數(shù)據(jù)
SQLiteDatabase db = databaseHelper.getWritableDatabase();
Uri uriReturn = null;
switch (uriMatcher.match(uri)){
case BOOK_DIR:
case BOOK_ITEM:
long newBookId = db.insert("book",null,values);
uriReturn = Uri.parse("content://"+AUTHORITY+"/book/"+newBookId);
break;
default:
break;
}
return uriReturn;
}
@Override
public boolean onCreate() {
databaseHelper = new MyDatabaseHelper(getContext(),"BookStore.db",null,2);
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// 查詢數(shù)據(jù)
SQLiteDatabase db = databaseHelper.getReadableDatabase();
Cursor cursor = null;
switch (uriMatcher.match(uri)){
// 查詢?nèi)康臄?shù)據(jù)
case BOOK_DIR:
cursor = db.query("book",projection,selection,null,null,null,sortOrder);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
cursor = db.query("book",projection,"id = ?",new String[]{bookId},null,null,sortOrder);
break;
default:
break;
}
return cursor;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// 更新數(shù)據(jù)
SQLiteDatabase db = databaseHelper.getReadableDatabase();
int updateRows = 0;
switch (uriMatcher.match(uri)){
// 查詢?nèi)康臄?shù)據(jù)
case BOOK_DIR:
updateRows = db.update("book",values,selection,selectionArgs);
break;
case BOOK_ITEM:
String categoryId = uri.getPathSegments().get(1);
updateRows = db.update("book",values,"id = ?",new String[]{categoryId});
break;
default:
break;
}
return updateRows;
}
}
- 首先類一開始定義了兩個常量,用于訪問Book表中的所有數(shù)據(jù)和Book表中的單條數(shù)據(jù),然后
在靜態(tài)的代碼塊中對UriMatcher進行初始化操作,將期望匹配的集中URI格式添加進去 - 接下來就是每個抽象方法的具體實現(xiàn)了,在onCreate()方法中,創(chuàng)建了一個MyDatabaseHelper的實例,然后返回true表示內(nèi)容提供器初始化成功,這時數(shù)據(jù)庫就已經(jīng)完成了創(chuàng)建和升級操作
- 在query()方法中,這個方法首先獲取到了
SQLiteDatabase的實例,然后根據(jù)傳入的Uri參數(shù)判斷用戶想要訪問的那張表的數(shù)據(jù),再調(diào)用SQLiteDatabase的query()方法進行查詢,并將Cursor對象返回就可以了,注意當訪問單條數(shù)據(jù)的時候,這里調(diào)用Uri對象的getPathSegments()方法,他會將內(nèi)容URI權(quán)限之后的部分以/符號進行分割,并將分割后的結(jié)果放入到一個字符串列表中,這個列表的第0個位置就是存放路徑的,第1個位置存放的就是id,得到id,再通過約束實現(xiàn)了查詢單條數(shù)據(jù)的功能 - insert()方法,還是獲取到了SQLiteDatabase的實例,根據(jù)傳入的Uri參數(shù)判斷出用戶想要往那張表中添加數(shù)據(jù),在通過SQLiteDatabase的insert()方法進行添加就可以了,注意
insert()方法要求返回的是一個能夠表示這條新增數(shù)據(jù)的URI,所以這個時候還需要調(diào)用Uri.parse()方法將一個內(nèi)容URI解析成一個Uri對象,當然這個內(nèi)容URI是以新增數(shù)據(jù)的id結(jié)尾的 - update()方法,也是獲取到了SQLiteDatabase的實例,根據(jù)傳入的Uri參數(shù)判斷出用戶想要往那張表中更新數(shù)據(jù),在通過SQLiteDatabase的update()方法進行更新就可以了,受影響的行數(shù)返回就可以了
- delete()方法,和update()幾乎一樣
- getType()方法,這個方法完全是按照上面的格式進行編寫的
- 還有一點,內(nèi)容提供器一定要在AndroidManifest.xml文件中進行注冊在可以使用,由于是使用快捷方式創(chuàng)建的內(nèi)容提供器,這個時候注冊就自動生成了
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.md.d1">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
...
<provider
android:name=".DatabaseProvider"
android:authorities="com.example.md.d1"
android:enabled="true"
android:exported="true">
</provider>
</application>
</manifest>
- 這個時候多了一個provider標簽,使用這個來對DatabaseProvider這個內(nèi)容提供器進行注冊
- 這個時候這個項目就已經(jīng)擁有跨程序共享數(shù)據(jù)的功能了,共享的是book表,首先把模擬器上的這個app刪除掉,從新運行項目,將這個程序從新安裝到模擬器上(此時可以不用任何的操作),關閉這個項目,新建一個項目ProviderTest,通過這個程序訪問DatabaseTest中的共享數(shù)據(jù)
- 修改activity_main.xml中的代碼
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:id="@+id/add_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Add To Book"/>
<Button
android:id="@+id/query_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Query Book"/>
<Button
android:id="@+id/updata_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Updata Book"/>
<Button
android:id="@+id/dalete_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Delete Book"/>
</LinearLayout>
- 這四個按鈕用于添加,查詢,更新,刪除數(shù)據(jù),
- 修改MainActivity中的代碼
public class MainActivity extends AppCompatActivity {
private String newId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button addDate = (Button)findViewById(R.id.add_data);
addDate.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 添加數(shù)據(jù)
Uri uri = Uri.parse("content://com.example.md.d1/book");
ContentValues values = new ContentValues();
values.put("name","The Da Vinci Code");
values.put("author","Dan Brown");
values.put("pages","454");
values.put("price","17.89");
Uri newUri = getContentResolver().insert(uri,values);
Log.d("aaaaaaaaaaaaaaaa",":"+newUri);
newId = newUri.getPathSegments().get(1);
}
});
Button queryData = (Button)findViewById(R.id.query_data);
queryData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 查詢數(shù)據(jù)
Uri uri = Uri.parse("content://com.example.md.d1/book");
Cursor cursor = getContentResolver().query(uri,null,null,null,null);
if (cursor != null){
while (cursor.moveToNext()){
// 遍歷Cursor對象,取出數(shù)據(jù)并打印
String name = cursor.getString(cursor.getColumnIndex("name"));
String author = cursor.getString(cursor.getColumnIndex("author"));
int pages = cursor.getInt(cursor.getColumnIndex("pages"));
double price = cursor.getDouble(cursor.getColumnIndex("price"));
Log.d("Mainactivity","book name is "+name);
Log.d("Mainactivity","book author is "+author);
Log.d("Mainactivity","book pages is "+pages);
Log.d("Mainactivity","book price is "+price);
}
cursor.close();
}
}
});
Button updateDate = (Button)findViewById(R.id.updata_data);
updateDate.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 更新數(shù)據(jù)
Uri uri = Uri.parse("content://com.example.md.d1/book/"+newId);
ContentValues values = new ContentValues();
values.put("name","A Storm of Swords");
values.put("price","20");
getContentResolver().update(uri,values,null,null);
Log.d("Mainactivity","更新成功");
}
});
Button deleteData = (Button)findViewById(R.id.dalete_data);
deleteData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 刪除數(shù)據(jù)
Uri uri = Uri.parse("content://com.example.md.d1/book/"+newId);
getContentResolver().delete(uri,null,null);
Log.d("Mainactivity","刪除成功");
}
});
}
}
- 添加數(shù)據(jù)的時候調(diào)用Uri.parse()方法將一個內(nèi)容URI解析成Uri對象,然后把要添加的數(shù)據(jù)存放到ContentValues對象中,接著調(diào)用COntentResolver的insert()方法執(zhí)行添加的操作就可以了,
注意insert()方法返回一個Uri對象,這個對象中包含了新增數(shù)據(jù)的id,通過getPathSegments()方法將這個id取出 - 查詢數(shù)據(jù)的時候,同樣是Uri.parse()方法將一個內(nèi)容URI解析成Uri對象,然后調(diào)用ContentResolver的query()方法去查詢數(shù)據(jù),查詢的數(shù)據(jù)還是存放在了Cursor中進行遍歷,從中去出查詢結(jié)果
- 更新數(shù)據(jù)的時候,前面的操作都是一樣,這里為了不想讓Book表中的其他數(shù)據(jù)受到影響,在調(diào)用Uri.parse()方法時,給內(nèi)容的URI尾部增加了一個id,這個id正是添加數(shù)據(jù)時返回的id,這就表示只希望更新剛剛添加的那條數(shù)據(jù),Book表中的其他行不受影響
- 刪除數(shù)據(jù)的時候也是這個樣子
-
運行這個程序,
主頁面.png
點擊按鈕,點擊Add按鈕,此時數(shù)據(jù)就應該添加到了這個程序的(com.example.md.d1)/book/的book表中了,點擊Query按鈕
查詢數(shù)據(jù).png -
接下來點擊別的按鈕
更新后的數(shù)據(jù).png - 此時跨程序共享數(shù)據(jù)功能就基本完成了,現(xiàn)在任何一個程序都可以訪問com.example.md.d1/book/中的數(shù)據(jù)了,而且也不用擔心隱私數(shù)據(jù)泄漏的問題



