Android 15 核心子系统系列 - 第26篇
本篇深入分析Android的跨应用数据共享机制,理解ContentProvider的生命周期、Uri权限授予和ContentObserver通知机制。
引言
想象一个场景:你的相机应用拍了一张照片,现在想通过微信分享。微信如何访问你相机应用的私有数据?如果直接给文件路径,会因为权限限制无法访问;如果把文件复制一份,浪费存储空间...
这就是ContentProvider 解决的核心问题------在保证安全的前提下,实现跨应用数据共享 。通过Uri权限机制,应用可以临时授权其他应用访问特定数据,无需暴露文件系统路径,也不需要用户授予运行时权限。
在上一篇任务调度中,我们看到Android如何智能管理后台任务;今天我们将看到,Android如何在多个应用之间安全共享数据。
一、ContentProvider整体架构
1.1 架构设计哲学
ContentProvider的设计遵循几个核心原则:
统一接口 :使用标准的CRUD接口(query/insert/update/delete) Uri寻址 :通过Uri(content://authority/path)定位数据 权限控制 :支持静态Manifest权限和动态Uri权限 进程隔离 :Provider运行在独立进程,Binder IPC通信 数据观察:ContentObserver监听数据变化
1.2 四层架构

核心流程:
- 客户端:通过ContentResolver发起CRUD操作
- Framework:AMS管理Provider生命周期和权限检查
- Provider进程:执行实际数据操作
- 数据存储:SQLite、文件系统、SharedPreferences等
- 通知机制:数据变化时通知所有观察者
1.3 核心组件分析
ContentResolver - 客户端代理
java
// frameworks/base/core/java/android/content/ContentResolver.java
public abstract class ContentResolver {
// 查询数据
public final Cursor query(
Uri uri,
String[] projection,
String selection,
String[] selectionArgs,
String sortOrder) {
// 1. 通过AMS获取Provider
IContentProvider provider = acquireProvider(uri);
// 2. 跨进程调用Provider的query方法
Cursor cursor = provider.query(
mPackageName,
uri,
projection,
selection,
selectionArgs,
sortOrder,
null // CancellationSignal
);
return cursor;
}
// 插入数据
public final Uri insert(Uri url, ContentValues values) {
IContentProvider provider = acquireProvider(url);
Uri result = provider.insert(mPackageName, url, values);
// 通知数据变化
notifyChange(result, null);
return result;
}
// 更新数据
public final int update(
Uri uri,
ContentValues values,
String where,
String[] selectionArgs) {
IContentProvider provider = acquireProvider(uri);
int count = provider.update(
mPackageName, uri, values, where, selectionArgs);
// 通知数据变化
notifyChange(uri, null);
return count;
}
// 删除数据
public final int delete(
Uri url,
String where,
String[] selectionArgs) {
IContentProvider provider = acquireProvider(url);
int count = provider.delete(mPackageName, url, where, selectionArgs);
// 通知数据变化
notifyChange(url, null);
return count;
}
}
ActivityManagerService - Provider管理
java
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public class ActivityManagerService extends IActivityManager.Stub {
// Provider缓存:authority → ContentProviderRecord
private final ProviderMap mProviderMap = new ProviderMap();
// 获取ContentProvider
public ContentProviderHolder getContentProvider(
IApplicationThread caller,
String callingPackage,
String authority,
int userId,
boolean stable) {
synchronized (this) {
// 1. 从缓存查找
ContentProviderRecord cpr = mProviderMap.getProviderByName(
authority, userId);
if (cpr != null && cpr.proc != null) {
// Provider已存在且进程存活
return cpr.newHolder(null);
}
// 2. 需要启动Provider进程
ProcessRecord proc = startProcessLocked(
cpi.processName,
cpr.appInfo,
"content provider",
new ComponentName(cpi.packageName, cpi.name),
userId
);
// 3. 安装Provider(在目标进程)
proc.thread.scheduleInstallProvider(cpi);
// 4. 等待Provider发布
synchronized (cpr) {
while (cpr.provider == null) {
cpr.wait(); // 等待Provider启动完成
}
}
return cpr.newHolder(null);
}
}
// Provider发布回调
public void publishContentProviders(
IApplicationThread caller,
List<ContentProviderHolder> providers) {
synchronized (this) {
for (ContentProviderHolder src : providers) {
ContentProviderRecord dst = mProviderMap.getProviderByClass(
src.info.name, getUserIdFromUid(src.info.applicationInfo.uid));
if (dst != null) {
dst.provider = src.provider;
dst.proc = getRecordForAppLocked(caller);
// 唤醒等待线程
synchronized (dst) {
dst.notifyAll();
}
}
}
}
}
}
Provider生命周期管理:
- 延迟加载:首次访问时才启动Provider进程
- 单例模式:每个authority只有一个Provider实例
- 进程绑定:Provider与宿主进程生命周期绑定
- 自动清理:进程死亡时自动清理Provider记录
二、ContentProvider生命周期
2.1 Provider启动流程
java
// frameworks/base/core/java/android/app/ActivityThread.java
public final class ActivityThread extends ClientTransactionHandler {
// 应用启动时安装Provider
private void handleBindApplication(AppBindData data) {
// 1. 创建Application
Application app = data.info.makeApplication(false, mInstrumentation);
// 2. 安装ContentProvider(在Application.onCreate之前!)
List<ProviderInfo> providers = data.providers;
if (providers != null) {
installContentProviders(app, providers);
}
// 3. 调用Application.onCreate()
mInstrumentation.callApplicationOnCreate(app);
}
private void installContentProviders(
Context context,
List<ProviderInfo> providers) {
final ArrayList<ContentProviderHolder> results = new ArrayList<>();
for (ProviderInfo cpi : providers) {
// 1. 实例化ContentProvider
ContentProvider localProvider = installProvider(
context, null, cpi, false, true);
// 2. 创建Holder
IContentProvider provider = localProvider.getIContentProvider();
ContentProviderHolder cph = new ContentProviderHolder(cpi);
cph.provider = provider;
results.add(cph);
}
// 3. 发布到AMS
ActivityManager.getService().publishContentProviders(
mAppThread, results);
}
private ContentProvider installProvider(
Context context,
IContentProvider provider,
ProviderInfo info,
boolean noisy,
boolean noReleaseNeeded) {
// 1. 反射创建Provider实例
ClassLoader cl = context.getClassLoader();
ContentProvider localProvider = (ContentProvider)
cl.loadClass(info.name).newInstance();
// 2. 调用attachInfo初始化
localProvider.attachInfo(context, info);
// 3. 调用onCreate()
localProvider.onCreate();
return localProvider;
}
}
关键时序:
scss
Application启动
↓
1. makeApplication()
↓
2. installContentProviders() ← Provider.onCreate()在这里
↓
3. Application.onCreate()
⚠️ 重要:ContentProvider.onCreate()在Application.onCreate()之前调用!不要在Application.onCreate()中访问自己的Provider,会导致循环依赖。
2.2 Provider实现示例
kotlin
class ContactsProvider : ContentProvider() {
private lateinit var dbHelper: DatabaseHelper
private lateinit var uriMatcher: UriMatcher
companion object {
const val AUTHORITY = "com.example.contacts"
val CONTENT_URI: Uri = Uri.parse("content://$AUTHORITY/contacts")
const val CONTACTS = 1
const val CONTACT_ID = 2
}
override fun onCreate(): Boolean {
// 1. 初始化数据库
dbHelper = DatabaseHelper(context!!)
// 2. 初始化UriMatcher
uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
addURI(AUTHORITY, "contacts", CONTACTS)
addURI(AUTHORITY, "contacts/#", CONTACT_ID)
}
return true
}
override fun query(
uri: Uri,
projection: Array<String>?,
selection: String?,
selectionArgs: Array<String>?,
sortOrder: String?
): Cursor? {
val db = dbHelper.readableDatabase
val cursor = when (uriMatcher.match(uri)) {
CONTACTS -> {
// 查询所有联系人
db.query(
"contacts",
projection,
selection,
selectionArgs,
null, null,
sortOrder
)
}
CONTACT_ID -> {
// 查询单个联系人
val id = uri.lastPathSegment
db.query(
"contacts",
projection,
"_id = ?",
arrayOf(id),
null, null,
sortOrder
)
}
else -> null
}
// 设置通知Uri
cursor?.setNotificationUri(context?.contentResolver, uri)
return cursor
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
val db = dbHelper.writableDatabase
val id = when (uriMatcher.match(uri)) {
CONTACTS -> {
db.insert("contacts", null, values)
}
else -> throw IllegalArgumentException("Unknown URI: $uri")
}
if (id > 0) {
val newUri = ContentUris.withAppendedId(CONTENT_URI, id)
// 通知数据变化
context?.contentResolver?.notifyChange(newUri, null)
return newUri
}
return null
}
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<String>?
): Int {
val db = dbHelper.writableDatabase
val count = when (uriMatcher.match(uri)) {
CONTACTS -> {
db.update("contacts", values, selection, selectionArgs)
}
CONTACT_ID -> {
val id = uri.lastPathSegment
db.update(
"contacts",
values,
"_id = ?",
arrayOf(id)
)
}
else -> 0
}
if (count > 0) {
context?.contentResolver?.notifyChange(uri, null)
}
return count
}
override fun delete(
uri: Uri,
selection: String?,
selectionArgs: Array<String>?
): Int {
val db = dbHelper.writableDatabase
val count = when (uriMatcher.match(uri)) {
CONTACTS -> {
db.delete("contacts", selection, selectionArgs)
}
CONTACT_ID -> {
val id = uri.lastPathSegment
db.delete("contacts", "_id = ?", arrayOf(id))
}
else -> 0
}
if (count > 0) {
context?.contentResolver?.notifyChange(uri, null)
}
return count
}
override fun getType(uri: Uri): String? {
return when (uriMatcher.match(uri)) {
CONTACTS -> "vnd.android.cursor.dir/vnd.example.contacts"
CONTACT_ID -> "vnd.android.cursor.item/vnd.example.contacts"
else -> null
}
}
}
2.3 AndroidManifest声明
xml
<provider
android:name=".ContactsProvider"
android:authorities="com.example.contacts"
android:exported="true"
android:readPermission="com.example.READ_CONTACTS"
android:writePermission="com.example.WRITE_CONTACTS"
android:grantUriPermissions="true">
<!-- 路径权限:特定路径需要特殊权限 -->
<path-permission
android:path="/contacts/private"
android:readPermission="com.example.READ_PRIVATE_CONTACTS"/>
<!-- 元数据 -->
<meta-data
android:name="android.content.ContactDirectory"
android:value="true"/>
</provider>
关键属性:
authorities:唯一标识符,推荐使用包名exported:是否对其他应用可见readPermission/writePermission:静态权限声明grantUriPermissions:是否支持临时Uri权限multiprocess:是否在多个进程创建实例(通常false)
三、Uri权限授予机制
3.1 Uri权限设计理念
问题:传统权限模型的局限
- Manifest权限:要么全部授予,要么全部拒绝,粒度太粗
- 运行时权限:需要用户确认,体验不佳
- 文件路径:SELinux限制跨应用文件访问
解决:Uri临时权限
- 精细控制:针对单个Uri授予权限,不是整个Provider
- 临时性:Activity/Service结束后自动撤销
- 无需用户确认:应用间自动授权
- 安全:不暴露文件系统路径

