Android学习笔记(十九)——内容提供器
//此系列博文是《第一行Android代码》的学习笔记,如有错漏,欢迎指正!
内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性。当一个应用程序通过内容提供器对其数据提供了外部访问接口,任何其他的应用程序就都可以对这部分数据进行访问。Android 系统中自带的电话簿、短信、媒体库等程序都提供了类似的访问接口,这就使得第三方应用程序可以充分地利用这部分数据来实现更好的功能。
一、ContentResolver 的基本用法
对于每一个应用程序来说,如果想要访问内容提供器中共享的数据,就一定要借助ContentResolve 类——通过 Context 中的 getContentResolver()方法获取到该类的实例。ContentResolver 中提供了一系列的方法用于对数据进行 CRUD 操作,这些操作与SQL相似,不过在方法参数上有点区别:与SQL相比,ContentResolver 中的增删改查方法都是不接收表名参数的,而是使用一个 Uri参数代替,这个参数被称为内容 URI。内容 URI给内容提供器中的数据建立了唯一标识符,它主要由两部分组成:
1)权限(authority):用于对不同的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式来进行命名。比如某个程序的包名是 com.example.app,那么该程序对应的权限就可以命名为 com.example.app.provider;
2)路径(path):用于对同一应用程序中不同的表做区分的,通常都会添加到权限的后面。比如某个程序的数据库里存在两张表,table1和 table2,这时就可以将路径分别命名为/table1和/table2。
把权限和路径进行组合,内容 URI 就变成了 com.example.app.provider/table1和 com.example.app.provider/table2。不过,目前还很难辨认出这两个字符串就是两个内容URI,我们还需要在字符串的头部加上协议声明。因此,内容 URI最标准的格式写法如下:
content://com.example.app.provider/table1
content://com.example.app.provider/table2
在得到了内容 URI字符串之后,我们还需要将它解析成 Uri 对象才可以作为参数传入。解析的方法也相当简单,代码如下所示:
Uri uri = Uri.parse("content://com.example.app.provider/table1")
只需要调用 Uri.parse()方法,就可以将内容 URI字符串解析成 Uri 对象了。现在我们就可以使用这个 Uri对象来进行CRUD操作:
(1)查询数据:
若想查询table1 表中的数据,代码如下所示:
1 Cursor cursor = getContentResolver().query(
2 uri, //指定查询某个应用程序下的某一张表
3 projection, //指定查询的列名
4 selection, //指定 where 的约束条件
5 selectionArgs, //为 where中的占位符提供具体的值
6 sortOrder //指定查询结果的排序方式
7 );
查询完成后返回的仍然是一个 Cursor 对象,这时我们就可以将数据从 Cursor 对象中逐个读取出来了。 读取的思路仍然是通过移动游标的位置来遍历 Cursor 的所有行, 然后再取出每一行中相应列的数据,代码如下所示:
1 if (cursor != null) {
2 while (cursor.moveToNext()) {
3 String column1 = cursor.getString(cursor.getColumnIndex("column1"));
4 int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
5 }
6 cursor.close();
7 }
(2)添加数据:
将待添加的数据组装到 ContentValues 中,然后调用 ContentResolver的 insert()方法,将 Uri 和 ContentValues作为参数传入即可。
1 ContentValues values = new ContentValues();
2 values.put("column1", "text");
3 values.put("column2", 1);
4 getContentResolver().insert(uri, values);
(3)更新数据:
如果我们想要更新这条新添加的数据,把 column1 的值清空,可以借助ContentResolver 的 update()方法实现,代码如下所示:
1 ContentValues values = new ContentValues();
2 values.put("column1", "");
3 getContentResolver().update(uri, values, "column1 = ? and column2 = ?",new String[] {"text", "1"});
(4)删除数据:
我们可以调用 ContentResolver 的 delete()方法将刚才数据删除掉,代码如下所示:
1 getContentResolver().delete(uri, "column2 = ?", new String[] { "1" });
初步认识了CRUD操作后,我们来实践一下。
二、实践:读取联系人:
先确保手机里有联系人,然后新建一个project:ContactsTest.
(1)设置布局文件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 android:layout_width="match_parent"
3 android:layout_height="match_parent" >
4 <ListView
5 android:id="@+id/contacts_view"
6 android:layout_width="match_parent"
7 android:layout_height="match_parent" >
8 </ListView>
9 </LinearLayout>
在LinearLayout 里我们放置了一个 ListView,用于显示联系人。
(2)修改主活动代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 public class MainActivity extends AppCompatActivity {
2
3 ListView contactsView;
4 ArrayAdapter<String> adapter;
5 List<String> contactsList = new ArrayList<String>();
6 @Override
7 protected void onCreate(Bundle savedInstanceState) {
8 super.onCreate(savedInstanceState);
9 setContentView(R.layout.activity_main);
10 contactsView = (ListView) findViewById(R.id.contacts_view);
11 adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, contactsList);
12 contactsView.setAdapter(adapter);
13 readContacts();
14 }
15 private void readContacts() {
16 Cursor cursor = null;
17 try {
18 // 查询联系人数据
19 cursor = getContentResolver().query(
20 ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
21 null, null, null, null);
22 while (cursor.moveToNext()) {
23 // 获取联系人姓名
24 String displayName = cursor.getString(cursor.getColumnIndex(
25 ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
26 // 获取联系人手机号
27 String number = cursor.getString(cursor.getColumnIndex(
28 ContactsContract.CommonDataKinds.Phone.NUMBER));
29 contactsList.add(displayName + "\n" + number);
30 }
31 }
32 catch (Exception e) {
33 e.printStackTrace();
34 }
35 finally {
36 if (cursor != null) {
37 cursor.close();
38 }
39 }
40 }
41 }
在 onCreate()方法中,我们首先获取了 ListView控件的实例,并给它设置好了适配器,然后就去调用 readContacts()方法。在readContacts()方法中,我们使用了 ContentResolver 的 query()方法来查询系统的联系人数据。 不过传入的 Uri参数显得有些奇怪,为什么没有调用 Uri.parse()方法去解析一个内容 URI 字符串呢?这是因为ContactsContract.CommonDataKinds.Phone类已经帮我们做好了封装, 提供了一个CONTENT_URI常量,这个常量就是使用 Uri.parse()方法解析后的结果。接着我们历遍 Cursor 对象读取数据,联系人姓名这一列对应常量为ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,联系人手机号这一列对应的常量是 ContactsContract.CommonDataKinds.Phone.NUMBER。两个数据都取出之后,将它们进行拼接,并且中间加上换行符,然后将拼接后的数据添加到 ListView 里。最后千万不要忘记将 Cursor 对象关闭掉。
(3)修改权限:
读取系统联系人也是需要声明权限的,因此修改AndroidManifest.xml 中的代码,如下所示:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <manifest xmlns:android="http://schemas.android.com/apk/res/android" 2 package="com.mycompany.contactstest" > 3 <uses-permission android:name="android.permission.READ_CONTACTS" /> 4 <application 5 android:allowBackup="true" 6 android:icon="@mipmap/ic_launcher" 7 android:label="@string/app_name" 8 android:supportsRtl="true" 9 android:theme="@style/AppTheme" > 10 11 <activity android:name=".MainActivity" > 12 <intent-filter> 13 <action android:name="android.intent.action.MAIN" /> 14 15 <category android:name="android.intent.category.LAUNCHER" /> 16 </intent-filter> 17 </activity> 18 </application> 19 20 </manifest>
现在便可以成功地运行程序了。
//End.