安卓四大组件之ContentProvider

目录

  • 前言
  • 一、ContentProvider基础介绍
    • 1.1 简介
    • 1.2 作用
    • 1.3 实现原理
  • 二、具体使用
    • 2.1 统一资源标识符(URI)
    • 2.2 MIME数据类型
      • 2.2.1 MIME类型组成
      • 2.2.2 常见的MIME类型
      • 2.2.3 ContentProvider根据 URI 返回MIME类型
      • 2.2.4 类型分类
      • 2.2.5 示例
    • 2.3 ContentProvider三剑客
      • 2.3.1 ContentProvider内容提供者
        • 2.3.1.1 组织数据方式
        • 2.3.1.2 主要方法
        • 2.3.1.3 URI匹配规则
      • 2.3.2 ContentResolver内容解析者
        • 2.3.2.1 作用
        • 2.3.2.2 存在价值
        • 2.3.2.3 主要方法
        • 2.3.2.4 使用示例
      • 2.3.3 ContentObserver内容监听器
        • 2.3.3.1 重要方法
        • 2.3.3.2 使用示例
    • 2.4 辅助工具类
      • 2.4.1 ContentUris
      • 2.4.2 UriMatcher
      • 2.4.3 ContentObserver
  • 三、实例讲解
    • 3.1 进程内通信demo
    • 3.2 进程间通信demo
  • 四、总结


前言

ContentProvider是Android四大组件之一,另外三个是Activity、Service和Broadcast。它可以被其他应用程序调用,从而实现数据的共享和交互。

一、ContentProvider基础介绍

1.1 简介

ContentProvider是Android系统中的一个组件,用于在不同的应用程序之间共享数据。它提供了一种统一的接口,使得应用程序可以访问和修改其他应用程序中的数据,同时还可以对数据进行安全性和权限控制。

1.2 作用

实现进程间的数交互 & 共享,即跨进程通信。
ContentProvider通常用于提供数据访问的接口,例如访问联系人信息、媒体文件、日历事件等。它可以将数据存储在SQLite数据库中,也可以通过其他方式进行数据存储。

在这里插入图片描述

1.ContentProvider=中间者角色(搬运工) 真正存储和操作数据的数据源还是原来存储数据的方式(数据库、文件、xml或网络)
2.数据源可以是:数据库(如Sqlite)、文件、XML、网络等等

1.3 实现原理

ContentProvider是通过Binder机制来实现跨进程通信的,它通过Binder对象来与其他应用程序或组件进行通信。当其他应用程序或组件通过ContentResolver请求数据时,ContentResolver会将请求转发给ContentProvider,而ContentProvider会通过Binder机制将数据返回给请求方。
Binder是什么呢? 浅浅的先了解一下
Binder是Android系统中用于实现跨进程通信的机制,它提供了一种轻量级的IPC(进程间通信)方式,可以实现进程间数据的传输和通信。ContentProvider利用Binder机制来实现数据共享和访问,保证了数据的安全性和权限控制。


二、具体使用

2.1 统一资源标识符(URI)

ContentProvider使用 URI(统一资源标识符)来标识数据,每个数据都有一个唯一的URI来访问。当其他应用程序通过ContentResolver发起数据请求时,ContentProvider会根据请求的URI来匹配相应的数据,并返回给请求方。
定义:Uniform Resource Identifier,即统一资源标识符
作用:唯一标识 ContentProvider 其中的数据
外界进程通过 URI 找到对应的ContentProvider 其中的数据,再进行数据操作
URI分类:
URI分为 系统预置 & 自定义,分别对应系统内置的数据(如通讯录、日程表等等)和自定义数据库
系统预置URI可以在源码中找到,比如:

管理联系人的UriContactsContract.Contacts.CONTENT_URI 
管理联系人的电话的UriContactsContract.CommonDataKinds.Phone.CONTENT_URI 
管理联系人的EmailUriContactsContract.CommonDataKinds.Email.CONTENT_URI 
发送箱中的短信URIContent://sms/outbox
收信箱中的短信URIContent://sms/sent
草稿中的短信URIContent://sms/draft

自定义URI:
例如:
URl= content:// com.henry.provider/User/1

