【Android】四大组件之ContentProvider

目录

[一、什么是 ContentProvider](#一、什么是 ContentProvider)

[二、创建和使用 ContentProvider](#二、创建和使用 ContentProvider)

三、跨应用权限控制

四、数据变更通知

五、多表关联与视图

六、异步处理


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

一、什么是 ContentProvider

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

1. 核心特点

  • 数据共享桥梁 :提供安全的跨应用数据共享机制,允许不同应用通过 ‌**URI(统一资源标识符)**‌ 访问数据,无需直接操作对方数据库或文件系统。
  • 统一数据接口 :独立于底层数据存储形式(SQLite、文件或网络),对外均以‌表格形式‌组织数据,简化调用方操作逻辑(query()、insert()、update()、delete()等标准接口)。
  • 跨进程通信支持 ‌:底层基于 Android ‌Binder 机制‌实现进程间通信(IPC),确保数据交互高效且安全。
  • 数据安全:ContentProvider可以通过权限控制来保护数据。比如,你可以设置只有特定应用才能访问你的数据。

2. 典型使用场景

场景 说明
系统数据访问 读取通讯录、短信、媒体文件等系统内置数据。
应用间数据共享 例如电商应用向物流应用提供订单状态数据。
内部数据封装 统一管理应用内多模块数据访问逻辑(如本地缓存与网络数据整合)。

二、创建和使用 ContentProvider

1. 定义数据模型与 Contract 类

使用 ‌Contract 类‌ 统一管理 URI、表名及列名,提升代码可维护性:

java 复制代码
public final class UserContract {  
    public static final String AUTHORITY = "com.example.provider";  
    public static final Uri CONTENT_URI
        = Uri.parse("content://" + AUTHORITY + "/users");  

    public static class UserEntry implements BaseColumns {  
        public static final String TABLE_NAME = "users";  
        public static final String COLUMN_NAME = "name";  
        public static final String COLUMN_AGE = "age";  
    }  
}  

2. 实现 Provider 类

继承 ContentProvider 并重写关键方法,集成 SQLite 数据库操作( CRUD) :

java 复制代码
public class UserProvider extends ContentProvider {  
    private SQLiteOpenHelper mDbHelper;  

    @Override  
    public boolean onCreate() {  
        mDbHelper = new UserDatabaseHelper(getContext()); // 自定义数据库帮助类  
        return true; // 初始化数据库等资源,返回 true 表示成功   
    }  

    @Override  
    public Cursor query(Uri uri, String[] projection, String selection,  
                        String[] selectionArgs, String sortOrder) {  
        SQLiteDatabase db = mDbHelper.getReadableDatabase();  
        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();  
        qb.setTables(UserEntry.TABLE_NAME);  

        Cursor cursor = qb.query(db, projection, selection, selectionArgs,  
                null, null, sortOrder);  
        cursor.setNotificationUri(getContext().getContentResolver(), uri);  
        return cursor;  // 查询数据,返回 Cursor 对象  
    }

    @Override  
    public Uri insert(Uri uri, ContentValues values) {  
        // 插入数据,返回新记录的 URI  
    }  

    @Override  
    public int update(Uri uri, ContentValues values, String selection,  
                      String[] selectionArgs) {  
        // 更新数据,返回受影响的行数  
    }  

    @Override  
    public int delete(Uri uri, String selection, String[] selectionArgs) {  
        // 删除数据,返回受影响的行数  
    }  

    @Override  
    public String getType(Uri uri) {  
        // 返回 URI 对应的 MIME 类型  
    }  
}  
  • onCreate() 在主线程执行,要避免耗时操作。可以在 onCreate() 中初始化线程池,或在 CRUD 方法中启动 AsyncTask
  • 所有 CRUD 方法需处理线程安全问题。

3. 定义 URI 规则

  • URI 结构 ‌:content://<Authority>/<Path>/<ID>
    • Authority ‌:唯一标识 Provider(需在 AndroidManifest.xml 中声明)。
    • Path ‌:标识数据表或资源类型(如 userorder)。
  • 使用 UriMatcher:根据 URI 区分操作类型(如单条或多条数据) ‌
java 复制代码
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);  
static {  
    sUriMatcher.addURI(UserContract.AUTHORITY, "users", USERS);  
    sUriMatcher.addURI(UserContract.AUTHORITY, "users/#", USER_ID);  
}  

@Override  
public Uri insert(Uri uri, ContentValues values) {  
    switch (sUriMatcher.match(uri)) {  
        case USERS:  
            long id = db.insert(UserEntry.TABLE_NAME, null, values);  
            return ContentUris.withAppendedId(uri, id);  
        default:  
            throw new IllegalArgumentException("Unsupported URI: " + uri);  
    }  
}  

4. 注册与权限控制

AndroidManifest.xml 中声明provider:

XML 复制代码
<!-- 定义自定义权限 -->  
<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" />  
  • android:name:指向继承 ContentProvider 的具体实现类(如 UserProvider)。
  • android:authorities:唯一标识 Provider 的 URI 前缀(如 content://com.example.provider/users
  • exported:是否允许其他应用访问(默认为 false)。
  • protectionLevel 设为 dangerous 表示需用户手动授权。
  • readPermission/writePermission:自定义权限控制。

在 Provider 方法中动态检查权限:

java 复制代码
@Override  
public Cursor query(Uri uri, String[] projection, String selection,  
                    String[] selectionArgs, String sortOrder) {  
    if (getContext().checkCallingPermission("com.example.READ_DATA")  
            != PackageManager.PERMISSION_GRANTED) {  
        throw new SecurityException("Permission denied");  
    }  
    // 执行查询逻辑  
}  


@Override  
public int delete(Uri uri, String selection, String[] selectionArgs) {  
    if (getContext().checkCallingPermission("com.example.WRITE_USERS")  
            != PackageManager.PERMISSION_GRANTED) {  
        throw new SecurityException("Permission denied");  
    }  
    // 删除逻辑  
}  

注意‌:静态权限声明需配合动态校验

5. 客户端调用

调用方配置:

XML 复制代码
<manifest ...>  
    <!-- 声明权限 -->  
    <uses-permission android:name="com.example.READ_USERS" />  
    <uses-permission android:name="com.example.WRITE_USERS" />  

    <application ...>  
        <!-- 无 Provider 声明,直接通过 ContentResolver 调用 -->  
    </application>  
</manifest>  

通过 ContentResolver 访问和操作 Provider 数据:

java 复制代码
// 插入数据  
ContentValues values = new ContentValues();  
values.put(UserEntry.COLUMN_NAME, "Alice");  
values.put(UserEntry.COLUMN_AGE, 25);  
getContentResolver().insert(UserContract.CONTENT_URI, values);  

// 查询数据  
Cursor cursor = getContentResolver().query(  
    UserContract.CONTENT_URI,  
    new String[]{UserEntry._ID, UserEntry.COLUMN_NAME},  
    UserEntry.COLUMN_AGE + " > ?",  
    new String[]{"20"},  
    null  
);  

调用方无需声明 <provider>,但需通过权限声明和 URI 匹配访问目标 Provider,并遵循 Provider 方的路径与权限规则。

  • 客户端无需直接操作 Provider 实例。
  • 跨进程时需声明权限。

三、跨应用权限控制

配置目标 实现方式
跨应用调用权限 调用方声明 <uses-permission>,Provider 方配置 android:exported="true"
动态权限申请 针对 dangerous 级别权限,调用方需在运行时请求用户授权。
路径级访问控制 Provider 方通过 <path-permission> 细化权限,调用方需匹配声明。

1. 声明 Provider 权限

XML 复制代码
<!-- 定义自定义权限 -->  
<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> 限制特定路径,调用方需确保拥有对应权限:

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

3. 调用方配置

XML 复制代码
<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 中实现动态权限申请流程:

java 复制代码
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_CONTACTSWRITE_CONTACTS 属于同一组)

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

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

java 复制代码
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。

java 复制代码
// 使用者(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 方法触发回调:

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

3. 系统级支持

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

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

五、多表关联与视图

场景 CODE_BOOKS_WITH_AUTHORS (多表关联查询) CODE_VIEW_BOOKS (视图查询)
实现方式 运行时动态拼接 SQL JOIN 预定义视图(静态 SQL)
灵活性 支持动态筛选条件(如 LIKE 筛选条件受视图定义限制
性能 依赖索引优化 视图可预编译优化
适用场景 需要灵活关联条件的查询 高频复杂查询(如报表)
字段冲突解决 需手动设置列别名(AS 视图定义时已解决别名问题

1. 定义 Contract 类

java 复制代码
public final class BookstoreContract {
    public static final String AUTHORITY = "com.example.bookstore.provider";
    public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY);

    // 动态 JOIN 查询的 URI 路径
    public static class BooksWithAuthors implements BaseColumns {
        public static final String PATH = "books_with_authors";
        public static final Uri CONTENT_URI
            = Uri.withAppendedPath(BASE_URI, PATH);
        public static final String CONTENT_TYPE
            = "vnd.android.cursor.dir/vnd.bookstore.books_with_authors";
    }

    // 视图查询的 URI 路径
    public static class ViewBooks implements BaseColumns {
        public static final String PATH = "view_books";
        public static final Uri CONTENT_URI
            = Uri.withAppendedPath(BASE_URI, PATH);
        public static final String CONTENT_TYPE
            = "vnd.android.cursor.dir/vnd.bookstore.view_books";
    }
}

2. ContentProvider 实现类

  • 多表动态关联查询 ‌(CODE_BOOKS_WITH_AUTHORS
    • SQLiteQueryBuilder 动态构建 LEFT JOIN 查询,关联 books 表和 authors 表。
    • setTables() 指定关联逻辑,setProjectionMap() 解决字段冲突(如列别名定义)。
  • 视图查询 ‌(CODE_VIEW_BOOKS
    • 预定义视图 view_books_with_authors,将 LEFT JOIN 逻辑固化到数据库中。
    • 直接通过视图名称查询数据,简化复杂查询的调用过程。
java 复制代码
public class BookstoreProvider extends ContentProvider {
    private static final String TAG = "BookstoreProvider";
    private SQLiteOpenHelper mDbHelper;

    // UriMatcher 标识码定义
    private static final int CODE_BOOKS_WITH_AUTHORS = 100;  // 动态 JOIN 查询
    private static final int CODE_VIEW_BOOKS = 200;           // 视图查询

    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    static {
        sUriMatcher.addURI(
            BookstoreContract.AUTHORITY,
            BookstoreContract.BooksWithAuthors.PATH,
            CODE_BOOKS_WITH_AUTHORS
        );
        sUriMatcher.addURI(
            BookstoreContract.AUTHORITY,
            BookstoreContract.ViewBooks.PATH,
            CODE_VIEW_BOOKS
        );
    }

    // 列名映射(解决多表字段冲突)
    private static final HashMap<String, String> sBooksMap
    = new HashMap<>();
    static {
        sBooksMap.put(BooksWithAuthors._ID, "books._id AS _id");
        sBooksMap.put("title", "books.title");
        sBooksMap.put("author_name", "authors.name AS author_name");
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
        String table = null;  // 默认表名

        switch (sUriMatcher.match(uri)) {
            // 动态 JOIN 查询(CODE_BOOKS_WITH_AUTHORS)
            case CODE_BOOKS_WITH_AUTHORS:
                queryBuilder.setTables("books LEFT JOIN authors ON books.author_id = authors._id");
                queryBuilder.setProjectionMap(sBooksMap);
                break;

            // 视图查询(CODE_VIEW_BOOKS)
            case CODE_VIEW_BOOKS:
                table = "view_books_with_authors";  // 直接查询预定义的视图
                queryBuilder.setTables(table);
                break;

            default:
                throw new IllegalArgumentException("Unknown URI: " + uri);
        }

        SQLiteDatabase db = mDbHelper.getReadableDatabase();
        Cursor cursor;

        if (table == null) {
            // 动态 JOIN 查询
            cursor = queryBuilder.query(
                db,
                projection,
                selection,
                selectionArgs,
                null,  // groupBy
                null,  // having
                sortOrder
            );
        } else {
            // 视图查询(直接使用标准查询)
            cursor = db.query(
                table,
                projection,
                selection,
                selectionArgs,
                null,  // groupBy
                null,  // having
                sortOrder
            );
        }

        cursor.setNotificationUri(getContext().getContentResolver(), uri);
        return cursor;
    }

    @Override
    public boolean onCreate() {
        mDbHelper = new BookstoreDbHelper(getContext());
        return true;
    }

    // 其他必要方法(insert/update/delete/getType)省略...
}

3. 创建数据库视图(SQLiteOpenHelper)

java 复制代码
public class BookstoreDbHelper extends SQLiteOpenHelper {
    private static final String DATABASE_NAME = "bookstore.db";
    private static final int DATABASE_VERSION = 1;

    // 基表定义
    private static final String SQL_CREATE_BOOKS =
        "CREATE TABLE books (" +
        "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
        "title TEXT NOT NULL," +
        "author_id INTEGER," +
        "FOREIGN KEY(author_id) REFERENCES authors(_id))";

    private static final String SQL_CREATE_AUTHORS =
        "CREATE TABLE authors (" +
        "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
        "name TEXT NOT NULL)";

    // 视图定义(固化多表关联逻辑)
    private static final String SQL_CREATE_VIEW_BOOKS_WITH_AUTHORS =
        "CREATE VIEW view_books_with_authors AS " +
        "SELECT books._id, books.title, authors.name AS author_name " +
        "FROM books LEFT JOIN authors ON books.author_id = authors._id";

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(SQL_CREATE_AUTHORS);
        db.execSQL(SQL_CREATE_BOOKS);
        db.execSQL(SQL_CREATE_VIEW_BOOKS_WITH_AUTHORS); // 创建视图
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP VIEW IF EXISTS view_books_with_authors");
        db.execSQL("DROP TABLE IF EXISTS books");
        db.execSQL("DROP TABLE IF EXISTS authors");
        onCreate(db);
    }
}

4. 客户端调用对比

java 复制代码
// 动态 JOIN 查询(CODE_BOOKS_WITH_AUTHORS)
Cursor dynamicJoinCursor = getContentResolver().query(
    BookstoreContract.BooksWithAuthors.CONTENT_URI,
    new String[]{"title", "author_name"},
    "author_name LIKE ?",
    new String[]{"张%"},
    null
);

// 视图查询(CODE_VIEW_BOOKS)
Cursor viewCursor = getContentResolver().query(
    BookstoreContract.ViewBooks.CONTENT_URI,
    new String[]{"title", "author_name"},
    null,
    null,
    "title ASC"
);

六、异步处理

场景 推荐方案 优势 缺点
批量写入 线程池 + Handler 高吞吐量,可控并发度 代码复杂度高,回调繁琐
列表数据加载 AsyncQueryHandler 自动线程管理,简化代码 Android 11+ 已废弃,推荐用协程
实时数据同步 ContentObserver 精准监听特定数据源变化 频繁更新可能引发过多回调,延迟+消耗

1. 异步批量插入(线程池 + Handler)

场景‌:后台线程执行大数据量插入,避免阻塞 UI 线程(主线程)。

java 复制代码
public class BookProvider extends ContentProvider {
    private ExecutorService mExecutor = Executors.newFixedThreadPool(4);
    private Handler mHandler = new Handler(Looper.getMainLooper());
    private SQLiteOpenHelper mDbHelper;

    @Override
    public boolean onCreate() {
        mDbHelper = new BookDbHelper(getContext());
        return true;
    }

    @Override
    public int bulkInsert(Uri uri, ContentValues[] values) {
        mExecutor.execute(() -> {
            SQLiteDatabase db = mDbHelper.getWritableDatabase();
            db.beginTransaction();
            try {
                for (ContentValues value : values) {
                    db.insert("books", null, value);
                }
                db.setTransactionSuccessful();
            } finally {
                db.endTransaction();
                // 主线程通知数据变更
                mHandler.post(() -> {
                    getContext().getContentResolver().notifyChange(uri, null);
                });
            }
        });
        return values.length; // 返回预计插入数量(实际需回调确认)
    }
}

调用示例‌:

java 复制代码
ContentValues[] valuesArray = new ContentValues[100];
// 填充 valuesArray...
ContentResolver resolver = getContentResolver();
resolver.bulkInsert(BookContract.CONTENT_URI, valuesArray);
  • 使用线程池管理并发写入
  • 通过 Handler 切换至主线程发送变更通知

‌2. 异步查询(AsyncQueryHandler)

场景‌:列表页异步加载数据并自动更新 UI。

java 复制代码
public class BookListActivity extends AppCompatActivity {
    private ListView mListView;
    private SimpleCursorAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book_list);
        mListView = findViewById(R.id.list_view);

        // 初始化适配器
        mAdapter = new SimpleCursorAdapter(this, 
            android.R.layout.simple_list_item_2, 
            null, 
            new String[]{"title", "author"}, 
            new int[]{android.R.id.text1, android.R.id.text2}, 
            0);

        mListView.setAdapter(mAdapter);

        // 启动异步查询:
        AsyncQueryHandler asyncHandler
                = new AsyncQueryHandler(getContentResolver()) {
            @Override
            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
                mAdapter.swapCursor(cursor);
            }
        };

        asyncHandler.startQuery(0, null, BookContract.CONTENT_URI,
            new String[]{"_id", "title", "author"}, 
            "category=?", 
            new String[]{"tech"}, 
            "rating DESC");
    }
}
  • AsyncQueryHandler 自动管理线程切换
  • 查询结果直接更新 CursorAdapter 关联的 UI

