Android-ContentProvider的跨应用通信学习总结

一、ContentProvider的概念

        1. ContentProvider 是什么?(核心概念)

        ContentProvider 是 Android 四大组件之一。它的核心职责是管理和共享应用的结构化数据

我们可以把它想象成一个应用的**“数据大使馆”**。在一个国家里(Android 系统),不同的城市(应用进程)有自己的法律和领土(私有数据目录),不能随意闯入。如果你想从 B 城市获取信息,你不能直接派人去 B 城市的档案室里翻找(直接访问文件/数据库),而是需要去 B 城市设立的“大使馆”(ContentProvider),通过标准的外交辞令(URI)和流程(query, insert, call 等方法),提出你的请求。大使馆会验证你的身份和权限,然后决定给你什么信息,以及给多少。

它通过一套基于 URI (统一资源标识符) 的机制工作:

  • 提供方 (Provider):实现 ContentProvider 类,定义自己能响应的 URI,并实现对这些 URI 的增删改查等操作。

  • 请求方 (Client):使用 ContentResolver 对象,传入 Provider 定义好的 URI 来发起请求。

这个机制保证了进程隔离安全性,是 Android 系统中跨进程通信(IPC)和数据共享的基石。


2. 为什么在这个项目中要用 ContentProvider?(设计意图和意义)

在车载系统或任何复杂的 Android 系统中,应用通常不是孤立运行的。你的爱奇艺媒体应用需要和系统的其他部分进行深度交互。例如:

  • 系统桌面 (Dashboard):需要在桌面上显示一个“每日推荐”的卡片。

  • 全局搜索:用户在系统的搜索框里输入“周杰伦”,需要能搜出你应用里的相关视频。

  • 系统媒体服务:在播放音乐或视频时,系统需要在锁屏界面或通知栏显示封面图。

  • 账号中心:系统的账号中心可能需要查询或触发你应用的登录状态。

这些“系统桌面”、“全局搜索”、“媒体服务”和“账号中心”很可能都是独立的应用或进程。你的媒体应用无法直接调用它们的方法,反之亦然。

意义就在于ContentProvider 提供了一套标准、稳定且安全的跨进程通信(IPC)解决方案。它不是唯一的 IPC 方式(还有 AIDL/Binder、Broadcast等),但对于数据共享和功能调用来说,它是最规范、最被系统原生支持的方式。使用 ContentProvider 意味着爱奇艺应用能够以一种标准化的方式“融入”到整个车载系统中,实现深度集成。


二、 四种不同的应用模式(结合代码详解)

这四个文件恰好展示了 ContentProvider 的四种典型且高级的用法,而不仅仅是简单的数据库查询。

A. 数据查询模式 (IqiyiSearchProvider.kt)

这是最经典、最传统的 ContentProvider 用法。

  • 作用:响应系统的全局搜索请求,返回匹配关键词的媒体内容。

  • 核心方法queryWithKeyword() (内部调用了 query())。

  • 代码分析

    1. 当系统搜索框架调用 query() 时,这个 Provider 会被唤醒。

    2. 它使用 runBlocking 启动一个协程来执行耗时操作(iqiyiDataRepository.globalSearch(keyword)),这通常是一个网络请求。

    3. 它没有使用真实的数据库,而是创建了一个 MatrixCursor。这是一个内存中的 Cursor,非常适合将 List 或其他内存数据结构包装成 Cursor 对象返回。

    4. 最后,它将搜索结果逐行添加到 MatrixCursor 中并返回。

  • 入口:当系统搜索框架或其他应用调用 ContentResolver.query(uri, ...) 时,IqiyiSearchProviderquery() 方法被触发,进而调用 queryWithKeyword(keyword)

  • 异步处理:搜索通常涉及网络请求,是耗时操作。这里使用了 runBlocking 启动一个协程,避免阻塞主线程。