content: 主题名
com.henry.provider:授权信息
User:表名
1:记录

  • 主题(Schema):ContentProvider的URI前缀(Android 规定)
  • 授权信息(Authority):ContentProvider的唯一标识符·
  • 表名(Path):ContentProvider指向数据库中的某个表名·
  • 记录(ID):表中的某个记录(若无指定,则返回全部记录

具体使用

 设置URI
Uri uri = Uri.parse("content://com.henry.provider/User/1") 上述URI指向的资源是:名为 `com.henry.provider`的`ContentProvider` 中表名 为`User` 中的 `id`为1的数据

特别注意:URI模式存在匹配通配符* & #
*:匹配任意长度的任何有效字符的字符串
以下的URI 表示 匹配provider的任何内容
content://com.example.app.provider/ *
#:匹配任意长度的数字字符的字符串
以下的URI 表示 匹配provider中的table表的所有行
content://com.example.app.provider/table/#

参考链接:ContentProvider的URI

2.2 MIME数据类型

ContentProvider中的MIME类型用于标识数据的类型和格式,帮助客户端应用程序正确解析和处理数据。开发者在使用ContentProvider时需要注意正确指定数据的MIME类型,以确保数据能够被正确处理。

作用:指定某个扩展名的文件用某种应用程序来打开
如指定.html文件采用text应用程序打开、指定.pdf文件采用flash应用程序打开

2.2.1 MIME类型组成

每种MIME类型 由2部分组成 = 类型 + 子类型
MIME类型是 一个 包含2部分的字符串

text / html
// 类型 = text、子类型 = html
text/css
text/xml
application/pdf

2.2.2 常见的MIME类型

在Android开发中,常见的MIME类型包括但不限于以下几种:

text/plain:纯文本数据
text/html:HTML格式数据
image/jpeg:JPEG格式图像数据
image/png:PNG格式图像数据
audio/mpeg:MP3格式音频数据
video/mp4:MP4格式视频数据
application/json:JSON格式数据
application/xml:XML格式数据

2.2.3 ContentProvider根据 URI 返回MIME类型

ContentProvider.geType(uri)

2.2.4 类型分类

两种常见的MIME类型形式是单条记录和多条记录(集合)

  • 单条记录形式(vnd.android.cursor.item/自定义)
    用于表示返回的数据是单个记录(一行数据)。
    MIME类型的格式为"vnd.android.cursor.item/自定义",其中"自定义"部分是开发者自定义的标识符,通常用于指示数据表的类型。
    示例:vnd.android.cursor.item/vnd.example.contacts,表示返回的数据是单个联系人记录。
  • 多条记录(集合)形式(vnd.android.cursor.dir/自定义)
    用于表示返回的数据是多个记录(多行数据,集合)。
    MIME类型的格式为"vnd.android.cursor.dir/自定义",其中"自定义"部分是开发者自定义的标识符,通常用于指示数据表的类型。
    示例:vnd.android.cursor.dir/vnd.example.contacts,表示返回的数据是多个联系人记录的集合。

2.2.5 示例

假设我们有一个自定义的ContentProvider,提供了一个名为"contacts"的数据表,包含联系人的姓名和电话号码。

在ContentProvider中定义MIME类型:

public class MyContentProvider extends ContentProvider {定义数据表的列名public static final String COLUMN_NAME = "name";public static final String COLUMN_PHONE = "phone";定义数据表的MIME类型public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.example.contacts";public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.example.contacts";实现query()方法@Overridepublic Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {查询数据表,返回Cursor对象Cursor cursor = ...;设置返回数据的MIME类型cursor.setNotificationUri(getContext().getContentResolver(), uri);cursor.setNotificationUri(getContext().getContentResolver(), ContactsContract.Contacts.CONTENT_URI);return cursor;}其他ContentProvider方法的实现...
}

在客户端应用程序中使用MIME类型:

Uri uri = Uri.parse("content://com.example.mycontentprovider/contacts");查询数据表
Cursor cursor = getContentResolver().query(uri, null, null, null, null);获取返回数据的MIME类型
String mimeType = cursor.getType(cursor.getColumnIndexOrThrow(MyContentProvider.COLUMN_NAME));
Log.d("MIME Type", mimeType);处理返回的数据
if (cursor.moveToFirst()) {do {String name = cursor.getString(cursor.getColumnIndexOrThrow(MyContentProvider.COLUMN_NAME));String phone = cursor.getString(cursor.getColumnIndexOrThrow(MyContentProvider.COLUMN_PHONE));处理数据...} while (cursor.moveToNext());
}cursor.close();

2.3 ContentProvider三剑客

在这里插入图片描述

  • ContentProvider内容提供者
    对外提供数据,其他应用可以通过ContentProvider对你应用中的数据进行添删改查
  • ContentResolver内容解析者
    按一定规则访问内容提供者的数据
  • ContentObserver内容监听器
    监听指定Uri引起的变化,当ContentObserver所观察的Uri发生变化时,便会触发

2.3.1 ContentProvider内容提供者

ContentProvider类用于提供数据访问接口,允许应用程序对数据进行查询、插入、更新和删除操作。ContentProvider类的组织数据方式和主要方法如下:

2.3.1.1 组织数据方式

ContentProvider类通常会继承自Android提供的ContentProvider基类,并实现对应的数据操作方法。
数据通常以表的形式组织,每个表对应一个URI(Uniform Resource Identifier)。
ContentProvider类会定义URI匹配规则,根据URI的不同来执行相应的数据操作。
数据通常存储在SQLite数据库中,ContentProvider类会通过ContentResolver与SQLite数据库进行交互。

2.3.1.2 主要方法

进程间共享数据的本质是:添加、删除、获取 & 修改(更新)数据
所以ContentProvider的核心方法也主要是上述4个作用

<-- 4个核心方法 -->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 中的数据注:1. 上述4个方法由外部进程回调,并运行在ContentProvider进程的Binder线程池中(不是主线程)2. 存在多线程并发访问,需要实现线程同步3.ContentProvider的数据存储方式是使用SQLite & 一个,则不需要,因为SQLite内部实现好了线程同步,若是多个SQLite则需要,因为SQL对象之间无法进行线程同步4.ContentProvider的数据存储方式是内存,则需要自己实现线程同步<-- 3个其他方法 -->
public boolean onCreate() ContentProvider创建后 或 打开系统后其它进程第一次访问该ContentProvider时 由系统进行调用,用于初始化ContentProvider,如创建数据库连接注:运行在ContentProvider进程的主线程,故不能做耗时操作public String getType(Uri uri)得到数据类型,即返回当前 Url 所代表数据的MIME类型public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
用于批量插入数据,接收插入数据的ContentValues数组,并返回插入数据的数量。
2.3.1.3 URI匹配规则

在这里插入图片描述

ContentProvider类会通过URI来匹配对应的数据表和操作。
URI通常包括 scheme、authority(ContentProvider的授权信息)、path(数据表路径)、ID等部分。
ContentProvider类会根据URI的不同来执行对应的数据操作,如查询、插入、更新或删除。

2.3.2 ContentResolver内容解析者

2.3.2.1 作用

管理不同 ContentProvider间的操作
即通过 URI 即可操作 不同的ContentProvider 中的数据,外部进程通过 ContentResolver类 从而与ContentProvider类进行交互

2.3.2.2 存在价值

一般来说,一款应用要使用多个ContentProvider,若需要了解每个ContentProvider的不同实现从而再完成数据交互,操作成本高且难度大,所以在ContentProvider类上加多了一个 ContentResolver类对所有的ContentProvider进行统一管理。

使用ContentResolver类来与ContentProvider进行交互,而不是直接访问ContentProvider类。还有其他好处:

  • 数据访问的统一性: ContentResolver类提供了统一的接口来访问不同应用程序中的ContentProvider。这种统一性使得应用程序可以通过相同的方式来访问不同应用程序中的数据,而无需关心数据存储的具体细节。
  • 权限控制: ContentResolver类可以帮助应用程序进行权限检查,确保应用程序只能访问其具有权限的ContentProvider。这样可以有效地保护数据的安全性,防止未经授权的应用程序访问敏感数据。
  • 进程间通信(IPC): ContentResolver类封装了底层的进程间通信(IPC)细节,可以在应用程序和ContentProvider之间进行数据交换。这种封装可以简化应用程序与ContentProvider之间的通信,提高了代码的可维护性和可扩展性。
  • 异步操作支持: ContentResolver类提供了支持异步操作的方法,可以在后台线程中执行数据操作,避免在主线程中进行耗时的数据库操作,从而提高了应用程序的响应性和性能。
  • 解耦合: 通过ContentResolver类来访问ContentProvider可以实现应用程序与数据存储之间的解耦合,使得应用程序和数据存储可以独立进行演化和维护,降低了系统的耦合性。
2.3.2.3 主要方法

ContentResolver 类提供了与ContentProvider类相同名字和作用的4个方法

 外部进程向 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)

