目录
10.数据存储技术(四)
4.使用Content Provider实现数据共享
Content Provider 主要用于在不同的应用程序之间实现数据共享。它提供了一套完整的机制, 允许一个程序访问另一个程序中的数据,同时还能保证被访问数据的安全性。
在Android程序中,共享数据的实现需要继承ContentProvider基类,该基类为其他应用程序使用和存储数据实现了一套标准方法,然而应用程序并不直接调用这些方法,而是使用一个ContentResolver对象去操作指定数据。
4-1.Content Provider概述
Content Provider内部如何保存数据由其设计者决定,但是所有的Content Provider都实现一组通用的方法,用来提供数据的增、删、改、查功能。
客户端通常不会直接使用这些方法,而是通过ContentResolver对象实现对Content Provider的操作。开发人员可以通过调用Activity或者其他应用程序组件的实现类中的getContentResolver()方法来获得ContentResolver对象,例如:
ContentResolver cr = getContentResolver();
使用ConentResover提供的方法可以获得Content Provider 中任何想要的数据。
当开始查询时,Android 系统确认查询的目标Conent Provider 并确保它正在运行。系统会初始化所有ContentProvider类的对象,开发人员不必完成此类操作,实际上,开发人员根本不会直接使用ContentProvider类的对象。通常,每个类型的Content Provider仅有一个单独的实例。但是该实例能与位于不同应用程序和进程的多个ContentResolver类的对象通信。不同进程之间的通信由ContentProvider类和ContentResolver类处理。
使用Content Provider时,通常会用到以下两个概念。
1.数据模型
Content Provider使用基于数据库模型的简单表格来提供其中的数据,这里每行代表一条记录, 每列代表特定类型和含义的数据。例如,联系人的信息可能以表10.2 所示的方式提供。
表10.2 联系方式
| _ID | NAME | NUMBER | |
|---|---|---|---|
| 001 | 张XX | 123*** | 123**@163.com |
| 002 | 王XX | 132*** | 132**@google.com |
每条记录包含一个数值型的_ID字段,用于在表格中唯一标识该记录。 _ID 能用于匹配相关表格中的记录,例如,在一个表格中查询联系人的电话,在另一表格中查询其照片。
注: _ID 字段前还包含了一条下划线,在编写代码时不要忘记。
查询返回一个Cursor对象,它能遍历各行各列来读取各个字段的值。对于各个类型的数据,它都提供了专用的方法。因此,为了读取字段的数据,开发人员必须知道当前字段包含的数据类型。
2.URI的用法
每个Content Provider提供公共的URI(使用Uri类包装)来唯一标识其数据集。 管理多个数据集(多个表格)的Content Provider 为每个数据集提供了单独的URI。所有为Content Provider提供的URI都以“content://”作为前缀,它表示数据由Content Provider来管理。
如果自定义Content Provider,则需要为其URI也定义一个常量,来简化客户端代码并让日后更新更加简洁。Android为当前平台提供的Content Provider定义了CONTENT_URI常量。例如,匹配电话号码到联系人表格的URI和匹配保存联系人照片表格的URI分别如下:
android.provider.Contacts.Phones.CONTENT_URI
android.provider.Contacts.Photos.CONTENT_URI
URI常量用于所有与Contlenr Povider的交互中。每个ContentResolver方法使用URI作为其第一个参数。它标识ContentResolver应该使用哪个Content Provider及其中的哪个表格。
例:
content://com.example.employeeprovider/person/001
◆content://:标准的前缀,用于标识该数据由Content Provider管理,不需修改。
◆com.example.employeeprovider:URI的权限(authority) 部分,用于对不同的应用程序做区分,一 股会采用完整的类名(使用小写形式)来保证其唯一性。例如,一个包名为com.example的应用,对应的权限就可以命名为com.example.provider。
◆/person/001:Content Provider的路径(path)部分,用于指定要操作的数据,可以是数据表、文件、XML等。例如,要访问数据表person中的所有记录,可以使用“/person" ;而要访问person中的ID为001的记录的name字段,则需要使用“/person/001/name'
◆/001:被请求的特定记录的ID。这是被请求记录的_ID 值。如果请求不仅限于单条记录,该部分及其前面的 “/” 应该删除。
注:URI:统一资源标识符,不局限客户端和服务器
URL:统一资源定位符,能定位网站上的一切资源
4-2.创建Content Provider
可以通过继承ContentProvider 类,创建一个新的数据提供者。 通常情况下,需要完成以下操作。
◆继承ContentProvider类来提供数据访问方式。
◆在应用程序的AndroidManifest.xml文件中声明Content Provider.
1.继承ContentProvider类
通过定义ContentProvider 类的子类以便使用ContentResolver和Cursor类来共享数据。原则上,这意味着需要实现ContentProvider类定义的以下6个抽象方法:
public boolean onCreate()
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,String sortOrder)
public Uri insert(Uri uri, ContentValues values )
public int update(Uri uri, ContentValues values ,String selection, String[] selectionArgs)
public int delete(Uri uri, String selection, String[] selectionArgs )
public String getType(Uri uri)
各个方法的说明如表10.3 所示。
表10.3 Content Provider中抽象方法说明
| 方法 | 说明 |
|---|---|
| onCreate() | 用于初始化Content Provider |
| query() | 返回数据给调用者 |
| insert() | 插入新数据到Content Provider |
| update() | 更新Content Provider中已存在的数据 |
| delete() | 从Content Provider中删除数据 |
| getType() | 返回Content Provider数据的MIME类型 |
query() 方法必须返回Cursor对象,它用于遍历查询结果。Cursor自身是一个接口,但是Android提供了一些该接口的实现类,例如,SQLiteCursor 能遍历存储在SQLite数据库中的数据。通过调用SQLiteDatabase的query()方法可以获得Cursor对象。它们都位于android.database包中。
由于这些ContentProvider()方法能被不同进程和线程的不同ContentResolver对象调用,所以它们必须以线程安全的方式实现。
此外,开发人员可能也想调用ContentResolver.notifyChange()方法以便在数据修改时通知监听器。除了定义子类自身,还应采取下面的措施,以便简化客户端工作并让类更加易用。
(1)定义public static final Uri CONTENT_URI变量(CONTENT_URI是变量名称)。该字符串表示自定义的Content Provider处理的完整content:URI。开发人员必须为该值定义唯一的字符串。 最佳的解决方式是使用Content Provider的完整类名(小写)。例如,EmployeeProvider 的URI可能按以下格式定义:
public static final Uri CONTENT_URI = Uri.parse("content://com.example.employeeprovider");
如果Content Provider包含子表,也应该为各个子表定义URI。这些URI应该有相同的权限(因为它标志Content Provider),然后使用路径进行区分,例如:
content:/com.example.employeeprovider/dba content:/com.example.employeeprovider/programmer content:/com.example.employeeprovider/ceo
(2)定义Content Provider将返回给客户端的列名。如果要使用底层数据库,这些列名通常与SOL数据库列名相同。同样定义public static String常量,客户端用它们来指定查询中的列和其他指令。确保包含名为“_ID”的整数列用来作为记录的ID值,无论记录中其他字段是否唯一, 都应该包含该字段。如果使用SQLite数据库,ID 字段应该是如下类型:
INTEGER PRIMARY KEY AUTOINCREMENT
(3)仔细注释每列的数据类型,客户端需要使用这些信息来读取数据。
(4)如果开发人员正在处理新数据类型,则必须定义新的MIME类型以便在ContentProvider.getType()方法中实现返回。
(5)如果开发人员提供的byte数据太大而不能放到表格中,如Bitmap文件,则提供给客户端的字段应该包含content:URI字符串。
2.声明Content Provider
为了让Android 系统知道开发人员编写的Content Provider, 需要在应用程序的AndroidManifest.xml文件中定义<provider> 元素。没有在配置文件中声明的自定义Content Provider 对于Android系统不可见。
name属性的值是ContentProvider类的子类的完整名称。authorities 属性是Content Provider定义的content:URI中authority部分。ContentProvider 的子类是EmployeeProvider, <provider> 元素应该如下:
<provider android:name="com.example.EmployeeProvider"
android: authorities="com.example.employeeprovider"
....../>
</provider>
注: authorities 属性删除了content:URI中的路径部分。
其他<provider>属性能设置读写数据的权限,提供显示给用户的图标或文本,启用或禁用ContentProvider等。如果数据不需要在多个运行中的Content Provider间同步,则设置multiprocess为true。这允许在各个客户端进程创建一个 Content Provider实例,从而避免执行IPC(进程间通信)。
4-3使用Content Provider
Android系统为常用数据类型提供了很多预定义的Content Provider(声音、视频、图片、联系人等),它们大都位于android.provider 包中,开发人员可以查询这些provider以获得其中包含的信息(有些需要适当的权限来读取数据)。Android 系统提供的常见Content Provider如表10.4所示。
表10.4 常见Content Provider说明
| 名称 | 说明 |
|---|---|
| Browser | 用于管理浏览器相关信息(例如,书签、浏览历史或网络搜索) |
| CallLog | 用于管理通话历史信息 |
| Contacts | 用于管理联系人信息 |
| LiveFolders | 用于管理由Content Provider提供内容的特定文件夹 |
| MediaStore | 用于管理多媒体信息(例如,声音、视频和图片等) |
| Setting | 用于管理系统设置和偏好设置(例如,蓝牙设置、铃声和其他设备偏好) |
| SearchRecentSuggestions | 用于为应用程序创建简单的查询建议 |
| SyncStateContract | 用于使用数据数组账号关联数据的ContentProvider约束 |
| UserDictionary | 用于在可预测文本输入时,提供用户定义的单词给输入法使用 |
1.查询数据
要查询Content Provider中的数据,需要以下3个信息:
(1)标识该Content Provider的URI。
(2)需要查询的数据字段名称。
(3)字段中数据的类型。
为了查询Content Provider 中的数据,需要使用ContentResolver.query()方法,该方法返回Cursor对象。query()方法的语法格式如下:
public final Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
◆uri:povider的URI用于标识特定Content Provider 和数据集的CONTENT_URI常量。如果仅需要返回一条记录,可以在 URI结尾增加该记录的_ID值,即将匹配ID值的字符串作为URI路径部分的结尾片段。
◆projection:想要返回的数据列名称。null值表示返回全部列;否则仅返回指定的列。全部预定义Content Provider为其列都定义了常量。例如,android.providerc.Contacts.Phones类定义了_ID、NUMBER、NUMBER_KEY、NAME等常量。
◆selection:决定哪些行被返回的过滤器,格式类似SQL的WHERE语句(但是不包含WHERE自身)。null 值表示返回全部行(除非URI限制查询结果为单行记录)。
◆selectionArgs:选择参数。
◆sortOrder:返回记录的排序器,格式类似SQL的ORDER_BY语句(但是不包含ORDERBY自身)。null值表示以默认顺序返回记录,这可能是无序的。
使用Cursor对象来获得数据,它能向前或向后遍历整个结果集。所以可以使用Cursor对象来读取数据,而增加、修改和删除数据则必须使用ContentResolver对象。
2.增加记录
为了向Content Provider 中增加新数据,首先需要在 ContentValues对象中建立键值对映射,这里每个键匹配Content Provider中列名,每个值是该列中希望增加的值;然后调用ContentResolver.insert()方法并传递给它Content Provider的URI参数和ContentValues映射,该方法返回新记录的完整URI,即增加了新记录ID的URI。开发人员可以使用该URI来查询并获取该记录的Cursor, 以便修改该记录。insert()方法的语法格式如下:
public abstract Uri insert (Uri uri, ContentValues values)
◆uri:Content Provider的URI。
◆values: 要插入记录的列名和值组成的键/值对,不能为空。
3.增加新值
一旦记录存在,就可以向其中增加新信息或者修改已经存在的信息。向Contacts数据库中增加记录的最佳方式是增加保存新数据的表名到代表记录的URI,然后使用组装好的URI来增加新数据。每个Contacts表格以CONTENT_DIRECTORY常量的方式提供名称。
开发人员可以调用使用byte数组作为参数的ContentValues.put()方法向表格中增加少量二进制数据,这适用于类似小图标的图片、短音频片段等。然而,如果需要增加大量二进制数据,如图片或者完整的歌曲等,则需要保存代表数据的content:URI到表格,然后使用文件URI调用ContentResolver.openOutputStream()方法。这样就可以将Content Provider的数据保存到文件中并在记录的隐藏字段中保存文件路径。
4.批量更新记录
要批量更新数据(例如,将全部字段中“NY”替换成“New York”)。可使用ContentResolver.update()方法并提供需要修改的列名和值。
update()方法的语法格式如下:
public final int update (Uri uri, ContentValues values, String where, String[] selectionArgs)
◆uri:Content Provider的URI;
◆values:要修改记录的列名和值对,不能为空。
◆where:决定哪些行被更新的过滤器,格式类似SQL的WHERE语句(但是不包含WHERE自身);
◆selectionArgs:选择参数。
5.删除记录
如果需要删除单条记录,可调用ContentResolver.delete()方法并提供特定行的URI。如果需要删除多条记录,可调用ContentResolver.delete()方法并提供删除记录类型的URI (如android.provider.Contacts.People.CONTENT_URI)和一个SQL WHERE语句,它定义哪些行需删除。
delete()方法的语法格式如下:
public final int delete (Uri uri, String where, String[] selectionArgs)
◆uri:Content Provider的URI。
◆where:SQL条件语句,用于定义哪些行要删除。
◆selectionArgs:选择参数。
注:要确保提供了一个合适的WHERE语句,否则可能删除全部数据。
例:

MainActivity.java
package com.example.wechatphonebook;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ContentResolver;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private String columns= ContactsContract.Contacts.DISPLAY_NAME;//希望获得姓名
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActionBar actionBar=getSupportActionBar();
actionBar.hide();
TextView textView=findViewById(R.id.main_tv1);//获得文本框组件
textView.setText(getQueryData());//显示获取的通讯录信息
}
private CharSequence getQueryData(){
StringBuilder stringBuilder=new StringBuilder();//用于保存获取的联系人
ContentResolver resolver=getContentResolver();
//查询记录
Cursor cursor=resolver.query(ContactsContract.Contacts.CONTENT_URI,
null,null,null,null);
int displayNameIndex=cursor.getColumnIndex(columns);//获得姓名记录的索引值
for (cursor.moveToFirst();!cursor.isAfterLast();cursor.moveToNext()){
String displayName=cursor.getString(displayNameIndex);
stringBuilder.append(displayName+"\n");
}
cursor.close();//关闭记录集
return stringBuilder.toString();//返回查询结果
}
}
Manifest.xml
<!-- 允许访问通讯录信息-->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
注:要开启应用访问通讯录的权限,还需要在通讯录中创建一些联系人信息。
5.难点解答
5-1.内部存储与外部存储的区别
在Android中存在内部存储与外部存储两个概念,它们之间的区别如下:
◆内部存储并不是内存。如果想将文件存储在内部存储中,那么该文件默认只能被应用访问,且一个应用所创建的所有文件都在和应用包名相同的目录下。当应用卸载之后,内部存储中的这些文件也被删除。SharedPreferences 和SQLite数据库都是存储在内部存储空间上的。
◆最容易混淆的就是外部存储,例如一个32G的Android 手机,一般认为机身固有存储是内部存储,而扩展的TF卡是外部存储。但是Android的编程中并不是,这32G仍然是外部存储。所以不管Android手机是否有可移动的SDcard,它们总是有外部存储和内部存储。
5-2.SharedPreferences 存储与文件存储的区别
在Android中存在SharedPreferences存储与文件存储两个概念,容易混淆,它们之间的区别如下:
◆SharedPreferences 存储是通过键值对的方式来存储数据的,并且支持多种不同的数据类型存储,如果存储的数据类型是整型,读取出来的数据类型也是整型;如果存储的数据是一个字符串,读取出来的数据也是字符串。
◆文件存储简单的来说就是通过Java中的I/O流进行数据的存储。在Android的Context类中提供了一个openFileOutput() 方法,可以将数据通过流的方式存储到指定的文件中。这个方法接收两个参数,第一个参数是文件的名称,创建文件时使用的就是这个名称,注意这里指定的文件名不可以包含路径,因为所有的文件都是默认存储到“/data/data/< 包名>/files/”目录下的。
本文详细介绍了Android中Content Provider的概念、作用以及如何创建和使用Content Provider进行数据共享。内容包括Content Provider的概述,创建Content Provider的步骤,以及如何通过Content Resolver进行数据的增、删、改、查操作。此外,还探讨了内部存储与外部存储以及SharedPreferences存储与文件存储的区别。
&spm=1001.2101.3001.5002&articleId=125830567&d=1&t=3&u=08fad9b50ed44e3582f09584a6a76def)
4438

被折叠的 条评论
为什么被折叠?



