你手机里的通讯录,存储了所有联系人的信息。如果你想把这些联系人信息分享给其他App,就可以通过ContentProvider来实现。。

一、什么是 ContentProvider

‌ContentProvider‌ 是 Android 四大组件之一,负责实现‌跨应用程序的数据共享与访问‌,通过统一接口封装数据存储细节,提供标准化操作方式。其中主要功能包括:

  1. 数据抽象层:将应用内部的数据(如 SQLite 数据库、文件等)封装成统一的接口对外提供。
  2. 跨应用数据共享:允许其他应用安全地访问和操作本应用的数据。
  3. 数据权限控制:通过 URI 和权限机制,精确控制数据的访问范围。
  4. 统一数据访问:提供类似数据库的 CRUD 操作接口,简化数据使用。

二、ContentProvider 的核心概念

  1. URI(统一资源标识符)

    • 格式:content://authority/path/id
    • 示例:content://com.example.provider/users/1
    • authority:标识 ContentProvider,通常为应用包名 + provider 名
    • path:标识要访问的数据集合
    • id:可选,标识具体记录
  2. ContentResolver

    • 应用通过 ContentResolver 与 ContentProvider 通信
    • 提供 query ()、insert ()、update ()、delete () 等方法
  3. Cursor

    • 查询结果的返回类型,类似数据库查询结果集
    • 通过 Cursor 获取和遍历数据

三、ContentProvider 的实现步骤

以下是实现一个简单 ContentProvider 的完整步骤:

1.创建数据模型

// User.java
public class User {private int id;private String name;private int age;// getters and setters
}

2.创建 SQLiteOpenHelper 管理数据库

// DatabaseHelper.java
public class DatabaseHelper extends SQLiteOpenHelper {private static final String DB_NAME = "user.db";private static final int DB_VERSION = 1;public static final String TABLE_NAME = "users";public DatabaseHelper(Context context) {super(context, DB_NAME, null, DB_VERSION);}@Overridepublic void onCreate(SQLiteDatabase db) {db.execSQL("CREATE TABLE " + TABLE_NAME + " (" +"_id INTEGER PRIMARY KEY AUTOINCREMENT, " +"name TEXT, " +"age INTEGER);");}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);onCreate(db);}
}

3.实现 ContentProvider

// UserProvider.java
public class UserProvider extends ContentProvider {private DatabaseHelper dbHelper;public static final String AUTHORITY = "com.example.provider";public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/users");@Overridepublic boolean onCreate() {dbHelper = new DatabaseHelper(getContext());return true;}@Overridepublic Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {SQLiteDatabase db = dbHelper.getReadableDatabase();return db.query(DatabaseHelper.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);}@Overridepublic Uri insert(Uri uri, ContentValues values) {SQLiteDatabase db = dbHelper.getWritableDatabase();long id = db.insert(DatabaseHelper.TABLE_NAME, null, values);return ContentUris.withAppendedId(CONTENT_URI, id);}@Overridepublic int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {SQLiteDatabase db = dbHelper.getWritableDatabase();return db.update(DatabaseHelper.TABLE_NAME, values, selection, selectionArgs);}@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {SQLiteDatabase db = dbHelper.getWritableDatabase();return db.delete(DatabaseHelper.TABLE_NAME, selection, selectionArgs);}@Overridepublic String getType(Uri uri) {return "vnd.android.cursor.dir/vnd.com.example.provider.users";}
}

4.在 AndroidManifest.xml 中注册 Provider

<providerandroid:name=".UserProvider"android:authorities="com.example.provider"android:exported="true"android:grantUriPermissions="true">
</provider>

四、ContentProvider 的使用示例

其他应用通过 ContentResolver 访问该 Provider:

// 查询所有用户
Cursor cursor = getContentResolver().query(UserProvider.CONTENT_URI, null, null, null, null
);// 插入新用户
ContentValues values = new ContentValues();
values.put("name", "John");
values.put("age", 30);
Uri newUri = getContentResolver().insert(UserProvider.CONTENT_URI, values);// 更新用户
ContentValues updateValues = new ContentValues();
updateValues.put("age", 31);
int count = getContentResolver().update(UserProvider.CONTENT_URI, updateValues, "name=?", new String[]{"John"}
);// 删除用户
int deleted = getContentResolver().delete(UserProvider.CONTENT_URI, "age > ?", new String[]{"40"}
);

五、跨应用权限控制

配置目标实现方式
跨应用调用权限

调用方声明<uses-permission>,Porvider方配置android:exported="true"。

动态权限申请针对dangerous级别权限,调用方需在运行时请求用户授权
路径级访问控制Provider方通过<path-permission>细化权限,调用方需匹配声明

1. 声明 Provider 权限

<!-- 定义自定义权限 -->  
<permission android:name="com.example.READ_USERS"  android:protectionLevel="dangerous" />  
<permission android:name="com.example.WRITE_USERS"  android:protectionLevel="dangerous" />  <!-- 应用权限到 Provider --> 
<provider  android:name=".UserProvider" <!-- Provider 实现类的全路径 -->  android:authorities="com.example.provider" <!-- 唯一标识符,与Contract类一致 -->  android:exported="true" <!-- 是否允许其他应用访问(默认 false) -->   android:readPermission="com.example.READ_USERS"  android:writePermission="com.example.WRITE_USERS" />  
  • protectionLevel 设为 dangerous 表示需用户手动授权。
  • readPermission/writePermission:自定义权限控制。

2. 路径级权限细化(可选)

若 Provider 方通过 <path-permission> 限制特定路径,调用方需确保拥有对应权限:

<!-- Provider 方配置 -->  
<provider ...>  <path-permission  android:pathPrefix="/admin"  android:permission="com.example.ADMIN_PERMISSION" />  
</provider>  

3. 调用方配置

<manifest ...>  <!-- 声明权限 -->  <uses-permission android:name="com.example.READ_USERS" />  <uses-permission android:name="com.example.WRITE_USERS" />  <!-- 如果存在路径细化,调用方需声明额外权限 -->  <uses-permission android:name="com.example.ADMIN_PERMISSION" />  <application ...>  <!-- 无 Provider 声明,直接通过 ContentResolver 调用 -->  </application>  
</manifest>  

4. 动态权限申请 

在调用方的 Activity/Fragment 中实现动态权限申请流程:

public class MainActivity extends AppCompatActivity {  private static final int REQUEST_READ_PERMISSION = 100;  @Override  protected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity_main);  // 检查权限  if (ContextCompat.checkSelfPermission(this, "com.example.READ_USERS")  != PackageManager.PERMISSION_GRANTED) {  // 权限未授予,显示申请弹窗  ActivityCompat.requestPermissions(this,  new String[]{"com.example.READ_USERS"},  REQUEST_READ_PERMISSION);  } else {  // 已授权,执行数据访问  queryData();  }  }  // 处理权限申请结果  @Override  public void onRequestPermissionsResult(int requestCode,  @NonNull String[] permissions, @NonNull int[] grantResults) {  super.onRequestPermissionsResult(requestCode, permissions, grantResults);  if (requestCode == REQUEST_READ_PERMISSION) {  if (grantResults.length > 0 && grantResults[0]== PackageManager.PERMISSION_GRANTED) {  queryData();  } else {  // 权限被拒绝,提示用户  Toast.makeText(this, "权限被拒绝,无法读取数据",Toast.LENGTH_SHORT).show();  }  }  }  private void queryData() {  // 通过 ContentResolver 访问 Provider 数据  Cursor cursor = getContentResolver().query(  UserContract.CONTENT_URI,  null, null, null, null  );  // 处理查询结果...  }  
}  

同一权限组内的权限只需申请一次(如 READ_CONTACTS 和 WRITE_CONTACTS 属于同一组)

4. ‌用户拒绝后引导设置‌

