将Android进行到底之内容提供者(ContentProvider)

news2024/11/16 19:39:34

在这里插入图片描述

文章目录

  • 前言
  • 一、ContentProvider是什么?
  • 二、使用示例
    • 1.为应用创建内容提供者
    • 2.使用内容提供者
      • 2.1 内容URI
      • 2.2 Uri参数解析
      • 2.2 使用内容URI操作数据
    • 3.ContentProvider妙用
    • 4 内容URI对应的MIME类型
    • 5.ContentProvider重点注意
    • 6 演示demo源码
  • 总结


前言

随着现在的应用越做越大,出现了多进程的架构,因为Android的应用能申请的内存是有限制的,所以很多APP为了能够最大程度的保证自己的应用能够逃脱系统的围杀,设计出多进程架构,让App可以多使用几份内存。但是由于进程间内存是不共享的。所以需要做进程间的通信(IPC)。而Android进程间的通信有很多种。比如socket,基于Binder的AIDL以及ContentProvider等,而本章内容要介绍的就是内容提供者(ContentProvider)


一、ContentProvider是什么?

内容提供者ContentProvider是Android的四大组件之一,是Android进程间通信的一种实现方式,底层也是基于Binder实现的。它主要用于在不同的应用程序之间实现数据共享的功能,而且它提供了一套完整的机制。允许一个程序访问另一个程序中的数据,同时还能保证被访问数据的安全性。不同于文件存储和sharedPreferences存储中两种全局可读可写操作模式,内容提供者可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会泄漏。目前,内容提供者已经成为官方推荐的进程间共享数据的标准方式。


二、使用示例

1.为应用创建内容提供者

创建一个类继承自Android官方提供的ContentProvider类,官方提供的ContentProvider类共有6个抽象方法,我们在使用子类继承它的时候,需要将这6个方法全部重写。我们以经典例子雇员和雇主的例子介绍内容提供者的使用,假设我们要共享雇员的信息给另外一个程序。代码如下所示:

public class EmployeeProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
    //初始化ContentProvider的时候调用通常会在这里完成对数
    //据库的创建和升级操作,返回true表示内容提供者初始
    //化成功,false表示失败。注:只有当存在ContentProvider
    //尝试访问我们程序中的数据时,ContentProvider才会被初始化
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] 
    projection, @Nullable String selection, @Nullable String[]
    selectionArgs, @Nullable String sortOrder) {
    //query 方法用于从ContentProvider中查询数据。使用Uri参数
    //确定查询的表,projection参数确定查询的列,selection和
    //selectionArgs用于约束查询哪些行,sortOrder用于对查询结果
    //排序,查询的结果存放在Cursor对象中返回
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
    //根据传入的内容URI来返回相应的MIME类型
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
    //向ContentProvider中添加一条数据,使用Uri参数来确定要添加的表,待添加的数据保存
    //在values参数中,添加完成后,返回一个用于表示这条新纪录的Uri
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, 
    @Nullable String[] selectionArgs) {
    //从ContentProvider中删除数据,使用uri参数来确定删除哪一张表中的数据,selection
    //和selectionArgs参数用于约束删除哪些行,被删除的行数作为返回值返回
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values,
     @Nullable String selection, @Nullable String[] selectionArgs) {
     //更新ContentProvider中已有的数据,使用Uri参数确定更新哪一张表中的数据新数据
     //保存在values参数中,selection和selectionArgs参数用于约束更新哪些行,受影响
     //的行数会作为返回值返回
        return 0;
    }
}

上面的六个抽象方法中相信聪明的你一眼就能看出,这是一些增删改查的方法。但是我们几乎可以在每个方法中看到Uri这个参数,我们在使用内容提供者的时候会传入这个参数,接下来先让我们看下如何使用内容提供者。

2.使用内容提供者

我们使用ContentProvider一般都是用来查询数据的,下面就让我们重点看下内容提供者的query方法

