内容提供器(ContentProvider)主要用于不同的程序之间实现数据共享的功能,并保证被访数据的安全性。使用内容提供器是Android是实现跨程序共享数据的标准方式。
内容提供器的用法一般有两种:
- 使用现有的内容提供器来读取和操作相应程序中的数据
- 创建自己的内容提供器给我们程序的数据提供外部访问的接口
想访问内容提供器中共享的数据,要借助工具类ContentResolver类。可通过Context中的getContentResolver()方法获取该类的实例。
ContentResolver类中提供了一系列方法对数据进行CRUD操作:
- insert()用于添加数据.
- update()用于更新数据.
- delete()用于删除数据.
- query()用于查询数据.
使用步骤:
-
ContentResolver中的增删改查方法都不接收表名参数,而使用一个Uri参数代替,这个参数称为内容URI.
内容URI为内容提供器的数据建立了唯一的标识符.
Uri由authority和path组成。其中authority为“包名.provider”,path为表名。标准的Uri如下:content://com.example.app.provider/table1
-
获取的URI字符串需要解析为URI对象:
Uri uri = Uri.parse("content://com.example.app.provider/table1");
-
利用Uri对象查询数据
Cursor cursor = getContentResolver().query(uri,projection,selection,selectionArgs,sortOrder); if (cursor != null) { while (cursor.moveToNext()) { String column1 = cursor.getString(cursor.getColumnIndex("column1")); int column2 = cursor.getInt(cursor.getColumnIndex("column2")); } cursor.close(); }
填加:
ContentValues values = new ContentValues();
values.put("column1", "text");
values.put("column2", 1);
getContentResolver().insert(uri, values);
修改:
ContentValues values = new ContentValues();
values.put("column1", "");
getContentResolver().update(uri, values, "column1 = ? and column2 = ?", newString[] {"text", "1"});
删除:
getContentResolver().delete(uri, "column2 = ?", new String[] { "1" });
实例:用内容提供器读取系统联系人
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/contacts_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
MainActivity
public class MainActivity extends AppCompatActivity {
ArrayAdapter<String> adapter;
List<String> contactList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获取ListView实例
ListView contactsView = (ListView)findViewById(R.id.contacts_view);
//生成适配器,提供参数 上下文、ListView子项Item、ListView中数据
adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,contactList);
//给ListView设置适配器
contactsView.setAdapter(adapter);
//判断用户是否已经授权执行读取通讯录的操作,借助ContextCompat.checkSelfPermission()方法
if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED){
/*调用运行时权限的处理逻辑,即在程序运行时请求用户授权,这里请求READ_CONTACTS的权限
参数1:Activity实例; 参数2:请求权限的数组; 参数3:请求码(要唯一值)*/
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_CONTACTS},1);
}else {
//若已授权,则直接读取
readContacts();
}
}
private void readContacts(){
Cursor cursor = null;
try{
//查询联系人数据
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);
if(cursor != 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));
contactList.add(displayName + "\n" + number);
}
//刷新ListView
adapter.notifyDataSetChanged();
}
}catch (Exception e){
e.printStackTrace();
}finally {
//一定要close cursor
if(cursor != null)
cursor.close();
}
}
//运行时请求权限弹出选择框 选择的回调方法
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
//requestCode就是请求时传入的唯一值请求码
switch (requestCode){
case 1:
//请求权限数组对应的请求结果返回数组,一一对应
if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
readContacts();
}else {
Toast.makeText(this,"You denied the permission",Toast.LENGTH_SHORT).show();
}
}
}
}
Manifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.contactstest">
//最重要的是在Manifest文件中,声明读取系统联系人的权限!!!
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<application
...
</application>
</manifest>
创建自己的内容提供器
可以通过新建一个类去继承ContentProvider的方式来创建一个自己的内容提供器。
在项目中右击程序包->New->Other->Content Provider可新建内容提供器。
需要重写以下6个方法:
- onCreate():初始化内容提供器时调用,用于数据库的创建和升级等操作返回true则初始化成功,false则失败.只有ContentResolver尝试访问程序中的数据时,内容提供器才会被初始化.
- query():使用 uri 参数来确定查询哪张表, projection 参数用于确定查询哪些列,selection 和 selectionArgs 参数用于约束查询哪些行,sortOrder 参数用于对结果进行排序, 查询的结果存放在 Cursor 对象中返回.
- insert():向内容提供器中添加一条数据,使用 uri 参数来确定要添加到的表,待添加的数据保存在 values 参数中,添加完成后,返回一个用于表示这条新记录的 URI.
- update():更新内容提供器中已有的数据.使用 uri 参数来确定更新哪一张表中的数据,新数据保存在 values 参数中,,selection 和 selectionArgs 参数用于约束更新哪些行,,受影响的行数将作为返回值返回.
- delete():从内容提供器中删除数据.使用 uri 参数来确定删除哪一张表中的数据,和 selectionArgs 参数用于约束删除哪些行, 被删除的行数将作为返回值返回.
- getType():根据传入的内容 URI 来返回相应的 MIME 类型.
URI写法:
访问com.example.app这个app中的table1表
content://com.example.app.provider/table1
访问com.example.app这个app中table1表中id为1的数据
content://com.example.app.provider/table1/1
通配符匹配:
*: 表示匹配任意长度的任意字符串
#: 表示匹配任意长度的数据
content://com.example.app.provider/* 能匹配任意表的内容
content://com.example.app.provider/table1/# 能匹配table1 表中任意一行数据的内容
需要借助UriMatcher类来 实现匹配URI的功能
UriMatcher提供了addURI(),接受三个参数(权限、路径、自定义代码).
UriMatcher还提供了match()方法,返回一个匹配这个Uri对象所对应的自定义代码。
public class DatabaseProvider extends ContentProvider {
public static final int BOOK_DIR = 0;
public static final int BOOK_ITEM = 1;
public static final int CATEGORY_DIR = 2;
public static final int CATEGORY_ITEM = 3;
public static final String AUTHORITY = "com.example.a123.databasetest.provider";
private static UriMatcher uriMatcher;
private MyDatabaseHelper dbHelper;
static {
//获得UriMatcher实例
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY,"book",BOOK_DIR);
uriMatcher.addURI(AUTHORITY,"book/#",BOOK_ITEM);
uriMatcher.addURI(AUTHORITY,"category",CATEGORY_DIR);
uriMatcher.addURI(AUTHORITY,"category/#",CATEGORY_ITEM);
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = null;
//match()方法,返回对应的自定义代码
switch(uriMatcher.match(uri)){
case BOOK_DIR:
cursor = db.query("Book",projection,selection,selectionArgs,null,null,sortOrder);
break;
case BOOK_ITEM:
//getPathSegments(),将内容URI 权限之后的部分以"/"符号进行分割,得到字符串列表
String bookID = uri.getPathSegments().get(1);
cursor = db.query("Book",projection,"id = ?",new String[]{bookID},null,null,sortOrder);
break;
case CATEGORY_DIR:
cursor = db.query("Category",projection,selection,selectionArgs,null,null,sortOrder);
break;
case CATEGORY_ITEM:
String categoryID = uri.getPathSegments().get(1);
cursor = db.query("Category",projection,"id = ?",new String[]{categoryID},null,null,sortOrder);
}
return cursor;
}
}
getType()方法,用于获取uri对象所对应的MIME类型。一个内容URI所对应的MIME字符串主要由3部分组成
-
必须以vnd开头
-
如果内容 URI 以路径结尾,则后接 android.cursor.dir/,如果内容 URI 以 id 结尾,则后接 android.cursor.item/
-
最后接上 vnd.<authority>.<path>
@Override public String getType(Uri uri) { switch (uriMatcher.match(uri)){ case BOOK_DIR: return "vnd.android.cursor.dir/vnd.com.example.a123.databasetest.provider.book"; case BOOK_ITEM: return "vnd.android.cursor.item/vnd.com.example.a123.databasetest.provider.book"; case CATEGORY_DIR: return "vnd.android.cursor.dir/vnd.com.example.a123.databasetest.provider.category"; case CATEGORY_ITEM: return "vnd.android.cursor.item/vnd.com.example.a123.databasetest.provider.category"; } return null; }
然后在项目中Manifest.xml注册
<provider
android:name=".DatabaseProvider"
android:authorities="com.example.a123.databasetest.provider"
android:enabled="true"
android:exported="true">
</provider>
这样别的程序就能通过Uri来共享数据了!
Uri uri = Uri.parse("content://com.example.a123.databasetest.provider/book");
ContentValues values = new ContentValues();
values.put("name","A Clash of Kings");
values.put("author","George Martin");
values.put("pages",1040);
values.put("price",55.55);
Uri newUri = getContentResolver().insert(uri,values);
网友评论