ContentProvider是Android四大组件之一,它为存储和获取数据提供统一的接口。即将应用程序的数据提供给外部程序使用的一种手段和方法。最大特点是可以实现不同的应用程序之间共享数据。
对于ContentProvider来说,无论数据的来源是什么,他都认为是一种表,它会把数据组织成表格。
两种使用情景:1.A应用访问B应用,2A应用访问系统应用(通讯录)
1A应用访问B应用:(主要是实现定义一个ContentProvider的操作)
(1)自定义一个类继承于ContentProvider,并且实现它的方法:注意操作步骤
想要建立文件的地方->右键->new->other->Content Provider->修改名称,填写唯一的uri->完成。
public class OutContentProvider extends ContentProvider {
public OutContentProvider() {
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Implement this to handle requests to delete one or more rows.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public String getType(Uri uri) {
// TODO: Implement this to handle requests for the MIME type of the data
// at the given URI.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO: Implement this to handle requests to insert a new row.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public boolean onCreate() {
// TODO: Implement this to initialize your content provider on startup.
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO: Implement this to handle query requests from clients.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO: Implement this to handle requests to update one or more rows.
throw new UnsupportedOperationException("Not yet implemented");
}
}
然后看一下manifest.xml:
<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">
<provider
android:name=".OutContentProvider"
android:authorities="com.stone.mycontentprovider.outcp"
android:enabled="true"
android:exported="true"></provider>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
当然也可以先自定义个类,然后继承ContentProvider,实现他的方法,然后配置mainfest.xml。
ok,下一步。
(2)由于这是一个简单的使用步骤,所以就简单的按照sqlite数据库来写,当然数据的来源还可以是其他的任何的一种,这里只是举个例子
由于ContentProvider的创建是在应用第一次打开的时候就创建的,所以为了方便吧数据也在ContentProvider中创建了
SQLiteDatabase sqLiteDatabase;
@Override
public boolean onCreate() {
// TODO: Implement this to initialize your content provider on startup.
//创建数据库
Log.e("###", "onCreate");
SQLiteOpenHelper helper = new SQLiteOpenHelper(getContext(), "outcp.db", null, 1) {
@Override
public void onCreate(SQLiteDatabase db) {
//创建表格
String sql = "create table demo_table(_id integer primary key autoincrement," +
"name varchar(20)," +
"age integer," +
"gender varchar(2))";
db.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
};
sqLiteDatabase = helper.getReadableDatabase();
//返回true,这样才能会返回数据
return true;
}
注意返回值改成true。
(3)insert()方法:增加数据,在OutContentProvider中
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO: Implement this to handle requests to insert a new row.
// throw new UnsupportedOperationException("Not yet implemented");
Long itemId = 0l;
try {
itemId = sqLiteDatabase.insertOrThrow("demo_table", null, values);
} catch (Exception e) {
Log.e("###", e.getLocalizedMessage());
}
Log.e("###", "调用了out中的insert方法");
//表名,非空列,值,对于sqlite数据库,添加完成之后会返回一个Long的id
//uri这个uri就是另外的程序调用contentprovider的时候传过来的uri,如果传回去那么跟调用程序的uri没有区别也没有意义
// 所以用ContentUris把uri和itemid整合一下,成为一个新的Uri,返回回去。
return ContentUris.withAppendedId(uri, itemId);
}
注意:这里的 try {}catch((Exception e){} 可以不写,insertOrThrow可以改成insert。另外ContentUris是处理uri的一个类, ContentUris.withAppendedId(uri, itemId)是吧uri追加一个id作为返回值,为了方便调用者获取到添加成功的状态和id。
(4)insert()方法的调用:这里需要新建一个其他的项目,在调用项目的合适的位置写一个调用方法。这里写了一个textview的点击监听事件:
Uri mUri = Uri.parse("content://com.stone.mycontentprovider.outcp");
ContentResolver resolver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获取ContentResolver对象
resolver = getContentResolver();
}
public void insertTv(View view) {
ContentValues values = new ContentValues();
values.put("name", MyUtils.getChineseName());
values.put("age", MyUtils.getNum(10, 60));
values.put("gender", MyUtils.getGender());
Uri reUri = resolver.insert(mUri, values);
Long reId = ContentUris.parseId(reUri);
Toast.makeText(MainActivity.this, reId + "", Toast.LENGTH_LONG).show();
}
注意:这里的uri就是ContentProvider提供者的insert方法返回的我们添加了id的那个uri,同样的我们使用ContentUris这个类的 ContentUris.parseId(reUri);方法拿到添加成功的数据的id。
(5)query()查询方法:
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO: Implement this to handle query requests from clients.
// throw new UnsupportedOperationException("Not yet implemented");
Cursor cursor = sqLiteDatabase.query("demo_table", projection, selection, selectionArgs, null, null, sortOrder);
return cursor;
}
(6)调用查询方法:
SimpleCursorAdapter simpleCursorAdapter;
ListView mListView;
public void queryTv(View view) {
// 根据contentprovider中的查询方法:Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder)
//rui,查询哪一列,查询条件,条件之,排序
Cursor cursor = resolver.query(mUri, null, null, null, null);
//这里的listview的adapter用到SimpleCursorAdapter
simpleCursorAdapter = new SimpleCursorAdapter(this,
R.layout.layout_adapter_cursor,
cursor,
new String[]{"_id", "name", "age", "gender"},
new int[]{R.id.item_id, R.id.item_name, R.id.item_age, R.id.item_gender},
CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
mListView.setAdapter(simpleCursorAdapter);
}
注意这里直接用的SimpleCursorAdapter其中的参数分别是:SimpleCursorAdapter(context,layout,cursor,键名的字符串数组,对应的控件的id,标志).关于SimpleCursorAdapter有一篇博客可以看一下
(7)delete():删除方法
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Implement this to handle requests to delete one or more rows.
// throw new UnsupportedOperationException("Not yet implemented");
int result = sqLiteDatabase.delete("demo_table", selection, selectionArgs);
return result;
}
(8)调用删除方法:
int delResult = resolver.delete(mUri, "_id=?", new String[]{itemId});
if (delResult > 0) {
Toast.makeText(MainActivity.this, "删除成功", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(MainActivity.this, "删除灰白", Toast.LENGTH_LONG).show();
}
(9)修改updata()方法
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO: Implement this to handle requests to update one or more rows.
// throw new UnsupportedOperationException("Not yet implemented");
int upReslut = sqLiteDatabase.update("demo_table", values, selection, selectionArgs);
return upReslut;
}
(10)调用修改方法:
ContentValues upValues = new ContentValues();
upValues.put("name", MyUtils.getChineseName());
upValues.put("age", MyUtils.getNum(10, 60));
upValues.put("gender", MyUtils.getGender());
int upResult=resolver.update(mUri,upValues,"_id=?",new String[]{itemId});
if (upResult>0){
Toast.makeText(MainActivity.this, "修改成功", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(MainActivity.this, "修改灰白", Toast.LENGTH_LONG).show();
}
ok,以上记录的是设置ContentProvider的方法,和相应的方法的调用然后再看一下关于Uri的解析。
Uri解析有两种方法,一种是使用UriMatcher解析,一种是Uri自带的解析。
(一)UriMatcher解析:事先制定好匹配规则,当调用ContentProvide中的方法时,利用制定好的匹配规则匹配uri,根据不同的uri给出不同的处理:以删除为例在oncreat中制定规则:
UriMatcher matcher;
@Override
public boolean onCreate() {
// TODO: Implement this to initialize your content provider on startup.
//闯将数据库
Log.e("###", "onCreate");
//匹配规则:
//UriMatcher.NO_MATCH:无法匹配
matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI("com.stone.mycontentprovider.outcp", "democp", 1000);
matcher.addURI("com.stone.mycontentprovider.outcp", "democp/child", 1001);
matcher.addURI("com.stone.mycontentprovider.outcp", "democp/#", 1002);//后面是数字
matcher.addURI("com.stone.mycontentprovider.outcp", "democp/*", 1003);//任意字符
//返回true,这样才能会返回数据
return true;
}
注意:其中的democp可以使任意的字符串,也可以没有。
删除方法:
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Implement this to handle requests to delete one or more rows.
// throw new UnsupportedOperationException("Not yet implemented");
int result = 0;
int code = matcher.match(uri);
switch (code) {
case 1000:
Log.e("###match","匹配到的路径是"+uri.toString());
break;
case 1001:
Log.e("###match","匹配到的路径是"+uri.toString());
break;
case 1002:
Log.d("###match","匹配到路径为"+uri.toString()+"任意数字的内容");
break;
case 1003:
Log.i("###match","匹配到路径为"+uri.toString()+"任意字符的内容");
break;
default:
Log.e("###match","执行删除数据库内容的方法");
result = sqLiteDatabase.delete("demo_table", selection, selectionArgs);
break;
}
return result;
}
那么调用删除方法:
resolver.delete(Uri.parse("content://com.stone.mycontentprovider.outcp/democp"),null,null);
resolver.delete(Uri.parse("content://com.stone.mycontentprovider.outcp/democp/child"),null,null);
resolver.delete(Uri.parse("content://com.stone.mycontentprovider.outcp/democp/123"),null,null);
resolver.delete(Uri.parse("content://com.stone.mycontentprovider.outcp/democp/090"),null,null);
resolver.delete(Uri.parse("content://com.stone.mycontentprovider.outcp/democp/89ii"),null,null);
resolver.delete(Uri.parse("content://com.stone.mycontentprovider.outcp/democp/ab90"),null,null);
resolver.delete(Uri.parse("content://com.stone.mycontentprovider.outcp/123/ab90"),null,null);
运行结果:
E/###match: 匹配到的路径是content://com.stone.mycontentprovider.outcp/democp
E/###match: 匹配到的路径是content://com.stone.mycontentprovider.outcp/democp/child
D/###match: 匹配到路径为content://com.stone.mycontentprovider.outcp/democp/123任意数字的内容
D/###match: 匹配到路径为content://com.stone.mycontentprovider.outcp/democp/090任意数字的内容
I/###match: 匹配到路径为content://com.stone.mycontentprovider.outcp/democp/89ii任意字符的内容
I/###match: 匹配到路径为content://com.stone.mycontentprovider.outcp/democp/ab90任意字符的内容
E/###match: 执行删除数据库内容的方法
(二)Uri自带的解析方法:以添加为例:
调用添加方法:
Uri othUri=resolver.insert(Uri.parse("content://com.stone.mycontentprovider.outcp/anypath?name="+MyUtils.getChineseName()
+"&age="+MyUtils.getNum(10, 60)
+"&gender="+ MyUtils.getGender()),new ContentValues());
注意其中的anypath可以使任意的字符串,也可以不要。
MyContentProvider中的添加方法:
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO: Implement this to handle requests to insert a new row.
// throw new UnsupportedOperationException("Not yet implemented");
Long itemId = 0l;
if (values.size()>0){
try {
itemId = sqLiteDatabase.insertOrThrow("demo_table", null, values);
} catch (Exception e) {
Log.e("###", e.getLocalizedMessage());
}
}else {
String authory=uri.getAuthority();
Log.e("###authory",authory);
String path=uri.getPath();
Log.e("###path",path);
String query=uri.getQuery();
Log.e("###query",query);
String name=uri.getQueryParameter("name");
Log.e("###name",name);
String age=uri.getQueryParameter("age");
Log.e("###age",age);
String gender=uri.getQueryParameter("gender");
Log.e("###gender",gender);
values.put("name", name);
values.put("age",age);
values.put("gender",gender);
itemId = sqLiteDatabase.insertOrThrow("demo_table", null, values);
}
Log.e("###", "调用了out中的insert方法");
//表名,非空列,值,对于sqlite数据库,添加完成之后会返回一个Long的id
//uri这个uri就是另外的程序调用contentprovider的时候传过来的uri,如果传回去那么跟调用程序的uri没有区别也没有意义
// 所以用ContentUris把uri和itemid整合一下,成为一个新的Uri,返回回去。
return ContentUris.withAppendedId(uri, itemId);
}
运行结果:
2020-07-23 20:07:33.190 15996-16123/com.stone.mycontentprovider E/###: 调用了out中的insert方法
2020-07-23 20:07:33.246 15996-16123/com.stone.mycontentprovider E/###authory: com.stone.mycontentprovider.outcp
2020-07-23 20:07:33.246 15996-16123/com.stone.mycontentprovider E/###path: /anypath
2020-07-23 20:07:33.247 15996-16123/com.stone.mycontentprovider E/###query: name=咸桂&age=45&gender=男
2020-07-23 20:07:33.247 15996-16123/com.stone.mycontentprovider E/###name: 咸桂
2020-07-23 20:07:33.248 15996-16123/com.stone.mycontentprovider E/###age: 45
2020-07-23 20:07:33.248 15996-16123/com.stone.mycontentprovider E/###gender: 男
2020-07-23 20:07:33.256 15996-16123/com.stone.mycontentprovider E/###: 调用了out中的insert方法
两种添加方法都执行了,所以打印了两边“” 调用了out中的insert方法“”。
ok关于解析就简单记录这些东西。下面简单说一下调用系统ContentProvider的问题:
(一)读取短信息:基本分三个步骤:
(1)获取ContentResolver
ContentResolver contentResolver = getContentResolver();
(2)查询短信息
Uri smsUri = Uri.parse("content://sms/");
Cursor smsCoursor = contentResolver.query(smsUri, null, null, null, null);
(3)遍历smsCoursor ,得到每一条的数据
//遍历所有的行
while (smsCoursor.moveToNext()) {
//参数是列索引,遍历所有的列
for (int i = 0; i < smsCoursor.getColumnCount(); i++) {
Log.e("###", smsCoursor.getString(i));//此时获取到的是每一条的所有内容
String address=cursor.getString(cursor.getColumnIndex("address"));//地址
String body=cursor.getString(cursor.getColumnIndex("body"));//短信内容
String dateString=cursor.getString(cursor.getColumnIndex("date"));//日期
}
}
(4)注意点:
"content://sms/":短信箱
"content://sms/inbox":收件箱
"content://sms/sent":发件箱
"content://sms/draft":草稿箱
(5)权限不要忘了动态申请,manifest也要注册。
<uses-permission android:name="android.permission.READ_SMS"/>
(6)还有我的红米手机获取不到短信息,查了资料说是没有权限,现在还没有解决,希望各位看官能给个指导意见和方法。华为的可以。
(二)读取联系人:通讯录中的姓名和其他内容是由不同的contentprovider提供的。也就是说姓名和其他内容是不同的表,姓名是主表,其他内容是从表,主表中的主键会在从表中作为外键使用
(1)权限:动态申请
<uses-permission android:name="android.permission.READ_CONTACTS"/>
(2)读取数据
ContentResolver resolver;
resolver = getContentResolver();
//先读取联系人信息
Cursor cCursor = resolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
while (cCursor.moveToNext()) {
// ContactsContract.Contacts.DISPLAY_NAME//姓名
// ContactsContract.Contacts._ID//id
//根据读取到的联系人信息遍历Cursor获取每一个联系人的姓名和id
String name = cCursor.getString(cCursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
String mId = cCursor.getString(cCursor.getColumnIndex(ContactsContract.Contacts._ID));
Log.e("###name", name + " ");
Log.e("###mId", mId + " ");
//根据id获取对应的联系人的其他信息
String selections = ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=?";//查询条件
Cursor phoneCursor = resolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, selections, new String[]{mId}, null);
//变美丽其他信息
while (phoneCursor.moveToNext()) {
//获取电话号码
String phoneNum = phoneCursor.getString(phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
name += " " + phoneNum;
}
//这个时候的log打印的就是姓名和联系电话
Log.e("###name", name + " ");
}
(三)添加联系人:上面我们知道了联系人姓名和电话是在两个表中,所以写入联系人也需要添加两次:
(1)权限:WRITE_CONTACTS,动态的你懂的
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
(2)写入联系人
//1.往一个contentprovider中插入一条空数据,获取新生成的id
ContentValues values = new ContentValues();
Uri uri = resolver.insert(ContactsContract.RawContacts.CONTENT_URI, values);
long id = ContentUris.parseId(uri);//获取到刚刚生成的id
//2.利用刚刚获取到的id分别组合姓名和电话号码往另外一个contentprovider中插入数据
//插入姓名
values.put(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME,MyUtils.getChineseName());//插入的姓名
values.put(ContactsContract.Data.RAW_CONTACT_ID,id);//指定和姓名关联的id
values.put(ContactsContract.Data.MIMETYPE,ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);//设置该行数据的类型
resolver.insert(ContactsContract.Data.CONTENT_URI,values);//插入操作
//插入电话
values.clear();
values.put(ContactsContract.Data.RAW_CONTACT_ID,id);//指定和电话关联的id
values.put(ContactsContract.CommonDataKinds.Phone.NUMBER,"13788883333");//插入的电话
values.put(ContactsContract.CommonDataKinds.Phone.TYPE,ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);//插入的电话的类型
values.put(ContactsContract.Data.MIMETYPE,ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);//设置该行数据的类型
resolver.insert(ContactsContract.Data.CONTENT_URI,values);//插入操作