Cursor cursor = query(@RequiresPermission.Read @NonNull Uri uri,
            @Nullable String[] projection, @Nullable String selection,
            @Nullable String[] selectionArgs, @Nullable String sortOrder,
            @Nullable CancellationSignal cancellationSignal);

参数解释
uri:指定查询某个程序下的某一张表
projection:指定查询的列名
selection:指定查询条件,相当于Sql语句中where后面的条件
selectionArgs:给selection中的占位符提供具体的值
sortOrder:指定查询的结果排序方式
cancellationSignal:取消正在进行操作的信号量

访问内容提供者共享的数据需要借助于Android提供的ContentProvider类,我们可以通过Context中的getContentResolver()方法获取该类的实例。ContentResolver中提供了一系列的方法用于对数据进行CRUD(增、删、改、查)操作。类似于Sqlite中对数据的操作。但又不是完全相同。ContentResolver中的增删改查方法是不接受表名参数的,而是使用一个Uri参数代替。这个参数被称为内容URI

2.1 内容URI

内容提供者URI给内容提供者中的数据建立了唯一标识符,它主要由两部分组成:authority和path。

authority 是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用包名的方式来进行命名,比如应用的包名为:com.example.app,那对应的authority可以命名为:com.example.app.provider

path 则是用于对同一应用程序中不同的表做区分的,通常会添加到authority的后面。比如某个成T恤的数据库里面存在两张表:table1和table2,这时可以将path分别命名为/table1 和 /table2;然后把authority和path组合在一起就成了下面两行字符串:

com.example.app.provider/table1
com.example.app.provider/table2

不过目前这两个字符串还是无法辨认它就是内容URI,我们需要在字符串的头部加上协议声明:所以内容URI的标准形式就出来了:

content://com.example.app.provider/table1
content://com.example.app.provider/table2

得到内容URI后我们还需要将它解析程Uri对象才可以作为参数传入。解析的方法也很简单。如下所示

Uri uri = new Uri.parse("content://com.example.app.provider/table1");

只需要调用Uri的静态方法parse()就可以把内容URI字符串解析程URI对象,现在就可以通过这个URI去查询table1中的数据了。

2.2 Uri参数解析

经过上一节内容我们知道,一个标准的内容URI写法是:

content://com.example.app.provider/table1

这就表示调用方期望访问的是com.example.app这个应用的table1表中的数据。其实,我们还可以在内容URI的后面加上一个id,如下所示:

content://com.example.app.provider/table1/1

这就表示调用方期望访问的是com.example.app这个应用的table1表中的id为1的数据

所以,内容URI的格式主要有以上两种,以路径结尾就表示期望访问该表中的所有数据,以id结尾就表示期望访问该表中拥有相应id的数据。我们可以使用通配符的方式来分别匹配这两种格式的内容URI,规则如下:
‘ * ’:表示匹配任意长度的任意字符
‘ # ’:表示匹配任意长度的任意数字

所以一个能匹配任意表的内容URI格式就可以写成

content://com.example.app.provider/table1/*

一个可以匹配任意一行数字内容的URI格式就可以写成

content://com.example.app.provider/table1/#

然后我们就可以借助UriMatcher这个类实现匹配内容URI的功能

2.2 使用内容URI操作数据

1.查询数据

  public void query(View view) {
        String [] PROJECTION = new String[]{
                Employee._ID,
                Employee.NAME,
                Employee.GENDER,
                Employee.AGE
        };

        Cursor cursor = getContentResolver().query(
        Employee.CONTENT_URI,PROJECTION,null,null,Employee.DEFAULT_SORT_ORDER);
        StringBuilder sb = new StringBuilder();
        if(cursor !=null && cursor.moveToFirst()){
            for (int i = 0; i < cursor.getCount(); i++) {
                cursor.moveToPosition(i);
                int id = cursor.getInt(0);
                String name = cursor.getString(1);
                String gender = cursor.getString(2);
                int age = cursor.getInt(3);
                sb.append("id").append(id).append("name: ")
                .append(name).append(" ,gender: ")
                .append(gender).append(" ,age: ")
                .append(age).append("\n");
            }
        }
        
        if(cursor != null && !cursor.isClosed()){
            cursor.close();
        }

       //显示数据
        setText("");
        setText(sb.toString());
    }