// IqiyiSearchProvider.kt
override fun queryWithKeyword(keyword: String): Cursor? {// ...return runBlocking { // 启动协程执行耗时操作try {val result = iqiyiDataRepository.globalSearch(keyword) // 网络请求// ...}}
}
  • 这样做(使用 MatrixCursor)的意义:极大地简化了数据提供过程。你无需为了提供数据而专门创建一个数据库。任何可以转换成二维表格的数据(比如网络请求返回的 JSON 列表),都可以通过 MatrixCursor 轻松地提供给其他应用,完全符合 ContentProviderCursor 返回规范。

// IqiyiSearchProvider.kt
val cursor = MatrixCursor(MEDIA_COLUMNS) // 1. 定义列名
result.forEach { mediaItem ->// ...if (!listItem.isNullOrEmpty()) {for (item in listItem) {// ...cursor.addRow( // 2. 将List中的每一项数据,作为一行添加到MatrixCursorarrayOf(item.mediaId,item.description.title,subtitle,// ...))}}
}
return cursor // 3. 返回构建好的Cursor
B. 文件共享模式 (IqiyiArtworkProvider.kt)
  • 作用:向其他应用(如系统UI)提供图片文件(专辑或视频封面)。

  • 核心方法openFile()(在 AbstractArtworkProvider 基类中,由 imageNetLoader 实现)。

  • 代码分析

    1. 客户端(如系统媒体服务)请求一个 content://... 格式的图片 URI。

    2. IqiyiArtworkProvider 接收到请求,从 URI 中解析出真实的图片网络地址(http/https)。

    3. 它使用 Glide 这个强大的图片加载库来下载图片,并将其缓存到应用的私有缓存目录中。

    4. 最关键的一步,它调用 ParcelFileDescriptor.open() 返回一个指向该缓存文件的文件描述符 (File Descriptor)

  • URI 约定:客户端请求的不是一个普通的文件路径,而是一个特殊的 content:// URI,例如: content://com.jidouauto.media.iqiyiartwork/general?url=http://.../pic.jpg&...

  • 图片加载与缓存:Provider 接收到请求后,从 URI 中解析出真实的图片网络地址。然后,它使用强大的图片加载库 Glide 来下载和缓存图片。这利用了 Glide 成熟的缓存策略,避免重复下载。

// IqiyiArtworkProvider.kt -> imageNetLoader()
// 使用 Glide 下载图片文件
val cacheFile = Glide.with(context).asFile().load(finalUrl).submit().get(30000, TimeUnit.MILLISECONDS)// 将Glide下载的临时文件重命名为我们期望的缓存文件
cacheFile.renameTo(file)

返回文件描述符:这是最关键的一步。Provider 不会返回文件的真实路径(因为其他应用可能没有权限访问爱奇艺的私有缓存目录),而是返回一个 ParcelFileDescriptor

// IqiyiArtworkProvider.kt -> imageNetLoader()
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
  • 这样做的意义

    • 安全:它没有直接暴露文件的真实路径(其他应用可能没有权限访问)。而是通过文件描述符授权。系统会为持有这个描述符的客户端进程临时授予对该文件的只读权限。这是一种受控、临时的授权,比暴露文件路径或赋予存储权限要安全得多。

    • 高效:利用了 Glide 的强大缓存机制,避免了重复下载。


C. 远程过程调用 (RPC) 模式 (IqiyiAccountProvider.kt)

