跨程序共享数据:Android四大组件之内容提供器
创始人
2024-02-06 12:23:41
0

跨程序共享数据:Android四大组件之内容提供器

  • 前言
  • 七、跨程序共享数据:Android四大组件之内容提供器
    • 7.1 内容提供器(Content Provider)简介
    • 7.2 运行时权限(软件不能为所欲为,想要什么权限,还得主人决定)
    • 7.3 访问其它程序中的数据
      • 7.3.1 什么是URI
      • 7.3.2 基于内容提供器对其它程序的数据进行增删改查
      • 7.3.3 查询电话本程序中数据的核心代码示例
    • 7.4 创建自己的内容提供器(提供共享数据的接口)
      • 7.4.1 创建自己的内容提供器步骤
      • 7.4.2 创建自己的内容提供器核心代码示例
      • 7.4.2 创建自己的内容提供器完整代码示例
  • 参考书籍:第一行代码

前言

Android中包含了大量的应用程序,那么如何获取其它应用程序的数据,来做一些更有意思的事情呢?内容提供作为Android的四大组件之一就是用来实现应用程序之间数据共享的。

七、跨程序共享数据:Android四大组件之内容提供器

7.1 内容提供器(Content Provider)简介

内容提供器是实现Android跨程序之间数据共享的一种安全方式。前面我们讲到Android持久化技术,我们也可以让其它应用程序直接访问我们的持久化文件实现数据共享,但是这种方式存在安全隐患,因此又提出了内容提供器技术方案,通过内容提供器可以控制开发哪些数据让别的程序访问,进一步提高数据共享的安全性。

7.2 运行时权限(软件不能为所欲为,想要什么权限,还得主人决定)

内容提供器是实现跨程序数据共享,既然是访问别的程序数据,为了安全性起见,自然不是你想访问谁的数据就访问谁的数据,你需要先申请访问该程序的权限,然后由用户决定是否同意你的申请,只有权限申请通过了,才能利用内容提供器访问程序中的数据,而运行时权限就是实现这一过程的方案

申请的权限包括普通权限和危险权限,普通权限只需要在AndroidManifest.xml文件中声明该权限即可,由系统自动授权;危险权限不仅需要在AndroidManifest.xml文件中声明该权限,而且需要在代码中申请该权限,然后由用户决定是否授权

Android中涉及的危险权限如下表所示:
在这里插入图片描述

  • 普通权限申请,如网络状态权限申请的示例如下,只需要在AndroidManifest.xml文件中添加< use-permission >标签即可。,


  • 危险权限申请,如读取电话本程序数据权限申请示例如下,需要同时在代码中添加权限申请逻辑。

AndroidManifest.xml文件配置:



代码申请权限:

if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED)
{//没有读取电话簿的权限,需要代码申请权限,参数1:context 参数2:需要申请的权限列表 参数3:申请权限的标识,在申请结果的回调函数中使用ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_CONTACTS},1);
}
else//有读取电话簿的权限,可以进行读取电话簿数据readContacts();
 @Override//申请权限结果回调函数public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);switch (requestCode){case 1://如果用户同意了申请,则读取数据,进行相关操作if (grantResults.length>0 && grantResults[0]==PackageManager.PERMISSION_GRANTED)readContacts();//如果用户没有同意申请,进行其它逻辑elseToast.makeText(this, "未授予读取电话本权限", Toast.LENGTH_SHORT).show();break;default:break;}}

7.3 访问其它程序中的数据

Android提供ContentResolver类来获取其它应用程序中的数据,ContentResolver的对象通过Context的getContentResolver方法获得。ContentResolver包含操作数据库的常用方法如query,insert,delete,update等等。

7.3.1 什么是URI

我们知道操作数据库首先需要确定是操作数据库中的哪张表,通常我们直接通过表名来确定是哪张表。

而访问其它程序的数据,首先要确定访问的是哪个程序,然后才能确定是访问的哪张表,因此Android提供一个内容URI来内容提供器中的数据建立唯一的标准,简单来说URI就是指定哪个程序中的哪张表的唯一标识

