深入分析 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 的通知机制允许在数据发生变化时通知观察者,以便其更新数据。通知机制的实现主要依赖于 ContentObserver
和 ContentResolver.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,在实际项目中实现高效、安全的数据操作。
欢迎点赞|关注|收藏|评论,您的肯定是我创作的动力 |