【Android】四大组件之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、表名及列名,提升代码可维护性:

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) :

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 区分操作类型(如单条或多条数据) ‌
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:

<!-- 定义自定义权限 -->  
<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 方法中动态检查权限:

@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. 客户端调用

调用方配置:

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

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

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

// 插入数据  
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 权限

<!-- 定义自定义权限 -->  
<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 发起,两者通过系统服务协同实现实时数据同步。

五、多表关联与视图

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

1. 定义 Contract 类

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 逻辑固化到数据库中。
    • 直接通过视图名称查询数据,简化复杂查询的调用过程。
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)

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. 客户端调用对比

// 动态 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 线程(主线程)。

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; // 返回预计插入数量(实际需回调确认)
    }
}

调用示例‌:

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

‌2. 异步查询(AsyncQueryHandler)

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

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)

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

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+ 已弃用,建议替换为协程或线程池)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贫道绝缘子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值