Content provider管理android以结构化方式存放的数据。他以相对安全的方式封装数据并且提供简易的处理机制。Content provider提供不同进程间数据交互的标准化接口。
当你准备取出content provider中的数据时,你需要获得一个和当前上下文相关的ContentResolver对象作为客户端。这个对象和一个继承至ContentProvider的provider对象相关联。provider从客户端接收请求并且返回结果。
如果你不准备和其他应用共享你的数据,你不需要自定义provider。但是,你需要实现provider以满足个性化搜索数据的需求。同时,当你拷贝和粘贴复杂数据或者文件到他人应用中时,也需要provider。
上面摘自安卓开发指南。也指明了ContentProviders的作用就是在不同的应用程序之间传递数据,但是如果学过了Android文件与IO存储。就会发现SharedPreference,文件,数据库可以用于在应用程序之间传递数据,那么为什么不用这些来传递数据呢,因为这些方法存在安全漏洞:
Shared Preferences存储安全风险源于:
1 开发者在创建文件时没有正确的选取合适的创建模式(MODE_PRIVATE、MODE_WORLD_READABLE以及MODE_WORLD_WRITEABLE)进行权限控制;
2 开发者过度依赖Android系统内部存储安全机制,将用户信息、密码等敏感重要的信息明文存储在Shared Preferences文件中,导致攻击者可通过root手机来查看敏感信息。
正是因为存在这些问题,所以从Androdi4.2开始就不再支持SharedPreference方式在不同组件之间传递数据,在Android Studio中调用getSharedPrefence可以看见用于在程序之间传递数据的打开模式都已不再推荐
扯这么多废话,回到正题吧。如何创建一个自己的ContentProvider呢?
1 定义一个自己的CotentProvide类,该类需要继承ContentProvider类,并实现几个抽象方法
2 和定义一个Activity一样,在AndroidManfest.xml文件中注册该ContentProvider
上面第一步自定义ContentProvider类的时候需要实现几个抽象方法,这里说明一下:ContentProvider实际上是对数据库进行操作。学过数据库都知道数据库最基本的操作就是增,删,改,查。ContentProvider也正是定义了这四个抽象方法,所以在继承该类的时候需要实现这四个抽象方法。下面分别列出这四个抽象方法
重点说下query方法的参数:第一个Uri参数必须有其它随意,projection为数据库列名, selection为约束条件,selectionArgs为过滤参数,sortOrder为排序参数
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder)
public String getType(@NonNull Uri uri)
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values)
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs)
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs)
可以发现除了onCreate方法,其余每个方法都有一个Uri参数,这里介绍下Uri
想必大家都知道URL,这里做下对比学习,http://www.huya.com/g/lol
这里用了一个个人比较喜欢浏览的网址,有志同道合的撸友可以一起交流哈。好了,又扯偏了。回到正题:
这里URL可以分为三个部分
http:// :URL的协议部分,凡是通过HTTP协议来访问网址都需要该部分
www.huya.com : URL的域名部分,用于指定具体的网站
/g/lol :URL的网站资源部分,根据访问者的需求动态改变
URI(Universal Resource Identify)通用资源标识符
URI组成和URL类似这里举例一个:
content://com.example.mycontentprovider/test
其中:
content:// 是Android的ContentProvider规定的,类似URL的协议部分固定不变。
com.example.mycontentprovider : 这就是ContentProvider的authorities,类似URL的域名部分,用于指定ContentProvider
/test: 资源部分,根据访问者的需求动态变化
如果想详细了解URI可以参考URI详解。
有了URI基础后,我们需要知道ContentProvider就是根据匹配不同的URI提供不同的业务操作,
Android提供了UriMatcher工具类用于匹配Uri:
void addURI(String authority , String path , int code);该方法用于向UriMatcher对象注册Uri。其中authority和path组合成一个Uri,而code则代表该Uri的标志码。
int match(Uri uri)根据前面注册的Uri来判断指定Uri对应的标志码。如果没有匹配,则返回-1
举例:
private static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
private static final String AUTHORITY = "com.example.myprovider" ;
private static final int WORDS = 1 ;
private static final int WORD = 2 ;
static {
matcher.addURI(AUTHORITY , "words" ,WORDS);
matcher.addURI(AUTHORITY , "word/#" , WORD) ;
}
//上面创建的UriMatcher对象注册了两个Uri,其中com.example.myprovider/words对应的标识码为1,
//com.example.myprovider/word/#对应的标识码为2 ,其中#为通配符,表示任意数字。
//这意味着:
//返回标识码1
matcher.match(Uri.parse("com.example.myprovider/words")) ;
//返回标识码2
matcher.match(Uri.parse("com.example.myprovider/word/3")) ;
此外Android还提供了ContentUris工具类,用于Uri字符串.具体提供了如下两个方法:
1,withAppendedId(uri , id) 用于为指定uri添加id
Uri uri = Uri.parse("content://com.example.myprovider/word") ; Uri resultUri = ContentUris.withAppendedId(uri , 2); //生成后Uri为:content://com.example.myprovider/word/2
2,parseId(uri):用于从指定uri中解析出id
Uri uri = Uri.parse("content://com.example.myprovider/word/2") ; long wordId = ContentUris.parseId(uri); //wordId = 2
好了,现在我们假定已经自定义了一个继承自ContentProvider的类DicttProvider。那么我们要来完成创建ContentProvider的第二步:在AndroidManfest.xml中注册该ContentProvider。
我们需要在
<provider android:authorities="com.example.myprovider" android:exported="true" android:name=".DictProvider" />
从上面的配置信息我们可以看出,配置ContentProvider需要配置如下属性
* name : 指定该ContentProvider实现类的类名
* authorities :指定该ContentProvider的Uri。类似URL的域名部分。注意:上面已经提到URI由协议,域名,资源三部分组成。
* exported :指定该ContentProvider是否允许其它应用调用。
使用ContentResolver操作数据
根据前面讲解,ContentProvider可以说是提供了数据库操作的接口可以让其它应用程序调用。那么其它程序就是通过ContentResolver来操作数据。Context提供了如下方法来获取ContentResolver对象:
getContentResolver();获取该应用默认的ContentResolver对象
举例: ContentResolver cr = getContentResolver();
ContentResolver 类也提供了与ContentProvider类相对应的四个方法:
public Uri insert(Uri uri, ContentValues values) 该方法用于往ContentProvider添加数据。
public int delete(Uri uri, String selection, String[] selectionArgs) 该方法用于从ContentProvider删除数据。
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) 该方法用于更新ContentProvider中的数据。
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 该方法用于从ContentProvider中获取数据。
我们可以发现:ContentResolver和ContentProvider提供的insert, delete , query , update方法一样,而且都有一个参数Uri,那么我们是否可以猜想ContentResolver和ContentProvider是通过Uri进行的数据交互。其实事实情况也是这样:
假设A应用通过ContentResolver执行增删改查操作,这些操作都需要指定Uri参数,Android系统就根据该Uri找到对应的ContentProvider(该ContentProvider属于B应用),ContentProvider负责实现增删改查方法,完成对底层数据的各种操作。
ContentResolver Uri ContentProvider三者的关系为:
好了:基础知识就补充到这里了。下面上demo:
自定义ContentProvider
一:ContentProvider子类程序实现:
OpenHelperDb类:数据库表单创建类
package com.example.xukefeng.contentprovidertest; import android.content.ContentProvider; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; /** * Created by initializing on 2018/4/16. */ public class OpenHelperDb extends SQLiteOpenHelper { private final String BASE_NAME = "create table dict(_id integer primary key autoincrement" + ", word varchar(255) , detail varchar(255))" ; public OpenHelperDb(Context context , String baseName , int version) { super(context , baseName , null , version); } @Override public void onCreate(SQLiteDatabase db) { //创建数据库表 db.execSQL(BASE_NAME); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
MainActivity类:初始化数据库表单数据,并在Activity退出时关闭数据库资源。注意在MainActivity中用了setContentView(R.layout.activity_main);该字段用的默认的layout布局界面,没有做任何修改
package com.example.xukefeng.contentprovidertest; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private OpenHelperDb helperDb; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //默认layout界面 helperDb = new OpenHelperDb(this , "test.db3" , 1) ; //插入数据的操作 helperDb.getReadableDatabase().execSQL("insert into dict values(null , ? , ?)" , new String[]{"Android" , "安卓"}); helperDb.getReadableDatabase().execSQL("insert into dict values(null , ? , ?)" , new String[]{"CSDN" , "中国程序员大本营(China Software Developper Network)"}); helperDb.getReadableDatabase().execSQL("insert into dict values(null , ? , ?)" , new String[]{"github" , "社交编程及代码托管网站"}); Toast.makeText(this,"插入数据成功",Toast.LENGTH_SHORT).show(); } @Override protected void onDestroy() { super.onDestroy(); if (helperDb != null) { helperDb.close(); } } }
DictProvider类:继承自ContentPrivider类,实现增删改查方法:
package com.example.xukefeng.contentprovidertest; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; /** * Created by initializing on 2018/4/16. */ public class DictProvider extends ContentProvider { private static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); private static final int WORDS = 1 ; private static final String AUTHORITY = "com.example.myprovider" ; private static final int WORD = 2 ; private static final String _ID = "_id" ; private OpenHelperDb helperDb ; static { matcher.addURI(AUTHORITY , "words" ,WORDS); matcher.addURI(AUTHORITY , "word/#" , WORD) ; } @Override public boolean onCreate() { helperDb = new OpenHelperDb(getContext() , "test.db3" , 1) ; return true; } @Nullable @Override public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { SQLiteDatabase db = helperDb.getReadableDatabase() ; switch ( matcher.match(uri)) { case WORDS : return db.query("dict",null,null,null,null,null,null) ; case WORD : return db.query("dict",null,"_id = ?" ,selectionArgs ,null , null , null) ; default: throw new IllegalArgumentException("Unrecognized Uri !"); } } @Nullable @Override public String getType(@NonNull Uri uri) { return null; } @Nullable @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { SQLiteDatabase db = helperDb.getReadableDatabase() ; switch (matcher.match(uri)){ case WORDS : long rowId = db.insert("dict" ,null, values) ; if (rowId > 0) { Uri wordUri = ContentUris.withAppendedId(uri , rowId) ; //通知数据改变 getContext().getContentResolver().notifyChange(wordUri , null); return wordUri ; } break ; default: throw new IllegalArgumentException("Unrecognized Uri !"); } return null; } @Override public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { SQLiteDatabase db = helperDb.getReadableDatabase() ; //记录所删除的记录数目 int num = 0 ; switch (matcher.match(uri)) { case WORDS : num = db.delete("dict" , null , null) ; break ; case WORD : //解析处所要删除的记录Id long id = ContentUris.parseId(uri) ; String whereClause = _ID + "=" + id ; num = db.delete("dict" , whereClause ,selectionArgs) ; break ; default: throw new IllegalArgumentException("Unrecognized Uri !"); } //通知数据改变 getContext().getContentResolver().notifyChange(uri , null); return num; } @Override public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { SQLiteDatabase db = helperDb.getReadableDatabase() ; //记录需要修改的记录数目 int num = 0 ; switch (matcher.match(uri)) { case WORDS : //db.update() num = db.update("dict" , values ,selection ,selectionArgs) ; break; default: throw new IllegalArgumentException("Unrecognized Uri !"); } //通知数据改变 getContext().getContentResolver().notifyChange(uri , null); return num; } }
AndroidManfest.xml文件主要配置activity和contentprovider信息
xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.xukefeng.contentprovidertest"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> intent-filter> activity> <provider android:authorities="com.example.myprovider" android:exported="true" android:name=".DictProvider" /> application> manifest>
二:其它程序调用ContentProvider子类程序暴露的数据库操作接口
MainActivity:调用getContentResolver并且实现相关业务
package com.example.xukefeng.contentresolvertest;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
public class MainActivity extends AppCompatActivity {
private ListView mainActListView ;
private ContentResolver resolver ;
private static final String WORD = "word" ;
private static final String DETAIL = "detail" ;
private static final String AUTHORITY = "com.example.myprovider" ;
private static final Uri ALL_URI = Uri.parse("content://" + AUTHORITY + "/words") ;
private static final Uri SINGLE_URI = Uri.parse("content://" + AUTHORITY + "/word") ;
private static final String _ID = "_id" ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mainActListView = (ListView) findViewById(R.id.mainActListView) ;
//获取系统的ContentResolver对象
resolver = getContentResolver() ;
init() ;
}
private void init()
{
Cursor cursor = resolver.query(ALL_URI , null , null ,null ,null) ;
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this , R.layout.dict_adapt_listview,
cursor , new String[]{WORD , DETAIL} ,new int[] {R.id.DictWordTetView , R.id.DictDetailTetView}) ;
mainActListView.setAdapter(adapter);
System.out.println("init") ;
}
public void insertBtnClickLis(View view)
{
ContentValues values = new ContentValues() ;
values.put(WORD , "CSDN");
values.put(DETAIL , "中国程序员大本营(China Software Developper Network)" );
resolver.insert(ALL_URI , values ) ;
init();
}
public void deleteBtnClickLis(View view)
{
resolver.delete(ALL_URI , null , null) ;
init();
}
public void updateBtnClickLis(View view)
{
ContentValues values = new ContentValues() ;
values.put(WORD , "hello world!");
values.put(DETAIL , "世界 你好!" );
resolver.update(ALL_URI , values , new String("word = ?") , new String[]{"CSDN"} ) ;
init();
}
}
activity_main.xml布局文件:主界面布局,按钮和按钮点击事件监听
dict_adapt_listview.xml: SimpleCursorAdapt指定的布局文件。用于显示单词列表
AndroidManfest.xml:配置文件