这个 Provider 几乎完全颠覆了 ContentProvider 是“数据提供者”的传统印象。

  • 作用:对外暴露一系列功能接口,而不是数据。其他应用可以通过它来执行登录、登出、获取二维码、检查登录状态等操作。

  • 核心方法call()

  • 代码分析

    1. 这个类的 query(), insert(), delete(), update() 方法都直接返回 null0,表明它不处理传统的 CRUD(增删改查)请求。

    2. 所有的逻辑都集中在 call(method: String, ...) 方法中。

    3. 它使用一个巨大的 when 语句,根据传入的 method 字符串来判断客户端想要调用哪个功能,就像一个路由器。

    4. 例如,当 method"getQRCodeToken",它就调用 accountRepository.getQrCodeAndToken(),并将结果放入 Bundle 中返回。

  • 忽略 CRUD:这个类的 query(), insert(), delete(), update() 方法都直接返回 null0,表明它不处理传统的数据库增删改查请求。

  • 方法分发器:所有的逻辑都集中在 call(method: String, ...) 方法中。它使用一个巨大的 when 语句,根据客户端传入的 method 字符串来判断具体要调用哪个功能,就像一个API 路由器

    // IqiyiAccountProvider.kt
    override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {logd(TAG) { "jidouauto call, method: $method, arg:$arg, extras: $extras" }return runBlocking {when (method) { // 像一个路由器,根据 method 分发到不同的处理逻辑"getQRCodeToken" -> {Bundle().apply {try {// 调用内部业务逻辑putParcelable(EXTRA_RESULT, accountRepository.getQrCodeAndToken())putBoolean(EXTRA_SUCCESS, true) // 返回成功标志} catch (e: Exception) {putParcelable(EXTRA_RESULT, null)putBoolean(EXTRA_SUCCESS, false) // 返回失败标志putSerializable(EXTRA_ERROR, e) // 返回错误信息}}}"checkLoginResult" -> { /* ... */ }"logout" -> { /* ... */ }// ... 更多的方法else -> null}}
    }
    
  • Bundle 作为通用容器:所有方法的输入参数(arg, extras)和返回值(Bundle)都通过 Bundle 来传递。Bundle 可以携带各种类型的数据(布尔值、字符串、序列化对象等),通用性极强。

  • 这样做的意义:巧妙地利用 call() 方法将 ContentProvider 变成了一个轻量级的**远程过程调用(RPC)**框架。对于那些不需要持续双向通信、仅需“调用-返回”的简单跨进程功能调用,这种方式比实现复杂的 AIDL/Binder 服务要简单得多,也更稳定。


D. 现代 UI 片段模式 (IqiyiSliceProvider.kt)

这是基于 ContentProvider 的一个非常现代的应用。

  • 作用:提供一个被称为 Slice可交互的 UI 片段,供其他应用(如系统桌面 Dashboard)直接嵌入和显示。

  • 核心方法onBindSliceForDashboardNotification() (内部调用 onBindSlice())。

  • 代码分析

    1. 它继承自 AbsSliceProvider,而 SliceProvider 本身就是 ContentProvider 的一个特殊子类。

    2. 当 Dashboard 应用需要显示爱奇艺的推荐卡片时,它会请求这个 Provider 的 URI。

    3. onBindSlice 方法被触发,它从 IqiyiMediaPushHelper 获取推荐的媒体项,然后调用 MediaSliceUtil.createIqiyiSlice 来构建一个 Slice 对象。

    4. 这个 Slice 对象包含了一个UI模板所需的所有信息(标题、图片、点击事件等),然后被返回给 Dashboard 应用进行渲染。

  • 特殊子类IqiyiSliceProvider 继承自 AbsSliceProvider,而 SliceProvider 本身就是 ContentProvider 的一个特殊子类,专门用于处理 Slice 的绑定请求。

  • URI 匹配:和普通 Provider 一样,它也通过 UriMatcher 来识别客户端请求的是哪个 Slice

// IqiyiSliceProvider.kt
uriMatcher.addURI(authority,"$SLICE_PATH/$SLICE_ID", // content://<authority>/daily_recommend/1001SLICE_CODE
)

构建 Slice:当 Dashboard 需要显示爱奇艺的推荐卡片时,它会请求这个 Provider 的 URI。onBindSlice 方法被触发,它从 IqiyiMediaPushHelper 获取推荐的媒体项,然后调用工具类 MediaSliceUtil.createIqiyiSlice 来构建一个 Slice 对象。

// IqiyiSliceProvider.kt
private fun createNotification(sliceUri: Uri): Slice {// 1. 获取要展示的数据val recommend = IqiyiMediaPushHelper.getInstance()?.getSliceMediaItem()// 2. 使用工具类构建Slice对象return MediaSliceUtil.createIqiyiSlice(context!!,sliceUri,recommend?.description?.title?.toString() ?: "",false,recommend)
}
  • 这样做的意义Slice 机制让你的应用内容可以“走出”应用本身,以一种原生的、高性能的方式嵌入到系统的其他界面中,极大地提升了应用内容的曝光度和用户触达率。而 ContentProvider 正是实现这一切的底层技术基础。


三、 客户端的交互方式(IqiyiAccountManager.kt

IqiyiAccountManager 展示了作为客户端如何与 IqiyiAccountProvider 交互。

  • ContentResolver:它是客户端的代理,所有对 Provider 的请求都通过它发出。例如 contentResolver.call(uri, "loginUid", ...)

// IqiyiAccountManager.kt -> bindLogin()
suspend fun bindLogin(): Boolean {return withContext(Dispatchers.IO) {val uri = Uri.parse("content://${AUTHORITIES}/")// 使用 resolver 发起 call 请求,就像调用一个远程函数val bundle = contentResolver.call(uri, "bindLogin", null, null)// ... 处理返回的 bundle}
}
  • ContentObserver:这是一个观察者。IqiyiAccountManager 通过 contentResolver.registerContentObserver(...) 注册了多个观察者来监听不同的 URI。

    • IqiyiAccountProvider 中的数据发生变化时(例如用户登录成功,loginUid 改变),Provider 会调用 getContext().getContentResolver().notifyChange(uri, null)

    • 这个通知会通过系统广播给所有监听了该 uriContentObserver,触发其 onChange() 方法。

    • 这样,IqiyiAccountManager 就能被动地、响应式地收到数据变化的通知,并更新自己的状态,而不需要不停地去轮询查询。

// IqiyiAccountManager.kt -> init
init {// 监听代表“用户ID”变化的URIcontentResolver.registerContentObserver(Uri.parse("content://${AUTHORITIES}/loginUid"),false, uidObserver)// ... 监听其他URI
}// 当URI变化时,onChange会被回调
private val uidObserver: ContentObserver = object : ContentObserver(handler) {override fun onChange(selfChange: Boolean) {// ... 更新UI或内部状态}
}

通知与响应的闭环:这个模式如何工作的?

  • Provider (数据变化方):当 IqiyiAccountProvider 内部的用户状态发生变化时(例如,用户登录成功,loginUid 从 0 变为一个具体的值),它会主动发出通知。

// IqiyiAccountProvider.kt
private val uidObserver: Observer<Long> = Observer<Long> {// ...context?.contentResolver?.notifyChange( // 关键:通知系统这个URI的数据已变更Uri.parse("content://${AUTHORITIES}/loginUid"),null)
}
  • Client (监听方):系统收到通知后,会唤醒所有监听了 content://${AUTHORITIES}/loginUid 这个 URI 的 ContentObserver,触发它们的 onChange() 方法。

  • 结果IqiyiAccountManager 就能被动地、响应式地收到数据变化的通知,并更新自己的状态,而不需要设置定时器去不停地轮询查询,极大地节省了系统资源

总结

它是 Android 系统中一个功能强大、用途广泛的跨进程通信和数据共享的瑞士军刀。通过这几个例子,我们可以看到它如何被巧妙地用于:

  1. 提供标准化的查询数据 (IqiyiSearchProvider):经典的跨进程数据查询。

  2. 安全地共享私有文件 (IqiyiArtworkProvider):通过文件描述符实现安全可控的文件访问。

  3. 封装功能调用接口 (IqiyiAccountProvider):作为轻量级的 RPC 框架,实现跨进程方法调用。

  4. 支撑现代化的嵌入式 UI (IqiyiSliceProvider):作为提供可交互 UI 片段的后端。

理解并掌握这些模式,对于开发需要在复杂系统中与其他应用深度集成的高质量 Android 应用至关重要。

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

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

相关文章

Java数据结构第二十六期:解密位图,海量数据处理的 “空间魔法”

专栏&#xff1a;Java数据结构秘籍 个人主页&#xff1a;手握风云 目录 一、位图 1.1. 概念 1.2. 面试题 1.3. 位图的实现 1.4. 位图的应用 一、位图 1.1. 概念 在数据结构中&#xff0c;位图&#xff08;也称为位数组、位向量或位集&#xff09;是一种紧凑的方式来表示一…

芯科科技即将重磅亮相IOTE 2025深圳物联网展,以全面的无线技术及生态覆盖赋能万物智联

作为低功耗无线连接领域的创新性领导厂商&#xff0c;Silicon Labs&#xff08;亦称“芯科科技”&#xff09;将于8月27至29日携其最前沿的人工智能&#xff08;AI&#xff09;和物联网&#xff08;IoT&#xff09;解决方案在深圳举办的IOTE 2025国际物联网展中盛大展出。这场亚…

Linux上安装多个JDK版本,需要配置环境变量吗

简短回答&#xff1a;不需要同时配置多个 JDK 的 JAVA_HOME 和 PATH&#xff0c;但你可以安装多个版本&#xff0c;并通过灵活的方式在它们之间切换。 文章目录✅ 正确做法&#xff1a;安装多个 JDK&#xff0c;但只让一个生效&#xff08;通过环境变量或 alternatives&#xf…

MySQL有哪些高可用方案

大家好&#xff0c;我是锋哥。今天分享关于【MySQL有哪些高可用方案】面试题。希望对大家有帮助&#xff1b; MySQL有哪些高可用方案? 超硬核AI学习资料&#xff0c;现在永久免费了&#xff01; MySQL 高可用方案是指确保 MySQL 数据库在面对硬件故障、网络故障、负载过重等…

【Windows】Windows平台基于加速地址安装vcpkg并集成到Visual Studio 2017

基础运行环境 启动&#xff1a; 适用于 VS 2017 的 x64 本机工具命令提示 ninja 下载压缩包 https://gh-proxy.com/https:/github.com/ninja-build/ninja/releases/download/v1.13.1/ninja-win.zip 直接解压到c:/Windows (无需配置环境变量) CMake 下载安装包 https://gh-proxy…

LLMs之MCP:Chrome MCP的简介、安装和使用方法、案例应用之详细攻略

LLMs之MCP&#xff1a;Chrome MCP的简介、安装和使用方法、案例应用之详细攻略 目录 Chrome MCP的简介 1、特点 2、与类似项目的比较 Chrome MCP的安装和使用方法 1、安装 2、使用方法 加载 Chrome 扩展 与 MCP 协议客户端一起使用 使用 STDIO 连接&#xff08;替代方…

【Java EE】多线程-初阶 synchronized 关键字 - 监视器锁 monitor lock

synchronized 关键字 - 监视器锁 monitor lock5. synchronized 关键字 - 监视器锁 monitor lock5.1 synchronized 的特性5.2 synchronized 使⽤⽰例5.3 Java 标准库中的线程安全类本节⽬标• 掌握 synchronized关键字5. synchronized 关键字 - 监视器锁 monitor lock &#xf…

Java多线程:从基础到实战

引言多线程是Java并发编程的核心技术之一&#xff0c;广泛应用于服务器开发、数据处理、实时系统等领域。通过多线程&#xff0c;程序可以充分利用CPU资源&#xff0c;提高执行效率&#xff0c;同时处理多个任务。本文将从多线程的基本概念、实现方式、线程状态、同步与通信到常…

list集合可以一边遍历一遍修改元素吗?

今天看来一下Java中list集合部分的八股&#xff0c;发现了一个以前没注意过的问题&#xff0c;记录一下list可以一边遍历一边修改元素吗&#xff1f;答&#xff1a;在 Java 中&#xff0c;List在遍历过程中是否可以修改元素取决于遍历方式和具体的List实现类。①&#xff1a;对…

Infusing fine-grained visual knowledge to Vision-Language Models

Infusing fine-grained visual knowledge to Vision-Language Models Authors: Nikolaos-Antonios Ypsilantis, Kaifeng Chen, Andr Araujo, Ondřej Chum Deep-Dive Summary: 视觉-语言模型中注入细粒度视觉知识 摘要 大规模对比预训练产生了强大的视觉-语言模型&#xf…

RK3576赋能无人机巡检:多路视频+AI识别引领智能化变革

随着工业巡检任务的复杂度不断提升&#xff0c;无人机逐渐取代传统人工&#xff0c;成为电力、能源、林业、农业等行业的“高空作业主力”。然而&#xff0c;巡检并非简单的拍摄和回放&#xff0c;它要求无人机实时采集多路画面、快速分析异常&#xff0c;并稳定回传数据。这对…

ollama Modelfile 文件生成

输入 根据如下TEMPLATE和params写一个modelfile文件&#xff0c;TEMPLATE为&#xff1a;{{- $lastUserIdx : -1 -}} {{- range $idx, $msg : .Messages -}} {{- if eq $msg.Role “user” }}{{ $lastUserIdx $idx }}{{ end -}} {{- end }} {{- if or .System .Tools }}<|i…

关联规则挖掘2:FP-growth算法(Frequent Pattern Growth,频繁模式增长)

目录 一、核心思想&#xff1a;一个形象的比喻 二、核心思想的具体拆解 步骤一&#xff1a;构建FP-tree&#xff08;频繁模式树&#xff09; 步骤二&#xff1a;从FP-tree中挖掘频繁项集 为什么这很高效&#xff1f; 三、总结 核心思想与优势 适用场景与缺点 四、例题…

在IDEA中DEBUG调试时查看MyBatis-Plus动态生成的SQL语句

在IDEA中DEBUG调试时查看MyBatis-Plus动态生成的SQL语句前言&#xff1a;动态SQL调试的痛与解决方案一、准备工作&#xff1a;调试前的检查清单二、基础方法&#xff1a;SqlSessionTemplate断点调试步骤1&#xff1a;定位SqlSessionTemplate类步骤2&#xff1a;在invoke方法上设…

Linux 文本处理三剑客:awk、grep、sed 完全指南

Linux 文本处理三剑客&#xff1a;awk、grep、sed 完全指南 1. 概述 Linux 系统提供了三个强大的文本处理工具&#xff1a;awk、grep 和 sed&#xff0c;它们各有所长&#xff0c;结合使用可以高效地处理文本数据。 awk&#xff1a;擅长文本分析和格式化输出&#xff0c;是一…

pyecharts可视化图表组合组件_Grid:打造专业数据仪表盘

pyecharts可视化图表组合组件_Grid&#xff1a;打造专业数据仪表盘 目录pyecharts可视化图表组合组件_Grid&#xff1a;打造专业数据仪表盘引言图表1&#xff1a;Grid-Overlap-多X/Y轴示例代码解析1. 图表创建2. 多轴配置3. 图表重叠4. Grid布局效果与应用图表2&#xff1a;Gri…

【电气工程学习】

三极管中&#xff1a;集电极C,基极B&#xff0c;发射极E接线&#xff1a;棕正蓝负黑信号NPN开关输出的是我们的0V,也叫低电平PNP开关输出的是24V,也就是高电平&#xff08;NPN开关导通时&#xff0c;相当于把输出端“拉”到0V&#xff08;低电平&#xff09;&#xff0c;称为“…

【嵌入式】CAN通信

CAN 总线最初由博世于1980年代为汽车行业开发&#xff0c;能够简化复杂的布线网络&#xff0c;还确保可靠和安全的数据传输。 1.CAN技术解释 CAN网络中的每个节点&#xff0c;都是平等的&#xff0c;没有主次之分&#xff0c;这一点和SPI和I2C不同。每个节点都可以在需要的时…

Apache ShenYu网关与Nacos的关联及如何配合使用

Apache ShenYu 网关与 Nacos 之间的关系可以概括为 “协作互补”:Nacos 作为 服务注册与配置中心,为 ShenYu 提供动态的服务发现和配置管理能力,而 ShenYu 作为 流量网关,依赖 Nacos 实现路由信息的动态更新和实时生效。以下是详细解析: 1. 核心关系图解 拉取服务列表/路…

【CPP】一个CPP的Library(libXXXcore)和测试程序XXX_main的Demo

一个CPP的Library和测试程序Demo 1. 思路描述 目录结构 总控CMakeList.txt文件 2. Library代码实现 2.1 XXXLib.hpp文件(对外的接口定义文件)和XXXLib.cpp文件 2.1.1 XXXLib.hpp文件 2.1.2 XXXLib.cpp文件 2.2 CXXXLibApi.hpp文件和CXXXLibApi.cpp文件(内部的API基类) 2.2.1 CX…