若用户勾选“不再询问”,需引导用户前往系统设置手动开启权限(可通过 shouldShowRequestPermissionRationale 判断)。

if (ActivityCompat.shouldShowRequestPermissionRationale(this,"com.example.READ_USERS")) {  // 用户之前可能拒绝过权限但未勾选“不再询问”// 展示解释性弹窗后再次申请  
} else {  // 用户勾选“不再询问”或系统禁止权限(如厂商定制 ROM 限制)// 跳转系统设置界面手动开启权限 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);  intent.setData(Uri.parse("package:" + getPackageName()));  startActivity(intent);  
}  

若用户‌从未请求过该权限‌,shouldShowRequestPermissionRationale() 也会返回 false。但此时代码通常不会进入此分支(因首次请求时直接调用 requestPermissions())。

部分设备可能不支持 Settings.ACTION_APPLICATION_DETAILS_SETTINGS,需增加异常捕获并提示用户手动查找权限设置 。 

六、数据变更通知

角色职责
客户端注册ContentObserver并实现onChange回调逻辑(如刷新UI)
ContentProvider数据变更时调用notifyChange触发通知
系统服务通过ContentService统一管理观察者,完成消息分发

1. 客户端注册观察者

在使用数据的客户端(如 Activity、Fragment)中,通过 ContentResolver 注册 ContentObserver,并指定监听的目标 URI,从而实时更新UI。