2.插入数据

public void insert(View view) {
        Uri uri = Employee.CONTENT_URI;
        ContentValues values = new ContentValues();
        values.put(Employee.NAME,"walt");
        values.put(Employee.GENDER,"male");
        values.put(Employee.AGE,28);
        getContentResolver().insert(uri,values);
        Toast.makeText(this, "插入成功", Toast.LENGTH_SHORT).show();
    }

3.更新数据

    public void update(View view) {
        //更新ID为1的记录
        Uri uri = ContentUris.withAppendedId(Employee.CONTENT_URI,1);
        ContentValues values = new ContentValues();
        values.put(Employee.NAME,"walt-zhong");
        values.put(Employee.GENDER,"male");
        values.put(Employee.AGE,18);
        int update = getContentResolver().update(uri, values, null, null);
    }

4.删除数据

  public void delete(View view) {
        //删除ID为1的记录
        Uri uri = ContentUris.withAppendedId(Employee.CONTENT_URI,2);
        int delete = getContentResolver().delete(uri, null, null);
        setText("删除成功 result: " + delete);
    }

3.ContentProvider妙用

ContentProvider还有一个妙用,那就是可以跨进程调用方法。读者可能会很迷惑对于这个描述。我来假设一个场景,比如我们需要从A应用传递一个坐标给到B应用去做一些操作,比如在VR行业中有种手机模式场景,就是通过在VR眼镜中虚拟出一个手机桌面,让用户在3d的场景中也可以使用2d的手机应用。也称为2D应用3D化。这时候用户可能是通过游戏手柄去操作的,游戏手柄通过蓝牙和VR眼镜相连,当游戏手柄点击到一个专门检测用户点击的A进程后,A进程会把用户点击的屏幕坐标共享给VR眼镜,这时候就可以使用内容提供者的跨进程调用方法,并把坐标当成参数传入就可以了,在使用端解析参数就可以进行相关的操作了。
具体的做法是:
1.通过调用方法的方式,将参数携带的值传到ContentProvider的使用端

    public void callMethod(View view) {
         Bundle reqBundle = new Bundle();
         reqBundle.putInt("actionCode", 1129);
         reqBundle.putString("extraData","额外数据");

        Bundle func = getContentResolver().call(Employee.CONTENT_URI, "func", 
        getPackageName(), reqBundle);
       // Log.d("zhongxj: ","res= " + func.toString());
    }
call(@NonNull Uri uri, @NonNull String method,
            @Nullable String arg, @Nullable Bundle extras)

Uri:指定要操作的某个程序下的某一张表
method:方法名称,用于区分是谁调用了call方法
arg:参数,没有的话传null
extra:封装在Bundle的参数,没有的话传null

2.在使用方的ContentProvider中重写call()方法,接收参数,做后续处理

  @Nullable
    @Override
    public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
        Log.d("zhongxj", "method: " + method);
        if ("func".equals(method)) {
            int actionCode = extras.getInt("actionCode");
            String extraData = extras.getString("extraData");
            StringBuilder sb = new StringBuilder();
            sb.append("func arg: ").append(arg)
                    .append("actionCode: ").append(actionCode)
                    .append("extraData").append(extraData)
                    .append("methodName: ").append(method);
            callFuncRes = sb.toString();
            Log.d("zhongxj", "result: " + sb.toString());

        }

        return null;
    }

4 内容URI对应的MIME类型