答疑:URI中不需要指定是哪个数据库,因为开放内容提供器接口的程序内部已经设定了是哪个数据库,访问方只需要确定是哪张表就可以了,这个在下一节我们创建自己的内容提供器的时候你就能看到。

URI的格式为:content://authority和path

  • authority:是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式来进行命名。比如某个程序的包名是com.example.app,那么该程序对应的 authority就可以命名为com.example.app. provider。
  • path:是用于对同一应用程序中不同的表做区分的,通常都会添加到authority的后面。比如某个程序的数据库里存在两张表:table1和 table2,这时就可以将path分别命名为/table1和/table2。因此完整URI就是content://com.example.app. provider/table1 和content://com.example.app. provider/table2

7.3.2 基于内容提供器对其它程序的数据进行增删改查

  1. 查询数据语法
ContentResolver contentResolver = getContentResolver();
//参数1:唯一标识要查询的是哪个程序下的哪张表; 参数2:指定查询的列名
//参数3:指定where的约束条件; 参数4:为参数3的占位符提供具体的值
//参数5:对查询结果的排序方式
contentResolver.query(uri,projection,selection,selectionArgs,sortOrder);
  1. 插入数据语法
ContentResolver contentResolver = getContentResolver(# 参考书籍:第一行代码
链接:https://pan.baidu.com/s/1aXtOQCXL6qzxEFLBlqXs1Q?pwd=n5ag
提取码:n5ag);
//参数1:URI 参数2:待插入数据的键值对值
contentResolver.insert(uri,ContentValues)
  1. 更新数据语法
ContentResolver contentResolver = getContentResolver();
//参数1:URI 参数2:待插入数据的键值对值 参数3和参数4约束更新范围
contentResolver.update(uri,ContentValues,selection,selectionArgs)
  1. 删除数据语法
ContentResolver contentResolver = getContentResolver();
//参数1:URI; 参数2和参数3约束删除范围
contentResolver.delete(uri,selection,selectionArgs)

