当前位置:   article > 正文

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

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

深入分析 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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
注册的关键点:
  • 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");
        }
    }

    // 其他方法的实现同样进行权限检查
}
  • 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

在上述示例中,每次执行数据库操作前都会调用 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

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);
    }
}
  • 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

通过上述单元测试,可以验证 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);
}
  • 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
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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
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;
    }
}
  • 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

6. 总结

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

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

在这里插入图片描述

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号