Files
mkdocs/docs/android面试/核心组件/ContentProvider详解.md
2026-01-15 11:53:37 +08:00

10 KiB
Raw Permalink Blame History

ContentProvider详解

目录


ContentProvider作用

主要作用

  1. 数据共享:应用间共享数据
  2. 数据封装:封装数据访问逻辑
  3. 统一接口:提供统一的数据访问接口
  4. 权限控制:控制数据访问权限

使用场景

// 场景1应用间共享数据
// 应用A提供联系人数据
// 应用B访问联系人数据

// 场景2封装数据访问
// 统一管理数据库访问
// 隐藏数据存储细节

// 场景3跨进程数据访问
// 通过 Binder 机制跨进程访问

URI机制

URI 格式

// URI 格式
content://authority/path/id

// 示例
content://com.example.provider/user/1
content://com.example.provider/user
content://com.example.provider/user/1/name

URI 组成

// authorityContentProvider 的标识
// path数据路径
// id数据ID可选

// 解析 URI
Uri uri = Uri.parse("content://com.example.provider/user/1");
String authority = uri.getAuthority(); // com.example.provider
List<String> pathSegments = uri.getPathSegments(); // [user, 1]
String id = uri.getLastPathSegment(); // 1

URI 匹配

// 使用 UriMatcher 匹配 URI
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

static {
    uriMatcher.addURI("com.example.provider", "user", USER);
    uriMatcher.addURI("com.example.provider", "user/#", USER_ID);
}

@Override
public Cursor query(Uri uri, String[] projection, String selection,
                   String[] selectionArgs, String sortOrder) {
    switch (uriMatcher.match(uri)) {
        case USER:
            // 查询所有用户
            break;
        case USER_ID:
            // 查询指定用户
            String id = uri.getLastPathSegment();
            break;
    }
}

ContentResolver使用

查询数据

// 查询数据
Uri uri = Uri.parse("content://com.example.provider/user");
Cursor cursor = getContentResolver().query(
    uri,
    new String[]{"id", "name", "email"}, // projection
    "age > ?", // selection
    new String[]{"18"}, // selectionArgs
    "name ASC" // sortOrder
);

if (cursor != null) {
    while (cursor.moveToNext()) {
        int id = cursor.getInt(cursor.getColumnIndex("id"));
        String name = cursor.getString(cursor.getColumnIndex("name"));
        String email = cursor.getString(cursor.getColumnIndex("email"));
    }
    cursor.close();
}

插入数据

// 插入数据
Uri uri = Uri.parse("content://com.example.provider/user");
ContentValues values = new ContentValues();
values.put("name", "John");
values.put("email", "john@example.com");
Uri newUri = getContentResolver().insert(uri, values);

更新数据

// 更新数据
Uri uri = Uri.parse("content://com.example.provider/user/1");
ContentValues values = new ContentValues();
values.put("name", "Jane");
int count = getContentResolver().update(uri, values, null, null);

删除数据

// 删除数据
Uri uri = Uri.parse("content://com.example.provider/user/1");
int count = getContentResolver().delete(uri, null, null);

数据访问权限

权限声明

<!-- AndroidManifest.xml -->
<provider
    android:name=".MyProvider"
    android:authorities="com.example.provider"
    android:exported="true"
    android:readPermission="com.example.READ_PERMISSION"
    android:writePermission="com.example.WRITE_PERMISSION" />

权限检查

public class MyProvider extends ContentProvider {
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                       String[] selectionArgs, String sortOrder) {
        // 检查读取权限
        if (getContext().checkCallingOrSelfPermission(
                "com.example.READ_PERMISSION") != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("Permission denied");
        }
        // 查询数据
        return null;
    }
}

ContentObserver

ContentObserver 使用

// 监听数据变化
public class MyObserver extends ContentObserver {
    public MyObserver(Handler handler) {
        super(handler);
    }
    
    @Override
    public void onChange(boolean selfChange) {
        super.onChange(selfChange);
        // 数据变化时调用
    }
    
    @Override
    public void onChange(boolean selfChange, Uri uri) {
        super.onChange(selfChange, uri);
        // 指定 URI 的数据变化
    }
}