其他重要的方法

getType(Uri uri):
用于返回指定URIMIME类型,通常用于标识数据的类型(单条记录或多条记录)。bulkInsert(Uri uri, ContentValues[] values):
用于批量插入数据,接收插入数据的ContentValues数组,并返回插入数据的数量。registerContentObserver(Uri uri, boolean notifyForDescendants, ContentObserver observer):
用于注册ContentObserver对象,以便在数据发生改变时接收通知。unregisterContentObserver(ContentObserver observer):
用于取消注册ContentObserver对象,停止接收数据改变的通知。
2.3.2.4 使用示例
// 使用ContentResolver前,需要先获取ContentResolver
// 可通过在所有继承Context的类中 通过调用getContentResolver()来获得ContentResolver
ContentResolver resolver =  getContentResolver(); // 设置ContentProvider的URI
Uri uri = Uri.parse("content://cn.scu.myprovider/user"); // 根据URI 操作 ContentProvider中的数据
// 此处是获取ContentProvider中 user表的所有记录 
Cursor cursor = resolver.query(uri, null, null, null, "userid desc"); 

2.3.3 ContentObserver内容监听器

ContentObserver是一个观察者类,用于监听ContentProvider中数据的变化。当ContentProvider中的数据发生变化时,ContentObserver会收到通知并执行相应的操作。