3.2 UriGrantsManager核心实现
java
// frameworks/base/services/core/java/com/android/server/uri/UriGrantsManagerService.java
public class UriGrantsManagerService extends IUriGrantsManager.Stub {
// 授权记录:uid → GrantUri集合
private final SparseArray<ArrayMap<GrantUri, UriPermission>> mGrantedUriPermissions =
new SparseArray<>();
// 授予Uri权限
void grantUriPermission(
int callingUid,
String targetPkg,
Uri uri,
int modeFlags,
int targetUid) {
// 1. 验证源应用有权限授予
enforceNotIsolatedCaller("grantUriPermission");
// 2. 创建GrantUri对象
GrantUri grantUri = new GrantUri(uri.getAuthority(), uri, modeFlags);
// 3. 记录授权
UriPermission perm = findOrCreateUriPermissionLocked(
callingUid, targetPkg, targetUid, grantUri);
perm.grantModes(modeFlags, null);
// 4. 写入授权表
mGrantedUriPermissions.get(targetUid).put(grantUri, perm);
}
// 检查Uri权限
int checkUriPermission(
Uri uri,
int uid,
int modeFlags) {
// 1. 查找授权记录
UriPermission perm = findUriPermissionLocked(uid, new GrantUri(uri));
if (perm == null) {
return PackageManager.PERMISSION_DENIED;
}
// 2. 检查模式匹配
if ((perm.modeFlags & modeFlags) == modeFlags) {
return PackageManager.PERMISSION_GRANTED;
}
return PackageManager.PERMISSION_DENIED;
}
// 撤销Uri权限
void revokeUriPermission(
String targetPkg,
int targetUid,
Uri uri,
int modeFlags) {
synchronized (mLock) {
ArrayMap<GrantUri, UriPermission> perms =
mGrantedUriPermissions.get(targetUid);
if (perms != null) {
UriPermission perm = perms.get(new GrantUri(uri));
if (perm != null) {
perm.revokeModes(modeFlags);
// 如果权限完全撤销,移除记录
if (perm.modeFlags == 0) {
perms.remove(new GrantUri(uri));
}
}
}
}
}
}
3.3 Uri权限授予场景
场景1:通过Intent传递Uri
kotlin
// 发送方:授予读权限
fun shareImage(imageUri: Uri) {
val intent = Intent(Intent.ACTION_SEND).apply {
type = "image/*"
putExtra(Intent.EXTRA_STREAM, imageUri)
// 授予临时读权限
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivity(Intent.createChooser(intent, "分享图片"))
}
// 接收方:自动获得imageUri的读权限
class ShareActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val imageUri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
if (imageUri != null) {
// 可以直接访问,无需额外权限
val inputStream = contentResolver.openInputStream(imageUri)
val bitmap = BitmapFactory.decodeStream(inputStream)
imageView.setImageBitmap(bitmap)
}
}
}
权限授予流程:
java
// frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java
int startActivityLocked(..., Intent intent, ...) {
// 检查Intent中的FLAG_GRANT_*标志
if ((intent.getFlags() & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
Uri data = intent.getData();
if (data != null) {
// 授予目标Activity读权限
mUriGrantsManager.grantUriPermission(
callingUid,
targetPkg,
data,
Intent.FLAG_GRANT_READ_URI_PERMISSION,
targetUid
);
}
}
// 处理ClipData中的多个Uri
ClipData clipData = intent.getClipData();
if (clipData != null) {
for (int i = 0; i < clipData.getItemCount(); i++) {
Uri uri = clipData.getItemAt(i).getUri();
if (uri != null) {
mUriGrantsManager.grantUriPermission(..., uri, ...);
}
}
}
}
场景2:PendingIntent延迟授权
kotlin
// 创建PendingIntent并授予权限
fun createNotificationWithImage(imageUri: Uri) {
val intent = Intent(this, ViewImageActivity::class.java).apply {
data = imageUri
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
val pendingIntent = PendingIntent.getActivity(
this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("新图片")
.setContentText("点击查看")
.setSmallIcon(R.drawable.ic_image)
.setContentIntent(pendingIntent) // PendingIntent携带权限
.build()
notificationManager.notify(NOTIFICATION_ID, notification)
}
特点:
- 权限绑定到PendingIntent,不是当前进程
- 点击通知时,目标Activity自动获得权限
- 适用于跨进程延迟操作
场景3:持久化Uri权限
kotlin
// 请求持久化权限(存活超过Activity生命周期)
fun takePersistablePermission(uri: Uri) {
try {
contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
// 权限已持久化,可以保存Uri到数据库
saveUriToDatabase(uri)
} catch (e: SecurityException) {
// 源应用未设置FLAG_GRANT_PERSISTABLE_URI_PERMISSION
Log.e(TAG, "无法持久化权限", e)
}
}
// 释放持久化权限
fun releasePersistablePermission(uri: Uri) {
contentResolver.releasePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
}
// 查询已持久化的权限
fun listPersistedPermissions() {
val permissions = contentResolver.persistedUriPermissions
for (permission in permissions) {
Log.d(TAG, "Uri: ${permission.uri}, " +
"Read: ${permission.isReadPermission}, " +
"Write: ${permission.isWritePermission}")
}
}
实现机制:
java
// frameworks/base/services/core/java/com/android/server/uri/UriGrantsManagerService.java
void takePersistableUriPermission(Uri uri, int modeFlags, int uid) {
synchronized (mLock) {
UriPermission perm = findUriPermissionLocked(uid, new GrantUri(uri));
if (perm == null) {
throw new SecurityException("No permission grant found for URI");
}
// 标记为持久化
perm.persistedModeFlags = modeFlags;
// 写入磁盘(XML文件)
writeGrantedUriPermissions();
}
}
3.4 前缀匹配权限
kotlin
// 授予目录级别权限
fun grantDirectoryAccess(directoryUri: Uri) {
val intent = Intent(Intent.ACTION_SEND).apply {
data = directoryUri
addFlags(
Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION // 前缀匹配
)
}
startActivity(intent)
}
前缀匹配规则:
content://com.example/files授权content://com.example/files/image.jpg✓ 匹配content://com.example/files/docs/report.pdf✓ 匹配content://com.example/other/data.txt✗ 不匹配
四、ContentObserver数据观察
4.1 ContentObserver机制
kotlin
class ContactsObserver(handler: Handler) : ContentObserver(handler) {
override fun onChange(selfChange: Boolean, uri: Uri?) {
// 数据变化回调
Log.d(TAG, "Data changed at: $uri")
// 重新查询数据
refreshData(uri)
}
}
// 注册观察者
fun observeContacts() {
val observer = ContactsObserver(Handler(Looper.getMainLooper()))
contentResolver.registerContentObserver(
ContactsProvider.CONTENT_URI,
true, // notifyForDescendants: 监听子Uri
observer
)
}
// 取消注册
fun unregisterObserver(observer: ContentObserver) {
contentResolver.unregisterContentObserver(observer)
}
4.2 通知机制实现
java
// frameworks/base/core/java/android/content/ContentResolver.java
public void notifyChange(Uri uri, ContentObserver observer) {
notifyChange(uri, observer, true /* syncToNetwork */);
}
public void notifyChange(
Uri uri,
ContentObserver observer,
boolean syncToNetwork) {
try {
// 调用ContentService的notifyChange
getContentService().notifyChange(
uri,
observer == null ? null : observer.getContentObserver(),
observer != null && observer.deliverSelfNotifications(),
syncToNetwork,
UserHandle.getCallingUserId()
);
} catch (RemoteException e) {
}
}
java
// frameworks/base/services/core/java/com/android/server/content/ContentService.java
public class ContentService extends IContentService.Stub {
// 观察者注册表:Uri → Observer列表
private final ObserverNode mRootNode = new ObserverNode("");
@Override
public void registerContentObserver(
Uri uri,
boolean notifyForDescendants,
IContentObserver observer,
int userHandle) {
synchronized (mRootNode) {
mRootNode.addObserverLocked(
uri,
observer,
notifyForDescendants,
mRootNode,
userHandle
);
}
}
@Override
public void notifyChange(
Uri uri,
IContentObserver observer,
boolean observerWantsSelfNotifications,
boolean syncToNetwork,
int userHandle) {
synchronized (mRootNode) {
// 收集匹配的观察者
ArrayList<ObserverCall> calls = new ArrayList<>();
mRootNode.collectObserversLocked(
uri,
0,
observer,
observerWantsSelfNotifications,
calls
);
// 通知所有观察者
for (ObserverCall call : calls) {
try {
call.mObserver.onChange(
call.mSelfChange,
uri,
userHandle
);
} catch (RemoteException e) {
// 观察者进程已死亡
}
}
}
// 触发网络同步
if (syncToNetwork) {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
syncManager.scheduleLocalSync(null, userHandle, uri.getAuthority());
}
}
}
}
ObserverNode树形结构:
ini
content://
└─ com.example.contacts/
├─ contacts/ (Observer A, notifyForDescendants=true)
│ ├─ 1 (Observer B)
│ └─ 2
└─ groups/
匹配规则:
- 通知
content://com.example.contacts/contacts/1 - Observer A 收到通知(因为notifyForDescendants=true)
- Observer B 收到通知(精确匹配)
4.3 批量通知优化
kotlin
class ContactsProvider : ContentProvider() {
private val batchOperations = ThreadLocal<Boolean>()
override fun bulkInsert(uri: Uri, values: Array<ContentValues>): Int {
val db = dbHelper.writableDatabase
var count = 0
// 开启批量模式
batchOperations.set(true)
db.beginTransaction()
try {
for (value in values) {
val id = db.insert("contacts", null, value)
if (id > 0) count++
}
db.setTransactionSuccessful()
} finally {
db.endTransaction()
batchOperations.set(false)
}
// 批量操作完成后统一通知一次
context?.contentResolver?.notifyChange(uri, null)
return count
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
// ... 插入逻辑
// 仅在非批量模式通知
if (batchOperations.get() != true) {
context?.contentResolver?.notifyChange(newUri, null)
}
return newUri
}
}
五、FileProvider文件共享
5.1 FileProvider设计理念
问题:直接共享file:// Uri的风险
file:///storage/emulated/0/image.jpg暴露文件系统路径- 目标应用需要READ_EXTERNAL_STORAGE权限
- Android 7.0+ 抛出FileUriExposedException
解决:FileProvider将文件转换为content:// Uri
content://com.example.app.fileprovider/images/image.jpg- 通过Uri权限临时授权,无需存储权限
- 符合Scoped Storage要求
5.2 FileProvider配置
xml
<!-- AndroidManifest.xml -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.app.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
xml
<!-- res/xml/file_paths.xml -->
<paths>
<!-- 内部存储 Context.getFilesDir() -->
<files-path
name="internal"
path="images/"/>
<!-- 缓存目录 Context.getCacheDir() -->
<cache-path
name="cache"
path="."/>
<!-- 外部存储 Context.getExternalFilesDir() -->
<external-files-path
name="external"
path="Download/"/>
<!-- 外部缓存 Context.getExternalCacheDir() -->
<external-cache-path
name="external_cache"
path="."/>
<!-- 外部存储根目录 Environment.getExternalStorageDirectory() -->
<external-path
name="external_root"
path="."/>
</paths>
5.3 FileProvider使用示例
分享文件
kotlin
fun shareFile(file: File) {
// 将File转换为content Uri
val uri = FileProvider.getUriForFile(
this,
"${applicationContext.packageName}.fileprovider",
file
)
val intent = Intent(Intent.ACTION_SEND).apply {
type = "application/pdf"
putExtra(Intent.EXTRA_STREAM, uri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivity(Intent.createChooser(intent, "分享文件"))
}
拍照保存
kotlin
fun takePicture() {
// 创建图片文件
val photoFile = File(
getExternalFilesDir(Environment.DIRECTORY_PICTURES),
"photo_${System.currentTimeMillis()}.jpg"
)
// 转换为content Uri
val photoUri = FileProvider.getUriForFile(
this,
"${applicationContext.packageName}.fileprovider",
photoFile
)
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
putExtra(MediaStore.EXTRA_OUTPUT, photoUri)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}
startActivityForResult(intent, REQUEST_IMAGE_CAPTURE)
}
override fun onActivityResult(
requestCode: Int,
resultCode: Int,
data: Intent?
) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
// 照片已保存到photoFile
val bitmap = BitmapFactory.decodeFile(photoFile.absolutePath)
imageView.setImageBitmap(bitmap)
}
}
安装APK
kotlin
fun installApk(apkFile: File) {
val apkUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// Android 7.0+ 使用FileProvider
FileProvider.getUriForFile(
this,
"${applicationContext.packageName}.fileprovider",
apkFile
)
} else {
// Android 7.0以下使用file Uri
Uri.fromFile(apkFile)
}
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(apkUri, "application/vnd.android.package-archive")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
startActivity(intent)
}
5.4 FileProvider实现原理
java
// androidx/core/content/FileProvider.java
public class FileProvider extends ContentProvider {
private PathStrategy mStrategy;
@Override
public boolean onCreate() {
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, ...) {
// 将content Uri转换回File
File file = mStrategy.getFileForUri(uri);
if (projection == null) {
projection = COLUMNS;
}
String[] cols = new String[projection.length];
Object[] values = new Object[projection.length];
int i = 0;
for (String col : projection) {
if (OpenableColumns.DISPLAY_NAME.equals(col)) {
cols[i] = OpenableColumns.DISPLAY_NAME;
values[i++] = file.getName();
} else if (OpenableColumns.SIZE.equals(col)) {
cols[i] = OpenableColumns.SIZE;
values[i++] = file.length();
}
}
return new MatrixCursor(cols, 1).addRow(values);
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
// 打开文件返回文件描述符
File file = mStrategy.getFileForUri(uri);
int fileMode = modeToMode(mode);
return ParcelFileDescriptor.open(file, fileMode);
}
// 将File转换为content Uri
public static Uri getUriForFile(
Context context,
String authority,
File file) {
PathStrategy strategy = getPathStrategy(context, authority);
return strategy.getUriForFile(file);
}
}
Uri转换过程:
javascript
File: /data/user/0/com.example/files/images/photo.jpg
↓
PathStrategy解析: <files-path name="internal" path="images/"/>
↓
content Uri: content://com.example.fileprovider/internal/photo.jpg
六、调试与问题诊断
6.1 dumpsys查看Provider状态
bash
# 查看所有ContentProvider
adb shell dumpsys activity providers
# 输出示例
ACTIVITY MANAGER CONTENT PROVIDERS (dumpsys activity providers)
ContentProviderRecord{a1b2c3d com.example.contacts/.ContactsProvider}
package=com.example.contacts process=com.example.contacts
proc=ProcessRecord{e4f5g6h 12345:com.example.contacts/u0a123}
authority=com.example.contacts
singleton=true
External clients:
- ProcessRecord{h8i9j0k 12346:com.example.app/u0a124}
# 查看Uri权限
adb shell dumpsys activity permissions
# 输出示例
URI GRANTS:
Granted from uid 10123 to uid 10124:
Uri: content://com.example.contacts/contacts/1
Mode flags: READ
6.2 ContentResolver日志
kotlin
// 开启ContentResolver日志
adb shell setprop log.tag.ContentResolver VERBOSE
// 查看日志
adb logcat -s ContentResolver
6.3 常见问题诊断
问题1:SecurityException: Permission Denial
症状:
ini
java.lang.SecurityException: Permission Denial: opening provider
com.example.ContactsProvider from ProcessRecord{...} (pid=12345, uid=10124)
requires com.example.READ_CONTACTS or com.example.WRITE_CONTACTS
排查:
bash
# 1. 检查Provider权限配置
adb shell dumpsys package com.example | grep -A 20 "Provider"
# 2. 检查客户端权限声明
adb shell dumpsys package com.example.client | grep "requested permissions"
# 3. 检查运行时权限授予状态
adb shell dumpsys package com.example.client | grep "granted=true"
解决方案:
- 在AndroidManifest声明权限
- 请求运行时权限
- 使用Uri临时权限代替Manifest权限
问题2:IllegalArgumentException: Unknown URI
症状:
arduino
java.lang.IllegalArgumentException: Unknown URI: content://com.example/contacts/999
原因:UriMatcher未匹配到对应路径
解决:
kotlin
// 检查UriMatcher配置
override fun query(...): Cursor? {
val matchCode = uriMatcher.match(uri)
Log.d(TAG, "Uri: $uri, Match code: $matchCode") // 调试输出
return when (matchCode) {
CONTACTS -> { /* ... */ }
CONTACT_ID -> { /* ... */ }
else -> {
Log.e(TAG, "Unknown URI: $uri")
throw IllegalArgumentException("Unknown URI: $uri")
}
}
}
问题3:Cursor窗口泄漏
症状:
arduino
StrictMode policy violation: android.os.strictmode.LeakedClosableViolation:
A resource was acquired at attached stack trace but never released.
解决:
kotlin
// ✗ 错误:未关闭Cursor
fun queryContacts(): List<Contact> {
val cursor = contentResolver.query(uri, ...)
val contacts = mutableListOf<Contact>()
cursor?.let {
while (it.moveToNext()) {
contacts.add(parseContact(it))
}
}
// 忘记关闭cursor!
return contacts
}
// ✓ 正确:使用use自动关闭
fun queryContacts(): List<Contact> {
return contentResolver.query(uri, ...)?.use { cursor ->
generateSequence { if (cursor.moveToNext()) cursor else null }
.map { parseContact(it) }
.toList()
} ?: emptyList()
}
七、最佳实践
7.1 Provider设计建议
使用异步查询
kotlin
// ✗ 错误:主线程查询
fun loadContacts() {
val cursor = contentResolver.query(uri, ...) // 阻塞主线程!
// 处理cursor...
}
// ✓ 正确:使用协程异步查询
suspend fun loadContacts(): List<Contact> = withContext(Dispatchers.IO) {
contentResolver.query(uri, ...)?.use { cursor ->
// 解析cursor
} ?: emptyList()
}
// ✓ 正确:使用CursorLoader(已废弃,推荐协程)
class ContactsFragment : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
return CursorLoader(
requireContext(),
ContactsProvider.CONTENT_URI,
projection,
null, null, null
)
}
}
批量操作优化
kotlin
// ✗ 低效:逐条插入
fun insertContacts(contacts: List<Contact>) {
for (contact in contacts) {
contentResolver.insert(uri, contact.toContentValues())
}
}
// ✓ 高效:使用bulkInsert
fun insertContactsBatch(contacts: List<Contact>) {
val values = contacts.map { it.toContentValues() }.toTypedArray()
contentResolver.bulkInsert(uri, values)
}
// ✓ 更高效:使用applyBatch事务
fun insertContactsTransaction(contacts: List<Contact>) {
val operations = ArrayList<ContentProviderOperation>()
for (contact in contacts) {
operations.add(
ContentProviderOperation.newInsert(uri)
.withValues(contact.toContentValues())
.build()
)
}
try {
contentResolver.applyBatch(AUTHORITY, operations)
} catch (e: Exception) {
Log.e(TAG, "Batch insert failed", e)
}
}
7.2 Uri权限安全实践
kotlin
// ✓ 仅授予必要权限
fun shareReadOnly(uri: Uri) {
val intent = Intent(Intent.ACTION_SEND).apply {
setDataAndType(uri, "image/*")
// 仅授予读权限,不是写权限
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivity(intent)
}
// ✓ 及时撤销权限
override fun onDestroy() {
super.onDestroy()
// Activity销毁时撤销权限
sharedUris.forEach { uri ->
revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
}
// ✓ 验证Uri来源
fun handleSharedUri(uri: Uri) {
// 检查Uri是否来自可信来源
val authority = uri.authority
if (authority != TRUSTED_AUTHORITY) {
Log.w(TAG, "Uri from untrusted source: $authority")
return
}
// 验证权限
val hasPermission = checkUriPermission(
uri,
Binder.getCallingPid(),
Binder.getCallingUid(),
Intent.FLAG_GRANT_READ_URI_PERMISSION
) == PackageManager.PERMISSION_GRANTED
if (!hasPermission) {
throw SecurityException("No permission for Uri: $uri")
}
// 安全处理Uri
processUri(uri)
}
7.3 ContentObserver使用建议
kotlin
// ✓ 在生命周期方法中注册/取消
class ContactsActivity : AppCompatActivity() {
private lateinit var observer: ContactsObserver
override fun onStart() {
super.onStart()
observer = ContactsObserver(Handler(Looper.getMainLooper()))
contentResolver.registerContentObserver(
ContactsProvider.CONTENT_URI,
true,
observer
)
}
override fun onStop() {
super.onStop()
contentResolver.unregisterContentObserver(observer)
}
}
// ✓ 使用防抖避免频繁刷新
class ContactsObserver(handler: Handler) : ContentObserver(handler) {
private val refreshRunnable = Runnable { refreshData() }
override fun onChange(selfChange: Boolean, uri: Uri?) {
// 移除之前的刷新任务
handler?.removeCallbacks(refreshRunnable)
// 延迟300ms刷新,避免短时间内多次通知
handler?.postDelayed(refreshRunnable, 300)
}
private fun refreshData() {
// 重新查询数据
}
}
八、Android 15新特性
8.1 增强的Uri权限控制
java
// frameworks/base/services/core/java/com/android/server/uri/UriGrantsManagerService.java
// Android 15新增:细粒度时间控制
void grantUriPermissionWithExpiry(
int callingUid,
String targetPkg,
Uri uri,
int modeFlags,
int targetUid,
long expiryTimeMillis) { // 新增过期时间
UriPermission perm = findOrCreateUriPermissionLocked(...);
perm.grantModes(modeFlags, null);
perm.expiryTime = expiryTimeMillis; // 设置过期时间
// 注册过期检查
mHandler.postDelayed(() -> {
revokeExpiredPermissions();
}, expiryTimeMillis - System.currentTimeMillis());
}
8.2 Photo Picker集成
kotlin
// Android 15推荐使用Photo Picker代替READ_EXTERNAL_STORAGE
fun selectPhoto() {
val intent = Intent(MediaStore.ACTION_PICK_IMAGES).apply {
type = "image/*"
putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 5) // 最多选5张
}
startActivityForResult(intent, REQUEST_PHOTO_PICKER)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_PHOTO_PICKER && resultCode == RESULT_OK) {
// 获取选中的照片Uri(自动拥有权限)
val clipData = data?.clipData
if (clipData != null) {
for (i in 0 until clipData.itemCount) {
val uri = clipData.getItemAt(i).uri
// 直接使用,无需存储权限
loadImage(uri)
}
} else {
val uri = data?.data
uri?.let { loadImage(it) }
}
}
}
8.3 优化的Provider启动
java
// Android 15:Provider并行启动优化
private void installContentProviders(
Context context,
List<ProviderInfo> providers) {
final ArrayList<ContentProviderHolder> results = new ArrayList<>();
// 按依赖关系排序
List<ProviderInfo> sortedProviders = sortProvidersByDependency(providers);
// 并行安装互不依赖的Provider
ExecutorService executor = Executors.newFixedThreadPool(
Math.min(4, Runtime.getRuntime().availableProcessors()));
List<Future<ContentProvider>> futures = new ArrayList<>();
for (ProviderInfo cpi : sortedProviders) {
futures.add(executor.submit(() -> installProvider(context, null, cpi, ...)));
}
// 等待所有Provider安装完成
for (Future<ContentProvider> future : futures) {
ContentProvider provider = future.get();
results.add(createProviderHolder(provider));
}
executor.shutdown();
// 发布到AMS
ActivityManager.getService().publishContentProviders(mAppThread, results);
}
九、总结
核心要点回顾
-
ContentProvider架构
- 四层架构:Client App → Framework (AMS) → Provider Process → Data Storage
- 核心组件:ContentResolver客户端代理、AMS Provider管理、UriGrantsManager权限管理
- 生命周期:Provider.onCreate()在Application.onCreate()之前调用
-
Uri权限机制
- 临时授权:通过Intent FLAG_GRANT_*授予临时权限
- 精细控制:针对单个Uri授权,不是整个Provider
- 自动管理:Activity/Service结束后自动撤销
- 持久化支持:takePersistableUriPermission持久化权限
-
FileProvider文件共享
- content:// Uri代替file:// Uri
- 无需存储权限,通过Uri临时授权
- 符合Scoped Storage安全要求
-
ContentObserver通知
- ObserverNode树形结构管理观察者
- notifyForDescendants控制子Uri通知
- 批量操作优化:统一通知减少回调
-
最佳实践
- 异步查询避免阻塞主线程
- 批量操作使用bulkInsert/applyBatch
- 及时关闭Cursor避免泄漏
- 仅授予必要的Uri权限
与其他系统的协作
- ActivityManagerService:管理Provider生命周期和Uri权限
- PackageManagerService:解析Manifest中的Provider声明
- Binder:跨进程通信基础
- SELinux:强制访问控制,保护Provider进程
参考源码(基于Android 15 AOSP):
frameworks/base/core/java/android/content/ContentProvider.javaframeworks/base/core/java/android/content/ContentResolver.javaframeworks/base/services/core/java/com/android/server/am/ActivityManagerService.javaframeworks/base/services/core/java/com/android/server/uri/UriGrantsManagerService.javaframeworks/base/services/core/java/com/android/server/content/ContentService.java调试命令速查:
bash# ContentProvider状态 adb shell dumpsys activity providers # Uri权限 adb shell dumpsys activity permissions # ContentResolver日志 adb shell setprop log.tag.ContentResolver VERBOSE adb logcat -s ContentResolver
系列导航:
本文基于Android 15 (API Level 35)源码分析,不同厂商的定制ROM可能存在差异。 欢迎来我中的个人主页找到更多有用的知识和有趣的产品