安卓四大组件之ContentProvider


前言

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()方法
    @Override
    public 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);
    }

    @Override
    public 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_a
      case 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:注册内容观察者ContentObserver
    getContentResolver().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);
    }

    @Override
    public 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)");
    }

    @Override
    public 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 中注册URI
    private 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
     */
    @Override
    public 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;
    }

    /**
     * 添加数据
     */

    @Override
    public 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;
    }

    /**
     * 查询数据
     */
    @Override
    public 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);
    }

    /**
     * 更新数据
     */
    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        // 由于不展示,此处不作展开
        return 0;
    }

    /**
     * 删除数据
     */
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // 由于不展示,此处不作展开
        return 0;
    }

    @Override
    public 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文件中的注册:

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

MainActivity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        /**
         * 对user表进行操作
         */

        // 设置URI
        Uri uri_user = Uri.parse("content://cn.henry.myprovider/user");

        // 插入表中数据
        ContentValues values = new ContentValues();
        values.put("_id", 3);
        values.put("name", "wang");


        // 获取ContentResolver
        ContentResolver 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");

        // 获取ContentResolver
        ContentResolver 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

        <provider
            android: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 {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        /**
         * 对user表进行操作
         */

        // 设置URI
        Uri uri_user = Uri.parse("content://cn.henry.myprovider/user");

        // 插入表中数据
        ContentValues values = new ContentValues();
        values.put("_id", 4);
        values.put("name", "Jordan");


        // 获取ContentResolver
        ContentResolver 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");

        // 获取ContentResolver
        ContentResolver 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

  • 37
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值