Android基础知识(7)—ContentProvider实现数据共享
在上一节笔记中学会了Android数据持久化的技术,包括文件存储、SharePreference存储、SQLite数据库存储,他们只能在当前应用程序中访问。虽然文件存储和SharePreference存储中提供了两种模式:MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE,用于供给其他应用程序访问当前应用程序的数据,但这两种模式已经在4.2版本中废弃了。因为有了更可靠的技术来代替。
其实我在学习这章内容之前,已经学过一遍ContentProvider,只是没有那么掌握知识点。因为当时选择了《疯狂的android》这本书,其实这本书更像android百科全书,讲得太细致,不太适合新手区学习。于是我选择了另外一本比较适合我这样的小白看的书《第一行代码》,等我看完了再回头去看详细那本,相信基本知识点应该能掌握。
内容提供器简介
内容提供器(ContentProvider)是Android中的四大组件之一,主要用于在不同应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,关键的是还能保证数据的安全性。
内容提供器的用法有两种:一种是使用现有的、别人已经写好的内容提供器来读取、操作数据;另一种是创建自己的内容提供器给我们程序的数据提供外部访问接口。
再其次这章内容就分为两大部分,ContentResolver是去获取,ContentProvider去提供,中介人是uri。
访问其他程序中的数据ContentResolver
如果想要访问内容提供器中共享的数据,就一定要借助ContentResolver,可以通过Context中的getContentResolver()方法获取到该类的实例。
ContentResolver cr = getContentResolver();ContentResolver中提供了一系列的方法用来对数据进行CRUD操作:
- insert(uri, values):向Uri对应的ContentProvider中插入values对应的数据。
- delete(uri, where, selectionArgs):删除Uri对应的contentProvider中where提交匹配的数据。
- update(uri, values, where, selectionArgs):更新Uri对应的ContentProvider中where提交匹配的数据。
- query(uri, projection, selection, selectionArgs, SortOrder):查询Uri对应的ContentProvider中where提交匹配的数据。
不同于SQLiteDatabase的是,ContentResolver中的增删查改方法都不是接收表名参数,而是使用Uri参数代替,这个参数被称为内容URI。内容URI给内容提供器中的数据建立了唯一标识符,他由三部分组成:协议、权限、路径。
content://com.songsong.ContentProvider/table1
content://com.songsong.ContentProvider/table1/1
在得到了内容URI字符串之后,我们还需要将它解析成Uri对象才能作为参数传入,代码如下:
Uri uri = Uri.parse("content://com.songsong.ContentProvider/table1");
现在我们就可以使用这个Uri对象来查询table1表中的数据,代码如
Cursor cursor = getContentResolver().query(uri, projection, selection, slectionArgs, sortOrder);
查询完成后返回仍然是一个Cursor对象,这时我们就可以将数据从Cursor对象中逐个读取出来。读取思路仍然是通过移动游标的位置来遍历Cursor的所有行,然后取出每一行中相应的数据。代码如下:
if (cursor != null) {
while (cursor.moveToNext()) {
String column1 = cursor.getString(cursor.getColumnIndex("column1"));
String column2 = cursor.getString(cursor.getColumnIndex("column2"));
}
cursor.close();
}
插入数据:
ContentValues values = new ContentValues();
values.put("column1", "text");
values.put("column2", "1");
getContentResolver().insert(uri, values);
更新数据:要column1列的值为空
ContentValues values = new ContentValues();
values.put("column1", "");
getContentResolver().update(uri, values, "column1=?", new String[]{"text"});
删除数据:
getContentResolver().delete(uri, "column2=?", new String[]{"1"});
【实例】读取系统联系人
首先在模拟器上面创建四个联系人:
准备工作完毕,现在新建一个ContentResolverTest项目,首先还是要编写一下界面:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<ListView
android:id="@+id/contacts_view"
android:layout_width="match_parent"
android:layout_height="match_parent"></ListView>
</LinearLayout>
这里希望读取出来的联系人显示在listview上,接下来修改MainActivity文件代码:
public class MainActivity extends ActionBarActivity {
ListView contactsView;
ArrayAdapter<String> adapter;
List<String> contactsList = 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, contactsList);
contactsView.setAdapter(adapter);
readContacts();
}
private void readContacts() {
Cursor cursor = null;
ContentResolver cr = getContentResolver();
//查询联系人数据
try {
cursor = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, android.provider.ContactsContract.Contacts._ID + " ASC"); //进行对ID 排序
while (cursor.moveToNext()) {
// 获取联系人ID
int id = cursor.getInt(cursor.getColumnIndex("_id"));
//获取联系人姓名
String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
//获取联系人号码
String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
contactsList.add("ID:" + id + "\n姓名:" + displayName + "\n号码:" + number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null) {
cursor.close();
}
}
}
}
效果:
刚添加的4个联系人都能显示出来,跨程序读取数据的确挺方便的。
【实例】插入联系人:
新建一个项目ContentResolver_insert,布局文件不修改:
public class ContentResolver_insert extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_content_resolver_insert);
ContentResolver cr = getContentResolver();
//向联系人中插入一行数据
ContentValues values = new ContentValues();
Uri uri = cr.insert(ContactsContract.RawContacts.CONTENT_URI, values);
Long raw_contact_id = ContentUris.parseId(uri); //解析uri
values.clear();
//插入名字
values.put(StructuredName.RAW_CONTACT_ID, raw_contact_id);
values.put(StructuredName.DISPLAY_NAME, "张牛");
values.put(StructuredName.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
uri = cr.insert(Data.CONTENT_URI, values);
values.clear();
values.put(Phone.RAW_CONTACT_ID, raw_contact_id);
values.put(Phone.NUMBER, "520520520");
values.put(Phone.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
uri = cr.insert(Data.CONTENT_URI, values);
}
}
来看看效果:
创建内容提供器
以上学习的知识点是如果去读取其他应用程序的数据,那么自己的应用程序怎么提供数据给其他程序去调用,而且还需要保证自己的数据的安全性。接下来介绍创建内容提供器的步骤:
1、首先继承抽象类ContentProvider,并将六个方法全部重写;
- public boolean onCreate(): 在创建ContentProvider时调用,当其他应用程序第一次访问ContentProvider时,该ContentProvider会被创建出来,并立即回调该onCreate方法。
- public Uri insert(Uri uri, ContentValues valus) :根据该Uri插入values对应的数据。
- public int delete(Uri uri, String selection, String[] selectionArgs): 根据Uri删除selection条件所匹配的全部记录。
- public int update(Uri uri, ContentValues valus, String selection,String[] selectionArgs) :根据Uri修改selection条件所匹配的全部记录。
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder): 根据Uri查询出selection条件所匹配的全部记录。
- public String getType(Uri): 用于返回指定的Uri中的数据的MIME类型。如果URI对应的数据可能包括多条记录,那么MIME类型字符串就是以vnd.android.cursor.dir/开头。如果URI对应的数据只有一条记录,该MIME类型字符串就是以vnd.android.cursor.item/开头。
对于content://com.songsong.ContentProvider/table1这个内容URI,它所对应的MIME类型就可以写出:
vnd.android.cursor.dir/vnd.com.songsong.ContentProvider.table1对于content://com.songsong.ContentProvider/table1/1这个内容URI,它所对应的MIME类型就可以写出:
vnd.android.cursor.item/vnd.com.songsong.ContentProvider.table1
<provider
android:name=".MyContentProvider"
android:authorities="songsong.com.contentprovidertest" />
注意:注册的authorities是全局唯一的。
3、URI的匹配
借助UriMatcher这个类就可以轻松地实现匹配内容URI的功能,UriMatcher中提供了一个addURI()方法,这个方法接收三个参数,可以分别把权限、路径、自定义代码传进去。当调用UriMatcher的match()方法时,就可以根据自定义代码判断出调用方期望访问的是哪张表的数据。
public class MyContentProvider extends ContentProvider {
public static final int Table_a = 0;
public static final int Table_b = 1;
public static final int Table_c = 2;
public static final int Table_d = 3;
private static UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("songsong.com.contentprovidertest", "table1", Table_a);
uriMatcher.addURI("songsong.com.contentprovidertest", "table1/#", Table_b);
uriMatcher.addURI("songsong.com.contentprovidertest", "table2", Table_c);
uriMatcher.addURI("songsong.com.contentprovidertest", "table2/#", Table_a);
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
switch (uriMatcher.match(uri)) {
case Table_a:
//查询表1的所有数据
break;
case Table_b:
//查询表1的单条数据
break;
case Table_c:
//查询表2的所有数据
break;
case Table_d:
//查询表2的单条数据
break;
}
return null;
}