深入探索Android应用数据共享之ContentProvider

news2024/11/25 0:46:46

本文将深入探讨Android开发中非常重要的数据共享机制 - ContentProvider。

主要内容包括:

  • ContentProvider的基本定义及特点
  • 如何实现一个自定义的ContentProvider
  • ContentProvider对外提供的功能以及对外部应用的权限控制
  • 对ContentProvider的一些常见使用场景
  • 使用ContentProvider的最佳实践及注意事项

一、什么是ContentProvider?


ContentProvider是Android系统提供的一种在应用之间共享数据的机制,也是 Android 的四大组件之一,可见它在 Android 中的作用非同小可。

其核心思想是通过标准化的接口(增删改查等操作),让应用程序可以安全地访问和操作彼此的数据。相比直接访问文件或者数据库,ContentProvider提供了更加安全和标准化的数据共享方式。

ContentProvider 可以理解为 Android 应用对外开放的接口,只要是符合它所定义的 URI 格式的请求,均可以正常访问执行操作。

Android 应用之间可以使用 ContentResolver 对象通过与 ContentProvider 同名的方法请求执行,被执行的就是 ContentProvider 中的同名方法。


如图:

在这里插入图片描述


二、为什么要使用ContentProvider?


1、数据共享的需求

  • 在实际开发中,经常会遇到多个应用之间需要共享数据的场景,比如通讯录应用需要被其他应用访问。

  • 如果直接通过文件或数据库共享数据,会带来一些问题,比如数据访问权限难以控制,数据格式不统一等。

  • ContentProvider提供了一种标准化的数据共享机制,可以方便地在不同应用之间共享数据。


2、权限控制的需求

  • 数据的安全性是移动应用开发中的重中之重。如果直接暴露数据库或文件,很容易造成数据泄露和被篡改的风险。

  • ContentProvider可以对数据的访问权限进行精细化控制,开发者可以根据需求设置读写权限,充分保护数据安全。

  • 当其他应用请求访问数据时,ContentProvider会进行权限验证,阻止未授权的访问。


3、统一接口的需求

  • 不同的应用可能会采用不同的数据存储方式,比如SQLite、文件系统等。这会给数据访问带来一定的复杂性。
  • ContentProvider为数据访问提供了标准化的CRUD接口,开发者不需要关心底层的数据存储细节,可以专注于业务逻辑的实现。
  • 这种统一的接口不仅简化了开发,也大大提高了代码的可维护性。

4、跨进程通信的需求

  • Android应用是基于进程隔离的,不同应用之间的数据是隔离的。
  • ContentProvider基于Binder机制实现了进程间通信,让应用之间的数据交互更加顺畅。
  • 通过ContentProvider,开发者无需关心进程间通信的底层细节,就可以实现应用间的数据共享。

三、如何实现一个自定义的ContentProvider?


实现一个自定义的ContentProvider需要:

  • 第一步,继承ContentProvider类,并实现6个抽象方法。

  • 第二步,在AndroidManifest.xml中声明ContentProvider及其Authority。

  • 第三步,在其他应用中使用ContentResolver访问ContentProvider提供的数据。


1、首先,我们定义一个名为MyContentProvider的ContentProvider类,它继承自ContentProvider基类:


public class MyContentProvider extends ContentProvider {
    // 声明Authority,这是ContentProvider的唯一标识符
    public static final String AUTHORITY = "com.example.mycontentprovider";

    // 声明Uri常量,用于构建Uri
    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/users");

    // 声明表名常量
    private static final String TABLE_NAME = "users";

    // 声明数据库相关对象
    private SQLiteDatabase db;
    private MySQLiteOpenHelper dbHelper;

    @Override
    public boolean onCreate() {
        // 初始化数据库
        dbHelper = new MySQLiteOpenHelper(getContext());
        db = dbHelper.getWritableDatabase();
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                       String[] selectionArgs, String sortOrder) {
        // 执行查询操作
        return db.query(TABLE_NAME, projection, selection, selectionArgs, nullnull, sortOrder);
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // 执行插入操作
        long id = db.insert(TABLE_NAMEnull, values);
        return ContentUris.withAppendedId(CONTENT_URI, id);
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                     String[] selectionArgs) {
        // 执行更新操作
        return db.update(TABLE_NAME, values, selection, selectionArgs);
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // 执行删除操作
        return db.delete(TABLE_NAME, selection, selectionArgs);
    }

