Jacky's blog
首页
  • 学习笔记

    • web
    • android
    • iOS
    • vue
  • 分类
  • 标签
  • 归档
收藏
  • tool
  • algo
  • python
  • java
  • server
  • growth
  • frida
  • blog
  • SP
  • more
GitHub (opens new window)

Jack Yang

编程; 随笔
首页
  • 学习笔记

    • web
    • android
    • iOS
    • vue
  • 分类
  • 标签
  • 归档
收藏
  • tool
  • algo
  • python
  • java
  • server
  • growth
  • frida
  • blog
  • SP
  • more
GitHub (opens new window)
  • tutorial
  • jetpack

  • components

    • activity
    • ContentProvider
      • 1. 概述与定位
      • 2. 注册与 URI
        • 2.1 Manifest 中声明
        • 2.2 创建 Provider
      • 3. 生命周期与调用流程
      • 4. 权限管理
        • 4.1 Manifest 权限声明
        • 4.2 动态检查与临时授权
      • 5. 示例:带权限校验的简单 Provider
        • 5.1 AndroidManifest.xml
        • 5.2 ItemProvider.java
  • androidx

  • 动态化
  • apm

  • module

  • harmony

  • tool

  • other

  • kotlin

  • 《android》
  • components
Jacky
2025-04-21
目录

ContentProvider

以下文档汇总了 Android ContentProvider 的核心概念、注册与使用流程、生命周期、权限管理等要点,并附带一个带权限校验的示例(含 AndroidManifest.xml 配置与代码实现)。


在 Android 中,ContentProvider 是应用间共享数据的标准接口,它把底层数据访问(如 SQLite、文件或网络)封装起来,通过统一的 URI 方式进行增删改查操作,并提供多种权限控制手段,确保只有获准的客户端才能访问。