2.3.3.1 重要方法
    public void onChange(boolean selfChange) {// Do nothing.  Subclass should override.}ContentProvider中的数据发生变化时调用该方法。参数selfChange表示是否是由应用程序自身修改数据所引起的变化。
2.3.3.2 使用示例

创建了一个自定义的ContentObserver类MyContentObserver,并重写了onChange方法,在数据发生变化时打印日志。
然后通过getContentResolver().registerContentObserver()方法注册ContentObserver来监听指定的ContentProvider数据变化,最后通过getContentResolver().unregisterContentObserver()方法取消注册。

public class MyContentObserver extends ContentObserver {public MyContentObserver(Handler handler) {super(handler);}@Overridepublic void onChange(boolean selfChange) {super.onChange(selfChange);Log.d("ContentObserver", "Data in ContentProvider has changed");// 在这里可以执行相应的操作,比如更新UI或者进行数据同步等}
}// 注册ContentObserver
MyContentObserver contentObserver = new MyContentObserver(new Handler());
getContentResolver().registerContentObserver(Uri.parse("content://com.example.provider/data"), true, contentObserver);//然后在ContentProvider中的数据发生变化时,ContentProvider可以调用getContext().getContentResolver().notifyChange(uri, null)来通知ContentResolver
//然后ContentResolver会通知注册了对应URI的ContentObserver,从而触发ContentObserver的onChange()方法。// 取消注册ContentObserver
getContentResolver().unregisterContentObserver(contentObserver);

2.4 辅助工具类

Android 提供了3个用于辅助ContentProvide的工具类:

ContentUris
UriMatcher
ContentObserver

2.4.1 ContentUris

ContentUris类是Android中用于处理ContentProvider URI的工具类。它提供了一些静态方法来帮助我们处理URI中的ID部分,以及构建新的URI。
作用:操作 URI
具体使用:
核心方法有两个:withAppendedId()和parseId()
withAppendedId()
该方法用于在指定的contentUri后面追加一个ID,并返回一个新的Uri对象。通常用于构建一个包含ID的URI。 示例:

Uri contentUri = Uri.parse("content://com.example.provider/data");
long id = 123;
Uri newUri = ContentUris.withAppendedId(contentUri, id);
//最终生成后的Uri为:content://com.example.provider/data/123

parseId(Uri contentUri): 该方法用于从URI中提取出ID部分,并返回ID的值。如果URI中没有ID部分,则返回-1。

Uri uri = Uri.parse("content://cn.scu.myprovider/user/7") 
long personid = ContentUris.parseId(uri); 
//获取的结果为:7

2.4.2 UriMatcher

UriMatcher是Android中用于匹配URI的工具类,通常用于ContentProvider中对URI进行匹配和分发操作
看一个应用示例

1.在ContentProvider 中注册URI
2.根据 URI 匹配 ContentProvider 中对应的数据表

	// 步骤1:初始化UriMatcher对象UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); //常量UriMatcher.NO_MATCH  = 不匹配任何路径的返回码// 即初始化时不匹配任何东西// 步骤2:在ContentProvider 中注册URI(addURI())int URI_CODE_a = 1int URI_CODE_b = 2;matcher.addURI("cn.scu.myprovider", "user1", URI_CODE_a); matcher.addURI("cn.scu.myprovider", "user2", URI_CODE_b); // 若URI资源路径 = content://cn.scu.myprovider/user1 ,则返回注册码URI_CODE_a// 若URI资源路径 = content://cn.scu.myprovider/user2 ,则返回注册码URI_CODE_b// 步骤3:根据URI 匹配 URI_CODE,从而匹配ContentProvider中相应的资源(match())@Override   public String getType(Uri uri) {   Uri uri = Uri.parse(" content://cn.scu.myprovider/user1");   switch(matcher.match(uri)){   // 根据URI匹配的返回码是URI_CODE_a// 即matcher.match(uri) == URI_CODE_acase URI_CODE_a:   return tableNameUser1;   // 如果根据URI匹配的返回码是URI_CODE_a,则返回ContentProvider中的名为tableNameUser1的表case URI_CODE_b:   return tableNameUser2;// 如果根据URI匹配的返回码是URI_CODE_b,则返回ContentProvider中的名为tableNameUser2的表}   
}

然后看一下他的重点方法:
addURI(String authority, String path, int code): 该方法用于向UriMatcher中添加一个URI匹配规则。参数authority表示ContentProvider的authority,path表示URI路径模式,code表示匹配到该规则时返回的代码。 示例:

UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.example.provider", "data", 1);

match(Uri uri): 该方法用于匹配传入的URI,并返回匹配到的规则的代码。如果没有匹配到任何规则,则返回UriMatcher.NO_MATCH。 示例:

UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.example.provider", "data", 1);
Uri uri = Uri.parse("content://com.example.provider/data");
int code = uriMatcher.match(uri); // code为1

应用场景
getType(Uri uri): 该方法用于获取传入URI的MIME类型。通常在ContentProvider的getType()方法中使用UriMatcher来返回对应URI的MIME类型。 示例:

@Override
public String getType(Uri uri) {int code = uriMatcher.match(uri);switch (code) {case 1:return "vnd.android.cursor.dir/data";default:return null;}
}

2.4.3 ContentObserver

重复讲一下,无伤大雅。
定义:内容观察者
作用:观察 Uri引起 ContentProvider 中的数据变化 & 通知外界(即访问该数据访问者)
当ContentProvider 中的数据发生变化(增、删 、 改)时,就会触发该 ContentObserver类的onchange方法

// 步骤1:注册内容观察者ContentObservergetContentResolver().registerContentObserver(uri);// 通过ContentResolver类进行注册,并指定需要观察的URI// 步骤2:当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)public class UserContentProvider extends ContentProvider { public Uri insert(Uri uri, ContentValues values) { db.insert("user", "userid", values); getContext().getContentResolver().notifyChange(uri, null); // 通知访问者} 
}// 步骤3:解除观察者getContentResolver().unregisterContentObserver(uri);// 同样需要通过ContentResolver类进行解除

