本篇内容整理自郭霖的 《第一行代码》
访问其他应用中的数据——获取数据
ContentResolver 的基本用法
- 访问内容提供器中的共享数据就必要借助 ContentResolver 类
- 可以通过 Context 中的
getContentResolver()
方法获取实例 - 提供了一些列的 CRUD 操作,类似 SQLiteDatabase。不同的是 CRUD 操作不提供表名,使用 Uri 参数代替
- URI 权限:对于不同的应用程序做区分。一般采用程序包命名
- URI 路径:同一程序中的不同的表做区分。
标准的 URI 格式:content://com.example.app.provider(权限)/table1(路径)
如果使用表名,系统将无法得知我们访问的是哪个应用程序中的表
- 使用
Uri.parse()
方法,就可以将内容 URI 字符串解析为 Uri 对象
Uri uri = Uri.parse("content://com.example.app.provider/table1");
使用 Uri 对象来查询 table1 表中的数据
Cursor cursor = getContentResolver().query(
uri, //指定某个应用的某张表
projection, //指定查询的列名
selection, //指定 where 的约束条件
selectionArgs, //为 where 中的占位符提供具体的值
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 = ?", new String[]{ "text", "1" });
- 删
getContentResolver().delete(uri,"column2 = ?", new String[]{ "1" });
读取系统联系人
MainActivity.class
public class MainActivity extends Activity {
ListView contactsView;
ArrayAdapter<String> adapter;
List<String> contacstList = new ArrayList<String>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
contactsView = (ListView) findViewById(R.id.contacts_view);
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, contacstList);
contactsView.setAdapter(adapter);
readContacts();
}
private void readContacts() {
Cursor cursor = null;
try{
//查询联系人
cursor = getContentResolver().query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI, //获取手机通信录的 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
));
contacstList.add(displayName + "\n" + number);
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(cursor != null){
cursor.close();
}
}
}
}
AndroidManifest.xml 中获取权限
<uses-permission android:name="android.permission.READ_CONTACTS" />
创建自己的内容提供器——提供数据
- 之前是如何在自己的程序中访问其他应用的数据。思路:获取该应用程序的 URI,借助 ContentResolver 进行 CRUD 操作
- 现在来实现将数据提供出去,给其他应用访问,并保证安全性。
创建内容提供器的步骤
创建个类继承 ContentProvider 类并重写它的六个抽象方法
public class MyProvider extends ContentProvider {
//初始化内容提供器的时候调用。完成对数据库的创建和升级操作。返回 true 表示初始化成功,false 表示失败。
//ContentResolver 尝试访问本程序中的数据时,内容提供器才会被初始化
@Override
public boolean onCreate() {
return false;
}
//从内容提供器中查询数据
//1、Uri 确定查询哪一张表
//2、projection 确定查询哪些列
//3、selection 和 selectionArgs 参数约束哪些行
//4、sortOrder 对结果进行排序
//查询结果存放在 Cursor 对象中返回
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
//向内容提供器中添加一条数据
//1、URI 参数来确定添加哪一张表
//2、待添加的数据保存在 values 中
//返回一个用于表示这条新记录的 URI
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
//1、URI确定删除哪一张表
//2、selection,selectionArgs 来约束删除哪些行
//被删除的行将作为返回值
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
//1、URI确定更新哪一张表
//2、新数据保存在 values 中
//3、selection,selectionArgs 来约束更新哪些行
//受影响的行将作为返回值
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
//根据传入内容的 URI 返回相应的 MIME 类型
@Override
public String getType(Uri uri) {
return null;
}
}
-
每一个方法都有一个 URI参数,此参数是调用ContentResolver 的增删改查方法时传递过来的。而我们需要对 Uri 参数进行解析,从中分析出调用方期望访问的表和数据
-
URI后面可以加 id,以路径结尾表示期望访问该表中的所有数据,以 id 结尾表示期望访问该表中拥有相应 id 的数据
-
通配符方式匹配 URI
-
*****:表示匹配任意长度的任意字符
- content://com.example.app.provider/*
-
#:表示匹配任意长度的数字
- content://com.example.app.provider/#
public class MyProvider extends ContentProvider {
public static final int TABLE1_DIR = 0;
public static final int TABLE1_ITEM = 1;
public static final int TABLE2_DIR = 2;
public static final int TABLE2_ITEM = 3;
private static UriMatcher uriMatcher;
static{
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 将 uri 传入 uriMatcher
uriMatcher.addURI("com.example.app.provider","table1",TABLE1_DIR);
uriMatcher.addURI("com.example.app.provider","table1/#",TABLE1_ITEM);
uriMatcher.addURI("com.example.app.provider","table2",TABLE2_DIR);
uriMatcher.addURI("com.example.app.provider","table2/#",TABLE2_ITEM);
}
......
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// 在 uriMatcher 中逐个匹配 uri,选取调用方期望访问的那一张表
switch (uriMatcher.match(uri)){
case TABLE1_DIR:
// 查询 table1 表中的所有数据
break;
case TABLE1_ITEM:
// 查询 table1 表中的单条数据
break;
case TABLE2_DIR:
// 查询 table2 表中的所有数据
break;
case TABLE2_ITEM:
// 查询 table2 表中的单条数据
break;
default:
break;
}
......
}
......
上述代码只是用
query()
作为范例。insert()
、update()
、delete()
这个方法类似:1、它们都会携带 Uri 参数 2、利用 UriMatcher 的 match() 方法判断出调用方期望访问的是哪一张表,再对该表中的数据进行相应的操作就可以了。
- 除此之外还有个
getType()
方法。用于获取 Uri 对象所对应的 MIME 类型。MIME 字符串由三部分构成:
- 必须以vnd 开头
- 如果内容以 URI 路径结尾,则后接 android.cursor.dir/,如果内容 URI 以 id 结尾,则后接 android.cursor.item/。
- 最后接上 vnd.< authority >.< path >
列如
URI: content://com.example.app.provider/table1
MIME:vnd.android.cursor.dir/vnd.com.example.app.provider.table1
- 代码示例:
public String getType(Uri uri) {
switch(uriMatcher.match(uri)){
case TABLE1_DIR:
return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1";
case TABLE1_ITEM:
return "vnd.android.cursor.item/vnd.com.example.app.provider.table1";
case TABLE2_DIR:
return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2";
case TABLE2_ITEM:
return "vnd.android.cursor.item/vnd.com.example.app.provider.table2";
default:
break;
}
return null;
}
综合实例
实现跨程序的数据共享
- 程序对外开放内容
MyProvider.java
public class MyProvider 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.czx.sunorig.myapplication";
private MyDatabaseHelper dbHelper;
private static UriMatcher uriMatcher;
static{
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);
}
//初始化内容提供器的时候调用。完成对数据库的创建和升级操作。返回 true 表示初始化成功,false 表示失败。
//ContentResolver 尝试访问本程序中的数据时,内容提供器才会被初始化
@Override
public boolean onCreate() {
dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);
return true;
}
//从内容提供器中查询数据
//1、Uri 确定查询哪一张表
//2、projection 确定查询哪些列
//3、selection 和 selectionArgs 参数约束哪些行
//4、sortOrder 对结果进行排序
//查询结果存放在 Cursor 对象中返回
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = null;
switch (uriMatcher.match(uri)){
case BOOK_DIR:
// 查询 book 表中的所有数据
cursor = db.query("book", projection, selection, selectionArgs, null, null, sortOrder);
break;
case BOOK_ITEM:
// 查询 book 表中的单条数据
// getPathSegments() 方法将 URI 权限之后的部分以 “/” 符号进行分隔,并把分割结果放入一个字符串列表中,列表第0个位置是路径,第1个位置是 id
String bookId = uri.getPathSegments().get(1);
cursor = db.query("book", projection, "id = ?", new String[]{ bookId }, null, null, sortOrder);
break;
case CATEGORY_DIR:
// 查询 category 表中的所有数据
cursor = db.query("category", projection, selection, selectionArgs, null, null, sortOrder);
break;
case CATEGORY_ITEM:
// 查询 category 表中的单条数据
String categoryId = uri.getPathSegments().get(1);
cursor = db.query("category", projection, "id = ?", new String[]{ categoryId }, null, null, sortOrder);
break;
default:
break;
}
return cursor;
}
//向内容提供器中添加一条数据
//1、URI 参数来确定添加哪一张表
//2、待添加的数据保存在 values 中
//返回一个用于表示这条新记录的 URI
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
Uri uriReturn = null;
switch (uriMatcher.match(uri)){
case BOOK_DIR:
case BOOK_ITEM:
long newBookId = db.insert("book", null, values); //返回新添记录的行号,与主键id无关
uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + newBookId);
break;
case CATEGORY_DIR:
case CATEGORY_ITEM:
long newCategoryId = db.insert("Category", null, values);
uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" + newCategoryId);
break;
default:
break;
}
return uriReturn;
}
//1、URI确定更新哪一张表
//2、新数据保存在 values 中
//3、selection,selectionArgs 来约束更新哪些行
//受影响的行将作为返回值
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int updatedRows = 0;
switch (uriMatcher.match(uri)){
case BOOK_DIR:
updatedRows = db.update("book", values, selection, selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
updatedRows = db.update("Book", values, "id = ?", new String[]{ bookId });
case CATEGORY_DIR:
updatedRows = db.update("category", values, selection, selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
updatedRows = db.update("Book", values, "id = ?", new String[]{ categoryId });
default:
break;
}
return updatedRows;
}
//1、URI确定删除哪一张表
//2、selection,selectionArgs 来约束删除哪些行
//被删除的行将作为返回值
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int deleteRows = 0;
switch (uriMatcher.match(uri)){
case BOOK_DIR:
deleteRows = db.delete("book", selection, selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
deleteRows = db.delete("book", "id = ?", new String[]{ bookId });
break;
case CATEGORY_DIR:
deleteRows = db.delete("category", selection, selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
deleteRows = db.delete("category", "id = ?", new String[]{ categoryId });
break;
}
return deleteRows;
}
//根据传入内容的 URI 返回相应的 MIME 类型
@Override
public String getType(Uri uri) {
switch(uriMatcher.match(uri)){
case BOOK_DIR:
return "vnd.android.cursor.dir/vnd." + AUTHORITY + ".book";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd." + AUTHORITY + ".book";
case CATEGORY_DIR:
return "vnd.android.cursor.dir/vnd." + AUTHORITY + ".category";
case CATEGORY_ITEM:
return "vnd.android.cursor.item/vnd." + AUTHORITY + ".category";
default:
break;
}
return null;
}
}
MyDatabaseHelper.java
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "create table book("
+ "id integer primary key autoincrement,"
+ "author text,"
+ "price real,"
+ "pages integer,"
+ "name text)";
public static final String CREATE_CATEGORY = "create table category("
+ "id integer primary key autoincrement,"
+ "book_id integer)";
public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
AndroidManifest.xml
<provider
android:authorities="com.czx.sunorig.myapplication"
android:name="com.czx.sunorig.myapplication.MyProvider"
android:exported="true">
</provider>
- 另一个程序访问内容:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button add_data;
private Button query_data;
private Button update_data;
private Button delete_data;
private String newId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
add_data = (Button) findViewById(R.id.add_data);
query_data = (Button) findViewById(R.id.query_data);
update_data = (Button) findViewById(R.id.update_data);
delete_data = (Button) findViewById(R.id.delete_data);
add_data.setOnClickListener(this);
query_data.setOnClickListener(this);
update_data.setOnClickListener(this);
delete_data.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.add_data:
addData();
break;
case R.id.query_data:
queryData();
break;
case R.id.update_data:
updateData();
break;
case R.id.delete_data:
deleteData();
break;
default:
break;
}
}
private void queryData() {
Uri uri = Uri.parse("content://com.czx.sunorig.myapplication/book");
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
if(cursor != null){
while(cursor.moveToNext()){
String name = cursor.getString(cursor.getColumnIndex("name"));
String author = cursor.getString(cursor.getColumnIndex("author"));
int pages = cursor.getInt(cursor.getColumnIndex("pages"));
double price = cursor.getDouble(cursor.getColumnIndex("price"));
Log.d("MainActivity", "book name is " + name);
Log.d("MainActivity", "book author is " + author);
Log.d("MainActivity", "book pages is " + pages);
Log.d("MainActivity", "book price is " + price);
}
cursor.close();
}
}
private void addData() {
Uri uri = Uri.parse("content://com.czx.sunorig.myapplication/book");
ContentValues values = new ContentValues();
values.put("name","A Clash of Kings");
values.put("author", "George Martin");
values.put("pages", 1040);
values.put("price", 22.85);
Uri newUri = getContentResolver().insert(uri, values);
newId = newUri.getPathSegments().get(1);
}
private void updateData() {
Uri uri = Uri.parse("content://com.czx.sunorig.myapplication/book");
ContentValues values = new ContentValues();
values.put("name", "A Storm of Swords");
values.put("pages", 1216);
values.put("price", 24.05);
getContentResolver().update(uri, values, null, null);
}
private void deleteData() {
Uri uri = Uri.parse("content://com.czx.sunorig.myapplication/book" + newId);
getContentResolver().delete(uri, null, null);
}
}