// 使用者(Activity)通过ContentResolver注册观察者  
getContentResolver().registerContentObserver(  UserContract.CONTENT_URI,  true,  // 是否监听子 URI  new ContentObserver(new Handler()) {  @Override  public void onChange(boolean selfChange) {  // 数据变化时触发回调}  }  
);
  • registerContentObserver 是客户端主动调用的方法,用于绑定观察者与目标数据 URI。
  • true 表示监听该 URI 及其所有子路径(如 content://com.example.provider/users/)的数据变更。

2. 提供者触发通知

在 ‌ContentProvider‌ 中,当数据发生变更(如 insertupdatedelete)时,需调用 notifyChange 方法触发回调:

// 在 Provider 的 insert/update/delete 方法中  
getContext().getContentResolver().notifyChange(uri, null);  
  • notifyChange 会通知所有注册了该 URI 的观察者。
  • 可通过第二个参数 observer 指定跳过特定观察者(通常设为 null)。

3. 系统级支持

  • ContentService‌:负责管理所有注册的观察者,以树形结构维护 URI 监听关系,实现高效的跨进程通知分发。
  • Binder 机制‌:底层通过 Binder 传递观察者对象(封装为 Transport 代理),确保跨进程通信的可行性。

客户端需主动注册观察者监听 URI,而通知触发由 Provider 发起,两者通过系统服务协同实现实时数据同步。

七、ContentProvider 的性能优化

1.使用 SQLite 事务

  • 批量操作时使用事务提高性能
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.beginTransaction();
try {// 执行多个操作db.setTransactionSuccessful();
} finally {db.endTransaction();
}

索引优化

  • 对经常查询的字段添加索引
db.execSQL("CREATE INDEX IF NOT EXISTS idx_name ON users(name);");

避免在主线程进行耗时操作

  • 使用 Loader 或异步任务执行查询
getSupportLoaderManager().initLoader(0, null, this);

八、ContentProvider 的安全注意事项

  1. 谨慎设置 android:exported

    • 仅在需要对外共享数据时设置为 true
    • 默认值为 false,可防止外部访问
  2. 输入验证

    • 对传入的 selection 和 projection 参数进行验证
private void validateProjection(String[] projection) {if (projection != null) {for (String col : projection) {if (!allowedColumns.contains(col)) {throw new IllegalArgumentException("Invalid column: " + col);}}}
}

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue-19-前端框架Vue之应用基础组件通信(二)

文章目录 1 v-model(父子相传)1.1 App.vue1.2 Father.vue1.2.1 v-model用在html标签上1.2.2 v-model用在html标签上(本质写法)1.2.3 v-model用在组件标签上1.2.4 v-model用在组件标签上(本质写法)1.3 MyInput(自定义的组件)1.4 修改modelValue1.4.1 Father.vue1.4.2 MyInput.vu…

宝塔下载pgsql适配spring ai

1.宝塔安装pgvector 1.先去github下载pgvectorpgvector/pgvector: Open-source vector similarity search for Postgres 2.把压缩包上传到系统文件的/temp下解压&#xff0c;重命名文件名为pgvector&#xff0c;之后命令操作 cd /tmp cd pgvector export PG_CONFIG/www/serv…

RK3568项目(八)--linux驱动开发之基础外设(上)

目录 一、引言 二、准备工作 ------>2.1、驱动加载/卸载命令 三、字符设备驱动开发 ------>3.1、驱动模块的加载和卸载 ------>3.2、外部模块编译模板 Makefile ------>3.3、cdev 四、LED驱动 ------>4.1、原理图 ------>4.2、驱动 五、设备树 -…

BUUCTF在线评测-练习场-WebCTF习题[GXYCTF2019]BabySQli1-flag获取、解析

解题思路打开靶场&#xff0c;题目提示是sql注入输入数据&#xff0c;判断下闭合11123报错&#xff1a;Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 1 at line 1报错提示…

“AI 曼哈顿计划”:科技竞赛还是人类挑战?

美国国会下属的经济与安全审查委员会已将“推动建立并资助一项堪比曼哈顿计划的通用人工智能研发项目”列为其对国会的核心建议之一&#xff0c;明确显示出对AI竞赛战略意义的高度重视。与此同时&#xff0c;美国能源部在近几个月中多次公开将人工智能的突破比作“下一场曼哈顿…

音频信号的预加重:提升语音清晰度

一、预加重介绍预加重是一种信号处理技术&#xff0c;主要用于增强音频信号中的高频成分。由于人类语音的频谱特性&#xff0c;尤其是在辅音和音调的表达上&#xff0c;高频成分对于语音的清晰度至关重要。然而&#xff0c;在录音和传输过程中&#xff0c;这些高频成分往往会受…

WebSocket实战:实现实时聊天应用 - 双向通信技术详解

目录一、WebSocket&#xff1a;实时通信的"高速公路"1.1 HTTP的短板&#xff1a;永远的"单相思"1.2 WebSocket的优势&#xff1a;真正的"双向对话"二、30分钟搭建聊天服务器2.1 环境准备2.2 WebSocket配置类2.3 核心消息处理器三、前端实现&…

宏集案例 | 基于CODESYS的自动化控制系统,开放架构 × 高度集成 × 远程运维

​​案例概况客户&#xff1a;MACS Sterilisationsanlagen GmbH&#xff08;Ermafa Environmental Technologies GmbH 旗下&#xff09; 应用场景&#xff1a;医疗与感染性废弃物的无害化处理控制系统应用产品&#xff1a;宏集Berghof高性能控制器设备&#xff08;一&#xff0…

学习JNI 二

创建一个名为Learn1项目&#xff08;Android Studio&#xff09;。一、项目结构二、配置 build.gradlebuild.gradle.kts(:app)plugins {alias(libs.plugins.android.application)alias(libs.plugins.jetbrains.kotlin.android) }android {namespace "com.demo.learn1&quo…

基于Spring Boot+Vue的DIY手工社预约管理系统(Echarts图形化、腾讯地图API)

2.10 视频课程管理功能实现2.11手工互动&#xff08;视频弹幕&#xff09;2.8预约设置管理功能实现&#x1f388;系统亮点&#xff1a;Echarts图形化、腾讯地图API&#xff1b;文档包含功能结构图、系统架构图、用例图、实体属性图、E-R图。一.系统开发工具与环境搭建1.系统设计…

leetcode 每日一题 1353. 最多可以参加的会议数目

更多技术访问 我的个人网站 &#xff08;免费服务器&#xff0c;没有80/443端口&#xff09; 1353. 最多可以参加的会议数目 给你一个数组 events&#xff0c;其中 events[i] [startDayi, endDayi] &#xff0c;表示会议 i 开始于 startDayi &#xff0c;结束于 endDayi 。 …

AI+智慧园区 | 事件处置自动化——大模型重构园区治理逻辑

在智慧园区的建设浪潮中&#xff0c;事件管理一直是园区高效运营的关键环节。考拉悠然所推出的大模型 智慧园区解决方案&#xff0c;在事件智能闭环管理方面独树一帜&#xff0c;为园区的日常运营编织了一张严密、高效、智能的管理网络&#xff0c;实现了从事件感知到处置的全…

FFmpeg Windows安装

FFmpeg 用于音频文件转换 Builds - CODEX FFMPEG gyan.dev ffmpeg-release-full.7z 下载完成之后 zip解压 大概就是 ffmpeg/ └── bin/ └── ffmpeg.exe 配置环境变量 ffmpeg -version 有可能idea还是找不到命令 就把命令路径写在程序里 例如

【2025/07/10】GitHub 今日热门项目

GitHub 今日热门项目 &#x1f680; 每日精选优质开源项目 | 发现优质开源项目&#xff0c;跟上技术发展趋势 &#x1f4cb; 报告概览 &#x1f4ca; 统计项&#x1f4c8; 数值&#x1f4dd; 说明&#x1f4c5; 报告日期2025-07-10 (周四)GitHub Trending 每日快照&#x1f55…

JVM 基础 - JVM 内存结构

前言 本文主要对JVM 内存结构进行讲解&#xff0c;注意不要和Java内存模型混淆了。 运行时数据区 内存是非常重要的系统资源&#xff0c;是硬盘和 CPU 的中间仓库及桥梁&#xff0c;承载着操作系统和应用程序的实时运行。JVM 内存布局规定了 Java 在运行过程中内存申请、分配…

【案例】二手车交易价格预测-472

二手车交易价格预测 数据来源数据特征探索构建模型参考数据来源 天池 https://tianchi.aliyun.com/competition/entrance/231784/information 数据特征探索 目标特征工程做好之后,能同时进行 lightgbm catboost 神经网络等模型,所以尽量都转换为数值类特征。 如果仅仅是使用…

【Spring】Java SPI机制及Spring Boot使用实例

目录 一、SPI是什么 1.1 SPI 和 API 有什么区别&#xff1f; 二、使用场景 三、使用介绍 四、Spring Boot实例运用 五、总结 一、SPI是什么 SPI全称Service Provider Interface&#xff0c;是Java提供的一套用来被第三方实现或者扩展的API&#xff0c;它可以用来启用框架…

多维度数据资产测绘技术在安全管控平台中的应用实践

一、数据资产治理困境&#xff1a;从 “黑箱” 到 “可见性” 的行业挑战在数字化转型加速的当下&#xff0c;企业数据资产呈现爆发式增长&#xff0c;而传统资产梳理手段因维度单一、时效性差&#xff0c;导致 “资产黑箱” 问题频发。某省级运营商曾在安全评估中发现&#xf…

搭建react18+项目过程中遇到的问题(vite)

问题1. 页面中使用import.meta.env获取环境变量有红色波浪线提示错误按提示给ts.config.ts文件中的compilerOptions增加了"module": “esnext” (es2020 | es2022 | system)这几个也不行 但是另一个问题出现了安装的第三方库引入报错了 按照提示我们将module改成了’…

Linux epoll简介与C++TCP服务器代码示例

Linux epoll 简介与示例 TCP 服务器 1. 为什么要用 epoll select/poll 每次调用都把全部文件描述符从用户态拷贝到内核态,随连接数增长而线性变慢;epoll 采用事件驱动+就绪队列的方式,内核只把“已就绪”的描述符返回给用户态,O(1) 规模扩展;支持 边沿触发 Edge-Triggere…