三、实例讲解

由于ContentProvider不仅常用于进程间通信,同时也适用于进程内通信。
所以实例内容分为进程内通信和进程间通信。
数据源采用Android中的SQLite数据库。

3.1 进程内通信demo

在ContentProvider 中初始化创建SQLite数据库,并实现增删改查方法。最后在Activity中获取provider中的数据,其实就是对数据库进行了一层封装,但是ContentProvider 的注册过程需要记下笔记。

DBHelper.java

public class DBHelper extends SQLiteOpenHelper {// 数据库名private static final String DATABASE_NAME = "henry.db";// 表名public static final String USER_TABLE_NAME = "user";public static final String JOB_TABLE_NAME = "job";private static final int DATABASE_VERSION = 1;//数据库版本号public DBHelper(Context context) {super(context, DATABASE_NAME, null, DATABASE_VERSION);}@Overridepublic void onCreate(SQLiteDatabase db) {// 创建两个表格:用户表 和职业表db.execSQL("CREATE TABLE IF NOT EXISTS " + USER_TABLE_NAME + "(_id INTEGER PRIMARY KEY AUTOINCREMENT," + " name TEXT)");db.execSQL("CREATE TABLE IF NOT EXISTS " + JOB_TABLE_NAME + "(_id INTEGER PRIMARY KEY AUTOINCREMENT," + " job TEXT)");}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)   {}
}

自定义Provider

public class MyProvider extends ContentProvider {private Context mContext;DBHelper mDbHelper = null;SQLiteDatabase db = null;public static final String AUTOHORITY = "cn.henry.myprovider";// 设置ContentProvider的唯一标识public static final int User_Code = 1;public static final int Job_Code = 2;// UriMatcher类使用:在ContentProvider 中注册URIprivate static final UriMatcher mMatcher;static {mMatcher = new UriMatcher(UriMatcher.NO_MATCH);// 初始化mMatcher.addURI(AUTOHORITY, "user", User_Code);mMatcher.addURI(AUTOHORITY, "job", Job_Code);// 若URI资源路径 = content://cn.scu.myprovider/user ,则返回注册码User_Code// 若URI资源路径 = content://cn.scu.myprovider/job ,则返回注册码Job_Code}// 以下是ContentProvider的6个方法/*** 初始化ContentProvider*/@Overridepublic boolean onCreate() {mContext = getContext();// 在ContentProvider创建时对数据库进行初始化// 运行在主线程,故不能做耗时操作,此处仅作展示mDbHelper = new DBHelper(getContext());db = mDbHelper.getWritableDatabase();// 初始化两个表的数据(先清空两个表,再各加入一个记录)db.execSQL("delete from user");db.execSQL("insert into user values(1,'henry');");db.execSQL("insert into user values(2,'geng');");db.execSQL("delete from job");db.execSQL("insert into job values(1,'Android');");db.execSQL("insert into job values(2,'teacher');");return true;}/*** 添加数据*/@Overridepublic Uri insert(Uri uri, ContentValues values) {// 根据URI匹配 URI_CODE,从而匹配ContentProvider中相应的表名// 该方法在最下面String table = getTableName(uri);// 向该表添加数据db.insert(table, null, values);// 当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)mContext.getContentResolver().notifyChange(uri, null);//        // 通过ContentUris类从URL中获取ID
//        long personid = ContentUris.parseId(uri);
//        System.out.println(personid);return uri;}/*** 查询数据*/@Overridepublic Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {// 根据URI匹配 URI_CODE,从而匹配ContentProvider中相应的表名// 该方法在最下面String table = getTableName(uri);//        // 通过ContentUris类从URL中获取ID
//        long personid = ContentUris.parseId(uri);
//        System.out.println(personid);// 查询数据return db.query(table, projection, selection, selectionArgs, null, null, sortOrder, null);}/*** 更新数据*/@Overridepublic int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) {// 由于不展示,此处不作展开return 0;}/*** 删除数据*/@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {// 由于不展示,此处不作展开return 0;}@Overridepublic String getType(Uri uri) {// 由于不展示,此处不作展开return null;}/*** 根据URI匹配 URI_CODE,从而匹配ContentProvider中相应的表名*/private String getTableName(Uri uri) {String tableName = null;switch (mMatcher.match(uri)) {case User_Code:tableName = DBHelper.USER_TABLE_NAME;break;case Job_Code:tableName = DBHelper.JOB_TABLE_NAME;break;}return tableName;}
}

