定义:四大组件之一
参考博客:https://blog.youkuaiyun.com/carson_ho/article/details/76101093
既可跨进程通信,也可进程内通信,底层原理使用binder;它其实类似于一种中间件如下图所示,只是对数据源进行了一层封装,解耦了访问者和底层数据,使得底层数据可以多样化而不影响上层的使用。
如上图所示,contentProvider使用上包括四块:
1,统一资源标识符(URI):外界进程通过 URI 找到对应的ContentProvider & 其中的数据,再进行数据操作;
URI分为 系统预置 & 自定义,分别对应系统内置的数据(如通讯录、日程表等等)和自定义数据库。
2,MIME数据类型
作用:指定某个扩展名的文件用某种应用程序来打开, 由2部分组成 = 类型 + 子类型
text / html 类型 = text、子类型 = html,表示.html文件用text应用程序打开。
3,ContentProvider类主要以 表格的形式 组织数据;
进程间共享数据的本质是:添加、删除、获取 & 修改(更新)数据, ContentProvider核心方法也主要是上述4个作用。
4,ContentResolver类
统一管理不同 ContentProvider间的操作,即通过 URI 即可操作不同的ContentProvider 中的数据,外部进程通过ContentResolver类从而与ContentProvider类进行交互。
5,ContentUris类:操作 URI
核心方法有两个:withAppendedId()&parseId()。
6,UriMatcher类
在ContentProvider 中注册URI,根据 URI 匹配 ContentProvider 中对应的数据表。
7,ContentObserver类
观察 Uri引起 ContentProvider 中的数据变化 & 通知外界(即访问该数据访问者)
当ContentProvider 中的数据发生变化(增、删 & 改)时,就会触发该 ContentObserver类。
进程一即继承ContentProvider的类:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.contentproviderserver">
<permission android:name="com.example.PROVIDER"/>
<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">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name=".MyProvider"
android:authorities="com.example.provider"
android:permission="com.example.PROVIDER"
android:exported="true"
/>
</application>
</manifest>
package com.example.contentproviderserver;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class MyProvider extends ContentProvider {
private Context mContext;
private DbHelper mDbHelper;
private SQLiteDatabase mDb;
private static final String AUTHORITY = "com.example.provider";
private static final int USER_CODE = 1;
private static final int JOB_CODE = 2;
private static final UriMatcher mUriMatcher;
static {
mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mUriMatcher.addURI(AUTHORITY, "user", USER_CODE);
mUriMatcher.addURI(AUTHORITY, "job", JOB_CODE);
}
@Override
public boolean onCreate() {
mContext = getContext();
mDbHelper = new DbHelper(mContext);
mDb = mDbHelper.getWritableDatabase();
mDb.execSQL("delete from user");
mDb.execSQL("insert into user values(1, 'wang')");
mDb.execSQL("insert into user values(2, 'feng')");
mDb.execSQL("delete from job");
mDb.execSQL("insert into job values(1, 'android')");
mDb.execSQL("insert into job values(2, 'ios')");
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
String table = getTableName(uri);
Cursor query = mDb.query(table, projection, selection, selectionArgs, null, null, sortOrder, null);
return query;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
String table = getTableName(uri);
mDb.insert(table, null, contentValues);
mContext.getContentResolver().notifyChange(uri, null);
return uri;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
String table = getTableName(uri);
int delete = mDb.delete(table, selection, selectionArgs);
return delete;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
/**
* 根据uri得到对应的表名
* @param uri
* @return
*/
private String getTableName(Uri uri) {
String tabName = null;
switch (mUriMatcher.match(uri)) {
case USER_CODE:
tabName = DbHelper.TB_USER_NAME;
break;
case JOB_CODE:
tabName = DbHelper.TB_JOB_NAME;
break;
default:
break;
}
return tabName;
}
}
进程二中数据访问者:
其中registerContentObserver() 方法:
- 第一个参数:需要监听的 uri。
- 第二个参数:为 false 表示精确匹配,即只匹配该 Uri。为 true 表示可以同时匹配其派生的 Uri,如:
content://com.example.provider/student(精确匹配)
content://com.example.provider/student/# (派生,false 才能匹配到)
content://com.example.provider/student/schoolchild(派生,false 才能匹配到)- 如果该值为true,则所有地址包含此uri的数据项发生变化时都会触发通知。否则只有完全符合该uri地址的数据项发生变化时才会触发通知。以文件夹和其中的文件为例,若uri指向某文件夹,则需设置notifyForDescendents为true。即该文件夹下的任何文件发生变化,都需要通知监听者。
- 第三个参数:ContentObserver 的实例。
public class ProviderActivity extends AppCompatActivity {
private static final String TAG = "ProviderActivity";
private static final int USER_OBSERVER = 1;
private Handler mHandler;
private ProviderObserver mObserver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_provider);
userOperation();
jobOperation();
}
@Override
protected void onDestroy() {
super.onDestroy();
getContentResolver().unregisterContentObserver(mObserver);
}
private void userOperation() {
Uri userUri = Uri.parse("content://com.example.provider/user");
ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(userUri, null, null, null, null);
try {
if (cursor != null) {
cursor.moveToFirst();
do {
int id = cursor.getInt(cursor.getColumnIndex("id"));
String name = cursor.getString(cursor.getColumnIndex("name"));
Log.d(TAG, "id: " + id + ", name: " + name);
cursor.moveToNext();
} while (!cursor.isAfterLast());
}
} catch (Exception e) {
Log.e(TAG, "error: " + e.getMessage());
} finally {
if (cursor != null) {
cursor.close();
}
}
mHandler = new Handler(getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == USER_OBSERVER) {
Log.d(TAG, msg.obj.toString());
}
}
};
mObserver = new ProviderObserver(mHandler, getApplicationContext());
getContentResolver().registerContentObserver(userUri, false, mObserver);
ContentValues values = new ContentValues();
values.put("id", 4);
values.put("name", "observer");
resolver.insert(userUri, values);
}
}
public class ProviderObserver extends ContentObserver {
private static final String TAG = "ProviderObserver";
private static final int USER_OBSERVER = 1;
private Handler mHandler;
private Context mContext;
public ProviderObserver(Handler handler, Context context) {
super(handler);
mHandler = handler;
mContext = context;
}
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
StringBuilder stringBuilder = new StringBuilder();
Cursor cursor = mContext.getContentResolver().query(uri, null, null, null, null);
try {
if (cursor != null) {
cursor.moveToFirst();
do {
int id = cursor.getInt(cursor.getColumnIndex("id"));
String name = cursor.getString(cursor.getColumnIndex("name"));
stringBuilder
.append("change insert id: " + id + ", name: " + name)
.append("\n");
cursor.moveToNext();
} while (!cursor.isAfterLast());
}
} catch (Exception e) {
Log.e(TAG, "error: " + e.getMessage());
} finally {
if (cursor != null) {
cursor.close();
}
}
mHandler.obtainMessage(USER_OBSERVER, stringBuilder.toString()).sendToTarget();
}
}
总结:
ContentProvider主要用于跨进程通信,也可以用在同一个进程中;它为应用间的数据交互提供了一个安全的环境:允许把自己的应用数据根据需求开放给 其他应用 进行 增、删、改、查,而不用担心因为直接开放数据库权限而带来的安全问题。
ContentProvider解耦了底层数据的存储方式,使得无论底层数据存储采用何种方式,外界对数据的访问方式都是统一的,这使得访问简单 、高效。