# 1. 概述与定位

  • 作用:内置或自定义的 ContentProvider 能让一个应用将其私有数据安全地暴露给其他应用,或在自身内部提供统一的数据访问接口
  • 接口:通过 ContentResolver 客户端以 URI(content://authority/path)方式调用;ContentProvider 通过覆写 query()、insert()、update()、delete() 和 getType() 等方法处理请求

# 2. 注册与 URI

# 2.1 Manifest 中声明

  • 在 <application> 标签内使用 <provider> 元素声明,指定:
    • android:authorities:全局唯一的授权字符串
    • android:exported:是否允许外部应用访问
    • android:grantUriPermissions:是否允许临时 URI 访问授权

# 2.2 创建 Provider

  • 继承自 android.content.ContentProvider 并实现抽象方法。
  • 在 onCreate() 中初始化底层数据源(如打开数据库)。
  • 使用 UriMatcher 匹配客户端提交的 URI,并在各个 CRUD 方法中根据匹配结果操作数据

# 3. 生命周期与调用流程

  1. 进程启动:进程一旦需要访问 Provider,系统先加载其 ContentProvider 类并调用 onCreate()
  2. 客户端请求:客户端通过 ContentResolver 发起 IPC 调用,Binder 机制将 URI 与参数发送给 Provider。
  3. 方法执行:Provider 在调用线程中执行对应的 CRUD 方法,并返回 Cursor 或操作结果。
  4. 关闭清理:客户端和 Provider 均需在适当时机关闭 Cursor、释放资源。

# 4. 权限管理

# 4.1 Manifest 权限声明

  • 定义权限:在 Provider 应用的 AndroidManifest.xml 中,用 <permission> 定义独有权限,如:

    <permission android:name="com.example.app.provider.permission.READ_DATA"
                android:protectionLevel="signature" />
    
    1
    2
  • 引用权限:在 <provider> 标签中使用 android:readPermission/android:writePermission 强制客户端具有相应权限才能调用

  • 客户端授权:请求权限的应用在其 Manifest 中用 <uses-permission> 声明:

    <uses-permission android:name="com.example.app.provider.permission.READ_DATA" />
    
    1

# 4.2 动态检查与临时授权

  • grantUriPermissions:若启用此属性,Provider 可在运行时调用 Context.grantUriPermission() 授予某个包对特定 URI 的访问权限,且可在 Intent 中携带 FLAG_GRANT_READ_URI_PERMISSION、FLAG_GRANT_WRITE_URI_PERMISSION

  • 运行时校验:在 Provider 方法中可进一步使用:

    enforceCallingPermission("com.example.app.provider.permission.READ_DATA",
                             "Must have READ_DATA permission");
    
    1
    2

    或

    if (getContext().checkCallingOrSelfPermission(
            "com.example.app.provider.permission.READ_DATA")
        != PackageManager.PERMISSION_GRANTED) {
        throw new SecurityException("Permission denied");
    }
    
    1
    2
    3
    4
    5

    进行二次校验,防止恶意绕过

# 5. 示例:带权限校验的简单 Provider

# 5.1 AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.providerapp">

    <!-- 定义保护权限 -->
    <permission android:name="com.example.providerapp.permission.READ_ITEMS"
                android:protectionLevel="signature" />

    <application
        android:allowBackup="true"
        android:label="@string/app_name">
        
        <!-- 声明 ContentProvider -->
        <provider
            android:name=".ItemProvider"
            android:authorities="com.example.providerapp.items"
            android:exported="true"
            android:readPermission="com.example.providerapp.permission.READ_ITEMS"
            android:grantUriPermissions="true">
            <!-- 可选:定义可授权的 URI 路径 -->
            <grant-uri-permission android:pathPrefix="/items" />
        </provider>
    </application>
</manifest>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

客户端需在其 Manifest 中声明:

<uses-permission android:name="com.example.providerapp.permission.READ_ITEMS" />
1

# 5.2 ItemProvider.java

package com.example.providerapp;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

public class ItemProvider extends ContentProvider {
    public static final String AUTHORITY = "com.example.providerapp.items";
    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/items");
    private static final int ITEMS = 1;
    private static final int ITEM_ID = 2;
    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    private DatabaseHelper dbHelper;

    static {
        uriMatcher.addURI(AUTHORITY, "items", ITEMS);
        uriMatcher.addURI(AUTHORITY, "items/#", ITEM_ID);
    }

    @Override
    public boolean onCreate() {
        dbHelper = new DatabaseHelper(getContext());
        return true;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection,
                        @Nullable String selection, @Nullable String[] selArgs,
                        @Nullable String sortOrder) {
        // 权限二次校验
        if (getContext().checkCallingOrSelfPermission(
                "com.example.providerapp.permission.READ_ITEMS")
            != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("Requires READ_ITEMS permission");
        }

        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor;
        switch (uriMatcher.match(uri)) {
            case ITEMS:
                cursor = db.query(DatabaseHelper.TABLE_NAME,
                                  projection, selection, selArgs,
                                  null, null, sortOrder);
                break;
            case ITEM_ID:
                long id = ContentUris.parseId(uri);
                cursor = db.query(DatabaseHelper.TABLE_NAME,
                                  projection, "_id=?",
                                  new String[]{ String.valueOf(id) },
                                  null, null, sortOrder);
                break;
            default:
                throw new IllegalArgumentException("Unknown URI " + uri);
        }
        // 通知观察者数据可用
        cursor.setNotificationUri(getContext().getContentResolver(), uri);
        return cursor;
    }

    // insert/update/delete 等方法可类似添加权限校验并实现
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

以上即 Android ContentProvider 的核心要点与权限校验示例,帮助你快速上手并能在生产环境中安全地暴露数据。若有更深入的优化(如性能调优、批量操作、跨进程缓存等)需求,可在此基础上扩展。

上次更新: 2025/07/11, 11:39:32
activity
fragment

← activity fragment→

最近更新
01
npx 使用指南
10-12
02
cursor
09-28
03
inspect
07-20
更多文章>
Theme by Vdoing | Copyright © 2019-2025 Jacky | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式