电子网站风格设计,wordpress如何设置成伪静态页面,横向网站模板,全校网站建设与管理该系列文章总纲链接#xff1a;专题总纲目录 Android Framework 总纲 本章关键点总结 说明#xff1a; 说明#xff1a;本章节主要解读ContentProvider组件的基本知识。关注思维导图中左上侧部分即可。
有了前面activity组件分析、service组件分析、广播组件分析的基…该系列文章总纲链接专题总纲目录 Android Framework 总纲 本章关键点总结 说明 说明本章节主要解读ContentProvider组件的基本知识。关注思维导图中左上侧部分即可。
有了前面activity组件分析、service组件分析、广播组件分析的基础基于此接下来我们来分析ContentProvider组件的基本流程ContentProvider主要涉及2个
ContentProvider的注册开即启动后解析和处理ContentProvider组件。getContentResolver然后执行对应ContentProvider.query方法。
本章我们先对ContentProvider组件的应用有基本的了解再详细分析第一个流程。
1 ContentProvider组件解读
1.1 理解ContentProvider组件
ContentProvider 是 Android 框架中的一个核心组件它专门设计用于在应用之间共享数据。它通过URI标识数据并提供一组标准的CRUD操作创建、读取、更新、删除同时支持数据封装、安全性控制和多用户管理使得数据访问变得抽象化和标准化。
ContentProvider组件具体内容解读如下
数据共享机制ContentProvider 提供了一种标准化的接口允许不同应用或应用的不同部分访问和共享数据。URI 访问每个 ContentProvider 都有一个唯一的标识符authority客户端可以通过构建 content 类型的 Uri 来访问提供的数据。MIME 类型ContentProvider 能够返回数据的 MIME 类型这有助于客户端理解数据的性质并相应地处理。CRUD 操作ContentProvider 实现了创建Create、读取Retrieve、更新Update和删除Delete数据的基本操作。数据封装它封装了数据存储的细节客户端无需了解数据是如何存储和维护的。安全性通过权限控制ContentProvider 可以限制对敏感数据的访问确保只有授权的应用或用户可以访问。多用户支持在多用户环境中ContentProvider 可以管理不同用户的数据隔离。系统服务集成许多系统服务如联系人、日历和媒体库都是通过 ContentProvider 暴露给应用的。
那么ContentProvider为什么要这样设计呢解读如下
数据共享与封装设计ContentProvider的核心目的是为了在不同的应用之间共享数据同时隐藏数据的具体存储细节。这种封装允许数据提供者改变数据存储方式而不影响数据消费者。统一的数据访问接口提供一个统一的接口CRUD操作来访问数据使得不同的应用可以使用相同的方式与数据交互无论数据存储在SQLite数据库、文件系统还是其他存储介质中。安全性与权限管理通过ContentProviderAndroid可以控制对敏感数据的访问。使用URI授权和权限系统只有获得授权的应用才能访问特定的数据这增强了数据的安全性。解耦组件ContentProvider允许数据提供者和数据消费者之间保持松耦合关系。消费者无需知道数据是如何生成或存储的只需知道如何通过ContentProvider访问数据。支持内容URI和MIME类型内容URI提供了一种标准化的方式来标识和访问数据。MIME类型则允许客户端根据数据类型采取适当的操作这增加了数据交换的灵活性和多样性。跨应用功能ContentProvider使得不同的应用可以协同工作共享数据和功能为用户提供更加丰富的体验。系统服务集成许多系统服务如联系人、日历和媒体库都是通过ContentProvider暴露给应用的这使得开发者可以轻松地在自己的应用中集成这些服务。多用户支持在多用户环境中ContentProvider可以管理不同用户的数据隔离确保每个用户只能访问自己的数据。数据同步ContentProvider可以用于实现数据同步尤其是在处理需要与网络服务或云端数据同步的场景。
ContentProvider的设计反映了Android平台对于数据访问和共享的深刻理解它提供了一种强大而灵活的方式来管理应用内和应用间的数据流动。
1.2 ContentProvider被启动和使用的过程
ContentProvider 是 Android 系统中的一个组件它不像 Activity 或 Service 那样可以直接被启动。相反ContentProvider 是在系统启动时注册并在需要时被调用的。ContentProvider 是在系统启动时注册并在需要时被调用的。以下是 ContentProvider 被启动和使用的过程
1 声明 ContentProvider在应用的 AndroidManifest.xml 文件中声明 ContentProvider包括它的 authorities唯一标识和其他必要的属性。
providerandroid:name.MyContentProviderandroid:authoritiescom.example.myproviderandroid:exportedtrue /
2 实现 ContentProvider创建一个类继承自 ContentProvider并实现其抽象方法如 onCreate(), query(), insert(), delete(), update() 和 getType()。
3 系统启动时注册当应用安装到设备上时AndroidManifest.xml 中声明的 ContentProvider 会被系统读取并注册。这意味着系统知道了这个 ContentProvider 的存在和如何与其通信。
4 调用 ContentProvider其他应用或组件可以通过构建一个 ContentProvider 的 Uri 来与其交互。这个 Uri 通常是基于 ContentProvider 的 authorities 构建的。例如使用 ContentResolver 来查询 ContentProvider
ContentResolver contentResolver getContentResolver();
Uri uri Uri.parse(content://com.example.myprovider/items);
Cursor cursor contentResolver.query(uri, null, null, null, null);
5 ContentProvider 的生命周期ContentProvider 的 onCreate() 方法在 ContentProvider 第一次被调用时执行而不是在应用启动时执行。这类似于 Activity 的 onCreate() 方法。ContentProvider 通常在它们的 Uri 被查询时“启动”这意味着它们的相关方法如 query()被调用。
6 跨应用通信如果 ContentProvider 的 exported 属性设置为 true则其他应用可以访问它。如果设置为 false则只有声明它的应用可以访问。
7 多用户环境在多用户环境中ContentProvider 可以管理不同用户的数据隔离确保每个用户只能访问自己的数据。
ContentProvider 的启动和使用是被动的它们在需要时被调用而不是主动启动。这种设计使得 ContentProvider 可以作为数据共享的桥梁同时保持应用组件的解耦和灵活性。
1.3 ContentProvider应用解读
以下是两个简单的Android应用示例一个演示如何通过ContentProvider提供数据另一个演示如何通过ContentResolver获取数据。
示例1 通过ContentProvider提供数据
参考文件MyContentProvider.java的内容为:
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;public class MyContentProvider extends ContentProvider {public static final String AUTHORITY com.example.myprovider;public static final Uri CONTENT_URI Uri.parse(content:// AUTHORITY /items);private static final int ITEMS 1;private static final int ITEM_ID 2;private static final UriMatcher uriMatcher new UriMatcher(UriMatcher.NO_MATCH);static {uriMatcher.addURI(AUTHORITY, items, ITEMS);uriMatcher.addURI(AUTHORITY, items/#, ITEM_ID);}private MyDatabaseHelper dbHelper;Overridepublic boolean onCreate() {dbHelper new MyDatabaseHelper(getContext());return true;}Overridepublic Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {Cursor cursor;switch (uriMatcher.match(uri)) {case ITEMS:cursor dbHelper.getReadableDatabase().query(MyTable, projection, selection, selectionArgs, null, null, sortOrder);break;case ITEM_ID:String id uri.getLastPathSegment();cursor dbHelper.getReadableDatabase().query(MyTable, projection, _id?, new String[]{id}, null, null, sortOrder);break;default:throw new IllegalArgumentException(Unknown URI: uri);}cursor.setNotificationUri(getContext(), uri);return cursor;}// Implement other required ContentProvider methods (insert, update, delete, getType)
}
需要在AndroidManifest.xml中注册该组件具体内容为
providerandroid:name.MyContentProviderandroid:authoritiescom.example.myproviderandroid:exportedtrueandroid:grantUriPermissionstrue /
示例2 通过ContentResolver获取数据
参考文件MainActivity.java的内容为
import android.database.Cursor;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ContentResolver;
import android.content.UriMatcher;
import android.net.Uri;
import android.provider.BaseColumns;public class MainActivity extends AppCompatActivity {private static final String AUTHORITY com.example.myprovider;private static final Uri CONTENT_URI Uri.parse(content:// AUTHORITY /items);Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ContentResolver contentResolver getContentResolver();Cursor cursor contentResolver.query(CONTENT_URI, null, null, null, null);if (cursor ! null) {while (cursor.moveToNext()) {// Assuming theres a column named name in your databaseString name cursor.getString(cursor.getColumnIndex(name));// Do something with the data, e.g., display it in a ListView}cursor.close();}}
}
这两个示例展示了ContentProvider和ContentResolver的基本用法。
第一个示例中MyContentProvider提供了访问数据库的能力通过定义AUTHORITY和CONTENT_URI来标识数据的来源。第二个示例中MainActivity使用ContentResolver来查询由MyContentProvider提供的数据。
注意这两个示例仅提供了核心代码部分实际应用中需要实现ContentProvider的所有必需方法如insert、update、delete和getType。
基于此我们分析ContentProvider组件主要从两个大的方面进行分析
作为内容提供者ContentProvider随着开机启动启动注册和解析流程。我们主要关注Provider信息的存储和查询方式。也是本文接下来要解读的部分。作为数据获取者通过getContentResolver获取ContentResolver对应进而通过query方法获取数据流程。这里我们关注2个关键流程getContentResolver获取ContentProvider的流程和通过ContentProvider来查询的query流程。这部分我们放到下一篇文章来解读。
2 ContentProvider开机启动注册流程解读
ContentProvider组件是android应用在 AndroidManifest.xml 文件中声明的 receiver像这样
providerandroid:name.MyContentProviderandroid:authoritiescom.example.myproviderandroid:exportedtrueandroid:grantUriPermissionstrue /
它们的信息会在系统启动时由PMS解析并记录下来。
当 AMS 调用 PMS 的相关接口来查询 对应的Provider 时PMS 内部就会去查询当初记录下来的数据并把结果返回 AMS。
这里PMS在初始化时其中一部分是通过scanDirLI相关方法来初始化成员变量mProviders和mProvidersByAuthority这两个成员变量都是PMS中存储provider的关键变量关于PMS的初始化部分内容想有更多了解可参考文章
Android Framework 包管理子系统01PackageManagerService启动分析
这里主要看以scanDirLI为入口分析mReceivers和mProvidersByAuthority的初始化部分逻辑代码实现如下所示
//PMS//关键流程step1private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {final File[] files dir.listFiles();//...for (File file : files) {final boolean isPackage (isApkFile(file) || file.isDirectory()) !PackageInstallerService.isStageName(file.getName());if (!isPackage) {// Ignore entries which are not packagescontinue;}try {scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,scanFlags, currentTime, null);} catch (PackageManagerException e) {//...}}}//关键流程step2private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,long currentTime, UserHandle user) throws PackageManagerException {//...PackageParser.Package scannedPkg scanPackageLI(pkg, parseFlags, scanFlags| SCAN_UPDATE_SIGNATURE, currentTime, user);//...return scannedPkg;}//关键流程step3private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags,int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {boolean success false;try {final PackageParser.Package res scanPackageDirtyLI(pkg, parseFlags, scanFlags,currentTime, user);success true;return res;} finally {//...}}//关键流程step4private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {//...synchronized (mPackages) {//...// 获取应用包中ContentProvider的数量int N pkg.providers.size();int i;for (i 0; i N; i) {PackageParser.Provider p pkg.providers.get(i); // 获取ContentProvider的Provider对象// 修复ContentProvider的进程名称p.info.processName fixProcessName(pkg.applicationInfo.processName,p.info.processName, pkg.applicationInfo.uid);// 将ContentProvider添加到全局ContentProvider列表mProviders.addProvider(p);// 设置是否可同步p.syncable p.info.isSyncable;// 如果ContentProvider有authority声明if (p.info.authority ! null) {// 分割authority因为可能声明了多个String names[] p.info.authority.split(;);p.info.authority null; // 重置authorityfor (int j 0; j names.length; j) {// 如果是第二个authority并且ContentProvider是可同步的则创建一个新的Provider对象if (j 1 p.syncable) {p new PackageParser.Provider(p);p.syncable false; // 新的Provider对象不可同步}// 如果mProvidersByAuthority中没有这个authority则添加进去if (!mProvidersByAuthority.containsKey(names[j])) {mProvidersByAuthority.put(names[j], p);// 设置或更新authorityif (p.info.authority null) {p.info.authority names[j];} else {p.info.authority p.info.authority ; names[j];}} else {// 如果已经有相同的authority获取已经存在的Provider对象PackageParser.Provider other mProvidersByAuthority.get(names[j]);//...}}}}//...}//...//返回处理后的应用包对象return pkg;}
对于ContentProvider注册的解析实际上到此就结束了。这里我们主要分析和关注卡年我们提到的2个成员变量mProvidersByAuthority和mProviders详细解读如下
mProvidersByAuthority通过URI中的authority部分来快速查找ContentProvider的。ArrayMap是Android提供的一个优化版的HashMap它在存储键值对时更加高效特别是在键和值都是对象的情况下。mProvidersByAuthority是一个ArrayMap它将ContentProvider的authority即URI的authority部分映射到PackageParser.Provider对象这样可以快速通过authority查找对应的ContentProvider信息。这个映射表主要用于快速访问和检索ContentProvider特别是当你需要根据authority来获取ContentProvider的详细信息时这个映射表提供了一种快速查找的方式。mProviders通过解析Intent的URI和MIME类型来确定哪个ContentProvider应该响应这个Intent的。ProviderIntentResolver是一个用于解析和匹配Intent与ContentProvider的类。它主要负责处理Intent和ContentProvider之间的关系即根据Intent的URI和MIME类型来确定应该由哪个ContentProvider来处理这个请求。它内部使用了一些数据结构来存储和查找ContentProvider的信息以便快速匹配和解析Intent。ProviderIntentResolver通常用于动态查找和处理ContentProvider特别是在处理Intent时需要根据Intent的URI和MIME类型来确定具体的ContentProvider。
两者都是管理ContentProvider的重要机制但它们的查找依据和工作方式有所不同。接下来针对查询的流程分别进行解读。接下来针对查询的流程做更详细的解读。
2.1 通过解析intent的Uri和MIME来查找对应provider的流程
当有数据获取端想通过Uri和MIME来快速查找ContentProvider时则会通过PMS的queryContentProviders方法来查询。接下来我们来查看该方法的实现代码如下
//PMSOverridepublic ListProviderInfo queryContentProviders(String processName,int uid, int flags) {ArrayListProviderInfo finalList null; // 用于存储最终的ContentProvider信息列表// readersynchronized (mPackages) {final IteratorPackageParser.Provider i mProviders.mProviders.values().iterator();final int userId processName ! null ?UserHandle.getUserId(uid) : UserHandle.getCallingUserId();while (i.hasNext()) {final PackageParser.Provider p i.next(); // 获取下一个ProviderPackageSetting ps mSettings.mPackages.get(p.owner.packageName); // 获取PackageSettingif (ps ! null p.info.authority ! null // 确保PackageSetting不为空且Provider有authority (processName null // 如果processName为null或者|| (p.info.processName.equals(processName) // Provider的processName匹配并且 UserHandle.isSameApp(p.info.applicationInfo.uid, uid))) // UID匹配 mSettings.isEnabledLPr(p.info, flags, userId) // Provider是可用的 (!mSafeMode // 如果不在安全模式或者Provider是系统应用|| (p.info.applicationInfo.flags ApplicationInfo.FLAG_SYSTEM) ! 0)) {if (finalList null) {finalList new ArrayListProviderInfo(3); // 初始化最终列表}ProviderInfo info PackageParser.generateProviderInfo(p, flags,ps.readUserState(userId), userId); // 生成ProviderInfoif (info ! null) {finalList.add(info); // 将ProviderInfo添加到最终列表}}}}if (finalList ! null) {Collections.sort(finalList, mProviderInitOrderSorter); // 对最终列表进行排序}return finalList; // 返回ContentProvider信息列表}
queryContentProviders方法通过遍历所有已知的ContentProvider根据给定的条件过滤和生成ProviderInfo对象最终返回一个包含所有匹配ContentProvider信息的列表。这个过程确保了只有符合条件的ContentProvider被返回同时考虑了进程名、UID、可用性和安全模式等因素。
这里本质上是基于mProviders来进行处理。
2.2 通过Uri的authority来查找对应provider的流程
当有数据获取端想通过URI中的authority部分来快速查找ContentProvider时则会通过PMS的resolveContentProvider方法来查询。接下来我们来查看该方法的实现代码如下
Override
public ProviderInfo resolveContentProvider(String name, int flags, int userId) {// 检查指定的用户是否存在如果不存在则返回nullif (!sUserManager.exists(userId)) return null;// 同步代码块确保线程安全synchronized (mPackages) {// 从mProvidersByAuthority中获取指定名称的Providerfinal PackageParser.Provider provider mProvidersByAuthority.get(name);// 如果provider不为空则获取其所属包的PackageSettingPackageSetting ps provider ! null? mSettings.mPackages.get(provider.owner.packageName): null;// 如果PackageSetting不为空且provider是启用的且在安全模式下只允许系统应用return ps ! null mSettings.isEnabledLPr(provider.info, flags, userId) (!mSafeMode || (provider.info.applicationInfo.flagsApplicationInfo.FLAG_SYSTEM) ! 0)? PackageParser.generateProviderInfo(provider, flags,ps.readUserState(userId), userId): null;}
}
resolveContentProvider 方法是 PackageManagerService 中用于根据提供的 authority 名称、标志位和用户 ID 来解析特定 ContentProvider 的方法。这个方法主要用于在需要确定特定 authority 对应的 ContentProvider 信息时使用。
这里本质上是基于mProvidersByAuthority来进行处理。
总结下开机启动后PMS解析AndroidManifest.xml并处理将ContentProvider存储在PMS的变量中关键变量一个是mProvidersByAuthority另一个是mProviders两者主要是针对不同的查询需求。