Android四大元件之ContentProvider(一)
1.什麼是ContentProvider?
內容提供程式管理對資料結構化資料集的訪問。它們封裝資料,並提供用於定義資料安全性的機制。內容提供者是連線一個
程序
中的資料與另一個程序
中執行的程式碼的標準介面。
是不同應用程式之間進行資料交換的標準API,以某種Uri的形式對外提供資料,允許其他應用訪問或修改資料;其他程式使用ContentResolver根據Uri去訪問操作指定的資料。
是IPC通訊的一種。
2.ContentProvider和ContentResolver
- 建立ContentProvider的派生類,重寫必要的方法,方法有:(
ContentResolve
- onCreate():當其他程式第一個訪問ContentProvider時回撥此方法。
- String getType(Uri uri):用於返回當前Uri所代表的MIME型別,如果包含多條資料則返回
“vnd.android.cursor.dir/開頭
”,單條資料則返回“vnd.android.cursor.item/開頭
”,開頭部分格式:vnd.<authority>.<path>
- insert(Uri uri,ContentValue values):根據該uri插入對應的value值
- update(Uri uri,…)
- delete(Uri uri):根據uri刪除符合引數條件的所有值
- query(Uri uri):根據uri查詢符合條件的所有值
- AndroidManifest.xml檔案中為其註冊
<provider android:name=".contentProvider.MyContentProvider" android:authorities="org.wdl.book" android:enabled="true" android:exported="true" />
屬性 | 意義 |
---|---|
android:name | 類名 |
android:authorities | 指定相應的域名 |
android:exported | 是否提供給外部程式使用 |
- 客戶端程式通過ContentResolver的一系列方法呼叫ContentProvider提供的API進行資料的CRUD
相對應的流程如圖:
ContentResolver.query()。 query() 方法會某某提供程式所定義的 ContentProvider.query() 方法。 以下程式碼行顯示了 ContentResolver.query() 呼叫:(其他方法類似)
// Queries the user dictionary and returns results
mCursor = getContentResolver().query(
UserDictionary.Words.CONTENT_URI, // The content URI of the words table
mProjection, // The columns to return for each row
mSelectionClause // Selection criteria
mSelectionArgs, // Selection criteria
mSortOrder); // The sort order for the returned rows
其中引數與之前SQLite儲存提到的類似,詳見](https://blog.csdn.net/qq_34341338/article/details/84069391)
由上圖可以看出URI在這資料互動的過程中起到了關鍵性(進行資料交換的標識
)的作用,因此,該URI所對應的要求如下:`
- 以
content://
開頭,固定不變的 - 中間部分為之前xml檔案中配置的
android:authorities
,固定不變的 - 第三部分代表
資源
部份。這個部分是動態改變,根據訪問者的需求更改 content://authorities/source/#
,#代表萬用字元,可以為1,2,3等;content://authorities/*
,*代表任意長度字元,表示所有表
分析:
例:
content://org.wdl.provider/books
意味訪問books資料
content://org.wdl.provider/book/2
意味訪問books資料中ID為2的資料
content://org.wdl.provider/book/2/name
意味訪問books資料中ID為2的資料中的name欄位
Uri工具類提供了parse靜態方法,用於將字串轉為Uri
ContentResolver
呼叫方法中傳遞的Uri
必須能夠與ContentProvider
中所暴露Uri
的相匹配,否則會丟擲異常。為了確保正常進行資料CRUD,Android提供了UriMatcher
工具類:
void addURI(String authority,String path,int code):
向UriMatcher中註冊Uri,authority+path構成Uri,code表示標識碼,根據它來判斷是否能夠進行資料操作
int match(Uri uri):
根據註冊的Uri判斷指定Uri對應的標識碼,匹配不到返回-1
例:
matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI(Books.AUTHORITY, "books", MyContentProvider.BOOKS);
matcher.addURI(Books.AUTHORITY, "book/#", MyContentProvider.BOOK);
code = matcher.match(uri)
假如現在我有一個需求:
查詢Books中id為2的資料,需對原始的Uri進行拼接
,Android為此提供了另一個工具類ContentUris
,主要方法如下:
- withAppendedId(Uri uri,int id):在路徑上拼接ID部分
- parseId(Uri uri):解析出指定Uri所包含的ID值
例:
ContentUris.withAppendedId(Books.Book.BOOK_CONTENT_URI,1)
拼接後的uri為:content://org.wdl.provider/book/1
long id = ContentUris.parseId(uri);
id值為:1
3.詳細使用
MyContentProvider.class
package com.wdl.crazyandroiddemo.contentProvider;
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.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import com.wdl.crazyandroiddemo.MySQLiteHelper;
import java.util.Objects;
public class MyContentProvider extends ContentProvider {
private MySQLiteHelper helper;
private static UriMatcher matcher;
private static final int BOOKS = 1;
private static final int BOOK = 2;
static {
matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI(Books.AUTHORITY, "books", MyContentProvider.BOOKS);
matcher.addURI(Books.AUTHORITY, "book/#", MyContentProvider.BOOK);
}
public MyContentProvider() {
}
@Override
public boolean onCreate() {
Log.e("wdl", "----------onCreate----------");
helper = new MySQLiteHelper(getContext(), "demo.db", null, 3);
return true;
}
@Override
public int delete(@NonNull Uri uri, String where, String[] whereArgs) {
Log.e("wdl", "----------delete----------");
SQLiteDatabase db = helper.getReadableDatabase();
int num = 0;
switch (matcher.match(uri)) {
case BOOKS:
num = db.delete("book", where, whereArgs);
break;
case BOOK:
long id = ContentUris.parseId(uri);
String whereClause = Books.Book.ID + "=" + id;
if (!TextUtils.isEmpty(where)) {
whereClause = whereClause + " and " + where;
}
num = db.delete("book", whereClause, whereArgs);
break;
default:
throw new IllegalArgumentException("未知uri");
}
Objects.requireNonNull(getContext()).getContentResolver().notifyChange(uri, null);
return num;
}
@Override
public String getType(@NonNull Uri uri) {
Log.e("wdl", "----------getType----------");
switch (matcher.match(uri)) {
case BOOKS:
return "vnd.android.cursor.dir/wdl.books";
case BOOK:
return "vnd.android.cursor.item/wdl.book";
default:
throw new IllegalArgumentException("未知uri");
}
}
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
Log.e("wdl", "----------insert----------");
//獲取資料庫例項
SQLiteDatabase db = helper.getReadableDatabase();
switch (matcher.match(uri)) {
case BOOKS:
//插入
long rowId = db.insert("book", Books.Book.ID, values);
if (rowId > 0) {
//uri末尾新增id
Uri bookUri = ContentUris.withAppendedId(uri, rowId);
//通知資料已經改變
Objects.requireNonNull(getContext()).getContentResolver().notifyChange(bookUri, null);
return bookUri;
}
break;
default:
throw new IllegalArgumentException("Not yet implemented");
}
return null;
}
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
Log.e("wdl", "----------query----------");
SQLiteDatabase db = helper.getReadableDatabase();
switch (matcher.match(uri)) {
case BOOKS:
return db.query("book",
projection,
selection,
selectionArgs,
null,
null, sortOrder);
case BOOK:
long id = ContentUris.parseId(uri);
String whereClause = Books.Book.ID + "=" + id;
if (!TextUtils.isEmpty(selection)) {
whereClause = whereClause + " and " + selection;
}
return db.query("book", projection, whereClause, selectionArgs, null, null, sortOrder);
default:
throw new IllegalArgumentException("未知uri");
}
}
@Override
public int update(@NonNull Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
Log.e("wdl", "----------update----------");
SQLiteDatabase db = helper.getReadableDatabase();
int num = 0;
switch (matcher.match(uri)) {
case BOOKS:
num = db.update("book", values, selection, selectionArgs);
break;
case BOOK:
long id = ContentUris.parseId(uri);
String whereClause = Books.Book.ID + "=" + id;
if (!TextUtils.isEmpty(selection)) {
whereClause = whereClause + " and " + selection;
}
num = db.update("book", values, whereClause, selectionArgs);
break;
default:
throw new IllegalArgumentException("未知uri");
}
Objects.requireNonNull(getContext()).getContentResolver().notifyChange(uri, null);
return num;
}
}
Books.class,此類封裝了可訪問Uri以及可訪問的資料列:
package com.wdl.crazyandroiddemo.contentProvider;
import android.net.Uri;
import android.provider.BaseColumns;
/**
* author: wdl
* time: 2018/11/17 13:43
* des: TODO
*/
@SuppressWarnings("unused")
public class Books {
//定義contentProvider 的Authorities值
public static final String AUTHORITY = "org.wdl.book";
//定義靜態內部類,定義contentProvider所包含的資料列
public static final class Book implements BaseColumns {
//定義所允許操作的三個資料列
public final static String ID = "id";
public final static String NAME = "name";
public final static String PRICE = "price";
public final static String DATE = "publishdate";
//定義提供服務的兩個Uri
public final static Uri BOOKS_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/books");
public final static Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");
}
}
在Client中呼叫:
package com.wdl.contentproviderclient
import android.content.ContentUris
import android.content.ContentValues
import android.content.Intent
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.android.synthetic.main.activity_main.*
import java.lang.StringBuilder
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val contentResolver = contentResolver
btnQuery.setOnClickListener {
val intent = Intent("com.wdl.mime")
intent.data = Books.Book.BOOKS_CONTENT_URI
startActivity(intent)
val cursor = contentResolver.query(Books.Book.BOOKS_CONTENT_URI
,null,"price=?", arrayOf("50.2"),null)
//cursor.close()
val stringBuilder = StringBuilder()
while (cursor.moveToNext()){
val id = cursor.getInt(cursor.getColumnIndexOrThrow(Books.Book.ID))
val name = cursor.getString(cursor.getColumnIndexOrThrow(Books.Book.NAME))
val price = cursor.getInt(cursor.getColumnIndexOrThrow(Books.Book.PRICE))
stringBuilder.append("id=$id,name=$name,price=$price \n")
}
cursor.close()
Log.e("wdl",stringBuilder.toString())
}
btnDelete.setOnClickListener {
//val index = contentResolver.delete(uri,null,null)
val index = contentResolver.delete(Books.Book.BOOKS_CONTENT_URI,"price=?", arrayOf("55.2"))
Log.e("wdl",""+index)
}
btnInsert.setOnClickListener {
val value = ContentValues()
value.put(Books.Book.NAME,"smwdl")
value.put(Books.Book.PRICE,50.2)
val uri = contentResolver.insert(Books.Book.BOOKS_CONTENT_URI,value)
Log.e("wdl",uri.toString())
}
btnUpdate.setOnClickListener {
val value = ContentValues()
value.put(Books.Book.PRICE,55.2)
val uri = ContentUris.withAppendedId(Books.Book.BOOK_CONTENT_URI,1)
val index = contentResolver.update(uri
,value,"name like ?", arrayOf("smwdl"))
Log.e("wdl",""+index)
}
}
}
注意事項:
- 在MyContentPrivoder中實現的CRUD方法,都需要通過
uri判斷
,根據match返回值來執行對應的方法。例如:
switch (matcher.match(uri)) {
case BOOKS:
return db.query("book",
projection,
selection,
selectionArgs,
null,
null, sortOrder);
case BOOK:
long id = ContentUris.parseId(uri);
String whereClause = Books.Book.ID + "=" + id;
if (!TextUtils.isEmpty(selection)) {
whereClause = whereClause + " and " + selection;
}
return db.query("book", projection, whereClause, selectionArgs, null, null, sortOrder);
default:
throw new IllegalArgumentException("未知uri");
}
上段程式碼中,對匹配碼做出了判斷,並進行區分處理:Books代表對所有資料進行操作,Book代表對單項資料進行操作。而單項資料進行操作的化需要進行SQL語句的拼接
。通常為新增ID。
- 在static靜態程式碼塊中需進行UriMatcher的註冊
- 許可權宣告,在清單檔案中配置相對應的讀寫許可權。系統自帶的ContentProvider需進行其他許可權的宣告。在6.0之後需動態的申請所需許可權,否則無法進行資料的CRUD
- 在insert,update,delete之後都必須進行
getContext().getContentResolver().notifyChange(uri, null)
進行資料改變的響應