3. 数据变更监听(ContentObserver)

场景‌:实时监测收藏夹数据变化并刷新界面。

java 复制代码
public class FavoriteFragment extends Fragment {
    private FavoriteAdapter mAdapter;
    private ContentObserver mObserver;

    @Override
    public void onResume() {
        super.onResume();
        // 注册监听器:
        mObserver = new ContentObserver(new Handler()) {
            @Override
            public void onChange(boolean selfChange) {
                loadData(); // 数据变化时重新加载
            }
        };
        getActivity().getContentResolver().registerContentObserver(
            BookContract.Favorite.CONTENT_URI,
            true,
            mObserver
        );
    }

    @Override
    public void onPause() {
        super.onPause();
        // 注销监听防止内存泄漏:
        getActivity().getContentResolver().unregisterContentObserver(mObserver);
    }

    private void loadData() {
        new AsyncTask<Void, Void, Cursor>() {
            @Override
            protected Cursor doInBackground(Void... voids) {
                return getActivity().getContentResolver().query(
                    BookContract.Favorite.CONTENT_URI,
                    null, null, null, 
                    "last_updated DESC"
                );
            }

            @Override
            protected void onPostExecute(Cursor cursor) {
                mAdapter.swapCursor(cursor);
            }
        }.execute();
    }
}
  • ContentObserver 监听指定 URI 的数据变化
  • AsyncTask 执行后台查询(注意 Android 11+ 已弃用,建议替换为协程或线程池)
相关推荐
BD_Marathon1 小时前
【MySQL】函数
android·数据库·mysql
西西学代码2 小时前
安卓开发---耳机的按键设置的UI实例
android·ui
maki0776 小时前
虚幻版Pico大空间VR入门教程 05 —— 原点坐标和项目优化技巧整理
android·游戏引擎·vr·虚幻·pico·htc vive·大空间
千里马学框架6 小时前
音频焦点学习之AudioFocusRequest.Builder类剖析
android·面试·智能手机·车载系统·音视频·安卓framework开发·audio
fundroid10 小时前
掌握 Compose 性能优化三步法
android·android jetpack
TeleostNaCl11 小时前
如何在 IDEA 中使用 Proguard 自动混淆 Gradle 编译的Java 项目
android·java·经验分享·kotlin·gradle·intellij-idea
旷野说12 小时前
Android Studio Narwhal 3 特性
android·ide·android studio
maki07718 小时前
VR大空间资料 01 —— 常用VR框架对比
android·ue5·游戏引擎·vr·虚幻·pico
xhBruce1 天前
InputReader与InputDispatcher关系 - android-15.0.0_r23
android·ims
领创工作室1 天前
安卓设备分区作用详解-测试机红米K40
android·java·linux