    @Override
    public String getType(Uri uri) {
        // 返回MIME类型,这里以"vnd.android.cursor.dir/vnd.com.example.mycontentprovider.users"为例
        return "vnd.android.cursor.dir/vnd." + AUTHORITY + "." + TABLE_NAME;
    }
}

2、接下来,我们需要在应用的AndroidManifest.xml文件中声明这个自定义的ContentProvider:


<provider
    android:name=".MyContentProvider"
    android:authorities="com.example.mycontentprovider"
    android:exported="true" />

其中,android:authorities属性指定了ContentProvider的Authority,必须与前面定义的AUTHORITY字符串一致。android:exported属性表示ContentProvider是否对其他应用开放,这里设置为true


3、现在,我们可以在其他应用中使用这个自定义的ContentProvider了


比如,在另一个应用中,我们可以通过以下代码访问MyContentProvider中的数据:

// 构建Uri
Uri uri = MyContentProvider.CONTENT_URI;

// 执行查询操作
Cursor cursor = getContentResolver().query(uri, nullnullnullnull);

// 遍历查询结果
while (cursor.moveToNext()) {
    int id = cursor.getInt(cursor.getColumnIndex("id"));
    String name = cursor.getString(cursor.getColumnIndex("name"));
    // 处理数据
    Log.d("MyApp""id: " + id + ", name: " + name);
}

在这个示例中,我们首先通过MyContentProvider.CONTENT_URI构建了访问MyContentProvider的Uri。然后,使用getContentResolver().query()方法执行查询操作,并遍历查询结果,获取数据。

类似地,我们还可以使用insert()update()delete()方法对数据进行增删改操作。


四、ContentProvider的常见使用场景


1、访问系统级别的数据

  • Android系统自带了一些ContentProvider,例如Contacts、Media、Calendar等,开发者可以通过这些ContentProvider访问系统级别的数据,如通讯录、图片、日历等。

  • 这些系统级ContentProvider提供了标准化的接口,开发者无需关心底层实现,就可以方便地获取和操作系统数据。


2、实现应用间的数据共享

  • 在实际开发中,经常会有多个应用之间需要共享数据的需求,比如社交应用需要获取用户的联系人信息。
  • 开发者可以自定义ContentProvider,将应用内部的数据暴露给其他应用使用。通过设置合适的读写权限,既可以保证数据安全,又可以实现应用间的数据共享。

3、支持应用内部的数据持久化

  • 除了实现应用间的数据共享,ContentProvider也可以用于应用内部的数据持久化。

  • 例如,开发者可以结合Room或SQLite,将ContentProvider作为应用内部数据库的对外接口,为上层的ViewModel和UI层提供标准化的数据访问方式。


4、配合Android Jetpack实现数据驱动的UI

  • 近年来,随着Android Jetpack的广泛应用,ViewModel和LiveData成为了实现数据驱动型UI的主流方案。
  • 在这种架构中,ContentProvider可以作为数据源,为ViewModel提供标准化的数据访问接口,ViewModel则负责观察数据变化,实时刷新UI。这种设计可以大大提高代码的可维护性和可测试性。

5、实现文件共享

  • ContentProvider不仅可以用于共享结构化数据,也可以用于共享文件数据。

  • 开发者可以自定义ContentProvider,将应用内部的文件资源暴露给其他应用使用,例如实现文件或图片的跨应用共享。


五、使用ContentProvider的最佳实践及注意事项


1、合理设计Authority和Uri ,使用ContentUris管理Uri


  • Authority是ContentProvider的唯一标识符,应该具有唯一性和可读性。通常采用反域名的形式,如"com.example.myprovider"。
  • Uri是用于访问ContentProvider数据的标识,应该遵循一定的命名规范,例如"content://com.example.myprovider/users"。
  • 合理设计Authority和Uri可以提高代码的可读性和可维护性,同时也有利于权限控制。

public class MyContentProvider extends ContentProvider {
    public static final String AUTHORITY = "com.example.mycontentprovider";
    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/users");

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // 执行插入操作
        long id = db.insert(TABLE_NAMEnull, values);
        return ContentUris.withAppendedId(CONTENT_URI, id);
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                       String[] selectionArgs, String sortOrder) {
        // 判断操作对象是单个记录还是记录集合
        if (ContentUris.parseId(uri) != -1) {
            // 查询单个记录
            selection = BaseColumns._ID + " = ?";
            selectionArgs = new String[] { String.valueOf(ContentUris.parseId(uri)) };
        }
        // 执行查询操作
        return db.query(TABLE_NAME, projection, selection, selectionArgs, nullnull, sortOrder);
    }
}

