我们会经常发现app中有这样的功能:是否允许读取系统联系人?这时你点击是,然后发现你手机通讯录里面存的联系人信息竟然被你新安装的app读取到了。由于不同的app运行在不同的进程中,这不就是进程间的通信了吗? 没错ContentProvider是安卓提供的用于不同app(进程)之间进行共享数据。除了系统联系人外、手机信息、日程信息、这些系统的app都提供有对外的ContentProvider。接下来我们就来一次奇妙的ContentProvider之旅(总结一下哈哈、、、)
我们对ContentProvider也有所了解了,知道他可以对外暴露一些想要暴露的数据,供其他进程(app)来共享这些数据。然而ContentReslover又是啥鬼?其实ContentReslover也是安卓提供的一个类,我们要想获得ContentProvider提供的数据就要通过
ContentReslover这个类来实现。(参考下关系图,以手机通讯录为例子)
ContentProvider和数据库类似有一套增删除改查。ContentProvider吧一些数据暴露出来提供了增删改查操作,我们通过ContentReslover就可以对通讯录app这些暴露的数据进行增删改查。
信息获得:
1、抽象类
2、已知的直接子类是MockContentResolver
(2)对象获取
ContentResolver resolver = getContentResolver();
(3)常用的增删改查api
//1 增
final Uri insert(Uri url,
ContentValues values)//Inserts a row into a table at the given URL.
//2 删
final int delete(Uri url,
String where,
String[] selectionArgs) //Deletes row(s) specified by a content URI.
//3 改
final int update(Uri uri,
ContentValues values,
String where, String[] selectionArgs)//Update row(s) in a content URI.
// 4 查
final Cursor query(Uri uri,
String[] projection,
Bundle queryArgs,
CancellationSignal cancellationSignal)
final Cursor query(Uri uri,
String[] projection,
String selection,
String[] selectionArgs,
String sortOrder,
CancellationSignal cancellationSignal)
final Cursor query(Uri uri,
String[] projection,
String selection,
String[] selectionArgs,
String sortOrder)
//Query the given URI, returning a Cursor over the result set.
可以看到上面的这些方法是不是似曾相识?没错SQLiteDataBase也是这样来完成curd的。只不过SQLiteDataBase接受的参数为表名,这里变成了URI对象。而且这里的uri为内容URI。
ps:uri的介绍可以参考:Activity的生命周期和启动模式(二)中9.1uri结构。
(4)内容URI简介
内容uri给内容提供器中的数据建立了唯一标识。
内容uri的写法:
content://authority/path
其实authority就是ContentProvider注册时的authority字符串(自定义ContentProvider下文会总结。如下)
比如我们想暴露两张表的数据,表分别为table1、table2这时,内容uri就可以写为:
content://com.example.administrator.messangerdemo/table1
content://com.example.administrator.messangerdemo/table2
(5)使用URI原因
如果只使用表名,系统无法知道我们要访问哪个app中的表,而通过URI就能精确定位程序。
uri对象获得方式
Uri uri1 = Uri.parse("content://com.example.administrator.messangerdemo/table1");
final Cursor query(Uri uri,
String[] projection,
String selection,
String[] selectionArgs,
String sortOrder)
1、uri:指定查询某应用某一表
2、projection:指定查询列名
3、selection:where 约束
4、sortOrder:查询结果的排序方式
ps:其他的方法都比较简单不在介绍
我们通过ContentReslover来读取手机联系人。
由于xml文件就一个TextView不在给出,直接上段简单的代码:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkPermision();
}
/**
* 权限检测
*/
@TargetApi(Build.VERSION_CODES.O)
private void checkPermision() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, 1);
} else {
readContacts();
}
}
/**
* 读取联系人
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private void readContacts() {
StringBuffer sb = new StringBuffer(); //sb
String name = null; //联系人姓名
String number = null;// 手机号
Cursor cursor = null;
cursor = getContentResolver().query( //简单模拟 实际放线程中操作(查询也是耗时)
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
null,
null);
if (cursor != null) {
while (cursor.moveToNext()) {
name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
sb.append("联系人:")
.append(name)
.append("手机号:")
.append(number)
.append("\n");
}
}
((TextView) findViewById(R.id.tv_text)).setText(sb.toString());
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
readContacts();
} else {
Toast.makeText(this, "权限否定", Toast.LENGTH_SHORT).show();
}
break;
}
}
}
吧上面的代码简单的copy运行下就完成了简单的IPC,读取手机联系人成功,是不是感觉特别easy。哈哈接下来我们就分析下设计的知识点。
(1)动态权限申请
其实就是个安卓6.0新增的特性,需要时申请权限。可以参考《第一行代码》的7.2章节。
(2)Cursor
安卓数据库查询返回的结果集(query方法返回值类型)
常见方法:
行列相关:
- int getCount()——返回Cursor 中的行数
- int getColumnCount()——返回所有列的总数
遍历Cursou数据常用:
- boolean moveToFirst()——移动光标到第一行
- boolean moveToLast()——移动光标到最后一行
- boolean moveToNext()——移动光标到下一行(使用较多)
- boolean moveToPrevious()——移动光标到上一行
- boolean moveToPosition(int position)——移动光标到指定行
ps:返回值是否移动成功,有指定行就返回true,没有就返回false。取数据常用:(参考下图解)
- int getColumnIndex(String columnName) 根据列名返回列所在索引,列不存在返回-1
- String getString(int columnIndex) 返回指定列的值
根据列名获得列所在的索引这点我们明白,但是根据列的索引获得数据我们就有点迷惑了,这里不是应该返回数据集合吗?一列数据的。其实Cursor就是个结果集。我们只需要不断的移动指针让他指向不同的行,这时指针指的行与我们指定的列综合起来就确定了某行某列的数据。
(3)ContactsContract.CommonDataKinds.Phone相关介绍
上文中的Uri直接使用了这个系统字符创常量,嗯???其实这个是google规定好的系统电话的内容uri类似的还有Email,photo(联系人照片)这些系统都有封装,我们使用时查下就好啦。。。
使用系统的ContentProvider提供的数据我们可以查询到,下面就总结下自定义的ContentProvider提供共享数据。
1、自定义类 继承ContentProvider,实现要实现的方法。(增删改查等相关知识详见代码)
2、提供数据库,完成要暴露表的初始化工作。
数据库:
/**
* Created by sunnyDay on 2019/6/28 12:00
*/
public class DbOpenHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "book_provider_db";
public static final String BOOK_TABLE_NAME = "book";
public static final String USER_TABLE_NAME = "user";
private static final int DB_VERSION = 1;
// book table
private static final String CREATE_BOOK_TABLE = "create table if not exists " + BOOK_TABLE_NAME
+ "(id integer primary key,name text)";
// user table
private static final String CREATE_USER_TABLE = "create table if not exists " + USER_TABLE_NAME
+ "(id integer primary key,name text,sex integer)";
public DbOpenHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_USER_TABLE);
db.execSQL(CREATE_BOOK_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO nothing
// call this method when db version to upgrade
}
}
MyProvider:
/**
* Created by sunnyDay on 2019/6/28 11:39
*/
public class MyProvider extends ContentProvider {
private static final String authority = "com.example.contentresloverdemo.provider"; //authority strings from manifest file
public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + authority + "book");
public static final Uri USER_CONTENT_URI = Uri.parse("content://" + authority + "user");
private static final int BOOK_URI_CODE = 0;
private static final int USER_URI_CODE = 1;
// uri 和code 建立联系 。建立联系后我们可以根据uri的到code值,根据code值 的到数据表名称
private static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); //uriMatcher 对象 参数为UriMatcher 内部提供的常量
static {
uriMatcher.addURI(authority, "book", BOOK_URI_CODE);// 代表 BOOK_CONTENT_URI
uriMatcher.addURI(authority, "user", USER_URI_CODE); // 代表 USER_CONTENT_URI
}
private SQLiteDatabase mDB;
/**
* 根据 uri 获得表名
*/
private String getTableName(Uri uri) {
String tableName = null;
switch (uriMatcher.match(uri)) { //int match(uri)
case BOOK_URI_CODE:
tableName = DbOpenHelper.BOOK_TABLE_NAME;
break;
case USER_URI_CODE:
tableName = DbOpenHelper.USER_TABLE_NAME;
break;
default:
break;
}
return tableName;
}
@Override
public boolean onCreate() {
mDB = new DbOpenHelper(getContext()).getReadableDatabase();
mDB.execSQL("insert into book values (3,'android')");
mDB.execSQL("insert into book values (4,'java')");
mDB.execSQL("insert into user values (1,'tom',20)");
mDB.execSQL("insert into user values (2,'kate',20)");
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
String tableName = getTableName(uri);//获得用户想要访问的表名
if (tableName == null) {
throw new ArithmeticException("un support uri:" + uri);
}
return mDB.query(tableName, projection, selection, selectionArgs, null, null, sortOrder, null);
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
String tableName = getTableName(uri);//获得用户想要访问的表名
if (tableName == null) {
throw new ArithmeticException("un support uri:" + uri);
}
mDB.insert(tableName,null,values);
getContext().getContentResolver().notifyChange(uri,null);
return uri;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
String tableName = getTableName(uri);//获得用户想要访问的表名
if (tableName == null) {
throw new ArithmeticException("un support uri:" + uri);
}
int count = mDB.delete(tableName,selection,selectionArgs);
if (count>0){
getContext().getContentResolver().notifyChange(uri,null);
}
return count;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
String tableName = getTableName(uri);//获得用户想要访问的表名
if (tableName == null) {
throw new ArithmeticException("un support uri:" + uri);
}
int row = mDB.update(tableName,values,selection,selectionArgs);
if (row>0){
getContext().getContentResolver().notifyChange(uri,null);
}
return row;
}
}
mainActivity:进行测试:
/**
* 自定义 内容提供者逻辑处理
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private void customProvider() {
queryBook(); // 查询book表
queryUser();// 查询user 表
}
@RequiresApi(api = Build.VERSION_CODES.O)
private void queryUser() {
Uri uri = Uri.parse("content://com.example.contentresloverdemo.provider/user");
Cursor cursor = getContentResolver().query(uri,
null,
null,
null);
if (null != cursor){
while(cursor.moveToNext()){
String userName = cursor.getString(cursor.getColumnIndex("name"));
String userid = cursor.getString(cursor.getColumnIndex("id"));
Log.i("aaa", "customProvider: "+userid+userName);
}
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
private void queryBook() {
Uri uri = Uri.parse("content://com.example.contentresloverdemo.provider/book");
Cursor cursor = getContentResolver().query(uri,
null,
null,
null);
if (null != cursor){
while(cursor.moveToNext()){
String bookName = cursor.getString(cursor.getColumnIndex("name"));
String bookid = cursor.getString(cursor.getColumnIndex("id"));
Log.i("aaa", "customProvider: "+bookid+bookName);
}
}
}
效果:
参考总结于:
《第一行代码》第七章
《安卓开发艺术探索》第2.4.5章节
code