Provider在Manifest文件中的注册:

       <providerandroid:name="MyProvider"android:authorities="cn.henry.myprovider" />

MainActivity

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);/*** 对user表进行操作*/// 设置URIUri uri_user = Uri.parse("content://cn.henry.myprovider/user");// 插入表中数据ContentValues values = new ContentValues();values.put("_id", 3);values.put("name", "wang");// 获取ContentResolverContentResolver resolver = getContentResolver();// 通过ContentResolver 根据URI 向ContentProvider中插入数据resolver.insert(uri_user, values);// 通过ContentResolver 向ContentProvider中查询数据Cursor cursor = resolver.query(uri_user, new String[]{"_id", "name"}, null, null, null);while (cursor.moveToNext()) {Log.d("henry-----","query info:" + cursor.getInt(0) + " " + cursor.getString(1));// 将表中数据全部输出}cursor.close();// 关闭游标/*** 对job表进行操作*/// 和上述类似,只是URI需要更改,从而匹配不同的URI CODE,从而找到不同的数据资源Uri uri_job = Uri.parse("content://cn.henry.myprovider/job");// 插入表中数据ContentValues values2 = new ContentValues();values2.put("_id", 3);values2.put("job", "DNF Player");// 获取ContentResolverContentResolver resolver2 = getContentResolver();// 通过ContentResolver 根据URI 向ContentProvider中插入数据resolver2.insert(uri_job, values2);// 通过ContentResolver 向ContentProvider中查询数据Cursor cursor2 = resolver2.query(uri_job, new String[]{"_id", "job"}, null, null, null);while (cursor2.moveToNext()) {// 将表中数据全部输出Log.d("henry-----","query job:" + cursor2.getInt(0) + " " + cursor2.getString(1));}cursor2.close();// 关闭游标}
}

运行起来,看一下数据库和log打印
在这里插入图片描述在这里插入图片描述

在这里插入图片描述

3.2 进程间通信demo

实例说明:既然是进程间通信了,那么至少需要创建2个进程,即创建两个工程。
工程1创建ContentProvider,存储SQLite数据。
工程2访问ContentProvider中存储的SQLite数据

工程1:
就在进程内通信的demo基础之上进行修改吧。
只需修改工程1上的AndroidManifest.xml

        <providerandroid:name="MyProvider"android:authorities="cn.henry.myprovider"//设置此provider是否可以被其他进程使用android:exported="true"// 声明外界进程可访问该Provider的权限(读 & 写)android:permission="com.henry.PROVIDER" /><permission android:name="com.henry.PROVIDER" android:protectionLevel="normal"/>

在Manifest中注册ContentProvider的写法和含义

工程2:
声明可访问的权限

    <uses-permission android:name="scut.carson_ho.PROVIDER"/>// 细分读 & 写权限如下,但本Demo直接采用全权限// <uses-permission android:name="scut.carson_ho.Read"/>//  <uses-permission android:name="scut.carson_ho.Write"/> // 注:声明的权限必须与进程1中设置的权限对应

访问 ContentProvider的MainActivity

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);/*** 对user表进行操作*/// 设置URIUri uri_user = Uri.parse("content://cn.henry.myprovider/user");// 插入表中数据ContentValues values = new ContentValues();values.put("_id", 4);values.put("name", "Jordan");// 获取ContentResolverContentResolver resolver =  getContentResolver();// 通过ContentResolver 根据URI 向ContentProvider中插入数据resolver.insert(uri_user,values);// 通过ContentResolver 向ContentProvider中查询数据Cursor cursor = resolver.query(uri_user, new String[]{"_id","name"}, null, null, null);while (cursor.moveToNext()){Log.d("henry-----","query info:" + cursor.getInt(0) + " " + cursor.getString(1));// 将表中数据全部输出}cursor.close();// 关闭游标/*** 对job表进行操作*/// 和上述类似,只是URI需要更改,从而匹配不同的URI CODE,从而找到不同的数据资源Uri uri_job = Uri.parse("content://cn.henry.myprovider/job");// 插入表中数据ContentValues values2 = new ContentValues();values2.put("_id", 4);values2.put("job", "NBA Player");// 获取ContentResolverContentResolver resolver2 =  getContentResolver();// 通过ContentResolver 根据URI 向ContentProvider中插入数据resolver2.insert(uri_job,values2);// 通过ContentResolver 向ContentProvider中查询数据Cursor cursor2 = resolver2.query(uri_job, new String[]{"_id","job"}, null, null, null);while (cursor2.moveToNext()){Log.d("henry-----","query job:" + cursor2.getInt(0) + " " + cursor2.getString(1));}cursor2.close();// 关闭游标}
}