上面示例,我们使用了ContentUris工具类来管理Uri。

insert()方法中,我们使用ContentUris.withAppendedId()方法在基础Uri后附加记录id,生成唯一的Uri。

query()方法中,我们使用ContentUris.parseId()方法解析Uri中的记录id,根据id进行针对性的查询操作。

这种方式可以更好地规范化Uri的使用。


2、合理控制读写权限


  • ContentProvider提供了灵活的权限控制机制,开发者可以针对不同的操作(增删改查)设置不同的权限。
  • 合理控制读写权限可以提高数据的安全性,防止未经授权的访问。同时,也可以根据业务需求,灵活地控制其他应用对数据的访问范围。
public class MyContentProvider extends ContentProvider {
    public static final String AUTHORITY = "com.example.mycontentprovider";
    private static final String TABLE_NAME = "users";

    @Override
    public boolean onCreate() {
        // 初始化数据库
        // ...
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                       String[] selectionArgs, String sortOrder) {
        // 检查访问权限
        if (getContext().checkCallingOrSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("Permission denied to access the data");
        }
        // 执行查询操作
        return db.query(TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // 检查访问权限
        if (getContext().checkCallingOrSelfPermission(Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("Permission denied to access the data");
        }
        // 执行插入操作
        long id = db.insert(TABLE_NAME, null, values);
        return ContentUris.withAppendedId(CONTENT_URI, id);
    }

    // 其他方法的实现类似
}


上面示例中,我们在query()和insert()方法中分别检查了读和写权限。如果调用方没有所需的权限,则抛出SecurityException拒绝访问。这样可以确保数据的安全性。


3、合理使用MIME类型,遵循MIME类型约定


  • MIME类型描述了ContentProvider返回的数据类型,应该根据实际情况设置合理的MIME类型。
  • 正确的MIME类型可以帮助其他应用正确地解析和处理ContentProvider提供的数据。
@Override
public String getType(Uri uri) {
    // 根据Uri返回对应的MIME类型
    if (uri.equals(CONTENT_URI)) {
        return "vnd.android.cursor.dir/vnd." + AUTHORITY + "." + TABLE_NAME;
    } else if (ContentUris.parseId(uri) != -1) {
        return "vnd.android.cursor.item/vnd." + AUTHORITY + "." + TABLE_NAME;
    }
    throw new IllegalArgumentException("Unknown URI " + uri);
}

在上述实现中,我们根据Uri的类型返回了不同的MIME类型字符串。

当Uri表示记录集合时,我们返回"vnd.android.cursor.dir/"前缀。

当Uri表示单个记录时,我们返回"vnd.android.cursor.item/"前缀。

这样做可以让其他应用更好地识别和处理ContentProvider提供的数据。


4、注意线程安全,合理处理异常情况


  • 注意线程安全,避免多线程访问时出现的数据竞争问题

  • 在实现ContentProvider的各个方法时,应该合理地处理可能出现的异常情况,例如数据库操作异常、权限验证失败等。

  • 合理的异常处理可以提高应用的健壮性,同时也有利于定位和解决问题。

private final ReentrantLock lock = new ReentrantLock();

@Override
public Cursor query(Uri uri, String[] projection, String selection,
                   String[] selectionArgs, String sortOrder) {
    lock.lock();
    try {
        // 执行查询操作
        return db.query(TABLE_NAME, projection, selection, selectionArgs, nullnull, sortOrder);
    } finally {
        lock.unlock();
    }
}

@Override
public Uri insert(Uri uri, ContentValues values) {
    lock.lock();
    try {
        // 执行插入操作
        long id = db.insert(TABLE_NAMEnull, values);
        return ContentUris.withAppendedId(CONTENT_URI, id);
    } finally {
        lock.unlock();
    }
}

在上述示例中,我们使用了ReentrantLock来保证ContentProvider各个方法的线程安全。

在执行数据库操作时,我们先获取锁,操作完成后再释放锁。这样可以避免多线程访问时出现的数据竞争问题。


六、结语


ContentProvider作为Android系统提供的标准化数据共享机制,在开发过程中扮演着非常重要的角色。

通过深入理解其工作原理,我们不仅可以轻松实现应用间的数据交互,还能够提升数据访问的安全性和可维护性。

相信通过本文的介绍,你已经掌握了使用ContentProvider的核心知识,相信在后续的Android开发之路上,一定能游刃有余。

那么让我们一起期待下一篇文章的精彩内容吧!

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

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

相关文章

探索网站支付系统的奥秘,从Vue3和Spring Boot开始(入门级项目实战+在线教程)附赠项目源码!

你是否曾经在购物时&#xff0c;对着电脑屏幕前的“支付成功”四个字感到好奇&#xff1f;这背后的秘密究竟是什么&#xff1f; 今天&#xff0c;让我们一起揭开支付系统的神秘面纱&#xff0c;探索其背后的技术实现。 在这个基于Vue3和Spring Boot的支付项目实战中&#xff…

Docker 容器中 PHP 使用 Curl 访问本地服务异常

在 Docker 环境中&#xff0c;将应用程序和服务容器化是常见的做法&#xff0c;但是有时会遇到一些网络通信方面的问题。其中一个常见的问题是 PHP 容器无法使用 Curl 访问本地服务&#xff0c;这可能导致开发和调试过程中的困扰。 问题描述 通常情况下&#xff0c;我们会将 …

为什么Qt这么强大却不受欢迎?

在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「Qt的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;虽然这个问题并不被广泛讨论&#xff0c;但我根…

Vue3基础(API风格、监听、生命周期、toRefs、组件通信、插槽、axios,Promise)

Vue3基础&#xff08;API风格、监听、生命周期、toRefs、组件通信、插槽、axios&#xff0c;Promise&#xff09; 目录 Vue3基础&#xff08;API风格、监听、生命周期、toRefs、组件通信、插槽、axios&#xff0c;Promise&#xff09;API 风格选项式API组合式API混合式 事件监听…

第二证券|1.73万亿“聪明钱”A股扫货买了什么?

跟着A股上市公司一季报披露收官&#xff0c;备受商场重视的险资、社保基金和QFII等各大组织持仓数据浮出水面。 Wind计算数据显现&#xff0c;719家A股上市公司的十大流通股股东有QFII身影&#xff0c;险资和社保基金分别现身754只和659只个股的前十大流通股股东&#xff0c;Q…

ECC 号码总结

1、问题背景 在手机开发过程中&#xff0c;经常遇见各种紧急号码问题&#xff0c;在此特意总结下紧急号码相关知识。 2、紧急号码来源 在MTK RILD EccNumberSource.h中&#xff0c;定义了如下几种紧急号码来源。 按优先级排序介绍如下 2.1、SOURCE_NETWORK 网络下发&#xff…

MinimogWP WordPress 主题下载——优雅至上,功能无限

无论你是个人博客写手、创意工作者还是企业站点的管理员&#xff0c;MinimogWP 都将成为你在 WordPress 平台上的理想之选。以其优雅、灵活和功能丰富而闻名&#xff0c;MinimogWP 不仅提供了令人惊叹的外观&#xff0c;还为你的网站带来了无限的创作和定制可能性。 无与伦比的…

CentOS 7 :虚拟机网络环境配置+ 安装gcc(新手进)

虚拟机安装完centos的系统却发现无法正常联网&#xff0c;咋破&#xff01; 几个简单的步骤&#xff1a; 一、检查和设置虚拟机网络适配器 这里笔者使用的桥接模式&#xff0c;朋友们可以有不同的选项设置 二、查看宿主机的网络 以笔者的为例&#xff0c;宿主机采用wlan上网模…

Could not resolve placeholder ‘xx.xxx.host’ in value “xxx“问题解决

Could not resolve placeholder ‘xx.xxx.host’ in value "xxx"问题解决 众多原因其中之一 springboot 项目&#xff0c;idea 配置apollo 时&#xff0c;运行指定了配置文件 uat 所以使用本地配置文件启动 时&#xff0c;一直去找uat 配置文件&#xff0c;结果自…

CSS引用

CSS定义 层叠样式表&#xff1a;&#xff08;Cascading Style Sheets,缩写为css&#xff09;,是一种样式表语言&#xff0c;用来描述HTML文档的呈现&#xff08;美化内容&#xff09; 书写位置&#xff1a;title标签下方添加style双标签&#xff0c;style标签里写入CSS代码 在s…

LVS 集群

一、集群和分布式 系统性能扩展方式&#xff1a; Scale UP&#xff1a;垂直扩展&#xff0c;向上扩展,增强&#xff0c;性能更强的计算机运行同样的服务 Scale Out&#xff1a;水平扩展&#xff0c;向外扩展,增加设备&#xff0c;并行地运行多个服务调度分配问题&#xff0c;…

OpenSPG docker 安装教程

文章目录 前言自述 一、OpenSPG1.介绍 二、安装步骤1.安装服务端2.客户端部署 前言 自述 我最近是想结合chatglm3-6b和知识图谱做一个垂直领域的技术规范的问答系统&#xff0c;过程中也遇到了很多困难&#xff0c;在模型微调上&#xff0c;在数据集收集整理上&#xff0c;在知…

RJ45网口温湿度传感器MQTT/http协议配云平台

产品概述 RJ45网口版主机 SC-GP-THLAN温湿度传感终端是上海数采物联网科技有限公司推出的一款基于网口有线传输&#xff0c;直流宽电压供电的通用型温湿度传感器&#xff0c;可采集环境中的温度、湿度数据。 网口温湿度终端广泛应用于工业、农业等温湿度测量场景。 服务理念…

阿里easyExcel -- excel单元格自定义下拉选择(升级版)

背景 很久很久以前写了一篇类似的文章 阿里easyExcel – excel下载/导出/读取 (单元格自定义下拉选择、不支持图片) &#xff0c;用了没多久就发现不好用&#xff0c;限制太多&#xff08;以后遇到你就知道了&#xff09;&#xff0c;然后就有了现在迟到很久的文章&#xff0c…

从U盘到云端:企业数据泄露的那些事

在企业的日常运营中&#xff0c;数据安全无疑是极为关键的一环。无论是U盘还是云&#xff0c;数据泄露事件的发生都可能导致企业的核心机密被窃取&#xff0c;甚至损害企业的商业利益和声誉。以下是关于从U盘到云端&#xff0c;企业数据泄露的一些常见情况和应对策略。 U盘&…

数据结构-线性表-应用题-2.2-12

1&#xff09;算法的基本设计思想&#xff1a;依次扫描数组的每一个元素&#xff0c;将第一个遇到的整数num保存到c中&#xff0c;count记为1&#xff0c;若遇到的下一个整数还是等于num,count,否则count--,当计数减到0时&#xff0c;将遇到的下一个整数保存到c中&#xff0c;计…

Android 终端查看CPU信息源码分析

代码如下&#xff1a; 终端获取信息&#xff08;左为H9&#xff0c;右为H5&#xff09; 觉得本文对您有用&#xff0c;麻烦点赞、关注、收藏&#xff0c;您的肯定是我创作的无限动力&#xff0c;谢谢&#xff01;&#xff01;&#xff01;

DOTA-Gly-Asp-Tyr-Met-Gly-Trp-Met-Asp-Phe-NH2,1306310-00-8,是一种重要的多肽化合物

一、试剂信息 名称&#xff1a;DOTA-Gly-Asp-Tyr-Met-Gly-Trp-Met-Asp-Phe-NH2CAS号&#xff1a;1306310-00-8结构式&#xff1a; 二、试剂内容 DOTA-Gly-Asp-Tyr-Met-Gly-Trp-Met-Asp-Phe-NH2是一种重要的多肽化合物&#xff0c;其CAS号为1306310-00-8。该多肽包含一个DO…

微火全域外卖系统是什么?为什么市场占有率这么高?

近日&#xff0c;全域外卖领域又出现了新变动&#xff0c;一个名为微火的品牌凭借着其全域外卖系统&#xff0c;在短短几个月的时间里&#xff0c;就占领了大部分市场。截止发稿日期前&#xff0c;微火全域外卖系统的市场占有率已经超过48%。 据了解&#xff0c;所谓的全域外卖…

怿星 × NI丨联合成功打造行业领先的L4自动驾驶数据回灌系统

怿星NI 联合成功打造行业领先的L4自动驾驶数据回灌系统&#xff08;终版&#xff09; 怿星与于NI&#xff08;恩艾&#xff09;公司联合打造的L4自动驾驶数据回灌系统&#xff0c;在支持多种数据同步回灌、实时模拟故障、高带宽数据传输的同时&#xff0c;具有视频链路扩展性高…