Android15 ContentProvider 深度源码分析(上)
目录
1. 概述
ContentProvider是Android四大组件之一,是Android系统中用于跨进程数据共享的标准机制。它提供了一种统一的接口,允许应用程序安全地访问其他应用程序的数据,同时也能将自己的数据暴露给其他应用程序。
1.1 核心特点
- 统一的数据访问接口: 通过URI标识数据,提供CRUD操作
- 跨进程通信: 基于Binder机制实现进程间数据共享
- 权限控制: 支持多种权限保护机制
- 数据通知: 支持数据变更通知机制
- 多种存储后端: 可对接SQLite、文件、内存等多种存储方式
1.2 源码位置
frameworks/base/core/java/android/content/ContentProvider.java
frameworks/base/core/java/android/content/ContentResolver.java
frameworks/base/core/java/android/content/ContentProviderClient.java
frameworks/base/core/java/android/content/UriMatcher.java
frameworks/base/core/java/android/content/ContentValues.java
frameworks/base/core/java/android/database/Cursor.java
2. 源码结构分析
2.1 核心类层次结构
android.content.ContentProvider (抽象基类)
|
+-- ContentProvider.Transport (内部类, Binder服务端)
|
+-- 各种具体实现:
+-- SettingsProvider
+-- MediaProvider
+-- ContactsProvider
+-- CalendarProvider
...
2.2 关键源文件分析
2.2.1 ContentProvider.java
位置: frameworks/base/core/java/android/content/ContentProvider.java
ContentProvider是一个抽象类,定义了数据访问的标准接口:
java
public abstract class ContentProvider implements ContentInterface, ComponentCallbacks2 {
// 核心成员变量
private Context mContext = null;
private int mMyUid;
private String mAuthority;
private String[] mAuthorities;
private String mReadPermission;
private String mWritePermission;
private PathPermission[] mPathPermissions;
private boolean mExported;
private boolean mNoPerms;
private boolean mSingleUser;
// Transport是Binder服务端实现
private Transport mTransport = new Transport();
// 必须实现的抽象方法
public abstract boolean onCreate();
public abstract Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder);
public abstract Uri insert(Uri uri, ContentValues values);
public abstract int update(Uri uri, ContentValues values,
String selection, String[] selectionArgs);
public abstract int delete(Uri uri, String selection, String[] selectionArgs);
public abstract String getType(Uri uri);
}
2.2.2 Transport内部类
Transport是ContentProvider的Binder服务端实现,处理跨进程调用:
java
class Transport extends ContentProviderNative {
volatile AppOpsManager mAppOpsManager = null;
volatile int mReadOp = AppOpsManager.OP_NONE;
volatile int mWriteOp = AppOpsManager.OP_NONE;
volatile ContentInterface mInterface = ContentProvider.this;
@Override
public Cursor query(@NonNull AttributionSource attributionSource, Uri uri,
@Nullable String[] projection, @Nullable Bundle queryArgs,
@Nullable ICancellationSignal cancellationSignal) {
// 1. 验证URI
uri = validateIncomingUri(uri);
// 2. 权限检查
if (enforceReadPermission(attributionSource, uri)
!= PermissionChecker.PERMISSION_GRANTED) {
return new MatrixCursor(projection, 0);
}
// 3. 调用实际的query方法
return mInterface.query(uri, projection, queryArgs,
CancellationSignal.fromTransport(cancellationSignal));
}
// insert, update, delete 等方法类似
}
2.2.3 ContentResolver.java
位置: frameworks/base/core/java/android/content/ContentResolver.java
ContentResolver是客户端访问ContentProvider的统一入口:
java
public abstract class ContentResolver implements ContentInterface {
// 常用scheme常量
public static final String SCHEME_CONTENT = "content";
public static final String SCHEME_ANDROID_RESOURCE = "android.resource";
public static final String SCHEME_FILE = "file";
// MIME类型基础
public static final String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item";
public static final String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir";
// 查询参数
public static final String QUERY_ARG_SQL_SELECTION = "android:query-arg-sql-selection";
public static final String QUERY_ARG_SQL_SELECTION_ARGS = "android:query-arg-sql-selection-args";
public static final String QUERY_ARG_SORT_COLUMNS = "android:query-arg-sort-columns";
public static final String QUERY_ARG_SORT_DIRECTION = "android:query-arg-sort-direction";
// 核心方法
public final Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder) {
return query(uri, projection, selection, selectionArgs, sortOrder, null);
}
}
2.2.4 ContentProviderClient.java
位置: frameworks/base/core/java/android/content/ContentProviderClient.java
ContentProviderClient提供对特定ContentProvider的直接访问:
java
public class ContentProviderClient implements ContentInterface, AutoCloseable {
private final ContentResolver mContentResolver;
private final IContentProvider mContentProvider;
private final String mPackageName;
private final @NonNull AttributionSource mAttributionSource;
private final String mAuthority;
private final boolean mStable;
private final AtomicBoolean mClosed = new AtomicBoolean();
private final CloseGuard mCloseGuard = CloseGuard.get();
// 支持ANR检测
private long mAnrTimeout;
private NotRespondingRunnable mAnrRunnable;
public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
Bundle queryArgs, @Nullable CancellationSignal cancellationSignal)
throws RemoteException {
beforeRemote();
try {
ICancellationSignal remoteCancellationSignal = null;
if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();
remoteCancellationSignal = mContentProvider.createCancellationSignal();
cancellationSignal.setRemote(remoteCancellationSignal);
}
final Cursor cursor = mContentProvider.query(
mAttributionSource, uri, projection, queryArgs,
remoteCancellationSignal);
return new CursorWrapperInner(cursor);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
}
throw e;
} finally {
afterRemote();
}
}
}
2.2.5 UriMatcher.java
位置: frameworks/base/core/java/android/content/UriMatcher.java
UriMatcher用于URI模式匹配:
java
public class UriMatcher {
public static final int NO_MATCH = -1;
// 匹配类型
private static final int EXACT = 0; // 精确匹配
private static final int NUMBER = 1; // 数字匹配 (#)
private static final int TEXT = 2; // 文本匹配 (*)
private int mCode;
private final int mWhich;
private final String mText;
private ArrayList<UriMatcher> mChildren;
public void addURI(String authority, String path, int code) {
if (code < 0) {
throw new IllegalArgumentException("code must be positive");
}
// 解析path,构建匹配树
String[] tokens = path != null ? path.split("/") : null;
// ...
}
public int match(Uri uri) {
final List<String> pathSegments = uri.getPathSegments();
// 遍历匹配树进行URI匹配
// ...
return node.mCode;
}
}
2.2.6 ContentValues.java
位置: frameworks/base/core/java/android/content/ContentValues.java
ContentValues用于存储键值对数据:
java
public final class ContentValues implements Parcelable {
private final ArrayMap<String, Object> mMap;
// 支持的数据类型
public void put(String key, String value);
public void put(String key, Byte value);
public void put(String key, Short value);
public void put(String key, Integer value);
public void put(String key, Long value);
public void put(String key, Float value);
public void put(String key, Double value);
public void put(String key, Boolean value);
public void put(String key, byte[] value);
// 类型安全的获取方法
public String getAsString(String key);
public Long getAsLong(String key);
public Integer getAsInteger(String key);
// ...
}
2.2.7 Cursor接口
位置: frameworks/base/core/java/android/database/Cursor.java
Cursor定义了查询结果的访问接口:
java
public interface Cursor extends Closeable {
// 字段类型常量
static final int FIELD_TYPE_NULL = 0;
static final int FIELD_TYPE_INTEGER = 1;
static final int FIELD_TYPE_FLOAT = 2;
static final int FIELD_TYPE_STRING = 3;
static final int FIELD_TYPE_BLOB = 4;
// 位置操作
int getCount();
int getPosition();
boolean move(int offset);
boolean moveToPosition(int position);
boolean moveToFirst();
boolean moveToLast();
boolean moveToNext();
boolean moveToPrevious();
// 列信息
int getColumnIndex(String columnName);
int getColumnIndexOrThrow(String columnName);
String getColumnName(int columnIndex);
String[] getColumnNames();
int getColumnCount();
// 数据获取
byte[] getBlob(int columnIndex);
String getString(int columnIndex);
short getShort(int columnIndex);
int getInt(int columnIndex);
long getLong(int columnIndex);
float getFloat(int columnIndex);
double getDouble(int columnIndex);
int getType(int columnIndex);
boolean isNull(int columnIndex);
// 通知机制
void setNotificationUri(ContentResolver cr, Uri uri);
Uri getNotificationUri();
void registerContentObserver(ContentObserver observer);
void unregisterContentObserver(ContentObserver observer);
}
3. 核心类详解
3.1 IContentProvider接口
位置: frameworks/base/core/java/android/content/IContentProvider.java
IContentProvider定义了跨进程通信的接口:
java
public interface IContentProvider extends IInterface {
// 查询操作
Cursor query(@NonNull AttributionSource attributionSource, Uri url,
@Nullable String[] projection, @Nullable Bundle queryArgs,
@Nullable ICancellationSignal cancellationSignal) throws RemoteException;
// 类型获取
String getType(@NonNull AttributionSource attributionSource, Uri url)
throws RemoteException;
// CRUD操作
Uri insert(@NonNull AttributionSource attributionSource, Uri url,
ContentValues initialValues, Bundle extras) throws RemoteException;
int bulkInsert(@NonNull AttributionSource attributionSource, Uri url,
ContentValues[] initialValues) throws RemoteException;
int delete(@NonNull AttributionSource attributionSource, Uri url, Bundle extras)
throws RemoteException;
int update(@NonNull AttributionSource attributionSource, Uri url,
ContentValues values, Bundle extras) throws RemoteException;
// 文件操作
ParcelFileDescriptor openFile(@NonNull AttributionSource attributionSource,
Uri url, String mode, ICancellationSignal signal)
throws RemoteException, FileNotFoundException;
AssetFileDescriptor openAssetFile(@NonNull AttributionSource attributionSource,
Uri url, String mode, ICancellationSignal signal)
throws RemoteException, FileNotFoundException;
// 批量操作
ContentProviderResult[] applyBatch(@NonNull AttributionSource attributionSource,
String authority, ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException;
// 自定义调用
Bundle call(@NonNull AttributionSource attributionSource, String authority,
String method, @Nullable String arg, @Nullable Bundle extras)
throws RemoteException;
// 权限检查
int checkUriPermission(@NonNull AttributionSource attributionSource, Uri uri,
int uid, int modeFlags) throws RemoteException;
}
3.2 ContentProviderHelper (系统服务端)
位置: frameworks/base/services/core/java/com/android/server/am/ContentProviderHelper.java
ContentProviderHelper是ActivityManagerService中处理ContentProvider的辅助类:
java
public class ContentProviderHelper {
private final ActivityManagerService mService;
private final ArrayList<ContentProviderRecord> mLaunchingProviders = new ArrayList<>();
private final ProviderMap mProviderMap;
// 获取ContentProvider
ContentProviderHolder getContentProvider(IApplicationThread caller, String callingPackage,
String name, int userId, boolean stable) {
// 1. 权限检查
mService.enforceNotIsolatedCaller("getContentProvider");
// 2. 查找或启动Provider
return getContentProviderImpl(caller, name, null, callingUid, callingPackage,
null, stable, userId);
}
private ContentProviderHolder getContentProviderImpl(...) {
// 1. 从ProviderMap查找已注册的Provider
cpr = mProviderMap.getProviderByName(name, userId);
// 2. 如果Provider正在运行
if (providerRunning) {
// 权限检查
checkAssociationAndPermissionLocked(r, cpi, callingUid, userId, ...);
// 增加引用计数
conn = incProviderCountLocked(r, cpr, token, callingUid, ...);
return holder;
}
// 3. 如果Provider未运行,需要启动Provider进程
// ...
}
// 发布Provider (由应用进程调用)
void publishContentProviders(IApplicationThread caller,
List<ContentProviderHolder> providers) {
// ...
}
}
4. 类图设计
4.1 ContentProvider核心类图
contains
extends
implements
uses
holds
creates
uses
processes
returns
<<abstract>>
ContentProvider
-Context mContext
-String mAuthority
-String mReadPermission
-String mWritePermission
-PathPermission[] mPathPermissions
-boolean mExported
-Transport mTransport
+onCreate() : boolean
+query(Uri, String[], String, String[], String) : Cursor
+insert(Uri, ContentValues) : Uri
+update(Uri, ContentValues, String, String[]) : int
+delete(Uri, String, String[]) : int
+getType(Uri) : String
+getContext() : Context
+getReadPermission() : String
+getWritePermission() : String
+getCallingPackage() : String
Transport
-AppOpsManager mAppOpsManager
-int mReadOp
-int mWriteOp
-ContentInterface mInterface
+query() : Cursor
+insert() : Uri
+update() : int
+delete() : int
+openFile() : ParcelFileDescriptor
-enforceReadPermission() : int
-enforceWritePermission() : int
<<abstract>>
ContentProviderNative
+asBinder() : IBinder
+onTransact() : boolean
<<interface>>
IContentProvider
+query() : Cursor
+getType() : String
+insert() : Uri
+bulkInsert() : int
+delete() : int
+update() : int
+openFile() : ParcelFileDescriptor
+openAssetFile() : AssetFileDescriptor
+applyBatch() : ContentProviderResult[]
+call() : Bundle
<<abstract>>
ContentResolver
-Context mContext
-String mPackageName
+query(Uri, String[], String, String[], String) : Cursor
+insert(Uri, ContentValues) : Uri
+update(Uri, ContentValues, String, String[]) : int
+delete(Uri, String, String[]) : int
+getType(Uri) : String
+openInputStream(Uri) : InputStream
+openOutputStream(Uri) : OutputStream
+acquireContentProviderClient(Uri) : ContentProviderClient
+registerContentObserver() : void
+notifyChange() : void
ContentProviderClient
-ContentResolver mContentResolver
-IContentProvider mContentProvider
-String mAuthority
-boolean mStable
-AtomicBoolean mClosed
+query() : Cursor
+insert() : Uri
+update() : int
+delete() : int
+openFile() : ParcelFileDescriptor
+call() : Bundle
+close() : void
UriMatcher
+NO_MATCH int
-int mCode
-int mWhich
-String mText
-ArrayList<UriMatcher> mChildren
+addURI(String, String, int) : void
+match(Uri) : int
ContentValues
-ArrayMap<String,Object> mMap
+put(String, String) : void
+put(String, Integer) : void
+put(String, Long) : void
+getAsString(String) : String
+getAsInteger(String) : Integer
+getAsLong(String) : Long
+size() : int
+keySet() : Set
<<interface>>
Cursor
+getCount() : int
+moveToFirst() : boolean
+moveToNext() : boolean
+getString(int) : String
+getInt(int) : int
+getLong(int) : long
+getColumnIndex(String) : int
+close() : void
4.2 跨进程通信架构图
getContentProvider
delegate
lookup
stores
references
IPC calls
Binder transaction
local calls
ClientApp
+ContentResolver resolver
+query()
+insert()
+update()
+delete()
ActivityManagerService
+ContentProviderHelper helper
+getContentProvider()
+publishContentProviders()
ContentProviderHelper
-ProviderMap mProviderMap
-ArrayList mLaunchingProviders
+getContentProviderImpl()
+publishContentProviders()
ProviderMap
-SparseArray mProvidersByNamePerUser
-SparseArray mProvidersByClassPerUser
+getProviderByName()
+putProviderByName()
ContentProviderRecord
+ProviderInfo info
+ProcessRecord proc
+IContentProvider provider
+ArrayList connections
ProviderApp
+ContentProvider provider
+onCreate()
+query()
+insert()
<<IContentProvider>>
BinderProxy
+transact()
<<Transport>>
BinderStub
+onTransact()
5. 调用流程分析
5.1 ContentProvider调用流程图
- getContentResolver 2. query/insert/update/delete Yes
No - Get IContentProvider 4. getContentProvider 5. Check ProviderMap Yes
No - Launch process 7. installContentProviders 8. publishContentProviders 9. Return IContentProvider proxy 10. Binder IPC 11. Call ContentProvider method 12. Return result 13. Return to client Return result
Client App
ContentResolver
Provider in same process?
Direct call to ContentProvider
acquireProvider
ActivityManagerService
ContentProviderHelper
Provider running?
Return ContentProviderHolder
Start Provider Process
ActivityThread.handleBindApplication
ContentProvider.onCreate
Register to AMS
ContentProviderProxy
Transport.onTransact
ContentProvider.query/insert/...
Cursor/Uri/int
5.2 跨进程访问时序图
ContentProvider ActivityThread (Provider App) ProviderMap ContentProviderHelper ActivityManagerService ContentResolver Client App ContentProvider ActivityThread (Provider App) ProviderMap ContentProviderHelper ActivityManagerService ContentResolver Client App alt [Provider not running] Binder IPC query(uri, projection, ...) getContentProvider(authority) getContentProviderImpl() getProviderByName(authority, userId) null startProcessLocked() bindApplication() onCreate() true publishContentProviders() publishContentProviders() putProviderByName() ContentProviderRecord ContentProviderHolder IContentProvider (Binder proxy) query() via Binder Transport.query() enforceReadPermission() ContentProvider.query() Cursor Cursor
5.3 ContentProvider生命周期图
App installed
First access request
onCreate() returns true
onCreate() returns false
Serving requests
query/insert/update/delete
System low memory
Memory recovered
Process killed by LMK
Process killed
Process death
NotCreated
Creating
Created
Failed
Running
LowMemory
Killed
onCreate() called on main thread
Must return quickly
Lazy initialization recommended
CRUD operations can be called
from multiple threads
Must be thread-safe
5.4 URI匹配流程图
No
Yes
EXACT
Yes
No
NUMBER
Yes
No
TEXT
Yes
No
Yes
No
Yes
No
Example
content://contacts/people/123
UriMatcher codes:
PEOPLE = 1 (people)
PEOPLE_ID = 2 (people/#)
Receive URI
UriMatcher.match
Get authority from URI
Match authority?
Return NO_MATCH
Get path segments
Start matching from root
Current segment
Match type?
Text equals?
Move to next segment
Try next child
Is numeric?
Match any text
More segments?
Has code?
Return matched code
More children?
6. 实际案例分析
6.1 SettingsProvider分析
6.1.1 概述
SettingsProvider是Android系统设置的数据提供者,负责存储和管理系统设置项。
位置: frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
6.1.2 AndroidManifest.xml配置
xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.providers.settings"
coreApp="true"
android:sharedUserId="android.uid.system"> <!-- 系统UID -->
<application android:process="system" <!-- 运行在system_server进程 -->
android:defaultToDeviceProtectedStorage="true"
android:directBootAware="true">
<provider android:name="SettingsProvider"
android:authorities="settings"
android:multiprocess="false"
android:exported="true"
android:singleUser="true"
android:initOrder="100" <!-- 高优先级初始化 -->
android:visibleToInstantApps="true" />
</application>
</manifest>
6.1.3 数据存储架构
SettingsProvider使用XML文件存储设置数据,而非SQLite数据库:
java
public class SettingsProvider extends ContentProvider {
// 设置类型
public static final String TABLE_SYSTEM = "system"; // 系统设置
public static final String TABLE_SECURE = "secure"; // 安全设置
public static final String TABLE_GLOBAL = "global"; // 全局设置
public static final String TABLE_SSAID = "ssaid"; // 安全ID
public static final String TABLE_CONFIG = "config"; // 配置设置
// 设置存储
@GuardedBy("mLock")
private SettingsRegistry mSettingsRegistry;
@Override
public boolean onCreate() {
Settings.setInSystemServer();
synchronized (mLock) {
mSettingsRegistry = new SettingsRegistry(mHandlerThread.getLooper());
}
// 迁移旧设置
mSettingsRegistry.migrateAllLegacySettingsIfNeededLocked();
return true;
}
}
6.1.4 call()方法优化
SettingsProvider使用call()方法代替传统CRUD操作,性能更优:
java
@Override
public Bundle call(String method, String name, Bundle args) {
final int requestingUserId = getRequestingUserId(args);
switch (method) {
case Settings.CALL_METHOD_GET_CONFIG:
Setting setting = getConfigSetting(name);
return packageValueForCallResult(SETTINGS_TYPE_CONFIG, name,
requestingUserId, setting, isTrackingGeneration(args));
case Settings.CALL_METHOD_GET_GLOBAL:
Setting setting = getGlobalSetting(name);
return packageValueForCallResult(SETTINGS_TYPE_GLOBAL, name,
requestingUserId, setting, isTrackingGeneration(args));
case Settings.CALL_METHOD_PUT_SECURE:
String value = getSettingValue(args);
insertSecureSetting(name, value, tag, makeDefault, requestingUserId, ...);
break;
// ...
}
return null;
}
6.1.5 数据存储位置
/data/system/users/<userId>/
settings_system.xml # 系统设置
settings_secure.xml # 安全设置
settings_ssaid.xml # 应用安全ID
/data/system/
settings_global.xml # 全局设置 (所有用户共享)
6.2 MediaProvider分析
6.2.1 概述
MediaProvider是Android媒体数据库的提供者,管理设备上的图片、视频、音频等媒体文件索引。
位置: packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java
6.2.2 AndroidManifest.xml配置
xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.providers.media.module">
<!-- 存储权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<application android:name="com.android.providers.media.MediaApplication"
android:crossProfile="true">
<provider
android:name="com.android.providers.media.MediaProvider"
android:authorities="media"
android:grantUriPermissions="true"
android:forceUriPermissions="true"
android:exported="true" />
<!-- Documents Provider -->
<provider
android:name="com.android.providers.media.MediaDocumentsProvider"
android:authorities="com.android.providers.media.documents"
android:grantUriPermissions="true"
android:exported="true"
android:permission="android.permission.MANAGE_DOCUMENTS">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
</application>
</manifest>
6.2.3 数据存储架构
MediaProvider使用SQLite数据库存储媒体索引:
java
public class MediaProvider extends ContentProvider {
// 数据库常量
private static final String INTERNAL_DATABASE_NAME = "internal.db";
private static final String EXTERNAL_DATABASE_NAME = "external.db";
// DatabaseHelper管理数据库
private DatabaseHelper mInternalDatabase;
private DatabaseHelper mExternalDatabase;
// URI匹配器
private static final int IMAGES_MEDIA = 1;
private static final int IMAGES_MEDIA_ID = 2;
private static final int VIDEO_MEDIA = 100;
private static final int VIDEO_MEDIA_ID = 101;
private static final int AUDIO_MEDIA = 200;
private static final int AUDIO_MEDIA_ID = 201;
private static final int FILES = 700;
private static final int FILES_ID = 701;
// ...
}
6.2.4 DatabaseHelper
java
public class DatabaseHelper extends SQLiteOpenHelper implements AutoCloseable {
static final String INTERNAL_DATABASE_NAME = "internal.db";
static final String EXTERNAL_DATABASE_NAME = "external.db";
// 当前generation子句
public static final String CURRENT_GENERATION_CLAUSE =
"SELECT generation FROM local_metadata";
@Override
public void onCreate(SQLiteDatabase db) {
createLatestSchema(db);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 数据库升级逻辑
}
private void createLatestSchema(SQLiteDatabase db) {
// 创建files表
db.execSQL("CREATE TABLE files ("
+ "_id INTEGER PRIMARY KEY AUTOINCREMENT,"
+ "_data TEXT,"
+ "_size INTEGER,"
+ "format INTEGER,"
+ "parent INTEGER REFERENCES files(_id),"
+ "date_added INTEGER,"
+ "date_modified INTEGER,"
+ "mime_type TEXT,"
+ "title TEXT,"
+ "description TEXT,"
+ "_display_name TEXT,"
+ "picasa_id TEXT,"
+ "orientation INTEGER,"
+ "latitude DOUBLE,"
+ "longitude DOUBLE,"
+ "datetaken INTEGER,"
+ "mini_thumb_magic INTEGER,"
+ "bucket_id TEXT,"
+ "bucket_display_name TEXT,"
+ "isprivate INTEGER,"
+ "title_key TEXT,"
+ "artist_id INTEGER,"
+ "album_id INTEGER,"
+ "composer TEXT,"
+ "track INTEGER,"
+ "year INTEGER,"
+ "is_ringtone INTEGER,"
+ "is_music INTEGER,"
+ "is_alarm INTEGER,"
+ "is_notification INTEGER,"
+ "is_podcast INTEGER,"
+ "album_artist TEXT,"
+ "duration INTEGER,"
+ "bookmark INTEGER,"
+ "artist TEXT,"
+ "album TEXT,"
+ "resolution TEXT,"
+ "tags TEXT,"
+ "category TEXT,"
+ "language TEXT,"
+ "mini_thumb_data BLOB,"
+ "name TEXT,"
+ "media_type INTEGER,"
+ "old_id INTEGER,"
+ "is_drm INTEGER,"
+ "width INTEGER,"
+ "height INTEGER,"
+ "title_resource_uri TEXT,"
+ "owner_package_name TEXT,"
+ "color_standard INTEGER,"
+ "color_transfer INTEGER,"
+ "color_range INTEGER,"
+ "_hash BLOB,"
+ "is_pending INTEGER,"
+ "is_download INTEGER DEFAULT 0,"
+ "download_uri TEXT,"
+ "referer_uri TEXT,"
+ "is_audiobook INTEGER DEFAULT 0,"
+ "date_expires INTEGER DEFAULT NULL,"
+ "is_trashed INTEGER DEFAULT 0,"
+ "group_id INTEGER DEFAULT NULL,"
+ "primary_directory TEXT DEFAULT NULL,"
+ "secondary_directory TEXT DEFAULT NULL,"
+ "document_id TEXT DEFAULT NULL,"
+ "instance_id TEXT DEFAULT NULL,"
+ "original_document_id TEXT DEFAULT NULL,"
+ "relative_path TEXT DEFAULT NULL,"
+ "volume_name TEXT DEFAULT NULL,"
+ "artist_key TEXT DEFAULT NULL,"
+ "album_key TEXT DEFAULT NULL,"
+ "genre TEXT DEFAULT NULL,"
+ "genre_key TEXT DEFAULT NULL,"
+ "genre_id INTEGER,"
+ "author TEXT DEFAULT NULL,"
+ "bitrate INTEGER DEFAULT NULL,"
+ "capture_framerate REAL DEFAULT NULL,"
+ "cd_track_number TEXT DEFAULT NULL,"
+ "compilation INTEGER DEFAULT NULL,"
+ "disc_number TEXT DEFAULT NULL,"
+ "is_favorite INTEGER DEFAULT 0,"
+ "num_tracks INTEGER DEFAULT NULL,"
+ "writer TEXT DEFAULT NULL,"
+ "exposure_time TEXT DEFAULT NULL,"
+ "f_number TEXT DEFAULT NULL,"
+ "iso INTEGER DEFAULT NULL,"
+ "scene_capture_type INTEGER DEFAULT NULL,"
+ "generation_added INTEGER DEFAULT 0,"
+ "generation_modified INTEGER DEFAULT 0,"
+ "xmp BLOB DEFAULT NULL,"
+ "_transcode_status INTEGER DEFAULT 0,"
+ "_video_codec_type TEXT DEFAULT NULL,"
+ "_modifier INTEGER DEFAULT 0,"
+ "_special_format INTEGER DEFAULT NULL,"
+ "_user_id INTEGER DEFAULT " + UserHandle.myUserId()
+ ");");
}
}
6.2.5 URI结构
content://media/<volume>/<type>/<id>
示例:
content://media/internal/images/media # 内部存储图片列表
content://media/external/images/media/123 # 外部存储特定图片
content://media/external/video/media # 外部存储视频列表
content://media/external/audio/media # 外部存储音频列表
content://media/external/file # 文件表
6.2.6 数据存储位置
/data/data/com.android.providers.media.module/databases/
internal.db # 内部存储媒体数据库
external.db # 外部存储媒体数据库
实际媒体文件位置:
/storage/emulated/0/ # 外部存储根目录
DCIM/ # 相机照片/视频
Pictures/ # 图片
Movies/ # 视频
Music/ # 音乐
Download/ # 下载
6.3 两者对比
| 特性 | SettingsProvider | MediaProvider |
|---|---|---|
| Authority | settings | media |
| 进程 | system_server | 独立进程 |
| 存储方式 | XML文件 | SQLite数据库 |
| 数据类型 | 键值对 | 结构化媒体信息 |
| 权限模型 | 系统权限 | 运行时权限+URI权限 |
| 主要用途 | 系统配置 | 媒体文件索引 |
| call()优化 | 是 | 部分 |
7. 数据存储方式
ContentProvider支持多种数据存储后端,选择哪种取决于具体需求:
7.1 SQLite数据库
最常用的存储方式,适合结构化数据:
java
public class MyProvider extends ContentProvider {
private DatabaseHelper mDbHelper;
@Override
public boolean onCreate() {
mDbHelper = new DatabaseHelper(getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = mDbHelper.getReadableDatabase();
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(TABLE_NAME);
switch (sUriMatcher.match(uri)) {
case ITEMS:
break;
case ITEM_ID:
qb.appendWhere("_id=" + uri.getLastPathSegment());
break;
}
Cursor c = qb.query(db, projection, selection, selectionArgs,
null, null, sortOrder);
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = mDbHelper.getWritableDatabase();
long id = db.insert(TABLE_NAME, null, values);
if (id > 0) {
Uri newUri = ContentUris.withAppendedId(CONTENT_URI, id);
getContext().getContentResolver().notifyChange(newUri, null);
return newUri;
}
throw new SQLException("Failed to insert row into " + uri);
}
}
优点:
- 支持复杂查询(JOIN, GROUP BY等)
- 事务支持
- 数据完整性约束
- 成熟稳定
缺点:
- 初始化开销
- 需要处理数据库升级
7.2 文件存储
适合大型数据或二进制数据:
java
public class FileProvider extends ContentProvider {
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
File file = new File(getContext().getFilesDir(),
uri.getLastPathSegment());
int fileMode = ParcelFileDescriptor.parseMode(mode);
return ParcelFileDescriptor.open(file, fileMode);
}
@Override
public AssetFileDescriptor openAssetFile(Uri uri, String mode)
throws FileNotFoundException {
// 支持文件偏移和长度
ParcelFileDescriptor fd = openFile(uri, mode);
return new AssetFileDescriptor(fd, 0,
AssetFileDescriptor.UNKNOWN_LENGTH);
}
@Override
public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
// 返回支持的MIME类型
return new String[] {"image/*", "video/*"};
}
}
优点:
- 适合大文件
- 直接访问文件系统
- 支持流式传输
缺点:
- 不支持复杂查询
- 需要自己管理文件元数据
7.3 SharedPreferences
适合简单的键值对数据:
java
public class PrefsProvider extends ContentProvider {
private SharedPreferences mPrefs;
@Override
public boolean onCreate() {
mPrefs = getContext().getSharedPreferences("my_prefs",
Context.MODE_PRIVATE);
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
String key = uri.getLastPathSegment();
MatrixCursor cursor = new MatrixCursor(new String[]{"key", "value"});
if (key != null) {
String value = mPrefs.getString(key, null);
if (value != null) {
cursor.addRow(new Object[]{key, value});
}
} else {
// 返回所有键值对
for (Map.Entry<String, ?> entry : mPrefs.getAll().entrySet()) {
cursor.addRow(new Object[]{entry.getKey(),
String.valueOf(entry.getValue())});
}
}
return cursor;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
SharedPreferences.Editor editor = mPrefs.edit();
for (Map.Entry<String, Object> entry : values.valueSet()) {
editor.putString(entry.getKey(), String.valueOf(entry.getValue()));
}
editor.apply();
getContext().getContentResolver().notifyChange(uri, null);
return uri;
}
}
优点:
- 简单轻量
- 快速访问
- 自动持久化
缺点:
- 只支持基本数据类型
- 不支持复杂查询
- 不适合大量数据
7.4 内存存储
适合临时数据或缓存:
java
public class MemoryProvider extends ContentProvider {
private final Map<String, ContentValues> mData =
new ConcurrentHashMap<>();
@Override
public boolean onCreate() {
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
String key = uri.getLastPathSegment();
MatrixCursor cursor = new MatrixCursor(projection);
if (key != null && mData.containsKey(key)) {
ContentValues values = mData.get(key);
Object[] row = new Object[projection.length];
for (int i = 0; i < projection.length; i++) {
row[i] = values.get(projection[i]);
}
cursor.addRow(row);
}
return cursor;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
String key = String.valueOf(System.currentTimeMillis());
mData.put(key, values);
Uri newUri = Uri.withAppendedPath(uri, key);
getContext().getContentResolver().notifyChange(newUri, null);
return newUri;
}
}
优点:
- 最快访问速度
- 无I/O开销
- 适合缓存场景
缺点:
- 数据不持久
- 进程重启后丢失
- 内存占用
7.5 网络数据
通过ContentProvider代理远程数据:
java
public class NetworkProvider extends ContentProvider {
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// 从网络获取数据
MatrixCursor cursor = new MatrixCursor(projection);
try {
URL url = new URL("https://api.example.com/data");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 解析响应并填充cursor
// ...
} catch (IOException e) {
Log.e(TAG, "Network error", e);
}
return cursor;
}
}
8. 使用场景
8.1 跨进程数据共享
最主要的使用场景,允许不同应用安全地共享数据:
java
// 应用A: 提供数据
public class ContactsProvider extends ContentProvider {
@Override
public Cursor query(Uri uri, String[] projection, ...) {
// 返回联系人数据
}
}
// 应用B: 访问数据
public class ContactsReader {
public void readContacts(Context context) {
Uri uri = ContactsContract.Contacts.CONTENT_URI;
Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
while (cursor.moveToNext()) {
String name = cursor.getString(
cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
}
cursor.close();
}
}
8.2 统一数据访问接口
为同一数据提供统一的访问方式:
java
// 不同来源的数据通过同一接口访问
public class UnifiedMediaProvider extends ContentProvider {
@Override
public Cursor query(Uri uri, String[] projection, ...) {
switch (sUriMatcher.match(uri)) {
case LOCAL_MEDIA:
return queryLocalMedia(uri, projection, ...);
case CLOUD_MEDIA:
return queryCloudMedia(uri, projection, ...);
case COMBINED:
return new MergeCursor(new Cursor[] {
queryLocalMedia(uri, projection, ...),
queryCloudMedia(uri, projection, ...)
});
}
return null;
}
}
8.3 系统服务数据提供
为系统组件提供数据服务:
java
// SettingsProvider为整个系统提供设置数据
// 任何组件都可以通过统一接口访问
// 读取设置
String value = Settings.System.getString(
context.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS);
// 修改设置
Settings.System.putString(
context.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS,
"128");
8.4 数据同步机制
与SyncAdapter配合实现数据同步:
java
public class SyncableProvider extends ContentProvider {
@Override
public Uri insert(Uri uri, ContentValues values) {
long id = db.insert(TABLE_NAME, null, values);
if (id > 0) {
Uri newUri = ContentUris.withAppendedId(CONTENT_URI, id);
// 触发数据同步
getContext().getContentResolver().notifyChange(newUri, null,
ContentResolver.NOTIFY_SYNC_TO_NETWORK);
return newUri;
}
return null;
}
}
8.5 文件共享
使用FileProvider安全分享文件:
java
// 在AndroidManifest.xml中配置
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
// 分享文件
public void shareFile(Context context, File file) {
Uri contentUri = FileProvider.getUriForFile(context,
context.getPackageName() + ".fileprovider", file);
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("application/pdf");
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(Intent.createChooser(shareIntent, "Share"));
}
9. 安全处理
9.1 权限声明
9.1.1 基本权限
xml
<provider
android:name=".MyProvider"
android:authorities="com.example.myprovider"
android:exported="true"
android:readPermission="com.example.READ_DATA"
android:writePermission="com.example.WRITE_DATA" />
9.1.2 路径权限 (Path Permissions)
为不同路径设置不同权限:
xml
<provider
android:name=".MyProvider"
android:authorities="com.example.myprovider"
android:exported="true">
<path-permission
android:pathPrefix="/public"
android:readPermission="com.example.READ_PUBLIC"
android:writePermission="com.example.WRITE_PUBLIC" />
<path-permission
android:pathPrefix="/private"
android:readPermission="com.example.READ_PRIVATE"
android:writePermission="com.example.WRITE_PRIVATE" />
</provider>
9.1.3 签名权限
限制只有相同签名的应用可以访问:
xml
<!-- 定义权限 -->
<permission
android:name="com.example.SIGNATURE_PERMISSION"
android:protectionLevel="signature" />
<!-- 使用权限 -->
<provider
android:name=".SecureProvider"
android:authorities="com.example.secure"
android:permission="com.example.SIGNATURE_PERMISSION"
android:exported="true" />
9.2 URI权限
9.2.1 配置URI权限
xml
<provider
android:name=".MyProvider"
android:authorities="com.example.myprovider"
android:exported="true"
android:grantUriPermissions="true">
<!-- 限制可授权的URI -->
<grant-uri-permission android:pathPrefix="/shareable" />
</provider>
9.2.2 授予URI权限
java
// 方式1: 通过Intent授权
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(contentUri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);
// 方式2: 显式授权
context.grantUriPermission(
targetPackage,
contentUri,
Intent.FLAG_GRANT_READ_URI_PERMISSION);
// 撤销授权
context.revokeUriPermission(contentUri,
Intent.FLAG_GRANT_READ_URI_PERMISSION);
9.3 运行时权限检查
java
public class SecureProvider extends ContentProvider {
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// 检查调用者权限
if (getContext().checkCallingPermission(READ_PERMISSION)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Permission denied: " + READ_PERMISSION);
}
// 获取调用者信息
String callingPackage = getCallingPackage();
int callingUid = Binder.getCallingUid();
// 检查URI权限
if (getContext().checkCallingUriPermission(uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("No URI permission for: " + uri);
}
// 执行查询
return doQuery(uri, projection, selection, selectionArgs, sortOrder);
}
}
9.4 ContentProvider.Transport中的权限检查
java
class Transport extends ContentProviderNative {
@PermissionCheckerManager.PermissionResult
private int enforceReadPermission(@NonNull AttributionSource attributionSource,
Uri uri) throws SecurityException {
final int result = enforceReadPermissionInner(uri, attributionSource);
if (result != PermissionChecker.PERMISSION_GRANTED) {
return result;
}
// 检查AppOps
if (mTransport.mReadOp != AppOpsManager.OP_NONE) {
return PermissionChecker.checkOpForDataDelivery(getContext(),
AppOpsManager.opToPublicName(mTransport.mReadOp),
attributionSource, null);
}
return PermissionChecker.PERMISSION_GRANTED;
}
protected int enforceReadPermissionInner(Uri uri,
@NonNull AttributionSource attributionSource) throws SecurityException {
final Context context = getContext();
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
// 1. 同应用直接允许
if (UserHandle.isSameApp(uid, mMyUid)) {
return PermissionChecker.PERMISSION_GRANTED;
}
// 2. 检查组件权限
if (mExported && checkUser(pid, uid, context)) {
final String componentPerm = getReadPermission();
if (componentPerm != null) {
final int result = checkPermission(componentPerm, attributionSource);
if (result == PermissionChecker.PERMISSION_GRANTED) {
return PermissionChecker.PERMISSION_GRANTED;
}
}
}
// 3. 检查路径权限
final PathPermission[] pps = getPathPermissions();
if (pps != null) {
final String path = uri.getPath();
for (PathPermission pp : pps) {
final String pathPerm = pp.getReadPermission();
if (pathPerm != null && pp.match(path)) {
final int result = checkPermission(pathPerm, attributionSource);
if (result == PermissionChecker.PERMISSION_GRANTED) {
return PermissionChecker.PERMISSION_GRANTED;
}
}
}
}
// 4. 检查URI权限
if (context.checkUriPermission(uri, pid, uid,
Intent.FLAG_GRANT_READ_URI_PERMISSION)
== PackageManager.PERMISSION_GRANTED) {
return PermissionChecker.PERMISSION_GRANTED;
}
throw new SecurityException("Permission Denial...");
}
}
9.5 安全最佳实践
java
public class SecureBestPracticesProvider extends ContentProvider {
// 1. 默认不导出
// android:exported="false"
// 2. 使用强权限保护
// android:permission="signature|privileged"
// 3. 验证输入数据
@Override
public Uri insert(Uri uri, ContentValues values) {
// 验证ContentValues
validateInput(values);
// 使用参数化查询防止SQL注入
// 避免: db.execSQL("SELECT * FROM table WHERE id = " + id);
// 使用: db.query(..., "id = ?", new String[]{id}, ...);
// ...
}
// 4. 限制返回数据
@Override
public Cursor query(Uri uri, String[] projection, ...) {
// 验证和限制projection
String[] safeProjection = validateProjection(projection);
// 限制返回行数
String limitClause = "1000";
// ...
}
// 5. 清理调用者身份
private void doPrivilegedOperation() {
final long token = Binder.clearCallingIdentity();
try {
// 执行需要更高权限的操作
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
10. 性能优化建议
10.1 延迟初始化
java
public class OptimizedProvider extends ContentProvider {
private DatabaseHelper mDbHelper;
private volatile boolean mInitialized = false;
@Override
public boolean onCreate() {
// onCreate在主线程执行,应该快速返回
// 延迟数据库初始化到第一次使用时
return true;
}
private synchronized void ensureInitialized() {
if (!mInitialized) {
mDbHelper = new DatabaseHelper(getContext());
mInitialized = true;
}
}
@Override
public Cursor query(Uri uri, String[] projection, ...) {
ensureInitialized();
// ...
}
}
10.2 批量操作
java
// 使用applyBatch进行批量操作
ArrayList<ContentProviderOperation> operations = new ArrayList<>();
for (Contact contact : contacts) {
operations.add(ContentProviderOperation
.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(RawContacts.ACCOUNT_TYPE, accountType)
.withValue(RawContacts.ACCOUNT_NAME, accountName)
.build());
}
// 一次性执行所有操作
context.getContentResolver().applyBatch(
ContactsContract.AUTHORITY, operations);
10.3 使用bulkInsert
java
// Provider端实现
@Override
public int bulkInsert(Uri uri, ContentValues[] valuesArray) {
SQLiteDatabase db = mDbHelper.getWritableDatabase();
db.beginTransaction();
try {
int numInserted = 0;
for (ContentValues values : valuesArray) {
long id = db.insert(TABLE_NAME, null, values);
if (id > 0) {
numInserted++;
}
}
db.setTransactionSuccessful();
return numInserted;
} finally {
db.endTransaction();
}
}
// 客户端使用
ContentValues[] valuesArray = new ContentValues[data.size()];
for (int i = 0; i < data.size(); i++) {
ContentValues values = new ContentValues();
values.put("name", data.get(i).getName());
valuesArray[i] = values;
}
context.getContentResolver().bulkInsert(CONTENT_URI, valuesArray);
10.4 使用call()方法
call()方法比标准CRUD操作更高效:
java
// Provider端
@Override
public Bundle call(String method, String arg, Bundle extras) {
switch (method) {
case "GET_SETTING":
String value = getSetting(arg);
Bundle result = new Bundle();
result.putString("value", value);
return result;
case "PUT_SETTING":
String newValue = extras.getString("value");
putSetting(arg, newValue);
return null;
case "BATCH_GET":
String[] keys = extras.getStringArray("keys");
return batchGetSettings(keys);
}
return super.call(method, arg, extras);
}
// 客户端使用
Bundle result = context.getContentResolver().call(
Settings.AUTHORITY, "GET_SETTING", "screen_brightness", null);
String brightness = result.getString("value");
10.5 使用ContentProviderClient
java
// 获取稳定连接,避免重复连接开销
ContentProviderClient client = null;
try {
client = context.getContentResolver()
.acquireContentProviderClient(AUTHORITY);
// 执行多次操作
Cursor c1 = client.query(uri1, projection, null, null, null);
Cursor c2 = client.query(uri2, projection, null, null, null);
client.insert(uri3, values);
} finally {
if (client != null) {
client.close();
}
}
// 或使用try-with-resources (API 24+)
try (ContentProviderClient client =
context.getContentResolver().acquireContentProviderClient(AUTHORITY)) {
// ...
}
10.6 通知优化
java
// 批量通知
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
SQLiteDatabase db = mDbHelper.getWritableDatabase();
db.beginTransaction();
try {
for (ContentValues value : values) {
db.insert(TABLE_NAME, null, value);
// 不要在循环中通知
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
// 只发送一次通知
getContext().getContentResolver().notifyChange(uri, null);
return values.length;
}
10.7 投影限制
java
// 只查询需要的列
String[] projection = new String[] {
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.DATE_TAKEN
};
Cursor cursor = context.getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection, // 而不是null
selection,
selectionArgs,
sortOrder);
10.8 使用CancellationSignal
java
// 支持取消长时间查询
CancellationSignal cancellationSignal = new CancellationSignal();
// 在后台线程执行查询
executor.execute(() -> {
try {
Cursor cursor = context.getContentResolver().query(
uri,
projection,
null,
null,
null,
cancellationSignal);
// 处理结果
} catch (OperationCanceledException e) {
// 查询被取消
}
});
// 需要取消时
cancellationSignal.cancel();
10.9 内存管理
java
// 使用分页查询大数据集
int offset = 0;
int limit = 100;
while (true) {
Bundle queryArgs = new Bundle();
queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, offset);
queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, limit);
Cursor cursor = context.getContentResolver().query(
uri, projection, queryArgs, null);
if (cursor == null || cursor.getCount() == 0) {
if (cursor != null) cursor.close();
break;
}
// 处理当前批次
while (cursor.moveToNext()) {
// ...
}
cursor.close();
offset += limit;
}
总结
ContentProvider是Android系统中实现跨进程数据共享的核心组件,具有以下关键特点:
- 统一接口: 提供标准的CRUD操作和URI寻址机制
- 进程隔离: 基于Binder实现安全的跨进程通信
- 权限控制: 支持多层次的权限保护机制
- 灵活存储: 可对接多种数据存储后端
- 通知机制: 支持数据变更的实时通知
在实际开发中,应该:
- 根据数据特点选择合适的存储方式
- 合理设计URI结构和权限模型
- 注意性能优化和安全防护
- 遵循Android平台的最佳实践
参考资源
- AOSP源码:
frameworks/base/core/java/android/content/ - AOSP源码:
frameworks/base/core/java/android/app/ActivityThread.java - AOSP源码:
frameworks/base/services/core/java/com/android/server/am/ - SettingsProvider源码:
frameworks/base/packages/SettingsProvider/ - MediaProvider源码:
packages/providers/MediaProvider/ - ContactsProvider源码:
packages/providers/ContactsProvider/ - CalendarProvider源码:
packages/providers/CalendarProvider/ - Android官方文档: Content Providers Guide
致敬前辈,砥砺前行!