目录
- ContentProvider 是啥
- Google为啥要有这个东西
- 如何使用
- URI 是什么
- 如何自定义一个ContentProvider
- ContentUris使用
- ContentUris使用
- 项目示例(具体如何写)
- 数据共享(这才是我们的目的)
一.ContentProvider 是啥??
同为4大组件之一,ContentProvider一般为存储和获取数据提供统一的接口,可以在不同的应用程序之间共享数据。
简单点来说,他和数据库啥的差不多,都是对数据进行处理
二.Google为啥要有这个东西?
就有大兄弟问了,直接使用 数据库啥的 不一样嘛。Google 搞这么个东西干嘛
换个问法,Android为什么要设计ContentProvider这个组件吗?
1.封装。对数据进行封装,提供统一的接口,使用者完全不必关心这些数据是在DB,XML、Preferences或者网络请求来的。当项目需求要改变数据来源时,使用我们的地方完全不需要修改。
2.提供一种跨进程数据共享的方式。
三.如何使用
先把两个东西拿出来
- ContentProvider 内容提供者
- ContentResolver 内容消化器
假设我们要获取 手里的联系人
Cursor cursor = getContentResolver().query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null, null, null, null);
while (cursor.moveToNext()) {
String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
Log.e("TAG", displayName + "," + number);//联系人姓名 + 手机号
}
//打印出来的
//allens,188 5690 5555
//华为客服,4008308300
当然别忘了权限,如果是6.0以上的设备还需要动态申请需求权限,之后的文章会说明
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
说明
我们使用getContentResolver
获取到了ContentResolver
,然后通过ContentResolver
提供的query
方法,传入需要查询的URI
,得到含有数据集合的cursor
四.URI 是什么????
Uri
代表了要操作的数据,为系统的每一个资源给其一个名字,比方说通话记录,每一个ContentProvider
都拥有一个公共的URI
,这个URI
用于表示这个ContentProvider
所提供的数据。
URI 的组成
一个完成的URI- A:标准前缀
用来说明一个Content Provider控制这些数据,无法改变的;content://
- B:URI 的标识
用于唯一标识这个ContentProvider
,外部调用者可以根据这个标识来找到它。它定义了是哪个Content Provider
提供这些数据。对于第三方应用程序,为了保证URI
标识的唯一性,它必须是一个完整的、小写的类名。
这个标识在 元素的authorities
属性中说明:一般是定义该ContentProvider
的包.类的名称 - C:路径(path)
通俗的讲就是你要操作的数据库中表的名字,或者你也可以自己定义,记得在使用的时候保持一致就可以了;content://com.bing.provider.myprovider/tablename
- D:记录的ID
如果URI中包含表示需要获取的记录的ID;则就返回该id对应的数据,如果没有ID,就表示返回全部;content://com.bing.provider.myprovider/tablename/#
#表示数据id。
这样的话,基本上我们就知道了如何通过URI 获取到指定的信息了,
举个例子获取手机音乐
public class SoundModel implements SoundContract.Model {
@Override
public ArrayList<String> getSoundData(SoundAct soundAct) {
String[] projection = {
MediaStore.Audio.Media._ID,
MediaStore.Audio.Media.DISPLAY_NAME,
MediaStore.Audio.Media.DATA,
MediaStore.Audio.Media.ALBUM,
MediaStore.Audio.Media.ARTIST,
MediaStore.Audio.Media.DURATION,
MediaStore.Audio.Media.SIZE
};
ArrayList<String> list = new ArrayList<>();
ContentResolver contentResolver = soundAct.getContentResolver();
Cursor cursor = contentResolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, null, null, null);
assert cursor != null;
while (cursor.moveToNext()) {
int displayNameCol = cursor.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME);
int urlCol = cursor.getColumnIndex(MediaStore.Audio.Media.DATA);
String title = cursor.getString(displayNameCol);
String url = cursor.getString(urlCol);
list.add(title + "," + url);
}
return list;
}
}
五.如何自定义一个ContentProvider
1.Android系统提供了两个用于操作Uri的工具类,UriMatche
和ContentUris
1.1 ContentUris使用
UriMatcher类用于匹配Uri
//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码
UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//如果match()方法匹配content://com.bing.procvide.personprovider/person路径,返回匹配码为1
sMatcher.addURI("com.bing.procvide.personprovider", "person", 1);//添加需要匹配uri,如果匹配就会返回匹配码
//如果match()方法匹配content://com.bing.provider.personprovider/person/230路径,返回匹配码为2
sMatcher.addURI("com.bing.provider.personprovider", "person/#", 2);//#号为通配符
switch (sMatcher.match(Uri.parse("content://com.ljq.provider.personprovider/person/10"))) {
case 1
break;
case 2
break;
default://不匹配
break;
}
1.2 ContentUris使用
ContentUris类用于操作Uri路径后面的ID部分
- withAppendedId(uri, id)
用于为路径加上ID部分:
Uri uri = Uri.parse("content://com.bing.provider.personprovider/person")
Uri resultUri = ContentUris.withAppendedId(uri, 10);
//生成后的Uri为:content://com.bing.provider.personprovider/person/10
- parseId(uri)
方法用于从路径中获取ID部分:
Uri uri = Uri.parse("content://com.ljq.provider.personprovider/person/10")
long personid = ContentUris.parseId(uri);//获取的结果为:10
2.这才是真正开始写,淡淡忧伤
项目类我就写了4个类,先说明一下,最后会附上源码
- DBHelper
public class DBHelper extends SQLiteOpenHelper {
public DBHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(T_UserInfo);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
//使用者登录信息
private static final String T_UserInfo =
"create table T_UserInfo("
+ "id varchar,"//id
+ "account varchar,"//手动输入的账号
+ "Name varchar,"//账号姓名
+ "pwd varchar)";//pwd
}
- DBUtil
public class DBUtil {
public static SQLiteDatabase db(Context context) {
return new DBHelper(context, "text.db", null, 1).getWritableDatabase();
}
}
上面两个不重要,说一下MyProvider
public class MyProvider extends ContentProvider {
private SQLiteDatabase db;
private UriMatcher uriMatcher;
private static final String AUTHORITY = "com.allens.test.MyProvider"; // 包名 + 类名 在清单文件中也是一样的
public static final String TABLE = "T_UserInfo"; // 数据库 表名字
//两个类型 自己随意写的
private static final int PERSON_ALL = 0;
private static final int PERSON_ONE = 1;
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + TABLE); // 对外的URI 之前说的 URI 主成的部分
@Override
public boolean onCreate() {
db = DBUtil.db(getContext());
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//如果match()方法匹配content://com.com.allens.test.MyProvider/T_UserInfo,返回匹配码为1
//添加需要匹配uri,如果匹配就会返回匹配码
uriMatcher.addURI(AUTHORITY, TABLE, PERSON_ALL);
//如果match()方法匹配content://com.allens.test.MyProvider/T_UserInfo/230路径,返回匹配码为2
//#号为通配符
uriMatcher.addURI(AUTHORITY, TABLE + "/#", PERSON_ONE);
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
Cursor cursor = null;
int code = uriMatcher.match(uri);
if (code == PERSON_ALL) {
//就是数据库查询,只不过使用ConentProvider再一次封装了
cursor = db.query(
TABLE,
projection, selection,
selectionArgs, null, null, sortOrder);
} else if (code == PERSON_ONE) {
// 从uri中取出id
long id = ContentUris.parseId(uri);
cursor = db.query(
TABLE,
new String[]{"id", "account", "name", "pwd"}, "id = ?",
new String[]{String.valueOf(id)}, null, null, sortOrder);
}
return cursor;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
int match = uriMatcher.match(uri);
Log.e("TAG", "getType---->" + match);
switch (match) {
case PERSON_ALL:
break;
case PERSON_ONE:
break;
default://不匹配
//throw是处理异常的,java中的语法
throw new IllegalArgumentException("查询不到 URI" + uri);
}
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
long rowId = db.insert(TABLE, null, values);
if (rowId > 0) {
//发出通知给监听器,说明数据已经改变
Uri insertedUserUri = ContentUris.withAppendedId(CONTENT_URI, rowId);
getContext().getContentResolver().notifyChange(insertedUserUri, null);
return insertedUserUri;
}
throw new SQLException("Failed to insert row into" + uri);
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return db.delete(TABLE, selection, selectionArgs);
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return db.update(TABLE, values, selection, selectionArgs);
}
}
- Activity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_insert).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ContentValues values = new ContentValues();
values.put("id", "1");
values.put("account", "account1");
values.put("name", "allens");
values.put("pwd", "123456");
getContentResolver().insert(MyProvider.CONTENT_URI, values);
}
});
findViewById(R.id.btn_delete).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getContentResolver().delete(MyProvider.CONTENT_URI, "name = ?", new String[]{"allens"});
}
});
findViewById(R.id.btn_update).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ContentValues values = new ContentValues();
values.put("pwd", "654321");
getContentResolver().update(MyProvider.CONTENT_URI, values, "name = ?", new String[]{"allens"});
}
});
findViewById(R.id.btn_query).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/***
* 使用ContentProvider
*/
Cursor cursor = getContentResolver().query(MyProvider.CONTENT_URI, new String[]{"id", "account", "name", "pwd"}, null, null, null);
while (cursor.moveToNext()) {
Log.e("TAG", "id--->" + cursor.getString(0));
Log.e("TAG", "account--->" + cursor.getString(1));
Log.e("TAG", "name--->" + cursor.getString(2));
Log.e("TAG", "pwd--->" + cursor.getString(3));
}
}
});
}
}
- 清单文件
<!-- authorities 包名 + 类名 -->
<provider
android:name=".MyProvider"
android:authorities="com.allens.test.MyProvider" />
6.数据共享(这才是我们的目的)
刚刚我们写的都是只能在本应用中有用。其实没啥用。哈哈
首先说一下provider
的几个属性
属性 | 作用 |
---|---|
exported | 这个属性用于指示该服务是否能被其他程序应用组件调用或跟他交互;如果设置成true,则能够被调用或交互,否则不能;设置为false时,只有同一个应用程序的组件或带有相同用户ID的应用程序才能启动或绑定该服务 |
readPermission | 使用Content Provider的查询功能所必需的权限,即使用ContentProvider里的query()函数的权限; |
writePermission | 使用ContentProvider的修改功能所必须的权限,即使用ContentProvider的insert()、update()、delete()函数的权限; |
permission | 读写权限 |
注意
客户端读、写 Content Provider 中的数据所必需的权限名称。 本属性为一次性设置读和写权限提供了快捷途径。 不过,readPermission和writePermission属性优先于本设置。 如果同时设置了readPermission属性,则其将控制对 Content Provider 的读取。 如果设置了writePermission属性,则其也将控制对 Content Provider 数据的修改。也就是说如果只设置permission权限,那么拥有这个权限的应用就可以实现对这里的ContentProvider进行读写;如果同时设置了permission和readPermission那么具有readPermission权限的应用才可以读,拥有permission权限的才能写!也就是说只拥有permission权限是不能读的,因为readPermission的优先级要高于permission;如果同时设置了readPermission、writePermission、permission那么permission就无效了。
好啦,知道这个 就可以好好玩刷了
只需要修改一下我们的清单文件就好了
<provider
android:name=".MyProvider"
android:authorities="com.allens.test.MyProvider"
android:exported="true"
android:readPermission="com.allens.test.MyProvider.READ"
android:writePermission="com.allens.test.MyProvider.WRITE" />
既然自己写了权限那么肯定需要把权限申请下来
在 application标签的同级目录
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.allens.test">
<!-- 申请permission的代码 -->
<permission
android:name="com.allens.test.MyProvider.READ"
android:label="provider pomission"
android:protectionLevel="normal" />
<permission
android:name="com.allens.test.MyProvider.WRITE"
android:label="provider pomission"
android:protectionLevel="normal" />
</manifest>
其他怎么获取我提供的参数呢
首先 肯定是权限嘛
<uses-permission
android:name="com.allens.test.MyProvider.READ"
android:protectionLevel="normal" />
<uses-permission
android:name="com.allens.test.MyProvider.WRITE"
android:protectionLevel="normal" />
然后嘛
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Uri uri = Uri.parse("content://com.allens.test.MyProvider/T_UserInfo");// 对外的URI 之前说的 URI 主成的部分
Cursor cursor = getContentResolver().query(uri, new String[]{"id", "account", "name", "pwd"}, null, null, null);
while (cursor.moveToNext()) {
Log.e("TAG", "id--->" + cursor.getString(0));
Log.e("TAG", "account--->" + cursor.getString(1));
Log.e("TAG", "name--->" + cursor.getString(2));
Log.e("TAG", "pwd--->" + cursor.getString(3));
}
}
}
项目地址
写在最后
总觉得吧,写完一篇文章,总要说点上门,Android,现在再也不是以前那个回个Handler 就无敌的年代,身边很多小伙伴都觉得Android 就业很难,我觉得,只有正在的学好了,才是王道,先有深度,再有广度,这样最好
网友评论