我们在重写ContentProvider的方法中有一个方法叫getType(),这个方法可以根据传入的URI返回对应的MIME类型,接下来我们一起来了解下什么是MIME类型以及如何定义内容URI对应的MIME类型

MIME(Multipurpose Internet Mail Extensions): 多用途互联网邮件扩展类型。是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。它是一个互联网标准,扩展了电子邮件标准,使其能够支持:非ASCII字符文本;非文本格式附件(二进制、声音、图像等);由多部分(multiple parts)组成的消息体;包含非ASCII字符的头信息(Header information)。

一个内容URI所对应的MIME字符串主要由3部分组成,Android对这3个部分做了如下的格式规定:
(1)必须以vnd开头
(2)如果内容URI以路径结尾,则后接" android.cursor.dir/ " ;如果内容URI以ID结尾,
则后接 " android.cursor.item/ "
(3)最后接vnd..
例如:content://com.example.app.provider/table1 的对应MIME类型可以写成

vnd.android.cursor.dir/vnd.com.example.app.provider.table1

而content://com.example.app.provider/table1/1这个内容URI的MIME类型可以写成

vnd.android.cursor.item/vnd.com.example.app.provider.table1

5.ContentProvider重点注意

写完ContentProvider后需要在AndroidManifest.xml中注册,否则程序会闪退

 <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.walt.mycontentprovider">
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyContentProvider"
        tools:targetApi="31">
        <activity>
        .......
        </activity>
        <provider
            android:authorities="com.walt.provider.Employee"
            android:name=".EmployeeProvider"
            android:exported="true"
            android:enabled="true"/>
    </application>

</manifest>

另外,还需注意的是在调用方使用ContentProvider时,在高版本的Android中需要在Androidmanifest.xml中加入标签,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.walt.appdemo2">
    <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/Theme.MyContentProvider">
        <activity>
         ...
        </activity>
    </application>
  <!--高版本中必须加这个标签才能访问到对应应用的数据-->
    <queries>
        <provider android:authorities="com.walt.provider.Employee"/>
    </queries>
</manifest>

6 演示demo源码

为了方便读者了解ContentProvider的使用,我做了一个简单的小demo,供读者参考,demo中有两个应用,分别代表A,B进程,一个创建了内容提供者,另一个使用内容提供者。需要注意的是,当使用方正在使用内容提供者的时候,提供数据的一方,即创建内容提供者的进程应该在后台,不能杀掉,否则,使用方无法获取到数据。
在这里插入图片描述这里需要声明下,demo仅做演示使用,请勿用到项目中,读者需要了解透彻后自己再开发出更好的代码。demo未经过测试,请勿使用:
demo地址


总结

以上就是今天要讲的内容,本文介绍了ContentProvider的使用和一些基本概念。并提供了一个演示demo供读者熟悉ContentProvider,但是在项目中使用ContentProvider时,读者还需自己仔细理解每一个API的使用方法,防止项目出现严重的BUG,博客只能是某个知识入门了解使用的工具,不是绝对的权威,希望读者能够结合自己的思考去实现更酷更好玩的东西。内容提供者作为现在Android应用间共享数据的标准方式,也是官方推荐的方式,希望读者好好的去了解这部分知识,对项目的开发一定会非常的有用。

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

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

相关文章

java通过JDBC连接mysql8.0数据库,并对数据库进行操作

目录 一、JDBC简介 二、添加依赖 三、JDBC操作数据库的步骤 四、JDBC操作数据库——增删改查 (一)新增数据 (二)删除数据 (三)修改数据 (四)查询数据 (五)多表连接查询 一、JDBC简介 Java数据库连接&#xff0c;&#xff08;Java Database Connectivity&#xff0c;简…

C进阶:字符串相关函数及其模拟实现

