[Android]ContentProvider內容提供器
本文原始碼:https://github.com/gitEkko/MyApplication.git
一、內容提供者是什麼
內容提供者(Content Provider)主要用於在不同的應用程式之間實現資料共享的功能,它提供了一套完整的機制,允許一個程式訪問另一個程式中的資料,同時還能保證被訪資料的安全性。目前,使用內容提供者是Android實現跨程式共享資料的標準方式。
原理: ContentProvider
的底層是採用 Android
中的Binder
機制
二、ContentProvider的使用
1.統一資源識別符號-URI
定義:Uniform Resource Identifier,即統一資源識別符號
作用:唯一標識ContentProvider以及其中的資料
- 外界程序通過URI找到對應的ContentProvider,再進行資料操作。
URI分為:
- 系統預置:系統內建的資料,如通訊錄,簡訊,日程表等資料。
- 自定義:即自定義的資料庫,提供給外界程序對自定義資料庫進行操作。
需要將URI字串解析成URI物件才可以使用。
Uri uri = Uri.parse("content://com.example.myinterview.myprovider/person");
2.MIME資料型別
作用:指定某個副檔名的檔案用某種應用程式來開啟。
ContentProvider根據URI來返回MIME資料型別:ContentProvider.geType(uri) ;
有兩種形式:
- 單條記錄:"vnd.android.cursor.item/vnd.com.example.myinterview.myprovider.person";
- 多條記錄:"vnd.android.cursor.dir/vnd.com.example.myinterview.myprovider.person";
三、ContentProvider類
<-- 4個核心方法 -->
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 中的資料
// 注:
// 1. 上述4個方法由外部程序回撥,並執行在ContentProvider程序的Binder執行緒池中(不是主執行緒)
// 2. 存在多執行緒併發訪問,需要實現執行緒同步
// a. 若ContentProvider的資料儲存方式是使用SQLite & 一個,則不需要,因為SQLite內部實現好了執行緒同步,若是多個SQLite則需要,因為SQL物件之間無法進行執行緒同步
// b. 若ContentProvider的資料儲存方式是記憶體,則需要自己實現執行緒同步
<-- 2個其他方法 -->
public boolean onCreate()
// ContentProvider建立後 或 開啟系統後其它程序第一次訪問該ContentProvider時 由系統進行呼叫
// 注:執行在ContentProvider程序的主執行緒,故不能做耗時操作
public String getType(Uri uri)
// 得到資料型別,即返回當前 Url 所代表資料的MIME型別
在ContentProvider類中要對資料庫進行操作,所以要結合SQLiteOpenHelper。
ContentProvider類並不會直接與外部程序進行互動,而是通過ContentResolver類。
四、ContentResolver類
通過URI來操作不同的ContentProvider。
- 一般來說,一款應用要使用多個ContentProvider,若需要了解每個ContentProvider的不同實現從而再完成資料互動,操作成本高 & 難度大
- 所以再ContentProvider類上加多了一個 ContentResolver類對所有的ContentProvider進行統一管理。
ContentResolver 類提供了與ContentProvider類相同名字 & 作用的4個方法
// 向 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)
使用:
Uri uri = Uri.parse("content://" + AUTOHORITY + "/person");
ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(uri, null, null, null, null);
五、UriMatcher類
作用:
- 在ContentProvider 中註冊URI
- 根據 URI 匹配 ContentProvider 中對應的資料表
具體使用:
// 步驟1:初始化UriMatcher物件
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
//常量UriMatcher.NO_MATCH = 不匹配任何路徑的返回碼
// 即初始化時不匹配任何東西
// 步驟2:在ContentProvider 中註冊URI(addURI())
int URI_CODE_a = 1;
int URI_CODE_b = 2;
matcher.addURI("cn.scu.myprovider", "user1", URI_CODE_a);
matcher.addURI("cn.scu.myprovider", "user2", URI_CODE_b);
// 若URI資源路徑 = content://cn.scu.myprovider/user1 ,則返回註冊碼URI_CODE_a
// 若URI資源路徑 = content://cn.scu.myprovider/user2 ,則返回註冊碼URI_CODE_b
// 步驟3:根據URI 匹配 URI_CODE,從而匹配ContentProvider中相應的資源(match())
@Override
public String getType(Uri uri) {
Uri uri = Uri.parse(" content://cn.scu.myprovider/user1");
switch(matcher.match(uri)){
// 根據URI匹配的返回碼是URI_CODE_a
// 即matcher.match(uri) == URI_CODE_a
case URI_CODE_a:
return tableNameUser1;
// 如果根據URI匹配的返回碼是URI_CODE_a,則返回ContentProvider中的名為tableNameUser1的表
case URI_CODE_b:
return tableNameUser2;
// 如果根據URI匹配的返回碼是URI_CODE_b,則返回ContentProvider中的名為tableNameUser2的表
}
}
六、ContentObserver類
定義:內容觀察者
作用:觀察 Uri 引起 ContentProvider 中的資料變化 & 通知外界(即訪問該資料訪問者)
- 當
ContentProvider
中的資料發生變化(增、刪 & 改)時,就會觸發該ContentObserver
類
具體使用:
// 步驟1:註冊內容觀察者ContentObserver
getContentResolver().registerContentObserver(uri);
// 通過ContentResolver類進行註冊,並指定需要觀察的URI
// 步驟2:當該URI的ContentProvider資料發生變化時,通知外界(即訪問該ContentProvider資料的訪問者)
public class UserContentProvider extends ContentProvider {
public Uri insert(Uri uri, ContentValues values) {
db.insert("user", "userid", values);
getContext().getContentResolver().notifyChange(uri, null);
// 通知訪問者
}
}
// 步驟3:解除觀察者
getContentResolver().unregisterContentObserver(uri);
// 同樣需要通過ContentResolver類進行解除
七、Demo例項
通過訪問自定義的ContentProvider 來操作資料庫進行增刪改查。並且利用ContentObserver監聽資料庫如果發生變化,就進行 更新UI操作。
1.建立資料庫類:
SQLiteHelper.java:複用上篇程式碼:https://blog.csdn.net/Gods_magic/article/details/84706386
2.自定義ContentProvider類
/**
* 內容提供者
* ContentProvider是不同應用程式之間進行資料交換的標準API,
* ContentProvide以Uri的形式對外提供資料,允許其他應用訪問和修改資料;
* 其他應用使用ContentResolve根據Uri進行訪問操作指定的資料。
*/
public class MyContentProvider extends ContentProvider {
public static final String AUTOHORITY = "com.example.myinterview.myprovider";
public static final int Person_DIR = 0;
public static final int Person_ITEM = 1;
public static final int User_DIR = 2;
public static final int User_ITEM = 3;
private Context mContext;
private SQLiteHelper dbHelper = null;
private static final UriMatcher mMatcher;
static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mMatcher.addURI(AUTOHORITY, "person", Person_DIR);
mMatcher.addURI(AUTOHORITY, "person/#", Person_ITEM);
mMatcher.addURI(AUTOHORITY, "user", User_DIR);
mMatcher.addURI(AUTOHORITY, "user#", User_ITEM);
}
public MyContentProvider() {
}
@Override
public boolean onCreate() {
mContext = getContext();
dbHelper = new SQLiteHelper(mContext, 1);
return true;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
//根據URI匹配 URI_CODE,從而匹配ContentProvider中相應的表名
String table = getTableName(uri);
//向該表新增資料
long newId = db.insert(table, null, values);
Uri uriReturn = Uri.parse("content://" + AUTOHORITY + "/" + table + "/" + newId);
//當該URI的ContentProvider資料發生變化時,通知外界,即訪問該ContentProvider資料的訪問者
mContext.getContentResolver().notifyChange(uri, null);
//返回一個用於表示該條新紀錄的URI
return uriReturn;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int deleteRows = 0;
switch (mMatcher.match(uri)) {
case Person_DIR:
deleteRows = db.delete("person", selection, selectionArgs);
break;
case Person_ITEM:
String personId = uri.getPathSegments().get(1);
deleteRows = db.delete("person", "id = ?", new String[]{personId});
break;
case User_DIR:
deleteRows = db.delete("user", selection, selectionArgs);
break;
case User_ITEM:
String userId = uri.getPathSegments().get(1);
deleteRows = db.delete("user", "id = ?", new String[]{userId});
break;
}
//當該URI的ContentProvider資料發生變化時,通知外界,即訪問該ContentProvider資料的訪問者
mContext.getContentResolver().notifyChange(uri, null);
//返回被刪除的行數
return deleteRows;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int deleteRows = 0;
switch (mMatcher.match(uri)) {
case Person_DIR:
deleteRows = db.update("person", values, selection, selectionArgs);
break;
case Person_ITEM:
String personId = uri.getPathSegments().get(1);
deleteRows = db.update("person", values, "id = ?", new String[]{personId});
break;
case User_DIR:
deleteRows = db.update("user", values, selection, selectionArgs);
break;
case User_ITEM:
String userId = uri.getPathSegments().get(1);
deleteRows = db.update("user", values, "id = ?", new String[]{userId});
break;
}
//當該URI的ContentProvider資料發生變化時,通知外界,即訪問該ContentProvider資料的訪問者
mContext.getContentResolver().notifyChange(uri, null);
return deleteRows;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = null;
switch (mMatcher.match(uri)) {
case Person_DIR:
cursor = db.query("person", projection, selection, selectionArgs, null, null, sortOrder);
break;
case Person_ITEM:
String personId = uri.getPathSegments().get(1);
cursor = db.query("person", projection, "id = ?", new String[]{personId}, null, null, sortOrder);
break;
case User_DIR:
cursor = db.query("user", projection, selection, selectionArgs, null, null, sortOrder);
break;
case User_ITEM:
String userId = uri.getPathSegments().get(1);
cursor = db.query("user", projection, "id = ?", new String[]{userId}, null, null, sortOrder);
break;
}
return cursor;
}
@Override
public String getType(Uri uri) {
String mMIME = null;
switch (mMatcher.match(uri)) {
case Person_DIR:
mMIME = "vnd.android.cursor.dir/vnd.com.example.myinterview.myprovider.person";
break;
case Person_ITEM:
mMIME = "vnd.android.cursor.item/vnd.com.example.myinterview.myprovider.person";
break;
case User_DIR:
mMIME = "vnd.android.cursor.dir/vnd.com.example.myinterview.myprovider.user";
break;
case User_ITEM:
mMIME = "vnd.android.cursor.item/vnd.com.example.myinterview.myprovider.user";
break;
}
return mMIME;
}
private String getTableName(Uri uri) {
String tableName = null;
switch (mMatcher.match(uri)) {
case Person_DIR:
case Person_ITEM:
tableName = SQLiteHelper.PERSON_TABLE_NAME;
break;
case User_DIR:
case User_ITEM:
tableName = SQLiteHelper.USER_TABLE_NAME;
break;
}
return tableName;
}
}
在AndroidManifest.xml中進行註冊
<provider
android:name=".myprovider.MyContentProvider"
android:authorities="com.example.myinterview.myprovider"
android:enabled="true"
android:exported="true" />
如果想要外部程序訪問到此ContentProvider,必須設定 exported為 true
3.自定義ContentObserver類
/**
* 內容觀察者 觀察所監聽Uri 引起ContentProvider中的資料變化
*/
public class MyContentObsever extends ContentObserver {
private Context context;
private Handler handler;
public MyContentObsever(Context context, Handler handler) {
super(handler);
this.context = context;
this.handler = handler;
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Toast.makeText(context, "database has changed!", Toast.LENGTH_LONG).show();
//傳送handler訊息,監聽到資料庫變化後,更新UI
Message msg = new Message();
msg.obj = "database has changed";
handler.sendMessage(msg);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
//如果需要用到uri可以使用此方法。
}
}
4.MainActivity
操作資料庫的insert、delete、update等activity以及ListAdapter等檔案複用 https://blog.csdn.net/Gods_magic/article/details/84706386
public class CPMainActivity extends AppCompatActivity implements View.OnClickListener {
private static final int INSERT_REQUESTCODE = 1;
private static final int INSERT_RESULTCODE = 11;
private static final int DELETE_REQUESTCODE = 2;
private static final int DELETE_RESULTCODE = 22;
private static final int UPDATE_REQUESTCODE = 3;
private static final int UPDATE_RESULTCODE = 33;
private ListAdapter listAdapter;
public static final String AUTOHORITY = "com.example.myinterview.myprovider";
MyContentObsever myContentObsever;
TextView text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_cpmain);
Button insertPersonBtn = (Button) findViewById(R.id.person_insert);
Button deletePersonBtn = (Button) findViewById(R.id.person_delete);
Button updatePersonBtn = (Button) findViewById(R.id.person_update);
Button queryPersonBtn = (Button) findViewById(R.id.person_query);
text = (TextView) findViewById(R.id.change_text);
insertPersonBtn.setOnClickListener(this);
deletePersonBtn.setOnClickListener(this);
updatePersonBtn.setOnClickListener(this);
queryPersonBtn.setOnClickListener(this);
listAdapter = new ListAdapter(new ArrayList<Person>(), this);
ListView listView = (ListView) findViewById(R.id.list_data2);
listView.setAdapter(listAdapter);
//註冊ContentObsever 傳入Context 和 Handler
Uri uri = Uri.parse("content://com.example.myinterview.myprovider/person");
myContentObsever = new MyContentObsever(this, handler);
getContentResolver().registerContentObserver(uri, true, myContentObsever);
}
//主執行緒 Handler 更新UI
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
text.setText(msg.obj.toString());
}
};
@Override
public void onBackPressed() {
super.onBackPressed();
//取消註冊 ContentObserver
getContentResolver().unregisterContentObserver(myContentObsever);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.person_insert:
//case R.id.user_insert:
insert();
break;
case R.id.person_delete:
// case R.id.user_delete:
delete();
break;
case R.id.person_update:
//case R.id.user_update:
update();
break;
case R.id.person_query:
//case R.id.user_query:
query();
break;
default:
break;
}
}
private void insert() {
Intent intent = new Intent(this, InsertDialog.class);
startActivityForResult(intent, INSERT_REQUESTCODE);
}
private void delete() {
Intent intent = new Intent(this, DeleteDialog.class);
startActivityForResult(intent, DELETE_REQUESTCODE);
}
private void update() {
Intent intent = new Intent(this, UpdateDialog.class);
startActivityForResult(intent, UPDATE_REQUESTCODE);
}
private void query() {
Uri uri = Uri.parse("content://" + AUTOHORITY + "/person");
ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(uri, null, null, null, null);
List<Person> list = new ArrayList<>();
if (cursor != null && cursor.getCount() != 0) {
cursor.moveToFirst();
do {
int id = cursor.getInt(cursor.getColumnIndex("id"));
String name = cursor.getString(cursor.getColumnIndex("name"));
int age = cursor.getInt(cursor.getColumnIndex("age"));
String sex = cursor.getString(cursor.getColumnIndex("sex"));
Person person = new Person(id, name, age, sex);
list.add(person);
} while (cursor.moveToNext());
cursor.close();
}
loadData(list);
}
private void loadData(List<Person> list) {
listAdapter.setList(list);
listAdapter.notifyDataSetChanged();
}
private Cursor queryPersonById(Uri uri) {
ContentResolver resolver = getContentResolver();
return resolver.query(uri, null, null, null, null);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case INSERT_REQUESTCODE:
if (resultCode == INSERT_RESULTCODE) {
if (data != null) {
ContentResolver resolver = getContentResolver();
Person person = (Person) data.getSerializableExtra("person");
Uri uri = Uri.parse("content://" + AUTOHORITY + "/person");
ContentValues values = new ContentValues();
values.put("name", person.getName());
values.put("age", person.getAge());
values.put("sex", person.getSex());
resolver.insert(uri, values);
Toast.makeText(this, "Insert Success!", Toast.LENGTH_SHORT).show();
query();
} else {
Toast.makeText(this, "Insert Cancel!", Toast.LENGTH_SHORT).show();
}
}
break;
case DELETE_REQUESTCODE:
if (resultCode == DELETE_RESULTCODE) {
if (data != null) {
int id = data.getIntExtra("id", -1);
if (id != -1) {
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://" + AUTOHORITY + "/person/" + id);
if (queryPersonById(uri).getCount() != 0) {
resolver.delete(uri, null, null);
Toast.makeText(this, "Delete Success!", Toast.LENGTH_SHORT).show();
query();
} else {
Toast.makeText(this, "Delete Fail! id " + id + " not exist", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(this, "Delete Fail!", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(this, "Delete Cancel!", Toast.LENGTH_SHORT).show();
}
}
break;
case UPDATE_REQUESTCODE:
if (resultCode == UPDATE_RESULTCODE) {
if (data != null) {
Person person = (Person) data.getSerializableExtra("person");
int id = person.getId();
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://" + AUTOHORITY + "/person/" + id);
if (queryPersonById(uri).getCount() != 0) {
ContentValues values = new ContentValues();
values.put("name", person.getName());
values.put("age", person.getAge());
values.put("sex", person.getSex());
resolver.update(uri, values, null, null);
Toast.makeText(this, "Update Success!", Toast.LENGTH_SHORT).show();
query();
} else {
Toast.makeText(this, "Update Fail! id " + id + " not exist", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(this, "Update Cancel!", Toast.LENGTH_SHORT).show();
}
}
break;
default:
break;
}
}
}
大部分理論知識參考: https://blog.csdn.net/carson_ho/article/details/76101093