背景
这几天在学习安卓进程间通信,而做为安卓四大组件之一的ContentProvider(内容提供者),也可以实现IPC。
现在记录一下使用步骤
步骤
1、创建DatabaseOpenHelper
内容提供者的工作方式就和数据库操作是一样的,增删改查,所以我们要先创建一个帮助类来创建数据库,代码如下
public class MyDbHelper extends SQLiteOpenHelper {
public static final String DB_NAME = "peopleDatabase.db";
public static final String TABLE_NAME = "person";
public static final int VERSION = 1;
public MyDbHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
this(context);
}
public MyDbHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler) {
this(context);
}
public MyDbHelper(Context context) {
super(context, DB_NAME, null, VERSION, null);
}
@Override
public void onCreate(SQLiteDatabase db) {
String sql = "create table if not exists " + TABLE_NAME + "(id int(3) not null, name varchar(20), description varchar(50), " +
"constraint PK_PERSON primary key(id))";
db.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
2、创建自己的内容提供者
代码如下
public class PersonProvider extends ContentProvider {
private static final String TAG = "PersonProvider";
private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH); // uri匹配者
public static final String AUTH = "com.example.songzeceng.PersonProvider"; // 内容提供者的id
public static final Uri PERSON_URI = Uri.parse("content://" + AUTH + "/person");
public static final String TYPE_MORE = "vnd.android.cursor.dir/person"; // 多条查询的type
public static final String TYPE_SINGAL = "vnd.android.cursor.item/person"; // 单条查询的type
public static final int CODE_SINGAL = 0; // 单条查询的匹配码
public static final int CODE_MORE = 1; // 多条查询的匹配码
public static String TABLE_NAME = "person";
private SQLiteDatabase mDatabase;
private Context mContext;
static {
MATCHER.addURI(AUTH, "person", CODE_MORE); // 多条查询
MATCHER.addURI(AUTH, "person/#", CODE_SINGAL); // 单条查询,#是数字通配符,理解为id
}
@Override
public boolean onCreate() {
mContext = getContext();
mDatabase = new MyDbHelper(mContext).getWritableDatabase(); // 获取可写数据库,可写自然可读
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // 参数列表:表的uri、要查询的列、where子句、where子句的参数、排序语句
System.out.println("query uri:"+uri.toString());
String type = getType(uri);
if (isTypeValid(type)) {
Cursor cursor = mDatabase.query(TABLE_NAME, projection, selection, selectionArgs, null, sortOrder, null); // 两个null分别是groupBy和orderBy
return cursor; // 返回的是游标
}
return null;
}
private boolean isTypeValid(String type) {
return type != null && (TYPE_MORE.equals(type) || TYPE_SINGAL.equals(type));
}
@Override
public String getType(Uri uri) {
int code = MATCHER.match(uri);
switch (code) {
case CODE_SINGAL:
return TYPE_SINGAL;
case CODE_MORE:
return TYPE_MORE;
}
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
System.out.println("insert uri:"+uri.toString() + "--contentValues:"+values.toString());
if (isTypeValid(getType(uri))) {
mDatabase.insert(TABLE_NAME, null, values); // null是nullColumnHack,用于插入空行(也就是ContentValues内容是空)
mContext.getContentResolver().notifyChange(uri, null); // null是observer,观察者
}
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) { // 参数列表:uri、where子句、where参数
System.out.println("delete uri:"+uri.toString());
if (isTypeValid(getType(uri))) {
int count = mDatabase.delete(TABLE_NAME, selection, selectionArgs);
if (count > 0) {
mContext.getContentResolver().notifyChange(uri ,null);
}
}
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
System.out.println("update uri:"+uri.toString() + "--contentValues:"+values.toString());
if (isTypeValid(getType(uri))) {
int count = mDatabase.update(TABLE_NAME, values, selection, selectionArgs);
if (count > 0) {
mContext.getContentResolver().notifyChange(uri, null);
}
}
return 0;
}
}
可以看到,增加和更改的内容是放在ContentValues里面,这个相当于一个哈希映射
selection是where子句,selectionArgs则是参数,如果selection是"where id = ? and name = ? ",selectionArgs是[1,"szc"]的话,生成的完整的where子句就是"where id = 1 and name = \"szc\""
不过我直接把参数拼接到selection里了,所以selectionArgs就成了摆设
另外增加方法的返回值是表的url,修改和删除的返回值是影响的行数,感觉没啥大用..
3、清单文件中注册
<provider
android:name=".PersonProvider" <!--provider类名-->
android:authorities="com.example.songzeceng.PersonProvider" <!--provider的id-->
android:exported="true" <!--是否允许别的进程访问-->
android:grantUriPermissions="true" <!--是否允许解析uri-->
android:process=":provider"> <!--provider所属进程名-->
</provider>
4、在客户端进行CRUD检验
代码如下
public class MainActivity extends Activity {
public static final String TAG = "MainActivity";
private void insertData(Uri providerUrl, int id, String name, String description) {
ContentValues values = new ContentValues();
values.put("id", id); // 键值对
values.put("name", name);
values.put("description", description);
getContentResolver().insert(providerUrl, values); // 必须根据ContentResolver访问内容提供者
}
private void updateData(Uri providerUrl, int id, String name, String description) {
ContentValues values = new ContentValues();
values.put("id", id);
values.put("name", name);
values.put("description", description);
getContentResolver().update(providerUrl, values, "id = " + id, null); // 第二个参数是where子句 ,null是where子句参数
}
private void deleteData(Uri providerUrl, int id) {
getContentResolver().delete(providerUrl, "id = " + id, null); // 第二个参数是where子句,null则是其参数
}
private void queryData(Uri providerUrl) {
Cursor cursor = getContentResolver().query(providerUrl, null, null, null, null); // 查询所有记录
while (cursor.moveToNext()) { // 只要游标没到底
int id = cursor.getInt(cursor.getColumnIndex("id")); // getColumnIndex()根据列名获取列的位置,而后getInt()根据列的位置获取列的值
String userName = cursor.getString(cursor.getColumnIndex("name"));
String description = cursor.getString(cursor.getColumnIndex("description"));
System.out.println(id + "--" + userName + "--" + description);
}
cursor.close(); // 莫忘记关游标
System.out.println("----------------------------------");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
Uri providerUrl = PersonProvider.PERSON_URI;
insertData(providerUrl, 1, "szc", "a simple boy");
insertData(providerUrl, 2, "jason", "an interesting boy");
queryData(providerUrl);
updateData(providerUrl, 2, "dustin", "a brave boy");
queryData(providerUrl);
deleteData(providerUrl, 2);
queryData(providerUrl);
} catch (Exception e) {
e.printStackTrace();
}
}
5、查看结果
客户端截图
服务(provider)端截图
进程号不同,说明实现了进程间通信
结语
受限于水平和精力,没有怎么钻研内容提供者的源码,大致看了看源码和这篇文章,发现客户端contentResolver的insert()方法最终调用了IContentProvider的insert()方法,而IContentProvider接口继承了IInterface,似乎也是AIDL在幕后操纵,具体我就没去看了