运行起来,看一下数据库和log打印
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

四、总结

  • 数据共享:ContentProvider提供了一种标准的接口,允许不同应用程序之间共享数据。通过ContentProvider,应用程序可以将自己的数据暴露给其他应用程序,实现数据的共享和交互。
  • 访问控制:ContentProvider可以对数据进行访问控制,通过URI的权限控制和ContentProvider的权限设置,可以限制哪些应用程序可以访问数据,从而保护数据的安全性。
  • 数据封装:ContentProvider可以将数据封装起来,隐藏数据的具体存储方式和结构,只提供统一的接口供其他应用程序访问。这样可以提高数据的安全性和保护数据的完整性。
  • 数据变化通知:ContentProvider支持数据变化通知机制,可以通过ContentResolver注册ContentObserver监听数据的变化,当数据发生变化时,会及时通知监听者,实现数据的实时更新和同步。
  • 访问简单和高效
    对比于其他对外共享数据的方式,数据访问方式会因数据存储的方式而不同:
    采用 文件方式 对外共享数据,需要进行文件操作读写数据;
    采用 Sharedpreferences 共享数据,需要使用sharedpreferences API读写数据
    这使得访问数据变得复杂且难度大。
    而采用ContentProvider方式,其 解耦了 底层数据的存储方式,使得无论底层数据存储采用何种方式,外界对数据的访问方式都是统一的,这使得访问简单 & 高效
    如一开始数据存储方式 采用 SQLite 数据库,后来把数据库换成 MongoDB,也不会对上层数据ContentProvider使用代码产生影响

在这里插入图片描述

参考链接:Android四大组件之ContentProvider

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1409492.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Java IO流(一)

1. IO流概述 1.1 什么是IO流 在计算机中&#xff0c;input/output&#xff08;I/O、i/o 或非正式的 io 或 IO&#xff09;是信息处理系统&#xff08;例如计算机&#xff09;与外界&#xff08;可能是人类或其他信息处理系统&#xff09;之间的通信。 输入是系统接收到的信号或…

基于ssm+vue+Mysql的药源购物网站

开发语言&#xff1a;Java框架&#xff1a;ssmJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;Maven3.…

Ubuntu系统设置中文及中文输入法(手把手,学不会打我)

前言 最近开始搞C系统编程的学习&#xff0c;整了个Ubuntu系统&#xff0c;进去发现是英文系统&#xff0c;我一开始觉得也能接受&#xff0c;就当练英文&#xff0c;反正那些命令也都是用英文&#xff0c;不过后面等我暗转了一个Chrome并且开始用这里的软件去搜问题时&#x…

【08】JAVASE-面向对象-类和对象【从零开始学JAVA】

Java零基础系列课程-JavaSE基础篇 Lecture&#xff1a;波哥 Java 是第一大编程语言和开发平台。它有助于企业降低成本、缩短开发周期、推动创新以及改善应用服务。如今全球有数百万开发人员运行着超过 51 亿个 Java 虚拟机&#xff0c;Java 仍是企业和开发人员的首选开发平台。…

Linux:冯诺依曼体系结构、操作系统、初识进程

文章目录 1.冯诺依曼体系结构总线与数据传输通路为什么有内存这个部分计算机存储结构 2.操作系统(Operator System)2.1 概念2.2 设计OS的目的2.3 理解“管理”先描述再组织 2.4 用户使用系统调用和库函数&#xff08;lib&#xff09;概念 总结 3.初识进程3.1 基本事实与引入3.2…

(1)探索 SpringAI - 基本概述

人工智能简介 A system is ability to correctly interpret external data, to learn from such data, and to use those learnings to achieve specific goals and tasks through flexible adaptation. 翻译&#xff1a;系统正确解释外部数据的能力&#xff0c;从这些数据中学…

飞腾D2000+X100 TYPE6全国产核心板

飞腾D2000X100 TYPE6核心板 产品概述 飞腾D2000X100 TYPE6核心板为增强型自主控制器核心板&#xff0c;其核心芯片CPU采用飞腾D2000/8核工业版CPU、飞腾桥片X100、双通道DDR4L插槽、PHY芯片等。 产品特点 l 基于飞腾D2000X100桥片 l 丰富的PCIE扩展资源&#xff0c;一路PCIE…

