赞
踩
为了进一步深入理解 ContentProvider 的工作流程,我们将探讨其在实际应用中的设计和实现细节,包括 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 所需的权限。权限管理是 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()
方法,检查调用方是否具有相应的权限。
ContentProvider 的通知机制允许在数据发生变化时通知观察者,以便其更新数据。通知机制的实现主要依赖于 ContentObserver
和 ContentResolver.notifyChange()
方法。
@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; }
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()
方法中处理数据变化,执行相应的更新操作。
为了确保 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 的各项功能,确保其行为符合预期。
当应用首次访问 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); }
当应用通过 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); } }
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; } }
通过详细的系统代码分析,我们深入了解了 Android 中 ContentProvider 的设计和实现。从 ContentProvider 的注册、权限管理、数据访问到通知机制,每一步都体现了其在跨应用数据共享中的重要性和安全性。掌握这些底层实现和工作流程,开发者可以更好地设计和优化 ContentProvider,在实际项目中实现高效、安全的数据操作。
欢迎点赞|关注|收藏|评论,您的肯定是我创作的动力 |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。