深入分析 Android ContentProvider (十三)(完)

发布于:2024-08-08 ⋅ 阅读:(27) ⋅ 点赞:(0)

深入分析 Android ContentProvider (十三)

ContentProvider 的详细系统代码分析(续)

为了进一步深入理解 ContentProvider 的工作流程,我们将探讨其在实际应用中的设计和实现细节,包括 ContentProvider 的注册、权限管理、通知机制等。

1. ContentProvider 的注册

ContentProvider 需要在 AndroidManifest.xml 文件中注册。注册过程中需要指定 authority 属性,这是应用访问 ContentProvider 的唯一标识符。以下是一个示例:

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <provider
            android:name=".PlaylistProvider"
            android:authorities="com.example.musicprovider"
            android:exported="true"
            android:permission="com.example.permission.READ_DATA">
            <intent-filter>
                <action android:name="android.intent.action.PROVIDER_CHANGED"/>
            </intent-filter>
        </provider>

    </application>

</manifest>
注册的关键点:
  • android:name:指定 ContentProvider 的类名。
  • android:authorities:唯一标识 ContentProvider 的字符串。
  • android:exported:是否允许其他应用访问此 ContentProvider。
  • android:permission:访问 ContentProvider 所需的权限。

2. ContentProvider 的权限管理

权限管理是 ContentProvider 设计中非常重要的一部分,可以通过 android:permission 属性和代码检查来确保数据的安全性。以下是权限检查的实现示例:

public class SecurePlaylistProvider extends ContentProvider {

    @Override
    public boolean onCreate() {
        // 初始化操作
        return true;
    }

    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
                        @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        enforceReadPermission();
        // 执行查询操作
        return null;
    }

    private void enforceReadPermission() {
        if (getContext().checkCallingOrSelfPermission("com.example.permission.READ_DATA")
                != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("Permission denied: READ_DATA");
        }
    }

    // 其他方法的实现同样进行权限检查
}

在上述示例中,每次执行数据库操作前都会调用 enforceReadPermission() 方法,检查调用方是否具有相应的权限。

3. ContentProvider 的通知机制

ContentProvider 的通知机制允许在数据发生变化时通知观察者,以便其更新数据。通知机制的实现主要依赖于 ContentObserverContentResolver.notifyChange() 方法。

3.1 数据变化通知示例:
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection,
                  @Nullable String[] selectionArgs) {
    int rowsUpdated;
    switch (uriMatcher.match(uri)) {
        case PLAYLISTS:
            rowsUpdated = database.update(DatabaseHelper.TABLE_PLAYLIST, values, selection, selectionArgs);
            break;
        case PLAYLIST_ID:
            selection = DatabaseHelper.COLUMN_ID + "=?";
            selectionArgs = new String[]{String.valueOf(ContentUris.parseId(uri))};
            rowsUpdated = database.update(DatabaseHelper.TABLE_PLAYLIST, values, selection, selectionArgs);
            break;
        default:
            throw new IllegalArgumentException("Unknown URI: " + uri);
    }
    getContext().getContentResolver().notifyChange(uri, null);
    return rowsUpdated;
}
3.2 使用 ContentObserver 监听数据变化:
public class PlaylistObserver extends ContentObserver {
    public PlaylistObserver(Handler handler) {
        super(handler);
    }

    @Override
    public void onChange(boolean selfChange) {
        onChange(selfChange, null);
    }

    @Override
    public void onChange(boolean selfChange, Uri uri) {
        // 处理数据变化,更新 UI 或重新查询数据
    }
}

// 注册 ContentObserver
ContentResolver contentResolver = getContentResolver();
PlaylistObserver observer = new PlaylistObserver(new Handler());
contentResolver.registerContentObserver(PlaylistProvider.CONTENT_URI, true, observer);

ContentProvider 的数据发生变化时,通过 notifyChange() 方法通知所有注册的观察者。观察者会在 onChange() 方法中处理数据变化,执行相应的更新操作。

4. ContentProvider 的测试

为了确保 ContentProvider 的功能正确,可以编写单元测试进行验证。以下是一个 ContentProvider 的单元测试示例:

@RunWith(AndroidJUnit4.class)
public class PlaylistProviderTest {
    private ContentResolver contentResolver;

    @Before
    public void setUp() {
        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
        contentResolver = context.getContentResolver();
    }

