目录
- 1 前言
- 2 URI 简介
- 3 项目结构
- 4 服务端(Content_S)
- 5 客户端(Content_C)
- 5 监听者(Content_O)
- 6 效果展示
1 前言
ContentProvider 即内容提供器,是 Android 四大组件之一,为 App 存取数据提供统一的对外接口,让不同的应用之间可以共享数据。
如图,Server 端通过 ContentProvider 对外提供操作本地数据(DataBase、File 等)的接口,Client 端通过 ContentResolver 与 ContentProvider 通讯,从而实现跨进程操作 Server 端数据,Observer 端通过 ContentObserver 监听 Server 端的数据是否发生变化,并触发相关操作。
(1) ContentProvider 接口
ContentProvider 是一个抽象类,用户需要实现如下抽象方法。
// 创建数据库并获得数据库连接 | |
public abstract boolean onCreate() | |
// 查询数据 | |
public abstract Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) | |
// 插入数据 | |
public abstract Uri insert(Uri uri, ContentValues values) | |
// 删除数据 | |
public abstract int delete(Uri uri, String selection, String[] selectionArgs) | |
// 更新数据 | |
public abstract int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) | |
// 获取数据类型(MIME类型,如:"text/html"、"image/png"、"message/rfc882"、"vnd.android-dir/mms-sms") | |
public abstract String getType(Uri uri) |
参数说明:
- uri:数据表路径
- projection:需要查询的字段名称
- selection:查询条件
- selectionArgs:查询条件中的参数列表
- sortOrder:排序
(2)ContentResolver 接口
// 查询数据 | |
public final Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) | |
// 插入数据 | |
public final Uri insert(Uri url, ContentValues values) | |
// 删除数据 | |
public final int delete(Uri url, String selection, String[] selectionArgs) | |
// 更新数据 | |
public final int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) |
可以看到,ContentProvider 和 ContentResolver 都有增删改查操作,并且参数列表完全一致,以实现对 Server 端数据的操作。
(3)ContentObserver 接口
ContentObserver 是一个抽象类,用户需要重写其构造方法和 onChange() 方法。
//用户需要重写构造方法,并注入 handler | |
public ContentObserver(Handler handler) | |
//监听的内容发生变化时调用 | |
public void onChange(boolean selfChange) | |
//注册 ContentObserver | |
mContext.getContentResolver().registerContentObserver(uri, false, observer); | |
//注销 ContentObserver | |
mContext.getContentResolver().unregisterContentObserver(observer); |
2 URI 简介
URI 即统一资源标识符(Uniform Resource Identifier),能够唯一标识资源,如同资源的身份ID。数据库的 URI 在 ContentProvider 中定义,ContentResolver 通过资源 URI 找到对应的 ContentProvider,从而操作数据库。URI 类图如下。
URI 由3部分组成:scheme、authority、path,其中 authority 又由 host、port 组成,URI 一般格式如下:
scheme://authority/path?query | |
scheme://host:port/path?query |
URI 类的常用接口如下:
//根据 uriString 生成 StringUri 对象(Uri的子类,重写了Uri的抽象方法) | |
public static Uri parse(String uriString) | |
//根据 fiel 生成 HierarchicalUri 对象(Uri的子类,重写了Uri的抽象方法) | |
public static Uri fromFile(File file) | |
//根据 scheme、ssp、fragment 生成 OpaqueUri 对象(Uri的子类,重写了Uri的抽象方法) | |
public static Uri fromParts(String scheme, String ssp, String fragment) | |
public abstract String getScheme() | |
public abstract String getHost() | |
public abstract int getPort() | |
public abstract String getPath() | |
public abstract String getQuery() |
常见的 URI 实例如下:
//网络资源 | |
https://blog.csdn.net/m0_37602827 | |
//本地资源 | |
file:///sdcard/Pictures/WeiXin/a.mp4 | |
//ContentProvider | |
content://com.zhyan8.content.MyProvider/query | |
//打电话 | |
tel:10086 | |
//发短信 | |
smsto:10086 | |
//发邮件 | |
mailto:xxxx@qq.com | |
//定位 | |
geo:52.76,-79.0342 |
3 项目结构
本文将以一个案例讲解使用 ContentProvider 实现跨进程通讯,项目结构如下,一共包含 3 个 Module。
- content_s:服务端,数据持有者,对外提供 ContentProvider
- content_c:客户端,数据操作者,通过 ContentResolver 操作服务端数据
- content_o:数据监听者,通过 ContentObserver 监听服务端数据的变化
4 服务端(Content_S)
(1)数据库操作类
DBHelper.java
package com.zhyan8.content_s; | |
import android.content.ContentValues; | |
import android.content.Context; | |
import android.database.Cursor; | |
import android.database.sqlite.SQLiteDatabase; | |
import android.database.sqlite.SQLiteOpenHelper; | |
public class DBHelper extends SQLiteOpenHelper { | |
private static final String DATABASE = "test.db"; | |
private static final String TABLE = "user"; | |
public DBHelper(Context context) { | |
super(context, DATABASE, null, 1); | |
} | |
public void onCreate(SQLiteDatabase db) { | |
String create_table = "create table " + TABLE + "(id int primary key, name varchar not null)"; | |
db.execSQL(create_table); | |
db.execSQL("insert into " + TABLE + " values(1001, '张三'),(1002, '李四')"); | |
} | |
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {} | |
public Cursor query() { | |
SQLiteDatabase db = getReadableDatabase(); | |
String sql = "select * from " + TABLE; | |
Cursor cursor = db.rawQuery(sql, null); | |
return cursor; | |
} | |
public long insert(ContentValues cv) { | |
SQLiteDatabase db = getWritableDatabase(); | |
long result = db.insert(TABLE, null, cv); | |
db.close(); | |
return result; | |
} | |
} |
生成的数据库如下:
(2)自定义 ContentProvider
MyProvider.java
package com.zhyan8.content_s; | |
import android.content.ContentProvider; | |
import android.content.ContentValues; | |
import android.content.UriMatcher; | |
import android.database.Cursor; | |
import android.net.Uri; | |
import android.util.Log; | |
public class MyProvider extends ContentProvider { | |
private DBHelper mUserDBHelper; | |
private static UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); //用于匹配URI,并返回对应的操作编码 | |
private static final String AUTHORITES = "com.zhyan8.content.MyProvider"; | |
private static final int QUERY = 1; //查询操作编码 | |
private static final int INSERT = 2; //插入操作编码 | |
static { //添加有效的 URI 及其编码 | |
sUriMatcher.addURI(AUTHORITES, "/query", QUERY); | |
sUriMatcher.addURI(AUTHORITES, "/insert", INSERT); | |
} | |
public boolean onCreate() { | |
mUserDBHelper = new DBHelper(getContext()); | |
return false; | |
} | |
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { | |
Log.e("xxx-Provider", "query: "); | |
int code = sUriMatcher.match(uri); | |
if (code == QUERY) { | |
return mUserDBHelper.query(); | |
} | |
return null; | |
} | |
public Uri insert(Uri uri, ContentValues values) { | |
Log.e("xxx-Provider", "insert: "); | |
int code = sUriMatcher.match(uri); | |
if (code == INSERT) { | |
mUserDBHelper.insert(values); | |
} | |
getContext().getContentResolver().notifyChange(uri, null); //通知外界,数据发生变化 | |
return null; | |
} | |
public String getType(Uri uri) { //获取资源 MIME | |
return null; | |
} | |
public int delete(Uri uri, String selection, String[] selectionArgs) { | |
return 0; | |
} | |
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { | |
return 0; | |
} | |
} |
(3)配置
在 AndroidManifest.xml 的 application 节点下配置 ContentProvider,如下。
<provider | |
android:name=".MyProvider" | |
android:authorities="com.zhyan8.content.MyProvider" | |
android:exported="true" /> |
exported 属性用于定义是否允许外界访问。
(4)主类
MainActivity.java
package com.zhyan8.content_s; | |
import android.os.Bundle; | |
import android.support.v7.app.AppCompatActivity; | |
public class MainActivity extends AppCompatActivity { | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_main); | |
} | |
} |
5 客户端(Content_C)
(1)数据类
User.java
package com.zhyan8.content_c; | |
public class User { | |
private Integer id; | |
private String name; | |
public User(){}; | |
public User(Integer id, String name) { | |
this.id = id; | |
this.name = name; | |
} | |
public void setId(Integer id) { | |
this.id = id; | |
} | |
public void setName(String name) { | |
this.name = name; | |
} | |
public Integer getId() { | |
return id; | |
} | |
public String getName() { | |
return name; | |
} | |
public String toString() { | |
return "User{" + "id=" + id + ", name='" + name + '\'' + '}'; | |
} | |
} |
(2)自定义 ContentResolver
MyResolver.java
package com.zhyan8.content_c; | |
import android.content.ContentResolver; | |
import android.content.ContentValues; | |
import android.content.Context; | |
import android.database.ContentObserver; | |
import android.database.Cursor; | |
import android.net.Uri; | |
import java.util.ArrayList; | |
public class MyResolver { | |
private ContentResolver mContentResolver; | |
private static final String AUTHORITES = "com.zhyan8.content.MyProvider"; | |
public MyResolver(Context context) { | |
mContentResolver = context.getContentResolver(); | |
} | |
public ArrayList<User> query() { | |
ArrayList<User> list = new ArrayList<>(); | |
Uri uri = Uri.parse("content://" + AUTHORITES + "/query"); | |
Cursor cursor = mContentResolver.query(uri, null, null, null, null); | |
while (cursor.moveToNext()) { | |
User user = new User(); | |
user.setId(cursor.getInt(0)); | |
user.setName(cursor.getString(1)); | |
list.add(user); | |
} | |
cursor.close(); | |
return list; | |
} | |
public void insert(User user) { | |
Uri uri = Uri.parse("content://" + AUTHORITES + "/insert"); | |
ContentValues cv = new ContentValues(); | |
cv.put("id", user.getId()); | |
cv.put("name", user.getName()); | |
mContentResolver.insert(uri, cv); | |
} | |
} |
(3)主类
MainActivity.java
package com.zhyan8.content_c; | |
import android.os.Bundle; | |
import android.support.v7.app.AppCompatActivity; | |
import android.util.Log; | |
import android.view.View; | |
import java.util.ArrayList; | |
public class MainActivity extends AppCompatActivity { | |
private MyResolver myResolver; | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_main); | |
myResolver = new MyResolver(this); | |
} | |
public void clickQuery(View v) { | |
ArrayList<User> users = myResolver.query(); | |
Log.e("xxx", "clickQuery: " + users.toString()); | |
} | |
public void clickInsert(View v) { | |
User user = new User(1003, "王五"); | |
myResolver.insert(user); | |
} | |
} |
(4)布局
activity_main.xml
<LinearLayout | |
xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:tools="http://schemas.android.com/tools" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
tools:context=".MainActivity" | |
android:orientation="vertical"> | |
<Button | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:text="查询" | |
android:textSize="40sp" | |
android:layout_marginTop="50dp" | |
android:onClick="clickQuery"/> | |
<Button | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:text="添加" | |
android:textSize="40sp" | |
android:layout_marginTop="50dp" | |
android:onClick="clickInsert"/> | |
</LinearLayout> |
界面如下:
5 监听者(Content_O)
(1)自定义 ContentObserver
MyObserver.java
package com.zhyan8.content_o; | |
import android.database.ContentObserver; | |
import android.os.Handler; | |
public class MyObserver extends ContentObserver { | |
private Uri mUri = Uri.parse("content://com.zhyan8.content.MyProvider/insert"); | |
private ContentResolver mResolver; | |
Handler mHandler; | |
public MyObserver(Context context, Handler handler) { | |
super(handler); | |
mResolver = context.getContentResolver(); | |
mHandler = handler; | |
} | |
public void onChange(boolean selfChange) { | |
mHandler.sendEmptyMessage(0x111); | |
} | |
public void registerObserver() { | |
mResolver.registerContentObserver(mUri, false, this); | |
} | |
public void unregisterObserver() { | |
mResolver.unregisterContentObserver(this); | |
} | |
} |
(2)主类
MainActivity.java
package com.zhyan8.content_o; | |
import android.net.Uri; | |
import android.os.Bundle; | |
import android.os.Handler; | |
import android.os.Message; | |
import android.support.v7.app.AppCompatActivity; | |
import android.util.Log; | |
public class MainActivity extends AppCompatActivity { | |
private MyObserver mObserver; | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_main); | |
mObserver = new MyObserver(mHandler); | |
mObserver.registerObserver(); | |
} | |
private Handler mHandler = new Handler() { | |
public void handleMessage(Message msg) { | |
if (msg.what==0x111) { | |
Log.e("xxx", "监听的数据改变了"); | |
} | |
} | |
}; | |
public void onDestroy() { | |
mObserver.unregisterObserver(); | |
super.onDestroy(); | |
} | |
} |
6 效果展示
首先保证 Content_S、Content_C 和 Content_O 3个应用都打开了,再在 Content_C 端依次点击【查询】→【插入】→【查询】,打印日志如下:
2020-11-22 22:47:41.397 5227-5597/com.zhyan8.content_s E/xxx-Provider: query:
2020-11-22 22:47:41.401 6261-6261/com.zhyan8.content_c E/xxx: clickQuery: [User{id=1001, name='张三'}, User{id=1002, name='李四'}]
2020-11-22 22:47:49.848 5227-5493/com.zhyan8.content_s E/xxx-Provider: insert:
2020-11-22 22:47:49.855 7879-7879/com.zhyan8.content_o E/xxx: 监听的数据改变了
2020-11-22 22:47:52.055 5227-5597/com.zhyan8.content_s E/xxx-Provider: query:
2020-11-22 22:47:52.063 6261-6261/com.zhyan8.content_c E/xxx: clickQuery: [User{id=1001, name='张三'}, User{id=1002, name='李四'}, User{id=1003, name='王五'}]