目录 &#x1f431;&#x1f638;一.strlen &#x1f54a;️1.功能 &#x1f43f;️2.模拟实现 &#x1f42c;&#x1f40b;二.strcpy &#x1f432;1.功能 &#x1f916;2.注意事项 &#x1f47b;3.模拟实现 &#x1f431;&#x1f42f;三.strcat &#x1f984;1.功能…

i.MX8MP平台开发分享(IOMUX篇)- Linux注册PAD

专栏目录&#xff1a;专栏目录传送门 平台内核i.MX8MP5.15.71文章目录1. pinfunc.h2.pinctrl-imx8mp.c3.PAD信息注册这一篇开始我们深入Linux中的pinctl框架。1. pinfunc.h pinfunc.h中定义了所有的引脚&#xff0c;命名方式是MX8MP_IOMUXC___&#xff0c;例如下面的MX8MP_IO…

【JDBC】----------ServletContext和过滤器

分享第二十四篇励志语句 幸福是什么&#xff1f;幸福不一定是腰缠万贯、高官显禄、呼风唤雨。平凡人自有平凡人的幸福&#xff0c;只要你懂得怎样生活&#xff0c;只要你不放弃对美好生活的追求&#xff0c;你就不会被幸福抛弃。 一&#xff1a;ServletContext&#xff08;重要…

js对象篇

面向对象 对象 如果对象的属性键名不符合JS标识符命名规范&#xff0c;则这个键名必须用引号包裹 访问属性有2种方法&#xff0c;有点语法和方括号填写法&#xff0c;特别地&#xff0c;如果属性名不符合JS命名规范或者使用变量存储属性名&#xff0c;则必须使用方括号访问 属…

【王道操作系统】2.3.6 进程同步与互斥经典问题(生产者消费者问题、吸烟者问题、读者写者问题、哲学家进餐问题)

进程同步与互斥经典问题(生产者消费者问题、吸烟者问题、读者写者问题、哲学家进餐问题) 文章目录进程同步与互斥经典问题(生产者消费者问题、吸烟者问题、读者写者问题、哲学家进餐问题)1.生产者-消费者问题1.1 问题描述1.2 问题分析1.3 如何实现1.4 实现互斥的P操作一定在实现…

深化全面To C战略魏牌发布与用户共创大六座SUV蓝山

对魏牌而言&#xff0c;与用户共创不是吸引眼球的营销噱头&#xff0c;而是“直面用户需求&#xff0c;真实倾听用户意见”的有效途径。 2022年12月30日&#xff0c;第二十届广州国际汽车展览会&#xff08;以下简称“广州车展”&#xff09;正式启幕。魏牌以“品位蓝山 有咖有…

神经网络必备基础知识:卷积、池化、全连接(通道数问题、kernel与filter的概念)

文章目录卷积操作实际操作filter与kernel1x1的卷积层可视化的例子池化全连接卷积操作 这个不难理解。我们知道图像在计算机中是由一个个的像素组成的&#xff0c;可以用矩阵表示。 假设一个5x5的输入图像&#xff0c;我们定义一个3x3的矩阵&#xff08;其中的数值是随机生成的…

excel图表美化:设置标记样式让拆线图精巧有趣

折线图作为我们平时数据视图化非常常规的表现方式&#xff0c;想必大家已经司空见惯了。折线图很简单&#xff0c;每个人都会做&#xff0c;但是不同的人做出来的折线图却千差万别。大多数人的折线图都是直接插入默认折线图样式生成的&#xff0c;这样的折线图先不说有没有用心…

五、IDEA中创建Web项目

文章目录5.1 创建Web项目5.1.1 创建项目5.1.2 编写Servlet类5.2 手动部署项目5.3 自动部署项目5.3.1 IDEA集成Tomcat5.3.2 IDEA部署JavaWeb项目5.4 war包部署5.4.1 导出war包5.1 创建Web项目 5.1.1 创建项目 1、打开IDEA&#xff0c;单击“New Project”或者通过File–>ne…

Perl语言入门

