一. 什么是内容提供者
内容提供者是四大组件之一,但是一般开发的时候我们并不经常用到。内容提供者就是ContentProvider,它为不同的软件之间数据共享提供了统一的接口。它用一种类似数据表的方式将数据暴露。用图像来描述的话大致是这样的
因此对于其他软件来说ContentProvider就像一个“数据库”。因此外界获取其提供的数据,与从数据库中获取数据的操作基本一样。只不过需要URI来表示要访问的“数据库”。
也就是说内容提供者ContentProvider主要用在不同的应用程序之间实现数据共享功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访问的数据安全性。目前,使用内容提供者是Android实现跨程序共享数据的标准方法。不同于文件存储和sharedPerference存储中的两种全局可读写操作模式,内容提供者可以选择只对那一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄露的风险。
举一个我们常见的内容提供者。在Android系统中,很多数据如:联系人信息、短信信息、图片库、音频库等,这些信息在开发中还是经常用到的,这些信息谷歌工程师已经帮我们封装好了,我们可以使用谷歌给我的Uri去直接访问这些数据。
二. 内容提供者的用途
来点官方的话说明一下
Content Provider是Android系统的四大组件之一,他们封装好数据,并提供定义数据安全的机制。Content Provider 是一个进程同另一个进程连接数据的标准接口。在Android系统中,应用程序之间是相互独立的,分别运行在自己的进程中,相互之间没有数据交换。若应用程序之间需要程序共享那么用的手段就是Content Provider。
Content Provider以URI的形式对外提供数据存途径,在其他应用中。使用ContentResolver对象作为“客户端”同Provider进行通信,该ContentReslover对象同provider对象进行通信,他们之间自动处理IPC和安全。Provider对象从各个客户端中接收数据请求,执行请求动作并返回结果。
三. 先导知识
我们在使用Content Provider(内容提供者)的时候还用到了URI和ContentUris等相关内容,在最后的案例中会出现,为了更好的理解代码,所以需要提前了解一下。
3.1 URI的讲解
在上文中我们提到想要访问其他软件的内容提供者,就需要URI。那什么是URI呢?
URI是统一资源标识符是一个用于标识某一资源名称的字符串。该种标识允许用户对任何(包括本地和互联网)的资源通过特定的协议进行交互操作。
就Android平台而言,URI主要分为三个部分:scheme,authonity与path。其中authonity又分为host和port。
URI格式如下:
scheme://host:port/path
举例子:
在我们的内容提供者中authonity就是用来标识其唯一的。
3.2 ContentUris
是关于Content uri的工具类,Content Uri的语法如下:
content://authonity/path/id
- content部分:该部分的值总是content://
- authonity部分:这部分用于标识整个content provider
- path部分:通常用于区分不同的表。
- id 部分:用于标识数据的单独行,通常对应ID列
常用方法
- public static long parseld(Uri contentUri)。
就是将Uri中的id取出来,没有path返回-1,可能抛出异常。
四. 内容提供者的使用步骤
4.1 步骤1:定义一个类继承ContentProvider
ContentProvider封装数据并将数据通过ContentResolver向其他app提供,当某个数据请求通过ContentResolver执行时,会解析URI中的authority,并将请求发送到系统所注册authority的ContentProvider,在ContentProvider中再解析URI其余部分(主要是path部分)。流程如图所示。
ContentProvider这个类定义了六个抽象方法。
需要实现的主要方法是:
- public boolean onCreate () 在创建ContentProvider时调用
- public Cursor query (Uri, String[], String, String[], String) 用于查询指定Uri的ContentProvider,返回一个Cursor
- public Uri insert (Uri, ContentValues) 用于添加数据到指定Uri的ContentProvider中,(外部应用向ContentProvider中添加数据)
- public int update (Uri, ContentValues, String, String[]) 用于更新指定Uri的ContentProvider中的数据
- public int delete (Uri, String, String[]) 用于从指定Uri的ContentProvider中删除数据
- public String getType (Uri) 用于返回指定的Uri中的数据的MIME类型
数据访问方法(如 insert(Uri, ContentValues) 和 update(Uri, ContentValues, Bundle))可以同时从多个线程调用,并且必须是线程安全的。其他方法(如 onCreate() )仅从应用程序主线程调用,并且必须避免执行冗长的操作。
在这里不做代码举例,会在文章的最后举一个例子,展示使用的流程。
4.2 步骤2: 在清单文件中配置内容提供者(provider)
语法:
<provider android:authorities="list"
android:directBootAware=["true" | "false"]
android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:grantUriPermissions=["true" | "false"]
android:icon="drawable resource"
android:initOrder="integer"
android:label="string resource"
android:multiprocess=["true" | "false"]
android:name="string"
android:permission="string"
android:process="string"
android:readPermission="string"
android:syncable=["true" | "false"]
android:writePermission="string" >
. . .
</provider>
说明:
内容提供者组件需要在清单文件中注册(Android的四大组件其实都要注册)。编写内容提供者需要继承ContentProvider 类,应用中所有的内容提供者都必须在清单文件中注册,否则系统将不知道他们,也不会运行他们。
你只能在你的应用中注册你自己的内容提供者,而不能在你的应用中注册其他应用程序的内容提供者。
Android会根据你在清单配置文件中注册的内容提供者URI,存储内容提供者的引用(android:authorities="list"就是这个东西)。
属性:
- android:authorities:
这个属性没有默认值,所以必须要写。它是一个或多个URI的列表,用于标识内容提供者的数据。如果写多个要用分号隔开。为避免冲突,命名应遵循 Java 样式的命名惯例。通常,用的是内容提供者类的名称。com.example.provider.cartoonproviderContentProvider - android:enabled
系统是否可以实例化内容提供者。如果可以,则设为"true";如果不能,则设为"false"。默认值为"true"。 - android:exported
内容提供者是否可供其他应用使用。
true:内容提供者可供其他应用使用。任何应用均可使用内容提供者的 URI 来访问它,但需提供权限进行访问。
false:提供程序不可供其他应用使用。仅限您的应用访问提供者。只有与内容提供者具有相同的用户 ID (UID) 的应用或者通过android:grantUriPermissions属性被临时授予对内容提供者的访问权限的应用才能访问。
4.3 步骤3: 静态代码块添加匹配规则(UriMatcher的使用)
ContentProvider封装数据并将数据通过ContentResolver向其他app提供,当某个数据请求通过ContentResolver执行时,会解析URI中的authority,并将请求发送到系统所注册authority的ContentProvider,在ContentProvider中再解析URI其余部分(主要是path部分)。ContentProvider解析URI其余部分这个工作就是UriMatcher去完成的。用图像来表示的话,大致是这个样子。
该类中有一个常量,其值为-1
Public static final int NO_MATCH
一般情况下我们都会用上述的常量作为构造方法public UriMatcher(int code)的参数,创建Uri树的根节点。参数code为根节点的编码
主要使用方法:
- public UriMatcher(int code) :创建一个UriMatch对象,创建Uri树的根节点。
- public void addURI (String authority,String path, int code):添加一个URI到URI树中,参数code对应该URI。
第一个参数URI的authonity(用于标识整个ContentProvider,应与清单文件中注册的一致)
第二个参数URI的path(通常用于区分不同的表)
第三个参数URI匹配成功返回的值 - public int match(Uri uri) :参数uri和addURI()方法添加的URI进行匹配。
4.4 步骤4: 暴露自己想暴露的方法(CRUD)
首先说一下什么是CURD,CRUD是指在做计算处理时的增加(Create)、读取查询(Retrieve)、更新(Update)和删除(Delete)几个单词的首字母简写。
暴露自己的方法就是让其他应用对自己的内容提供者能执行什么操作,只需要在自己的内容提供者相应的方法中写入相应的表即可,下面举一个例子便于理解。
应用A有两个表,表a和表b希望暴露给外界。表a只允许外界添加和查询。表b允许外界增、删、改、查。
首先我们在写addURI需要添加两个返回值。因为a和b表都有查询操作,那么自己内容提供者的查询方法中就写上a和b的匹配值判断,像删除方法只有b表,那在内容提供者的删除方法只写b表匹配值判断。大致如下
//查询
query(){
switch(match方法返回匹配成功返回值){
case a的匹配成功返回值:
执行对a表查询操作;
break;
case b的匹配成功返回值:
执行对b表查询操作;
break;
}
}
//插入
insert(){
switch(match方法返回匹配成功返回值){
case a的匹配成功返回值:
执行对a表查插入操作;
break;
case b的匹配成功返回值:
执行对b表插入操作;
break;
}
}
//修改
update(){
switch(match方法返回匹配成功返回值){
case b的匹配成功返回值:
执行对b表修改操作
break;
}
}
//删除
delete(){
switch(match方法返回匹配成功返回值){
case b的匹配成功返回值:
执行对b表删除操作
break;
}
}
像这样将想暴露的操作写入内容提供者相应的方法中,进行match方法匹配,然后执行操作即可。
4.5 步骤5:其他应用通过内容提供者操作数据库
就是你使用contentResolver执行什么方法然后传入URI判断什么表就,然后match方法在ContentProvider相应操作执行match匹配什么表就行了
因为我的就一个例子,然后我就把增删改查四个方法写成树了,主要是为了体现这个addURI和match这个过程。
五. 实战演练
应用A
acticity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ListView
android:id="@+id/lv_lv"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</androidx.constraintlayout.widget.ConstraintLayout>
item.xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginLeft="5dp"
android:background="#445566"></ImageView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginLeft="20dp"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:text="姓名">
</TextView>
<TextView
android:id="@+id/tv_phone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:text="电话"></TextView>
</LinearLayout>
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity {
private ListView listView;
private List<Person> persons;
private Handler handler=new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch(msg.what){
case 100:
listView.setAdapter(new MyAdapter());
break;
}
}
};
private class MyAdapter extends BaseAdapter{
@Override
public int getCount() {
return persons.size();
}
@Override
public Object getItem(int i) {
return persons.get(i);
}
@Override
public long getItemId(int i) {
return ((Person) persons.get(i)).getId();
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
Person person=persons.get(i);
View view1=View.inflate(MainActivity.this,R.layout.item,null);
TextView tv_name= view1.findViewById(R.id.tv_name);
TextView tv_phone=view1.findViewById(R.id.tv_phone);
tv_name.setText("姓名:"+person.getName());
tv_phone.setText("电话:"+person.getNumber());
return view1;
}
}
public void addDate(){
PersonDao dao=new PersonDao(this);
long number=12300000000L;
for(int i=0;i<10;i++){
dao.add("卓"+i,number+i+"");
}
}
private void getPersons(){
Uri uri =Uri.parse("content://cn.edu.daqing.mt.provider/query");
ContentResolver contentResolver=getContentResolver();
Cursor cursor=contentResolver.query(uri,null,null,null,null);
persons=new ArrayList<>();
if(cursor==null){
return;
}
while (cursor.moveToNext()){
Person person=new Person();
person.setName(cursor.getString(cursor.getColumnIndex("name")));
person.setNumber(cursor.getString(cursor.getColumnIndex("number")));
person.setId(cursor.getColumnIndex("id"));
persons.add(person);
}
cursor.close();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView=findViewById(R.id.lv_lv);
new Thread(){
@Override
public void run() {
super.run();
//addDate();
getPersons();
if(persons.size()>0){
handler.sendEmptyMessage(100);
}
}
}.start();
}
}
Person.java
public class Person {
private int id;
private String name;
private String number;
public Person(){
}
public Person(int id, String name, String number) {
this.id = id;
this.name = name;
this.number = number;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", number='" + number + '\'' +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
}
PersonDao.java
public class PersonDao {
private PersonSQLiteOpenHelper helper;
public PersonDao(Context context){
this.helper=new PersonSQLiteOpenHelper(context);
}
public long add(String name ,String number){
SQLiteDatabase db=helper.getWritableDatabase();
ContentValues contentValues =new ContentValues();
contentValues.put("name",name);
contentValues.put("number",number);
long id =db.insert("person",null,contentValues);
db.close();
return id;
}
}
PersonDBProvider.java
public class PersonDBProvider extends ContentProvider {
private PersonSQLiteOpenHelper helper;
private static final int INSERT=1;
private static final int DELETE=2;
private static final int UPDATE=3;
private static final int QUERY=4;
private static final int QUERYONE=0;
private static UriMatcher matcher=new UriMatcher(UriMatcher.NO_MATCH);
static {
matcher.addURI("cn.edu.daqing.mt.provider","insert",INSERT);
matcher.addURI("cn.edu.daqing.mt.provider","delete",DELETE);
matcher.addURI("cn.edu.daqing.mt.provider","update",UPDATE);
matcher.addURI("cn.edu.daqing.mt.provider","query",QUERY);
matcher.addURI("cn.edu.daqing.mt.provider","query/#",QUERYONE);
}
@Override
public boolean onCreate() {
helper= new PersonSQLiteOpenHelper(getContext());
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
switch (matcher.match(uri)){
case QUERY:
SQLiteDatabase db= helper.getReadableDatabase();
Cursor cursor=db.query("person",strings,s,strings1,null,null,s1);
return cursor;
case QUERYONE:
db=helper.getReadableDatabase();
long id= ContentUris.parseId(uri);
cursor=db.query("person",strings,"id=?",new String[]{id+""},null,null,s1);
return cursor;
default:
try {
throw new IllegalAccessException("路径不匹配不执行查询");
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
switch (matcher.match(uri)){
case QUERY:
return "vnd.android.cusor.dir/person";
case QUERYONE:
return "vnd.android.cusor.item/person";
default:
break;
}
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
if(matcher.match(uri)==INSERT){
SQLiteDatabase db=helper.getWritableDatabase();
db.insert("person",null,contentValues);
db.close();
}else {
try {
throw new IllegalAccessException("路径不匹配不执行查询");
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return uri;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
int no = 0;
if(matcher.match(uri)==DELETE){
SQLiteDatabase db=helper.getWritableDatabase();
no = db.delete("person",s,strings);
db.close();
}else {
try {
throw new IllegalAccessException("路径不匹配不执行查询");
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return no;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
int no = 0;
if(matcher.match(uri)==UPDATE){
SQLiteDatabase db=helper.getWritableDatabase();
no = db.update("person",contentValues,s,strings);
db.close();
}else {
try {
throw new IllegalAccessException("路径不匹配不执行查询");
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return no;
}
}
PersonSQLiteOpenHelper.java
public class PersonSQLiteOpenHelper extends SQLiteOpenHelper {
public PersonSQLiteOpenHelper(Context context){
super(context,"person.db",null,1);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL("create table person (id integer primary key autoincrement,name varchar(20),number varchar(20))");
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
}
}
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.contentprovidera">
<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="cn.edu.daqing.mt.provider"
android:name=".PersonDBProvider"
android:permission="cn.edu.daqing.mt.provider.WZZ"
android:exported="true"/>
</application>
<permission android:name="cn.edu.daqing.mt.provider.WZZ"></permission>
</manifest>
应用B
MainActivity.java
public class MainActivity extends AppCompatActivity {
private ListView listView;
private List<Person> persons;
private Handler handler=new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch(msg.what){
case 100:
listView.setAdapter(new MyAdapter());
break;
}
}
};
private class MyAdapter extends BaseAdapter{
@Override
public int getCount() {
return persons.size();
}
@Override
public Object getItem(int i) {
return persons.get(i);
}
@Override
public long getItemId(int i) {
return ((Person) persons.get(i)).getId();
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
Person person=persons.get(i);
View view1=View.inflate(MainActivity.this,R.layout.item,null);
TextView tv_name= view1.findViewById(R.id.tv_name);
TextView tv_phone=view1.findViewById(R.id.tv_phone);
tv_name.setText("姓名:"+person.getName());
tv_phone.setText("电话:"+person.getNumber());
return view1;
}
}
private void getPersons(){
Uri uri =Uri.parse("content://cn.edu.daqing.mt.provider/query");
ContentResolver contentResolver=getContentResolver();
Cursor cursor=contentResolver.query(uri,null,null,null,null);
persons=new ArrayList<>();
if(cursor==null){
return;
}
while (cursor.moveToNext()){
Person person=new Person();
person.setName(cursor.getString(cursor.getColumnIndex("name")));
person.setNumber(cursor.getString(cursor.getColumnIndex("number")));
person.setId(cursor.getColumnIndex("id"));
persons.add(person);
}
cursor.close();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView=findViewById(R.id.lv_lv);
new Thread(){
@Override
public void run() {
super.run();
getPersons();
if(persons.size()>0){
handler.sendEmptyMessage(100);
}
}
}.start();
}
}
Person.java
public class Person {
private int id;
private String name;
private String number;
public Person(){
}
public Person(int id, String name, String number) {
this.id = id;
this.name = name;
this.number = number;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", number='" + number + '\'' +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
}
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.contentproviderb">
<uses-permission android:name="cn.edu.daqing.mt.provider.WZZ"></uses-permission>
<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>
</application>
</manifest>
应用B的显示布局和应用A的一致,将应用A中的activity_main.xml和item.xml复制过来即可。
后续文章持续更新中。。。。