Android基础笔记(十三)- 内容提供者原理和简单使用
为什么要有内容提供者
- 内容提供者技术的目的是:
- 把私有数据库的数据的内容暴露给外部使用;
我们知道,微信、QQ等应用都能够读取手机中联系人和短信的数据。而联系人和短信都是系统内置的应用,它们的数据都存储在相应的数据库中。
在com.android.provider.telephony/databases/mmssms.db
就是短信的数据库,通过以下的图能够知道mmssms.db
的权限为-rw-rw---
。也就是其它用户不能够直接訪问。是私有的。
相同的com.android.provider.contacts/databases/contact2.db
是联系人的数据库。相同也是私有的权限。
既然它们都是私有的。可是又有需求获取里面的数据应该怎么办?Google提供者了内容提供者的技术,就能够解决这一需求。
内容提供者的工作原理
由于数据库是私有的,外部应用不能够直接訪问。那么便在应用内部使用内容提供者,向外部暴露接口,这样外部能够借助“中间人”訪问到内部的数据库。原理图例如以下:
- 内容提供者的编写过程例如以下:
- ①创建数据库、表。由于内容提供者主要是把私有数据库数据提供给外部訪问。
- ②写一个类继承
ContentProvider
类,并重写当中的onCreate()
、query()
、insert()
、update()
、delete()
、getType()
等方法。 - ③确定主机名(
authority
),加入路径匹配规则。此处会利使用到UriMatcher
类。 - ④在清单文件里声明内容提供者。并加入关键属性
android:authorities
- ⑤编写内容提供者的
onCreate()
、query()
、insert()
、update()
、delete()
等方法。
接下来就一步一步把全部步骤说清。
第一步,创建数据库、表。非常easy写一个继承SQliteOpenHelper
得类,并创建数据库。代码例如以下:
public class MySQLiteOpenHelper extends SQLiteOpenHelper {
public MySQLiteOpenHelper(Context context) {super(context, "account.db", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
// 创建数据库,并初始化两条语句
db.execSQL("create table account (_id integer primary key autoincrement,name varchar(20),money varchar(20))");
db.execSQL("insert into account ('name','money') values ('张三','2000')");
db.execSQL("insert into account ('name','money') values ('李四','5000')");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { }
}
第二步,写一个类继承ContentProvider
类,并重写当中方法。主要都是一些骨架的代码。也没什么难度。
public class CopyOfAccountContentProvider extends ContentProvider {
// 内容提供者初始化的时候调用
@Override
public boolean onCreate() {
return true;
}
// 提供给外部应用调用的查询方法
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
// 提供给外部应用调用的添加方法
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
// 提供给外部应用调用的删除方法
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
// 提供给外部应用调用的更新方法
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
@Override
public String getType(Uri uri) {
// 没有什么用,此处就不写了
return null;
}
}
第三步,从第三步開始就比較关键了。要确定主机名(authority
),加入路径匹配规则,此处是使用内容解析者时。调用其它应用私有数据库所使用的。代码例如以下。对了这些代码是写在内容提供者类中的:
public static final int QUERY_SUCCESS = 0;
public static final int UPDATE_SUCCESS = 1;
public static final int DELETE_SUCCESS = 2;
public static final int INSERT_SUCCESS = 3;
// UriMatcher是一个工具类。用于帮助内容提供者匹配URIs
private static final UriMatcher MURI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
// 加入路径匹配规则
static {
// 加入一个path(匹配规则),假设匹配成功会返回code值。
// authority:主机名(我们能够自定义。任意写,当使用内容解析者时会使用到)
// path:路径。通常是操作名称
// code:响应码
MURI_MATCHER.addURI("com.bzh.account.contentprovider", "insert", INSERT_SUCCESS);
MURI_MATCHER.addURI("com.bzh.account.contentprovider", "delete", DELETE_SUCCESS);
MURI_MATCHER.addURI("com.bzh.account.contentprovider", "update", UPDATE_SUCCESS);
MURI_MATCHER.addURI("com.bzh.account.contentprovider", "query", QUERY_SUCCESS);
}
第四步。内容提供者是四大组件之中的一个,四大组件都须要在清单文件里声明。在清单文件里声明内容提供者。并加入关键属性android:authorities
,这个属性的内容和加入路径匹配规则addURI()
的第一个參数必须一致。代码例如以下:
<provider
android:name="com.bzh.contentprovider.AccountContentProvider"
android:authorities="com.bzh.account.contentprovider" >
</provider>
第五步,编写内容提供者的onCreate()
、query()
、insert()
、update()
、delete()
等方法。这些方法的内容都是使用SQLiteDatabase
内部的方法直接调用,也比較简单。
private SQLiteDatabase db;
private static final String ACCOUNT = "account";
// 内容提供者初始化的时候调用
@Override
public boolean onCreate() {
MySQLiteOpenHelper mySQLiteOpenHelper = new MySQLiteOpenHelper(getContext());
db = mySQLiteOpenHelper.getWritableDatabase();
return true;
}
// 提供给外部应用调用的查询方法
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
int MATCH_CODE = MURI_MATCHER.match(uri);
// 推断路径是否匹配成功
if (MATCH_CODE == QUERY_SUCCESS) {
// 调用SQLiteDatabase中的方法查询私有的数据库
return db.query(ACCOUNT, projection, selection, selectionArgs, null, null, null);
}
return null;
}
// 提供给外部应用调用的添加方法
@Override
public Uri insert(Uri uri, ContentValues values) {
int MATCH_CODE = MURI_MATCHER.match(uri);
// 推断路径是否匹配成功
if (MATCH_CODE == INSERT_SUCCESS) {
long insertResult = db.insert(ACCOUNT, null, values);
// 此处的Uri能够任意写
return Uri.parse("com.bzh.account.contentprovider-result:" + insertResult);
}
return null;
}
// 提供给外部应用调用的删除方法
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int MATCH_CODE = MURI_MATCHER.match(uri);
// 推断路径是否匹配成功
if (MATCH_CODE == DELETE_SUCCESS) {
return db.delete(ACCOUNT, selection, selectionArgs);
}
return 0;
}
// 提供给外部应用调用的更新方法
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int MATCH_CODE = MURI_MATCHER.match(uri);
// 推断路径是否匹配成功
if (MATCH_CODE == UPDATE_SUCCESS) {
return db.update(ACCOUNT, values, selection, selectionArgs);
}
return 0;
}
@Override
public String getType(Uri uri) {
// 没有什么用。此处就不写了
return null;
}
至此,整个内容提供者就编写完成了,接下就是怎样使用了,详细怎样使用将在下一节讲述。
使用内容解析者对内容提供者进行增删改查操作
回忆一下上面的原理图,訪问私有数据库的内容提供者已经准备完成,家下来是使用内容解析者间接调用数据库内容的时候了。
相对于创建内容提供者而言,使用解析者调用提供者提供的方法就简单非常多了。
创建一个工程,并加入几个增删改查的button,布局之类的比較简单不贴了。请看图:
接下来要做的事情就是。当点击button时调用内容提供者相应的方法。当中最值得注意的是Uri.parse()
内容的写法。
Uri uri = Uri.parse("content://com.bzh.account.contentprovider/query");
对于这句内容而言,主要由三部分组成–①content://②主机地址/③路径
,而这部分是固定的写法,当中主机地址是内容提供者清单文件里声明的authorities
属性;而路径是内容提供者中UriMatcher.addURI()
第二个參数相应的值。
以下是代码:
public void insert(View v) {
// 获取到内容解析者
ContentResolver resolver = getContentResolver();
// 匹配路径和数据
Uri url = Uri.parse("content://com.bzh.account.contentprovider/insert");
ContentValues values = new ContentValues();
values.put("name", "别志华");
values.put("money", "999");
// 使用解析者借助内容提供者向私有数据库插数据
Uri insert = resolver.insert(url, values);
System.out.println("数据插入---结果:" + insert.toString());
}
public void delete(View v) {
// 获取到内容解析者
ContentResolver resolver = getContentResolver();
// 匹配路径和数据
Uri url = Uri.parse("content://com.bzh.account.contentprovider/delete");
// 使用解析者借助内容提供者向私有数据库插数据
int delete = resolver.delete(url, "name=?
", new String[] { "别志华" });
System.out.println("删除数据---影响行数:" + delete);
}
public void update(View v) {
// 获取到内容解析者
ContentResolver resolver = getContentResolver();
// 匹配路径和数据
Uri uri = Uri.parse("content://com.bzh.account.contentprovider/update");
ContentValues values = new ContentValues();
values.put("money", "999");
// 使用解析者借助内容提供者向私有数据库插数据
int update = resolver.update(uri, values, "name=?
", new String[] { "别志华" });
System.out.println("更新数据---影响行数:" + update);
}
public void query(View v) {
// 获取内容解析者
ContentResolver resolver = getContentResolver();
// com.bzh.account.contentprovider是在上一个应用清单文件里authorities的属性值
// 固定写法:content://主机地址/路径
Uri uri = Uri.parse("content://com.bzh.account.contentprovider/query");
// 调用内容解析者的查询方法
Cursor cursor = resolver.query(uri, null, null, null, null);
// 拿出游标中的数据
if (cursor != null && cursor.getCount() > 0) {
while (cursor.moveToNext()) {
String name = cursor.getString(1);
String money = cursor.getString(2);
System.out.println("查询数据---数据内容(姓名:" + name + ",钱:" + money + ")");
}
// 关闭游标
cursor.close();
}
}
測试图例如以下:
到此为止,怎样定义一个内容提供者和怎样使用内容提供者已经解说完了。
利用内容提供者和内容解析者备份手机短信
像“微信电话薄”应用。能够读取手机中的短信数据。我们知道,手机中的短信应用内部维护者一个数据库,用于存储短信数据。
而“微信电话薄”就是读取系统短信应用的数据来达到自己的目的的,可是系统短信应用的数据库是私有的。那么外部的应用怎样拿到私有数据库的数据?你想的没错。Google工程师在短信应用中为我们提供了内容提供者,供我们开发者使用。
在上一节中,我们知道想要使用其它应用提供的内容提供者,须要知道主机名和路径。那么从哪里获取这些信息呢?这就须要去查看源代码了。我这里查看的是4.4的源代码。源代码的路径为android4.4\packages\providers\TelephonyProvider
。
文件夹结构例如以下图:
在上一节中。写自己的提供提供者时都是在清单文件里声明内容提供者。并声明主机名。让我们来看一下吧。
<provider android:name="SmsProvider"
android:authorities="sms"
android:multiprocess="false"
android:exported="true"
android:readPermission="android.permission.READ_SMS"
android:writePermission="android.permission.WRITE_SMS" />
主机名是sms
。而且从中可一知道使用该内容提供者,须要提供读取短信和写短信的权限。
知道了主机名,接下来就是去查看路径了。找到src\com\android\providers\telephony\SmsProvider.java
文件,并搜索UriMatcher
关键字,能够得到例如以下的结果:
private static final UriMatcher sURLMatcher =
new UriMatcher(UriMatcher.NO_MATCH);
static {
sURLMatcher.addURI("sms", null, SMS_ALL);
sURLMatcher.addURI("sms", "#", SMS_ALL_ID);
sURLMatcher.addURI("sms", "inbox", SMS_INBOX);
sURLMatcher.addURI("sms", "inbox/#", SMS_INBOX_ID);
sURLMatcher.addURI("sms", "sent", SMS_SENT);
sURLMatcher.addURI("sms", "sent/#", SMS_SENT_ID);
sURLMatcher.addURI("sms", "draft", SMS_DRAFT);
sURLMatcher.addURI("sms", "draft/#", SMS_DRAFT_ID);
... ... ...
}
知道了主机名和路径后。在看一下短信数据库的表结构,要不然我们怎么获取数据!从中能够发如今sms表中address
、date
、body
分别代表着发送人、时间、短信内容;
接下来。使用内容解析者查询数据并使用XML序列化器,把数据序列化到XML文件里。短信备份就算完成了,代码例如以下:
// 内容解析者
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://sms");
Cursor cursor = resolver.query(uri, new String[] { "address", "date", "body" }, null, null, null);
if (cursor != null && cursor.getCount() > 0) {
// XML序列化器
XmlSerializer serializer = Xml.newSerializer();
FileOutputStream fos;
try {
fos = new FileOutputStream(new File(Environment.getExternalStorageDirectory().getPath() + "/backsms.xml"));
// 初始化XML序列化器參数
serializer.setOutput(fos, "UTF-8");
// 文档開始
serializer.startDocument("UTF-8", true);
// 根节点開始
serializer.startTag(null, "smss");
while (cursor.moveToNext()) {
// 元素结点開始
serializer.startTag(null, "sms");
// 序列化发送人
serializer.startTag(null, "address");
String address = cursor.getString(0);
serializer.text(address);
serializer.endTag(null, "address");
serializer.startTag(null, "date");
String date = cursor.getString(1);
serializer.text(date);
serializer.endTag(null, "date");
serializer.startTag(null, "body");
String body = cursor.getString(2);
serializer.text(body);
serializer.endTag(null, "body");
System.out.println("短信发送人:" + address + ",日期:" + date + ",内容:" + body);
serializer.endTag(null, "sms");
}
serializer.endTag(null, "smss");
serializer.endDocument();
} catch (Exception e) {
e.printStackTrace();
}
cursor.close();
}
利用内容提供者插入短信
做完备份短信,再做一个插入短信玩玩吧。
首先提供一个界面:
代码也比較简单:
String body = et_body.getText().toString().trim();
String sender = et_sender.getText().toString().trim();
// 拿到内容解析者
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://sms");
ContentValues values = new ContentValues();
values.put("address", sender);
values.put("body", body);
// 插入数据
resolver.insert(uri, values);
測试结果:
利用提供者和解析这备份和插入短信还是比較简单的,有难度的在于备份联系人和插入联系人。这些将在下一个博客讲述。
Android基础笔记(十三)- 内容提供者原理和简单使用的更多相关文章
- Android学习笔记_10_ContentProvider内容提供者的使用
一.使用ContentProvider共享数据 当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据.以前我们学习过文件的操作模式,通过指定文 ...
- android基础---->DiskLruCache的使用及原理
DiskLruCache是谷歌推荐的用来实现硬盘缓存的类,今天我们开始对于DiskLruCache的学习.DiskLruCache的测试代码:DiskLruCache的测试代码下载.关于FidkLru ...
- Android组件系列----ContentProvider内容提供者
[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...
- Android基础笔记(十四)- 内容提供者读取联系人
利用内容提供者读取联系人 利用内容提供者插入联系人 内容观察者的原理 利用内容观察者监听系统应用数据库或者自己应用数据库的变化 利用内容提供者读取联系人 读取联系人相对于读取短信来说就复杂非常多了,我 ...
- Android开发学习—— ContentProvider内容提供者
* 应用的数据库是不允许其他应用访问的* 内容提供者的作用就是让别的应用访问到你的数据库.把私有数据暴露给其他应用,通常,是把私有数据库的数据暴露给其他应用. Uri:包含一个具有一定格式的字符串的对 ...
- [Android Pro] 监听内容提供者ContentProvider的数据变化
转载自:http://blog.csdn.net/woshixuye/article/details/8281385 一.提出需求 有A,B,C三个应用,B中的数据需要被共享,所以B中定义了内容提供者 ...
- Android初级教程之内容提供者获取联系人信息
内容提供折详细理论知识请参考之前的博文:http://blog.csdn.net/qq_32059827/article/details/51646513 这里新建了三个联系人信息,通过查看系统联系人 ...
- Android组件系列----ContentProvider内容提供者【4】
(4)单元測试类: 这里须要涉及到另外一个知识:ContentResolver内容訪问者. 要想訪问ContentProvider.则必须使用ContentResolver. 能够通过ContentR ...
- Android组件系列----ContentProvider内容提供者【1】
[正文] 一.ContentProvider简单介绍: ContentProvider内容提供者(四大组件之中的一个)主要用于在不同的应用程序之间实现数据共享的功能. ContentProvider能 ...
随机推荐
- Python-基础-day6
1.二进制 前言:计算机一共就能做两件事:计算和通信 2.字符编码 生活中的数字要想让计算机理解就必须转换成二进制.十进制到二进制的转换只能解决计算机理解数字的问题,那么文字要怎么让计算机理解呢? 于 ...
- POJ——T1125 Stockbroker Grapevine
http://poj.org/problem?id=1125 Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 36045 ...
- HDU 1131
N个节点的不同的树的数目.这样 随便取一个节点作为根,那么他左边和右边的儿子节点个数就确定了,假定根节点标号为x,那么左子树的标号就从1到x-1,共x-1个,右子树的标号就从x+1到n,共n-x个,那 ...
- 《Objective-C高级编程:iOS与OS X多线程和内存管理》读后感
拿到这本书的第一感觉是非常薄,可是内容就如同序里面所说,这不是一本面向刚開始学习的人的书,比較有深度,对C/C++全然不熟悉的话非常多东西会看不明确. 尽管此书在技术点上仅仅谈到了ARC.Blocks ...
- CentOS 安装SVNclient
1.检查是已经安装了svn: # rpm -qa subversion subversion-1.7.14-6.el7.x86_64 卸载旧版本号的svn: # yum remove subversi ...
- hibernate之4.延迟载入
延迟载入: 仅仅有当使用以实体对象的属性(除主键属性外)时,才会发送查询语句到数据库 get不支持延迟载入 @Test public void getTest(){ Session session=n ...
- centos6.0 配置SVN
基本步骤: 1.安装必需的subversion 2.创建版本库 3.配置用户和权限 4.钩子和svn常用命令说明 一.安装subversion 在这里我们使用yum来安装subversion,使用以下 ...
- ServiceStack.Redis之IRedisClient<第三篇>【转】
事实上,IRedisClient里面的很多方法,其实就是Redis的命令名.只要对Redis的命令熟悉一点就能够非常快速地理解和掌握这些方法,趁着现在对Redis不是特别了解,我也对着命令来了解一下这 ...
- ES6 | 关于class类 继承总结
子类必须在constructor方法中调用super方法,否则新建实例时会报错.这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工.如果不调用super方法,子类就得不到 ...
- 你不知道的JavaScript演示代码Github地址
你不知道的JavaScript博文相关代码托管至Github,每次写完博客会把代码提交上去. 代码地址:https://github.com/rongbo-j/you-dont-know-js 点击D ...