大面积车间降温用什么方法

生产车间降温用什么设备好&#xff0c;生产车间降温设备的选择取决于多种因素&#xff0c;如车间的大小、高度、通风条件、预算以及员工的工作环境需求等。以下是一些常见的生产车间降温设备及其特点&#xff1a; 工业风扇&#xff08;包括大型吊扇&#xff09;&#xff1a; …

街道征迁项目档案管理系统

街道征迁项目档案管理系统是一个用于管理街道征迁项目档案的软件系统。该系统的主要功能包括档案录入、档案存储、档案检索、档案共享等。 系统的用户可以通过该系统录入征迁项目相关的档案信息&#xff0c;包括项目名称、征迁范围、土地面积、征迁补偿费用等。同时&#xff0c…

el-table分页多选导出excel表格

需求&#xff1a;使用el-table分页查询表格的时候记录上一页已选中的数据&#xff0c;之后点击导出按钮后对表格已选中数据导出excel表格&#xff0c;导出成功后清空选中的状态&#xff0c;本文章只记录分页导出的关键代码&#xff0c;其中包含之前的导出全部表格&#xff0c;导…

2024五一数学建模C题Python代码+结果表数据教学

2024五一数学建模竞赛&#xff08;五一赛&#xff09;C题保姆级分析完整思路代码数据教学 C题 煤矿深部开采冲击地压危险预测 第一问 导入数据 以下仅展示部分&#xff0c;完整版看文末的文章 import numpy as np import pandas as pd import matplotlib.pyplot as plt imp…

Edge浏览器新特性深度解析,写作ai免费软件

首先&#xff0c;这篇文章是基于笔尖AI写作进行文章创作的&#xff0c;喜欢的宝子&#xff0c;也可以去体验下&#xff0c;解放双手&#xff0c;上班直接摸鱼~ 按照惯例&#xff0c;先介绍下这款笔尖AI写作&#xff0c;宝子也可以直接下滑跳过看正文~ 笔尖Ai写作&#xff1a;…

python u是什么意思

u&#xff1a;表示unicode字符串&#xff0c;默认模式&#xff0c;里边的特殊字符会被识别。 作用&#xff1a;后面字符串以unicode格式进行编码&#xff0c;一般用在中文字符串前面&#xff0c;防止因为源码储存格式问题&#xff0c;导致再次使用时出现乱码。 用法&#xff…

分享天某云对象存储开发的体验

最近体验了天某云对象存储的功能&#xff0c;作为一名资深开发者&#xff0c;开发体验差强人意&#xff0c;与阿里云存在一定的差距。 首先在开发文档上居然没有基于nodejs的代码示例&#xff0c;只有java,c#,go等的代码示例&#xff0c;虽然有javascript的&#xff0c;但那也只…

24 JavaScript学习:this

this在对象方法中 在 JavaScript 中&#xff0c;this 的值取决于函数被调用的方式。在对象方法中&#xff0c;this 引用的是调用该方法的对象。 让我们看一个简单的例子&#xff1a; const person {firstName: John,lastName: Doe,fullName: function() {return this.firstN…

牛客NC368 质数的计数【中等 基础数学,数论 C++/Java/Go/PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/190167d1990442da9adb133980259a27 思路 判断x是否是质数&#xff1a;这是判断质数最好的代码了public boolean isPrime(int x){if(x 2 || x3) return true;if(x%6!1 && x%6!5) return false; //不在6倍…

macOS13中切换不同jdk的“笨“方法

1.用以下命令先查看自己的jdk有哪些版本 /usr/libexec/java_home -V 2.sudo vim /etc/profile 修改前&#xff0c;jdk->1.8 修改后 source ~/.bash_profile

【95】哪些错误会记录Header log

关于header log怎么解析的见&#xff1a; https://blog.csdn.net/linjiasen/article/details/129823460?spm1001.2014.3001.5502 1、AER Mask Reg和Header Log Reg的关系 如果PCIe设备实现的AER capability&#xff0c;那么uncorrectable error mask reg和correctable error…

全栈开发之路——前端篇(1)介绍、框架确定、ide设置与项目创建

文章目录 前言一、前端框架React和VueReactVue 二、编译器选择和配置1.传统的下载和使用2.你可能遇到的问题1.ERR&#xff01; syscall mkdir2.vue : File C:\nodejs\node_global\vue.ps1 cannot be loaded because running scripts is disabled on3.出现乱码问题 3.运行Vue 三…

模型智能体开发之metagpt-多智能体实践

参考&#xff1a; metagpt环境配置参考模型智能体开发之metagpt-单智能体实践 需求分析 之前有过单智能体的测试case&#xff0c;但是现实生活场景是很复杂的&#xff0c;所以单智能体远远不能满足我们的诉求&#xff0c;所以仍然还需要了解多智能体的实现。通过多个role对动…