赞
踩
在 Android 中,ContentProvider 是四大组件之一,用于在不同应用程序之间共享数据。它提供了一种统一的接口来访问数据,并确保数据安全。
ContentProvider 可以用于共享各种类型的数据,包括:
要使用 ContentProvider,需要先创建一个 ContentProvider 类。该类必须继承自 ContentProvider 抽象类,并实现以下方法:
ContentProvider 类还可以提供其他方法,例如:
要访问 ContentProvider 中的数据,可以使用 ContentResolver 类。ContentResolver 类提供了一系列方法来查询、插入、更新和删除数据。
例如,以下代码将查询 ContentProvider 中的所有联系人:
Cursor cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
以下代码将插入一条新联系人到 ContentProvider:
ContentValues values = new ContentValues();
values.put(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, "John Doe");
values.put(ContactsContract.CommonDataKinds.Phone.NUMBER, "123-456-7890");
getContentResolver().insert(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, values);
ContentProvider 是 Android 中共享数据的重要机制。它提供了一种安全、统一的方式来访问不同应用程序中的数据。
以下是 ContentProvider 的一些优点:
以下是 ContentProvider 的一些缺点:
总体而言,ContentProvider 是 Android 中共享数据的重要机制。它提供了一种安全、统一的方式来访问不同应用程序中的数据。
ContentProvider 即是内容提供者,内容提供给谁?当然是当前Project本身(进程内)或者是Android系统中的其他Project(进程间),仅此而已。可以理解成提供数据给Android系统中的所有应用,这就是他存在的意义。
ContentProvider 的实现原理主要基于 Binder 和共享内存机制。
1. Binder 机制
Binder 是 Android 中一种进程间通信 (IPC) 机制,它允许不同进程之间相互调用方法。ContentProvider 使用 Binder 机制来实现跨进程的数据共享。
2. 共享内存机制
共享内存是一种 IPC 机制,它允许不同进程共享同一块内存区域。ContentProvider 使用共享内存机制来提高数据传输效率。
具体来说,ContentProvider 的实现原理如下:
private void handleBindApplication(AppBindData data) { //在运行时将当前执行线程注册为敏感线程 VMRuntime.registerSensitiveThread(); ... //标记进程起始时间 Process.setStartTimes(SystemClock.elapsedRealtime(), SystemClock.uptimeMillis()); ... //设置进程名字 Process.setArgV0(data.processName); //设置一个标记位,androidQ及以上版本一些不明确数组相关的类会抛出数组越界异常 //例如[SparseArray的keyAt和setValueAt在Q之前的版本不会抛出异常](/https://blog.csdn.net/wzz18749670290/article/details/109352466) UtilConfig.setThrowExceptionForUpperArrayOutOfBounds( data.appInfo.targetSdkVersion >= Build.VERSION_CODES.Q); //androidP之前用BitmapFactory解码Bitmap,会放大density。android P及以后,用ImageDecoder解码Bitmap,会跳过upscale节约内存 ImageDecoder.sApiLevel = data.appInfo.targetSdkVersion; //重置系统时区 TimeZone.setDefault(null); //断点调试相关 if (data.debugMode != ApplicationThreadConstants.DEBUG_OFF) { // XXX should have option to change the port. ... } ... //渲染调试相关 boolean isAppDebuggable = (data.appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; HardwareRenderer.setDebuggingEnabled(isAppDebuggable || Build.IS_DEBUGGABLE); HardwareRenderer.setPackageName(data.appInfo.packageName); //初始化HTTP代理 final IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); if (b != null) { final IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); try { Proxy.setHttpProxySystemProperty(service.getProxyForNetwork(null)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } //创建Instrumentation并初始化 // Continue loading instrumentation. if (ii != null) { ApplicationInfo instrApp; try { instrApp = getPackageManager().getApplicationInfo(ii.packageName, 0, UserHandle.myUserId()); } catch (RemoteException e) { instrApp = null; } if (instrApp == null) { instrApp = new ApplicationInfo(); } ii.copyTo(instrApp); instrApp.initForUser(UserHandle.myUserId()); final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo, appContext.getClassLoader(), false, true, false); final ContextImpl instrContext = ContextImpl.createAppContext(this, pi, appContext.getOpPackageName()); try { //通过ClassLoader创建Instrumentation final ClassLoader cl = instrContext.getClassLoader(); mInstrumentation = (Instrumentation) cl.loadClass(data.instrumentationName.getClassName()).newInstance(); } catch (Exception e) { ... } //初始化Instrumentation final ComponentName component = new ComponentName(ii.packageName, ii.name); mInstrumentation.init(this, instrContext, appContext, component, data.instrumentationWatcher, data.instrumentationUiAutomationConnection); } else { mInstrumentation = new Instrumentation(); mInstrumentation.basicInit(this); } ... Application app; try { //创建application,这里面会调用application的attachBaseContext,这里的info对应的class是LoadedApk.java,最终也是通过classLoader创建Application app = data.info.makeApplication(data.restrictedBackupMode, null); ... if (!data.restrictedBackupMode) { if (!ArrayUtils.isEmpty(data.providers)) { //这里调用installProvider()->AppComponentFactory.instantiateProvider-> //localProvider.attachInfo()->ContentProvider.onCreate(); //看到这里就明白了为什么LeakCanary2.0不需要在Application中手动初始化 installContentProviders(app, data.providers); } } //调用application的onCreate mInstrumentation.callApplicationOnCreate(app); } //预加载字体资源 FontsContract.setApplicationContextForResources(appContext); ... }
installContentProviders 方法就是会调用到 provider#onCreate() 方法,所以启动时机是application#attch 到 onCreate 之间。
详细:https://blog.csdn.net/guojingbu/article/details/117676114
要在 manifest 内配置 ContentProvider,需要在 <application>
标签内添加 <provider>
子标签。<provider>
标签的属性如下:
以下是一个示例:
<application>
<provider
android:name=".MyContentProvider"
android:authorities="com.example.myapp.provider"
android:exported="true" />
</application>
以下是各个属性的详细说明:
属性值:ContentProvider 类的完整类名。
说明:该属性是必需的,用于指定 ContentProvider 的实现类。
属性值:ContentProvider 的权限。
说明:该属性是必需的,用于指定 ContentProvider 的访问权限。权限是一个字符串,通常以 com.
开头,后面是应用程序的包名和 ContentProvider 的名称。
属性值:true 或 false。
说明:该属性默认为 false,表示 ContentProvider 只能被同一应用程序的其他组件访问。如果将其设置为 true,则 ContentProvider 可以被其他应用程序访问。
属性值:ContentProvider 所运行的进程。
说明:该属性默认为 :process
,表示 ContentProvider 与应用程序的主进程运行在同一个进程中。如果将其设置为 :remote
,则 ContentProvider 将运行在单独的进程中。
属性值:ContentProvider 的初始化顺序。
说明:该属性的取值范围是 0 到 99999,数字越大,初始化顺序越靠前。
注意:
<uses-permission>
标签。以下是一个使用 ContentProvider 访问数据库的示例:
<application>
<provider
android:name=".MyContentProvider"
android:authorities="com.example.myapp.provider"
android:exported="true" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</application>
代码示例:
public class MyContentProvider extends ContentProvider { @Override public boolean onCreate() { // 初始化数据库 SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase( getContext().getDatabasePath("my.db"), null); // 创建表 db.execSQL("CREATE TABLE IF NOT EXISTS my_table (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER)"); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // 查询数据库 SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase( getContext().getDatabasePath("my.db"), null); Cursor cursor = db.query("my_table", projection, selection, selectionArgs, null, null, sortOrder); return cursor; } @Override public Uri insert(Uri uri, ContentValues values) { // 插入数据到数据库 SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase( getContext().getDatabasePath("my.db"), null); long rowId = db.insert("my_table", null, values); Uri resultUri = Uri.parse("content://com.example.myapp.provider/my_table/" + rowId); return resultUri; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // 更新数据库中的数据 SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase( getContext().getDatabasePath("my.db"), null); int count = db.update("my_table", values, selection, selectionArgs); return count; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // 删除数据库中的数据 SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase( getContext().getDatabasePath("my.db"), null); int count = db.delete("my_table", selection, selectionArgs);
更多属性,ContentProvider
还有以下几个属性:
android:permission
android:multiprocess:指示 ContentProvider 是否可以在多个进程中运行。默认值为 false
,表示 ContentProvider 只能在单个进程中运行。如果设置为 true
,则 ContentProvider 可以运行在多个进程中。
android:readPermission:指定读取 ContentProvider 数据所需的权限。如果未设置,则使用 android:permission
属性的值。
android:writePermission:指定写入 ContentProvider 数据所需的权限。如果未设置,则使用 android:permission
属性的值。
android:grantUriPermissions:指示 ContentProvider 是否可以授予其他应用程序对特定 URI 的访问权限。默认值为 false
,表示 ContentProvider 不能授予其他应用程序对 URI 的访问权限。如果设置为 true
,则 ContentProvider 可以授予其他应用程序对 URI 的访问权限。
android:pathPermission:指定对 ContentProvider 中特定路径的访问权限。该属性可以用于定义更细粒度的权限控制。
android:syncable:指示 ContentProvider 是否支持数据同步。默认值为 false
,表示 ContentProvider 不支持数据同步。如果设置为 true
,则 ContentProvider 支持数据同步。
以下是这些属性的详细说明:
属性值:true 或 false。
说明:该属性的默认值为 false
,表示 ContentProvider 只能在单个进程中运行。如果设置为 true
,则 ContentProvider 可以运行在多个进程中。
属性值:权限字符串。
说明:该属性指定读取 ContentProvider 数据所需的权限。如果未设置,则使用 android:permission
属性的值。
属性值:权限字符串。
说明:该属性指定写入 ContentProvider 数据所需的权限。如果未设置,则使用 android:permission
属性的值。
属性值:true 或 false。
说明:该属性的默认值为 false
,表示 ContentProvider 不能授予其他应用程序对 URI 的访问权限。如果设置为 true
,则 ContentProvider 可以授予其他应用程序对 URI 的访问权限。
属性值:路径权限列表。
说明:该属性可以用于定义更细粒度的权限控制。例如,可以指定只有拥有 com.example.myapp.permission.READ_CONTACTS
权限的应用程序才能读取联系人数据。
属性值:true 或 false。
说明:该属性的默认值为 false
,表示 ContentProvider 不支持数据同步。如果设置为 true
,则 ContentProvider 支持数据同步。
android:permission 属性用于配置 ContentProvider 的读写权限。
以下是使用 android:permission 配置权限的示例:
<application>
<provider
android:name=".MyContentProvider"
android:authorities="com.example.myapp.provider"
android:exported="true"
android:permission="com.example.myapp.permission.READ_WRITE" />
</application>
代码示例:
public class MyContentProvider extends ContentProvider { @Override public boolean onCreate() { // 初始化数据库 SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase( getContext().getDatabasePath("my.db"), null); // 创建表 db.execSQL("CREATE TABLE IF NOT EXISTS my_table (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER)"); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // 检查权限 if (getContext().checkCallingOrSelfPermission("com.example.myapp.permission.READ") != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Permission denied to read data"); } // 查询数据库 SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase( getContext().getDatabasePath("my.db"), null); Cursor cursor = db.query("my_table", projection, selection, selectionArgs, null, null, sortOrder); return cursor; } @Override public Uri insert(Uri uri, ContentValues values) { // 检查权限 if (getContext().checkCallingOrSelfPermission("com.example.myapp.permission.WRITE") != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Permission denied to write data"); } // 插入数据到数据库 SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase( getContext().getDatabasePath("my.db"), null); long rowId = db.insert("my_table", null, values); Uri resultUri = Uri.parse("content://com.example.myapp.provider/my_table/" + rowId); return resultUri; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // 检查权限 if (getContext().checkCallingOrSelfPermission("com.example.myapp.permission.WRITE") != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Permission denied to write data"); } // 更新数据库中的数据 SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase( getContext().getDatabasePath("my.db"), null); int count = db.update("my_table", values, selection, selectionArgs); return count; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // 检查权限 if (getContext().checkCallingOrSelfPermission("com.example.myapp.permission.WRITE") != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Permission denied to write data"); } // 删除数据库中的数据 SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase( getContext().getDatabasePath("my.db"), null); int count = db.delete("my_table", selection, selectionArgs); return count; } }
在上述示例中:
<provider>
标签的 android:permission
属性设置为 com.example.myapp.permission.READ_WRITE
,表示只有拥有 com.example.myapp.permission.READ_WRITE
权限的应用程序才能访问 ContentProvider。query()
、insert()
、update()
和 delete()
方法会检查调用者的权限。如果调用者没有相应的权限,则会抛出 SecurityException
异常。使用 android:permission 配置权限的优点:
使用 android:permission 配置权限的缺点:
在实际开发中,可以根据需要选择使用 android:permission 或 android:grantUriPermissions 配置权限。
如果您需要更细粒度的权限控制,可以使用 android:permission 配置权限。
如果您只需要简单的读写权限控制,可以使用 android:grantUriPermissions 配置权限。
当 android:grantUriPermissions
属性设置为 false
时,其他应用程序不能通过直接访问 ContentProvider 来访问数据。但是,其他应用程序可以通过以下方式访问数据:
ContentResolver 提供了一系列方法来访问 ContentProvider 中的数据。其他应用程序可以使用这些方法来查询、插入、更新和删除数据。
例如,以下代码使用 ContentResolver 查询 ContentProvider 中的所有联系人:
Cursor cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
ContentProvider 可以使用 UriMatcher 来匹配 URI 并执行相应的操作。其他应用程序可以使用 UriMatcher 来确定要调用的 ContentProvider 方法。
例如,以下代码使用 UriMatcher 来匹配 URI 并执行相应的操作:
public class MyContentProvider extends ContentProvider {
private UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
@Override
public boolean onCreate() {
// 初始化 UriMatcher
uriMatcher.addURI(“com.example.myapp.provider”, “contacts”, ContactsContract.CommonDataKinds.Phone.CONTENT_URI);
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// 匹配 URI
int match = uriMatcher.match(uri);
switch (match) {
case ContactsContract.CommonDataKinds.Phone.CONTENT_URI:
// 查询联系人
Cursor cursor = db.query("contacts", projection, selection, selectionArgs, null, null, sortOrder);
return cursor;
}
return null;
}
}
ContentProvider 可以通过调用 grantUriPermission()
方法来授予其他应用程序对特定 URI 的访问权限。其他应用程序可以使用共享 URI 来访问数据。
例如,以下代码授予其他应用程序对联系人数据的访问权限:
public class MyContentProvider extends ContentProvider {
@Override
public boolean onCreate() {
// 初始化数据库
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(
getContext().getDatabasePath(“my.db”), null);
// 创建表
db.execSQL("CREATE TABLE IF NOT EXISTS contacts (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER)");
return true;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// 插入数据到数据库
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(
getContext().getDatabasePath(“my.db”), null);
long rowId = db.insert("contacts", null, values);
Uri resultUri = Uri.parse("content://com.example.myapp.provider/contacts/" + rowId);
// 授予其他应用程序对 URI 的访问权限
getContext().grantUriPermission("com.example.otherapp", resultUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
return resultUri;
}
}
在实际开发中,可以根据需要选择使用上述方法来访问数据。
如果您需要授予其他应用程序对数据的临时访问权限,可以使用 grantUriPermission()
方法。
如果您需要授予其他应用程序对数据的永久访问权限,可以使用 android:pathPermission
属性。
getType()
方法用于获取 ContentProvider 提供的数据类型。该方法返回一个 MIME 类型字符串,用于指示数据的类型。
MIME 类型是一种用于表示数据类型的标准化方法。它由两个部分组成:
image
表示图像数据,video
表示视频数据,text
表示文本数据。image/png
表示 PNG 图像数据,video/mp4
表示 MP4 视频数据,text/plain
表示纯文本数据。ContentProvider 的 getType()
方法可以用于以下目的:
应用程序可以使用 MIME 类型来确定如何处理数据。例如,如果应用程序收到一个 image/png
类型的 URI,则应用程序可以使用图像查看器来显示图像。
应用程序可以使用 MIME 类型来验证数据是否有效。例如,如果应用程序收到一个 text/plain
类型的 URI,则应用程序可以检查数据是否为纯文本。
应用程序可以使用 MIME 类型来过滤数据。例如,如果应用程序只想显示图像,则应用程序可以使用 image/*
MIME 类型来过滤其他类型的数据。
以下是一个示例:
public class MyContentProvider extends ContentProvider { @Override public String getType(Uri uri) { // 根据 URI 返回不同的 MIME 类型 switch (uri.getPath()) { case "/contacts": return ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE; case "/images": return "image/*"; case "/videos": return "video/*"; default: return null; } } }
在上述示例中:
getType()
方法根据 URI 返回不同的 MIME 类型。/contacts
URI,getType()
方法返回 ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE
,表示该 URI 提供联系人数据。/images
URI,getType()
方法返回 image/*
,表示该 URI 提供图像数据。/videos
URI,getType()
方法返回 video/*
,表示该 URI 提供视频数据。请注意,并非所有 ContentProvider 都需要实现 getType()
方法。如果 ContentProvider 提供的数据类型是固定的,则可以省略该方法。
Bard 和 chatGpt
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。