一、简介 Perl语言是拉里.沃尔&#xff08;Larry Wall&#xff09;在1987年开发的一种编程语言&#xff0c;借鉴了C、sed、awk、shell脚本语言以及其他语言的特性&#xff0c;专门用于文本处理。 它可以在各种平台上运行&#xff0c;例如Windows&#xff0c;Mac OS和各种UNIX…

bean生命周期

1.Aware和InitializingBean接口 Aware 接口用于注入一些与容器相关信息&#xff0c;例如 BeanNameAware: 注入bean的名字BeanFactorAware&#xff1a; 注入beanFactor容器ApplicationContextAware&#xff1a; 注入applicationContext容器EmbeddedValueResolverAware: ${} 代码…

爬虫进阶一(基础一)

文章目录简介cookie爬取雪球热帖代理模拟登陆防盗链异步爬虫协程asyncioM3U8HLS爬取seleniumbilibili无头浏览器规避检测MySQLMongoDBRedis简介 这个系列分四部分 基础进阶Scrapy 框架逆向分析实战运用 先补充一些爬虫需要的基础知识和技能预热&#xff0c;爬取个简历模板网站…

浅谈Git

Git是一个开源的分布式版本控制系统&#xff0c;可以有效、高速地处理从很小到非常大的项目版本管理,也是Linus Torvalds为了帮助管理Linux内核开发而开发的一个开放源码的版本控制软件。 版本控制 什么是版本控制&#xff1f; 版本控制是一种在开发的过程中用于管理我们对文…

如何评价唐卫国公李靖的战功、军事才能、政治才能?

link 一鞭直渡清河洛Research and no development已关注470 人赞同了该回答个人以为&#xff0c;在军事上&#xff0c;李靖是当之无愧的唐朝第一名将&#xff0c;他用兵如神&#xff0c;精于谋略&#xff0c;无论是在实际的军事指挥上&#xff0c;还是军事理论上&#xff0c;他…

Vue3 中computed计算属性的使用

目录前言&#xff1a;什么是计算属性选项式中的计算属性组合式中的计算属性计算属性和方法的区别&#xff1a;计算属性/计算方法注意事项&#xff1a;总结前言&#xff1a; 目标&#xff1a;现在vue3的使用越来越普遍了&#xff0c;vue3这方面的学习我们要赶上&#xff0c;今天…

银行家算法 源码+实验报告(用了自取)

XIAN TECHNOLOGICAL UNIVERSITY 课程设计报告 实验课程名称 操作系统—银行家算法 专 业&#xff1a;计算机科学与技术 班 级&#xff1a; 姓 名&#xff1a; 学 号&#xff1a; 实验学时&#xff1a; …

小程序03/ uni-app自定义全局组件 、 uni-app项目引入 Uview-ui 框架教程方法 和 Uview框架介绍

一. uni-app自定义全局组件 1.创建组件 注意: 在components文件夹下创建组件 、文件夹名要与文件名保持一致 2.使用组件 注意: 在pages文件夹下任意vue文件、 template标签内使用该组件即可 二.uni-app项目引入Uview-ui框架教程方法 和 Uview框架介绍 (1) Uview介绍: Uvi…

【自学Java】Java运算符

Java运算符 Java运算符 Java 程序是由许多语句组成的&#xff0c;而语句的基本操作单位是表达式与运算符。运算符就是数学中的运算符号&#xff0c;如 、-、*、 / 等等。 Java 中提供了许多的运算符&#xff0c;这些运算除了可以处理一般的数学运算外&#xff0c;还可以处理…

Android---AndroidX

目录 Android 支持库 Android Support Library AndroidX 如何迁移老项目到 AndroidX? 支持库的作用 Android 支持库 Android 支持库是每个Android 应用程序中必不可少的一部分&#xff0c;你会发现它们无处不在。支持库为开发人员提供了将 Android 的最新和最强大功能添加…