// 注册观察者
Uri uri = Uri.parse("content://com.example.provider/user");
ContentObserver observer = new MyObserver(new Handler());
getContentResolver().registerContentObserver(uri, true, observer);

// 注销观察者
getContentResolver().unregisterContentObserver(observer);

ContentProvider实现

完整实现

public class UserProvider extends ContentProvider {
    private static final String AUTHORITY = "com.example.provider";
    private static final String TABLE_USER = "user";
    
    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    private static final int USER = 1;
    private static final int USER_ID = 2;
    
    static {
        uriMatcher.addURI(AUTHORITY, TABLE_USER, USER);
        uriMatcher.addURI(AUTHORITY, TABLE_USER + "/#", USER_ID);
    }
    
    private SQLiteDatabase database;
    
    @Override
    public boolean onCreate() {
        DatabaseHelper helper = new DatabaseHelper(getContext());
        database = helper.getWritableDatabase();
        return database != null;
    }
    
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                       String[] selectionArgs, String sortOrder) {
        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
        queryBuilder.setTables(TABLE_USER);
        
        switch (uriMatcher.match(uri)) {
            case USER:
                break;
            case USER_ID:
                queryBuilder.appendWhere("id = " + uri.getLastPathSegment());
                break;
            default:
                throw new IllegalArgumentException("Unknown URI: " + uri);
        }
        
        Cursor cursor = queryBuilder.query(database, projection, selection,
            selectionArgs, null, null, sortOrder);
        cursor.setNotificationUri(getContext().getContentResolver(), uri);
        return cursor;
    }
    
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        long id = database.insert(TABLE_USER, null, values);
        if (id > 0) {
            Uri newUri = ContentUris.withAppendedId(uri, id);
            getContext().getContentResolver().notifyChange(newUri, null);
            return newUri;
        }
        return null;
    }
    
    @Override
    public int update(Uri uri, ContentValues values, String selection,
                     String[] selectionArgs) {
        int count;
        switch (uriMatcher.match(uri)) {
            case USER:
                count = database.update(TABLE_USER, values, selection, selectionArgs);
                break;
            case USER_ID:
                String id = uri.getLastPathSegment();
                count = database.update(TABLE_USER, values, "id = ?", new String[]{id});
                break;
            default:
                throw new IllegalArgumentException("Unknown URI: " + uri);
        }
        getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }
    
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int count;
        switch (uriMatcher.match(uri)) {
            case USER:
                count = database.delete(TABLE_USER, selection, selectionArgs);
                break;
            case USER_ID:
                String id = uri.getLastPathSegment();
                count = database.delete(TABLE_USER, "id = ?", new String[]{id});
                break;
            default:
                throw new IllegalArgumentException("Unknown URI: " + uri);
        }
        getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }
    
    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)) {
            case USER:
                return "vnd.android.cursor.dir/user";
            case USER_ID:
                return "vnd.android.cursor.item/user";
            default:
                return null;
        }
    }
}

ContentProvider最佳实践

1. 通知数据变化

// 数据变化时通知观察者
getContext().getContentResolver().notifyChange(uri, null);

2. 使用事务

database.beginTransaction();
try {
    // 批量操作
    database.setTransactionSuccessful();
} finally {
    database.endTransaction();
}

3. 权限控制

// 检查权限
if (getContext().checkCallingOrSelfPermission(permission) 
        != PackageManager.PERMISSION_GRANTED) {
    throw new SecurityException("Permission denied");
}

面试常见问题

Q1: ContentProvider 的作用?

答案:

  1. 数据共享:应用间共享数据
  2. 数据封装:封装数据访问逻辑
  3. 统一接口:提供统一的数据访问接口
  4. 权限控制:控制数据访问权限

Q2: URI 的格式?

答案: content://authority/path/id

  • authorityContentProvider 标识
  • path数据路径
  • id数据ID可选

Q3: ContentResolver 和 ContentProvider 的关系?

答案:

  • ContentResolver:客户端,用于访问数据
  • ContentProvider:服务端,提供数据访问接口
  • 通过 URI 和 Binder 机制通信

Q4: ContentObserver 的作用?

答案:

  • 监听 ContentProvider 数据变化
  • 数据变化时自动通知
  • 实现数据更新通知机制

最后更新2024年