6.ContentProvider存储
ContentProvider(内容提供者)是Android 的四大组件之一。主要用于对外共享数据,也就是说通过将ContentProvider将数据中的资源,分享给其他应用访问。其他应用可以通过ContentProvider对指定应用中的数据进行操作。ContentProvider分为系统的和自定义的,系统的也就是例如联系人,图片等数据。交互关系如图所示:
看了这个会觉得很奇怪,为什么不直接和数据库交互呢?还要通过ContentProvider进行交互。主要是Android的安全性问题决定的,它的数据库是私有的,所以外部数据无法直接访问这个数据库。所以提供了这个ContentProvider 内容提供者,将数据库的内容提供给外部应用,同时将外部应用的数据存储到数据库中。如果外部应用想要操作数据库暴露的数据时,需要ContentResolver来操作ContentProvider暴露的数据。
一旦某个应用通过ContentProvider暴露了数据,那么不管该应用程序是否启动,其他的应用都能通过该接口操作暴露的数据,对数据进行增删查改的操作。
6.1 ContentProvider的核心类
1、ContentProvider:(A应用暴露数据)
● 一个程序可以通过实现一个ContentProvider的抽象接口将自己的数据暴露出去;
● 外界根本看不到,也不用看到这个应用暴露的数据在应用当中是如何存储的,是用数据库存储还是用文件存储,还是通过网上获得,这些一切都不重要,重要的是外界可以通过这一套标准及统一的接口和程序里的数据打交道,可以读取程序的数据,也可以修改程序的数据。
2、ContentResolver:(操作A应用所暴露的数据)
● 外界的程序通过ContentResolver接口可以访问ContentProvider提供的数据;
● ContentResolver 可以理解成是HttpClient的作用。
3、 Uri:Uri是ContentResolver和ContentProvider进行数据交换的标识。
● 每个ContentProvider提供公共的URI来唯一标识其数据集。管理多个数据集的(多个表)的 ContentProvider 为每个数据集提供了单独的URI。
● Uri 的标准前缀:以“content://”作为前缀,这个是标准的前缀,表示该数据由ContentProvider 管理。Android所提供的ContentProvider都存放在andriod.provider这个包里面 Android的ContentProvider URI有固定的形式:content://contract/people。
● Uri 的authority部分:该部分是完整的类名。(使用小写形式)。
● Uri 的path部分(资源部分、数据部分): 用于决定哪类数据被请求。
● 被请求的特定记录的id值。如果请求不仅限于某个单条数据,该部分及其前面的斜线应该删除。
● 为了将一个字符串转换成Uri,Android中提供了Uri的parse()静态方法来实现。
通配符的使用
这个 * :表示匹配任意长度的任意字符
这个 # :表示匹配任意长度的数字
【备注:】URI、URL、URN的区别:
● 首先,URI,是uniform resource identifier,统一资源标识符,用来唯一的标识一个资源。
● URL是uniform resource locator,统一资源定位器,它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。
● URN,uniform resource name,统一资源命名,是通过名字来标识资源,比如mailto:[email protected]。
也就是说,URI是以一种抽象的,高层次概念定义统一资源标识,而URL和URN则是具体的资源标识的方式。URL和URN都是一种URI。
总结一下:URL是一种具体的URI,它不仅唯一标识资源,而且还提供了定位该资源的信息。URI是一种语义上的抽象概念,可以是绝对的,也可以是相对的,而URL则必须提供足够的信息来定位,所以,是绝对的。
6.2 ContentProvider 访问其他程序中的数据
ContentProvider 的用法有两种:一种是使用现有的内容提供器来读取和操作相应的程序中的数据,另一种是创建者自己的内容提供器暴露程序的外部数据。Android 系统中自带的电话簿,短信,媒体库等程序都提供了类似的访问接口,这就使得第三应用程序可以充分利用这部分数据来实现更好的功能。
6.2.1 ContentResolver的基本用法
如果一个外部应用程序想访问内容提供器中暴露的数据,那么就一定要借助ContentResolver()方法。Context类中提供了getContentResolver()的方法获取该类的实例。此外ContentResolver还提供了一系列的方法对数据进行CRUD操作。和SQLiteDatabase的使用的方法一样,insert()插入数据,update()更新数据,delete()删除数据,query()查询数据。不同的是ContentResolver中的增删查改方法方法都是不接受表名的,而是用Uri来替代。
上面说了Uri的一些知识点,这里再详细说一下。Uri给内容提供器的数据创建了一个唯一的标识符,它由两部分组成:authority 和path。authority是对不同应用程序做区分的,一般为了避免冲突,设置为包名。eg:我的项目包名为 com.demo.filesavedemo,那么这个程序的authority 就设置为com.demo.filesavedemo.provider。path是在该应用程序内的对不同的表的区分。通常加到authority的后面。eg:数据库里面有两张表 tab1和tab2,然后path 分别命名为/tab1和/tab2,最后别忘了在头部加上前缀content:// 。所以Uri最标准的写法为:
content://com.demo.filesavedemo.provider/tab1
content://com.demo.filesavedemo.provider/tab2
通过这样的Uri,我们就可以很清楚的表达了我们想要访问的哪个程序里面的哪张表的数据了。如果你还想访问表tab1里面的某个数据的话,可以在/tab1的后面加上你想访问的数据。eg:加入你想访问表tab1里面的person变量的某一个值,那么首先找到它对应的id 的值,比如是10,所以Uri的写法为:content://com.demo.filesavedemo.provider/tab1/person/10。这样你就能找到对应的值了。如果要找10这个id对应的name字段,再加上/name。content://com.demo.filesavedemo.provider/tab1/person/10/name。依次类推
6.2.1.1 查询数据的操作
得到Uri对应的字符串之后,还要将它解析成Uri对象才可以作为参数传入。需要调用ri.parse()方法。
Uri uri=Uri.parse("content://com.demo.filesavedemo.provider/tab1")
//查询tabl
Cursor cursor = getContentResolver().query(
uri,
projection,
selection,
selectionArgs,
sortOrder);
query() 方法参数 | 对应的SQL部分 | 描 述 |
---|---|---|
uri | from table_name | 指定查询某个应用的表名 |
projection | select column1 ,column2 | 指定查询的列名 |
selection | where column1 = value | 指定where的约束条件 |
selectionArgs | - | 为where的占位符提供具体的值 |
sortOrder | order by column1,colimn2 | 指定查询结果的排序方式 |
查询完成之后还是返回一个Cursor对象。取出每行中相应列的数据。
if(cursor!=null){
while(cursor.moveToNext()){
String column1=cursor.getString(cursor.getColumnIndex("列的名字"));
int column2=cursor.getInt(cursor.getColumnIndex("列的名字"));
}
cursor.close();
}
方法上大致和SQLDatabase差不多。
6.2.1.2 添加数据的操作
SQLDatabase的添加数据库操作,需要用上ContentValues对象,这里也是一样:
ContentValues values=new ContentValues();
values.put("name","小王");
values.put("age",18);
getContentResolver().insert(uri,values);
6.2.1.3 更新数据库操作
如果想要更新这新添加的数据库的数据,把name的值改为小六,然后借用update()的方法。
ContentValues values=new ContentValues();
values.put("name","小六");
getContentResolver().update(uri,values,"name=? and age = ?",new String[]{"小王","18"});
这里指定了selection 和selectionArgs,控制范围,放置别的行受影响。
6.2.1.4 删除数据库的操作
加入需要删除掉 age=18 这一列
getContentResolver.delete(uri,"age=?",new String []{"18"});
到这里,增删查改都过了一遍,和之前的差别不大,很容易理解,不过要注意参数的意思。
6.3 创建自己的内容提供器
6.3.1 UriMatcher类的介绍
我们知道Uri是ContentProvider暴露数据的关键,通过Uri可以指定需要查询的表。假设有这么一个场景,在一个数据源包含有多个内容(多张表),那么我们查询的时候就需要多个Uri来指向对应的表。那么在查询了tab1之后还需要查询tab2怎么办呢?可不可以从tab1过滤到tab2呢?显然是可以的。这时候使用UriMatcher就可以帮助我们方便的过滤到TableA还是TableB, 然后进行下一步查询, 如果不用UriMatcher也可以,我们就需要手动过滤字符串,用起来有点麻烦,可维护性也不好。
6.3.2 UriMatcher的使用
在使用之前可以看下UriMatcher的源码。
private static final int PEOPLE = 1;
private static final int PEOPLE_ID = 2;
private static final int PEOPLE_PHONES = 3;
private static final int PEOPLE_PHONES_ID = 4;
private static final int PEOPLE_CONTACTMETHODS = 7;
private static final int PEOPLE_CONTACTMETHODS_ID = 8;
private static final int DELETED_PEOPLE = 20;
private static final int PHONES = 9;
private static final int PHONES_ID = 10;
private static final int PHONES_FILTER = 14;
private static final int CONTACTMETHODS = 18;
private static final int CONTACTMETHODS_ID = 19;
private static final int CALLS = 11;
private static final int CALLS_ID = 12;
private static final int CALLS_FILTER = 15;
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static
{
sURIMatcher.addURI("contacts", "people", PEOPLE);
sURIMatcher.addURI("contacts", "people/#", PEOPLE_ID);
sURIMatcher.addURI("contacts", "people/#/phones", PEOPLE_PHONES);
sURIMatcher.addURI("contacts", "people/#/phones/#", PEOPLE_PHONES_ID);
sURIMatcher.addURI("contacts", "people/#/contact_methods", PEOPLE_CONTACTMETHODS);
sURIMatcher.addURI("contacts", "people/#/contact_methods/#", PEOPLE_CONTACTMETHODS_ID);
sURIMatcher.addURI("contacts", "deleted_people", DELETED_PEOPLE);
sURIMatcher.addURI("contacts", "phones", PHONES);
sURIMatcher.addURI("contacts", "phones/filter/*", PHONES_FILTER);
sURIMatcher.addURI("contacts", "phones/#", PHONES_ID);
sURIMatcher.addURI("contacts", "contact_methods", CONTACTMETHODS);
sURIMatcher.addURI("contacts", "contact_methods/#", CONTACTMETHODS_ID);
sURIMatcher.addURI("call_log", "calls", CALLS);
sURIMatcher.addURI("call_log", "calls/filter/*", CALLS_FILTER);
sURIMatcher.addURI("call_log", "calls/#", CALLS_ID);
}
很简单,首先是实例化UriMatcher ,然后是addURI()注册需要的Uri。源码中是放在一个静态块中的,我们在开发的时候只需要这样写就好,后面的PEOPLE,PEOPLE_ID这些都是在返回码。有了这些返回码,我们才能区别Uri,也可以自己定义。
总之,UriMatcher本质上是一个文本过滤器,用在contentProvider中帮助我们过滤,分辨出查询者想要查询哪个数据表。
6.3.3 创建自己的内容提供器
首先点击包->New->Other->Content Provider ,弹出的页面上,如下所示,新建MyContentProvider 类继承 ContentProvider,然后重写这6个方法,再借助UriMatch类就可以实现匹配Uri了。
Exported属性,表示是否允许外部程序访问ContentProvider;Enable表示是否启用这个ContentProvider。这样就会自动在AndroidManifest.xml自动注册
下面对着几个方法做一个简单的介绍:
1.onCreate() ,初始化ContentProvider 的时候调用,在这里完成数据库的创建和升级。返回true表示成功,返回false 表示失败。注意只有当ContentResolver尝试访问成熟中的数据的时候,内容提供器才会初始化。
2.query() 查询数据 ,之前提过。
3.insert() 插入数据 ,之前提过。
4.delete() 删除数据 , 之前提过。
5.update() 更新数据,之前提过。
6.getType() 这个方法有点特别,它根据传入的URI来返回相应的MIME类型( MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的因特网标准。 MIME 消息能包含文本、图像、音频、视频以及其他应用程序专用的数据。)。可以看到几乎每个方法都带有Uri这个参数,这个参数也正是调用ContentResolver的增删查改方法传过来的。现在需要对Uri进行解析,从中分析出希望访问的表和数据。一个Uri所对应的MIME字符串主要由3部分组成。Android 对这3个部分做了格式规定:
- 必须以 vnd开头
- 如果URI以路径结尾,则后接android.cursor.dir/,如果URI以id结尾,则后接上android.cursor.item/
- 最后接上vnd.
.
例如:1. content://com.demo.filesavedemo.provider/tab1 ,所对应的MIME类型为:
vnd.android.cursor.dir/vnd.com.demo.filesavedemo.provider.tab1
2. content://com.demo.filesavedemo.provider/tab1/person/10,所对应的MIME类型为:
vnd.android.cursor.item/vnd.com.demo.filesavedemo.provider.tab1
public class Myprovider extends ContentProvider {
private static final int TABLE_DIR = 0;
private static final int TABLE_ITEM = 1;
private static final int TABLE2_DIR = 2;
private static final int TABLE2_ITEM = 3;
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sURIMatcher.addURI("com.demo.filesavedemo.ContentProvider","table1",TABLE_DIR);
sURIMatcher.addURI("com.demo.filesavedemo.ContentProvider","table1/#",TABLE_ITEM);
sURIMatcher.addURI("com.demo.filesavedemo.ContentProvider","table2",TABLE2_DIR);
sURIMatcher.addURI("com.demo.filesavedemo.ContentProvider","table2/#",TABLE2_ITEM);
}
@Override
public boolean onCreate() {
return false;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
switch (sURIMatcher.match(uri)){
case TABLE_DIR:
return "vnd.android.cursor.dir/vnd.com.demo.filesavedemo.ContentProvider.table1";
case TABLE_ITEM:
return "vnd.android.cursor.item/vnd.com.demo.filesavedemo.ContentProvider.table1";
case TABLE2_DIR:
return "vnd.android.cursor.dir/vnd.com.demo.filesavedemo.ContentProvider.table2";
case TABLE2_ITEM:
return "vnd.android.cursor.item/vnd.com.demo.filesavedemo.ContentProvider.table2";
default:
break;
}
return null;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
switch (sURIMatcher.match(uri)){
case TABLE_DIR:
//查询table1表中的所有的数据
break;
case TABLE_ITEM:
//查询table1表中的单条的数据
break;
case TABLE2_DIR:
//查询table2表中的所有的数据
break;
case TABLE2_ITEM:
//查询table2表中的单条的数据
break;
}
return null;
}
//其他的方法和query方法一样,都会携带uri,然后根据UriMatcher的match()方法来判断,
// 出调方希望返回哪张表,再根据表来操作
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
return 0;
}
}
7.网络存储
网络存储一般都是将数据存储在服务器或者第三方服务器等,然后android 端通过http协议/Tcp协议等网络请求来获取数据。因为首先需要自己准备服务器,所以这里给一个网上的例子。
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import android.app.Activity;
import andorid.os.Bundle;
public class MyAndroidWeatherActivity extends Activity{
private static final String SERVER_URL="http://www.webservicex.net/WeatherForecast.asmx/GetWeatherByPlaceName";
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
HttpPost request=new HttpPost(SERVER_URL);//根据内容来源地址创建一个Http请求
//添加一个变量
List params=new ArrayList();
//设置一个地区名称
params.add(new BasicNameValuePair("PlaceName","NewYork"));//添加必须的参数
try{
//设置参数的编码
request.setEntity(new UrlEncodedFormEntity(params,HTTP.UTF_8));
//发送请求并获取反馈
HttpResponse httpResponse=new DefaultHttpClient().execute(request);
//解析返回的内容
if(httpResponse.getStatusLine().getStatusCode()!=404){
String result=EntityUtils.toString(httpResponse.getEntity());
System.out.printlin(result);
}
}catch(Exception e){
e.printStackTrace();
}}}
github地址:https://github.com/wangxin3119/myContentProvider