    @Test
    public void testInsert() {
        ContentValues values = new ContentValues();
        values.put(DatabaseHelper.COLUMN_NAME, "Test Playlist");
        Uri newUri = contentResolver.insert(PlaylistProvider.CONTENT_URI, values);
        assertNotNull(newUri);
    }

    @Test
    public void testQuery() {
        Cursor cursor = contentResolver.query(PlaylistProvider.CONTENT_URI, null, null, null, null);
        assertNotNull(cursor);
        assertTrue(cursor.getCount() > 0);
    }

    @Test
    public void testUpdate() {
        ContentValues values = new ContentValues();
        values.put(DatabaseHelper.COLUMN_NAME, "Updated Playlist");
        int rowsUpdated = contentResolver.update(PlaylistProvider.CONTENT_URI, values, DatabaseHelper.COLUMN_ID + "=?", new String[]{"1"});
        assertEquals(1, rowsUpdated);
    }

    @Test
    public void testDelete() {
        int rowsDeleted = contentResolver.delete(PlaylistProvider.CONTENT_URI, DatabaseHelper.COLUMN_ID + "=?", new String[]{"1"});
        assertEquals(1, rowsDeleted);
    }
}

通过上述单元测试,可以验证 ContentProvider 的各项功能,确保其行为符合预期。

5. ContentProvider 的系统代码流程

5.1 ContentProvider 的实例化

当应用首次访问 ContentProvider 时,系统会在 ActivityThread 中实例化 ContentProvider,并调用其 onCreate() 方法进行初始化:

// ActivityThread.java
private final void handleBindApplication(AppBindData data) {
    // ... 省略其他代码 ...

    // 实例化 ContentProvider
    if (data.providers != null) {
        installContentProviders(app, data.providers);
    }

    // ... 省略其他代码 ...
}

private final void installContentProviders(Context context, List<ProviderInfo> providers) {
    for (ProviderInfo info : providers) {
        ContentProviderHolder holder = installProvider(context, null, info, false, true, true);
        mAllProviders.put(holder.provider.getClass().getName(), holder);
    }
}

private final ContentProviderHolder installProvider(Context context, IContentProvider provider,
        ProviderInfo info, boolean noisy, boolean noReleaseNeeded, boolean stable) {
    ContentProvider localProvider = (ContentProvider)provider;
    localProvider.attachInfo(context, info);
    return new ContentProviderHolder(localProvider);
}
5.2 ContentResolver 与 ContentProvider 的交互

当应用通过 ContentResolver 访问 ContentProvider 时,系统会通过 IContentProvider 接口调用 ContentProvider 的方法,执行相应的数据库操作:

// ContentResolver.java
public final Cursor query(Uri uri, String[] projection, String selection,
                          String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
    IContentProvider provider = acquireProvider(uri);
    if (provider == null) {
        throw new IllegalArgumentException("Unknown URI " + uri);
    }

    try {
        return provider.query(mPackageName, uri, projection, selection, selectionArgs, sortOrder, cancellationSignal);
    } catch (RemoteException e) {
        throw new RuntimeException("Failed to query: " + uri, e);
    } finally {
        releaseProvider(provider);
    }
}
5.3 ContentProvider 的 URI 匹配

ContentProvider 使用 UriMatcher 匹配 URI,并根据匹配结果执行相应的操作:

public class PlaylistProvider extends ContentProvider {
    private static final int PLAYLISTS = 1;
    private static final int PLAYLIST_ID = 2;

    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        uriMatcher.addURI(AUTHORITY, BASE_PATH, PLAYLISTS);
        uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", PLAYLIST_ID);
    }

    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
                        @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
        queryBuilder.setTables(DatabaseHelper.TABLE_PLAYLIST);

        switch (uriMatcher.match(uri)) {
            case PLAYLISTS:
                break;
            case PLAYLIST_ID:
                queryBuilder.appendWhere(DatabaseHelper.COLUMN_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;
    }
}

6. 总结

通过详细的系统代码分析,我们深入了解了 Android 中 ContentProvider 的设计和实现。从 ContentProvider 的注册、权限管理、数据访问到通知机制,每一步都体现了其在跨应用数据共享中的重要性和安全性。掌握这些底层实现和工作流程,开发者可以更好地设计和优化 ContentProvider,在实际项目中实现高效、安全的数据操作。

欢迎点赞|关注|收藏|评论,您的肯定是我创作的动力

在这里插入图片描述