7.3.3 查询电话本程序中数据的核心代码示例

 Cursor cursor=null;try {ContentResolver contentResolver = getContentResolver();//电话本程序的URI由ContactsContract.CommonDataKinds.Phone封装好了,我们直接用可以了cursor= contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);if (cursor!=null){while (cursor.moveToNext()){String name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));}}}catch (Exception e){e.printStackTrace();}finally {if (cursor!=null)cursor.close();}

7.4 创建自己的内容提供器(提供共享数据的接口)

7.4.1 创建自己的内容提供器步骤

  1. 创建向其它程序共享自己数据的一系列方法的类,该类继承自ContentProvider
  2. 创建UriMatcher对象,并将自己想要共享的数据URI添加到UriMatcher对象中
  3. 重写ContentProvider类下的增删改查等方法,为其它程序访问自己的数据提供接口
  4. 在AndroidManifest.xml文件中的 < application>标签下添加 < provider>子标签
    在AndroidManifest.xml中配置自己的内容提供器示例:

android:authorities="com.xiaomi.databasetest.provider" android:enabled="true"android:exported="true" />

答疑:
UriMatcher对象添加URI的方法是addURI(String authority, String path, int code),authority和path在前面已经讲过,code是当前匹配URI的唯一标识。
上面讲到path代表的是表名,例如/table1,其实表名后面还可以加一个数字,代码这个表的ID,即访问表中的第几行数据,例如/table1/2。

7.4.2 创建自己的内容提供器核心代码示例

public class MyProvider extends ContentProvider {//自定义code,指定URI的唯一标识public static final int TABLE1_DIR=0;public static final int TABLE1_ITEM=1;private static UriMatcher uriMatcher;static {//正则表达式: *代码任意长度的字符,#代表任意长度的数字uriMatcher=new UriMatcher(UriMatcher.NO_MATCH);//将content://com.xiaomi.app.provider/table1添加到Matcher对象中,代码想要把table1整张表给其它程序访问uriMatcher.addURI("com.xiaomi.app.provider","table1",TABLE1_DIR);//将content://com.xiaomi.app.provider/table1/#添加到Matcher对象中,代码想要把table1表中的指定行给其它程序访问uriMatcher.addURI("com.xiaomi.app.provider","table1/#",TABLE1_ITEM);}@Overridepublic boolean onCreate() {//指定或者创建想要共享数据的数据库(这儿也能解释为什么URI中不需要指定数据库的原因了)return false;}@Nullable@Override//提供给其它程序查询本程序数据库的方法public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {switch (uriMatcher.match(uri)){case TABLE1_DIR:System.out.printf("向外部提供table1表下的所有数据");break;case TABLE1_ITEM:System.out.printf("向外部提供table1表下的单条数据");break;default:break;}return null;}@Nullable@Overridepublic Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {return null;}@Overridepublic int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {return 0;}@Overridepublic int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String selection, @Nullable String[] selectionArgs) {return 0;}@Nullable@Overridepublic String getType(@NonNull Uri uri) {//获取URI对应的MIME字符串//URI以路径结尾,MIME格式:vnd.android.cursor.dir/vnd..//URI以id结尾,MIME格式:vnd.android.cursor.item/vnd..switch (uriMatcher.match(uri)){case TABLE1_DIR:return "vnd:android.cursor.dir/vnd.com.xiaomi.app.provider.table1";case TABLE1_ITEM:return "vnd:android.cursor.item/vnd.com.xiaomi.app.provider.table1";default:break;}return null;}}

答疑:
创建的自己的内容提供器除了实现增删改查等操作数据库的方法外,还需要实现getType方法,该方法是根据传入的URI来返回相应的MIME类型。
一个URI对应的MIME字符串主要由一下三部分组成:

    1. 必须以vnd.开头
    1. 如果URI以表名结尾,则后接 android.cursor.dir/;如果URI以id结尾,则后接android.cursor.item/
    1. 最后接上vnd.< authority>.< path>

示例:
URI:content://com.example.app.provider/table1
对应MIME:vnd.android.cursor.dir/vnd.com.example.app.provider.table1
URI:content://com.example.app.provider/table1/3
对应MIME:vnd.android.cursor.item/vnd.com.example.app.provider.table1

7.4.2 创建自己的内容提供器完整代码示例

  1. 创建程序的SQLIte数据库帮助类
public class MyDatabaseHelper extends SQLiteOpenHelper {private Context mContext;public static final String CREATE_BOOK_TABLE="create table Book(" +"id integer primary key autoincrement," +"author text," +"price real," +"pages integer," +"name text)";public static final String DROP_BOOK="drop table if exists Book";public static final String CREATE_CATEGORY_TABLE="create table Category(" +"id integer primary key autoincrement," +"category_name text," +"category_code integer)";public static final String DROP_CATEGORY="drop table if exists Category";public MyDatabaseHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {super(context, name, factory, version);this.mContext=context;}//数据库不存在的前提下,创建数据库完成后会调用该方法@Overridepublic void onCreate(SQLiteDatabase sqLiteDatabase) {sqLiteDatabase.execSQL(CREATE_BOOK_TABLE);sqLiteDatabase.execSQL(CREATE_CATEGORY_TABLE);//新增这张表//Toast.makeText(mContext, "相关表创建完成", Toast.LENGTH_SHORT).show();}//数据库版本发生变化的时候,创建数据库操作后,会调用该方法@Overridepublic void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {sqLiteDatabase.execSQL(DROP_BOOK);sqLiteDatabase.execSQL(DROP_CATEGORY);onCreate(sqLiteDatabase);}
}
  1. 创建内容提供器为别的程序访问我们的数据提供接口
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;public class DatabaseProvider extends ContentProvider {public static final int BOOK_DIR=0;public static final int BOOK_ITEM=1;public static final int CATEGORY_DIR=2;public static final int CATEGORY_ITEM=3;public static final String AUTHORITY="com.xiaomi.databasetest.provider";private static UriMatcher uriMatcher;private MyDatabaseHelper dbHelper;static {uriMatcher=new UriMatcher(UriMatcher.NO_MATCH);uriMatcher.addURI(AUTHORITY,"book",BOOK_DIR);uriMatcher.addURI(AUTHORITY,"book/#",BOOK_ITEM);uriMatcher.addURI(AUTHORITY,"category",CATEGORY_DIR);uriMatcher.addURI(AUTHORITY,"category/#",CATEGORY_ITEM);}public DatabaseProvider() {}@Overridepublic boolean onCreate() {// TODO: Implement this to initialize your content provider on startup.dbHelper=new MyDatabaseHelper(getContext(),"BookStore.db",null,1);return true;}@Overridepublic Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {// TODO: Implement this to handle query requests from clients.Cursor cursor=null;SQLiteDatabase database = dbHelper.getReadableDatabase();switch (uriMatcher.match(uri)){case BOOK_DIR:cursor=database.query("Book",projection,selection,selectionArgs,null,null,sortOrder);break;case BOOK_ITEM:String bookID = uri.getPathSegments().get(1);cursor=database.query("Book",projection,"id = ?",new String[]{bookID},null,null,sortOrder);break;case CATEGORY_DIR:cursor=database.query("Category",projection,selection,selectionArgs,null,null,sortOrder);break;case CATEGORY_ITEM:String categoryID = uri.getPathSegments().get(1);cursor=database.query("Category",projection,"id = ?",new String[]{categoryID},null,null,sortOrder);break;default:break;}return cursor;}@Overridepublic Uri insert(Uri uri, ContentValues values) {// TODO: Implement this to handle requests to insert a new row.SQLiteDatabase writableDatabase = dbHelper.getWritableDatabase();Uri resUri=null;long newID=-1;switch (uriMatcher.match(uri)){case BOOK_DIR:case BOOK_ITEM:newID = writableDatabase.insert("Book", null, values);resUri=Uri.parse("content://"+AUTHORITY+"/book/"+newID);break;case CATEGORY_DIR:case CATEGORY_ITEM:newID = writableDatabase.insert("Category", null, values);resUri=Uri.parse("content://"+AUTHORITY+"/category/"+newID);break;default:break;}return resUri;}@Overridepublic int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) {// TODO: Implement this to handle requests to update one or more rows.SQLiteDatabase writableDatabase = dbHelper.getWritableDatabase();int updatedRows=0;switch (uriMatcher.match(uri)){case BOOK_DIR:updatedRows = writableDatabase.update("Book", values, selection, selectionArgs);break;case BOOK_ITEM:String bookID = uri.getPathSegments().get(1);updatedRows = writableDatabase.update("Book", values, "id = ?", new String[]{bookID});break;case CATEGORY_DIR:updatedRows = writableDatabase.update("Category", values, selection, selectionArgs);break;case CATEGORY_ITEM:String categoryID = uri.getPathSegments().get(1);updatedRows = writableDatabase.update("Category", values, "id = ?", new String[]{categoryID});break;default:break;}return updatedRows;}@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {// Implement this to handle requests to delete one or more rows.SQLiteDatabase writableDatabase = dbHelper.getWritableDatabase();int deleteRows=0;switch (uriMatcher.match(uri)){case BOOK_DIR:deleteRows = writableDatabase.delete("Book", selection, selectionArgs);break;case BOOK_ITEM:String bookID = uri.getPathSegments().get(1);deleteRows=writableDatabase.delete("Book","id = ?",new String[]{bookID});break;case CATEGORY_DIR:deleteRows = writableDatabase.delete("Category", selection, selectionArgs);break;case CATEGORY_ITEM:String categoryID = uri.getPathSegments().get(1);deleteRows=writableDatabase.delete("Category","id = ?",new String[]{categoryID});break;default:break;}return deleteRows;}@Overridepublic String getType(Uri uri) {// TODO: Implement this to handle requests for the MIME type of the data// at the given URI.switch (uriMatcher.match(uri)){case BOOK_DIR:return "vnd.android.cursor.dir/vnd.com.xiaomi.databasetest.provider.book";case BOOK_ITEM:return "vnd.android.cursor.item/vnd.com.xiaomi.databasetest.provider.book";case CATEGORY_DIR:return "vnd.android.cursor.dir/vnd.com.xiaomi.databasetest.provider.category";case CATEGORY_ITEM:return "vnd.android.cursor.item/vnd.com.xiaomi.databasetest.provider.category";}return null;}# 参考书籍:第一行代码
链接:https://pan.baidu.com/s/1aXtOQCXL6qzxEFLBlqXs1Q?pwd=n5ag
提取码:n5ag
}
  1. 在AndroidManifest.xml中注册内容提供器


  1. 其它程序通过我们定义的内容提供器访问我们的数据
  • 插入数据代码示例
ContentResolver contentResolver = getContentResolver();ContentValues contentValues = new ContentValues();
contentValues.put("name","这个世界真是很美好呀");
contentValues.put("author","从外部程序来的作者");
contentValues.put("pages",666);
contentValues.put("price",19.99);Uri uri = Uri.parse("content://com.xiaomi.databasetest.provider/book");
Uri newUri = contentResolver.insert(uri, contentValues);
String newID = newUri.getPathSegments().get(1);//返回的新插入数据的id
Toast.makeText(MainActivity.this, "添加数据成功", Toast.LENGTH_SHORT).show();
  • 查询数据代码示例
Uri uri = Uri.parse("content://com.xiaomi.databasetest.provider/book");
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
if (cursor==null)
{Toast.makeText(MainActivity.this, "未查询到数据", Toast.LENGTH_SHORT).show();return;
}
if (cursor.moveToFirst())
{StringBuilder stringBuilder = new StringBuilder();stringBuilder.append("name"+"     "+"author"+"     "+"pages"+"     "+"price\r\n");do {String name = cursor.getString(cursor.getColumnIndex("name"));String author = cursor.getString(cursor.getColumnIndex("author"));int pages = cursor.getInt(cursor.getColumnIndex("pages"));double price = cursor.getDouble(cursor.getColumnIndex("price"));stringBuilder.append(name+"     "+author+"     "+pages+"     "+price+"\r\n");
}while (cursor.moveToNext());
  • 更新数据示例代码:
Uri uri = Uri.parse("content://com.xiaomi.databasetest.provider/book/"+newID);
ContentValues contentValues = new ContentValues();
contentValues.put("price","26.66");
contentValues.put("name","这个世界多么美好");
getContentResolver().update(uri,contentValues,null,null);
  • 删除代码示例
Uri uri = Uri.parse("content://com.xiaomi.databasetest.provider/book/"+newID);
getContentResolver().delete(uri,null,null);

参考书籍:第一行代码

链接:https://pan.baidu.com/s/1aXtOQCXL6qzxEFLBlqXs1Q?pwd=n5ag
提取码:n5ag

相关内容

热门资讯

国常会部署上海自贸试验区经验复... 近日召开的国务院常务会议部署中国(上海)自由贸易试验区试点措施复制推广工作。会议提出,要用好中国(上...
雅本化学发布融资与对外担保管理... 2025年6月16日,雅本化学股份有限公司发布公告,公布了其融资与对外担保管理制度。 该制度旨在规范...
科技保险迎来政策红利期,如何撑... 科技保险作为支持科技创新的金融工具之一,重要性日益凸显。6月16日,上海出台科技保险新政,强化保险服...
中行北京分行携手北京海关举办A... 6月16日,北京海关联合中国银行北京市分行(以下简称“中行北京分行”)举办“北京海关-中国银行北京市...
原创 今... 最近一段时间,因为全国多地的家电“国补”政策调整,出现了暂停,甚至是限时限量领券等突发情况,导致一批...
上海离婚律师-婚姻律师:俞强律... 房产证上多了一个名字,离婚时却未必能分走一半财产。 李明(化名)在2019年以全款300万元购入上...
原创 两... 近期,台海局势出现微妙变化,赖清德“政治导师”洪奇昌低调现身北京,马英九率7000名台湾各界人士参加...
江苏南京3人深夜作案,流窜老旧... 新京报讯(记者张静姝)近期,江苏南京溧水警方破获一起蹊跷的电动自行车盗窃案。小区里闲置的电动自行车被...
中国心连心化肥(01866):... 中国心连心化肥(01866)发布公告,梁君慧女士已辞任公司联席公司秘书、香港联合交易所有限公司证券上...
地产:1 - 5月销售低位、投... 【1-5月地产销售低位徘徊,投资端大幅下滑】1-5月数据显示,地产销售处于低位徘徊状态,复苏动能不足...