Android插件化之ContentProvider

妖狐艹你老母 2021-11-27 05:34 502阅读 0赞

先来了解一下ContentProvider的安装和获取过程,查找Hook点。

安装ContentProvider过程

应用程序在创建Application的过程中,执行handleBindApplication(),会将contentprovider进行安装。

ActivityThread

  1. private void handleBindApplication(AppBindData data) {
  2. //...省略部分源码
  3. if (!data.restrictedBackupMode) {
  4. if (!ArrayUtils.isEmpty(data.providers)) {
  5. installContentProviders(app, data.providers);
  6. // For process that contains content providers, we want to
  7. // ensure that the JIT is enabled "at some point".
  8. mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
  9. }
  10. }
  11. }

installContentProviders():

  1. private void installContentProviders(
  2. Context context, List<ProviderInfo> providers) {
  3. final ArrayList<IActivityManager.ContentProviderHolder> results =
  4. new ArrayList<IActivityManager.ContentProviderHolder>();
  5. for (ProviderInfo cpi : providers) {
  6. if (DEBUG_PROVIDER) {
  7. StringBuilder buf = new StringBuilder(128);
  8. buf.append("Pub ");
  9. buf.append(cpi.authority);
  10. buf.append(": ");
  11. buf.append(cpi.name);
  12. Log.i(TAG, buf.toString());
  13. }
  14. IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,
  15. false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
  16. if (cph != null) {
  17. cph.noReleaseNeeded = true;
  18. results.add(cph);
  19. }
  20. }
  21. try {
  22. // 将安装好的ContentProvider代理对象和相应一些信息,存储在AMS中,供其他应用程序进行访问。
  23. ActivityManagerNative.getDefault().publishContentProviders(
  24. getApplicationThread(), results);
  25. } catch (RemoteException ex) {
  26. throw ex.rethrowFromSystemServer();
  27. }
  28. }

通过contextImpl和解析apk得到的ProviderInfo列表,进行安装。

hook点:只要解析出插件中的ContentProvider信息,再调用installContentProviders()就可以进行安装插件中的ConentProvider。

继续看安装过程,installProvider():

  1. private IActivityManager.ContentProviderHolder installProvider(Context context,
  2. IActivityManager.ContentProviderHolder holder, ProviderInfo info,
  3. boolean noisy, boolean noReleaseNeeded, boolean stable) {
  4. ContentProvider localProvider = null;
  5. IContentProvider provider;
  6. if (holder == null || holder.provider == null) {
  7. if (DEBUG_PROVIDER || noisy) {
  8. Slog.d(TAG, "Loading provider " + info.authority + ": "
  9. + info.name);
  10. }
  11. Context c = null;
  12. ApplicationInfo ai = info.applicationInfo;
  13. if (context.getPackageName().equals(ai.packageName)) {
  14. c = context;
  15. } else if (mInitialApplication != null &&
  16. mInitialApplication.getPackageName().equals(ai.packageName)) {
  17. c = mInitialApplication;
  18. } else {
  19. try {
  20. c = context.createPackageContext(ai.packageName,
  21. Context.CONTEXT_INCLUDE_CODE);
  22. } catch (PackageManager.NameNotFoundException e) {
  23. // Ignore
  24. }
  25. }
  26. if (c == null) {
  27. Slog.w(TAG, "Unable to get context for package " +
  28. ai.packageName +
  29. " while loading content provider " +
  30. info.name);
  31. return null;
  32. }
  33. try {
  34. // 创建ContentProvider对象,执行相应的生命周期
  35. final java.lang.ClassLoader cl = c.getClassLoader();
  36. localProvider = (ContentProvider)cl.
  37. loadClass(info.name).newInstance();
  38. provider = localProvider.getIContentProvider();
  39. localProvider.attachInfo(c, info);
  40. } catch (java.lang.Exception e) {
  41. if (!mInstrumentation.onException(null, e)) {
  42. throw new RuntimeException(
  43. "Unable to get provider " + info.name
  44. + ": " + e.toString(), e);
  45. }
  46. return null;
  47. }
  48. }
  49. IActivityManager.ContentProviderHolder retHolder;
  50. synchronized (mProviderMap) {
  51. if (DEBUG_PROVIDER) Slog.v(TAG, "Checking to add " + provider
  52. + " / " + info.name);
  53. IBinder jBinder = provider.asBinder();
  54. if (localProvider != null) {
  55. ComponentName cname = new ComponentName(info.packageName, info.name);
  56. ProviderClientRecord pr = mLocalProvidersByName.get(cname);
  57. if (pr != null) {
  58. if (DEBUG_PROVIDER) {
  59. Slog.v(TAG, "installProvider: lost the race, "
  60. + "using existing local provider");
  61. }
  62. provider = pr.mProvider;
  63. } else {
  64. holder = new IActivityManager.ContentProviderHolder(info);
  65. holder.provider = provider;
  66. holder.noReleaseNeeded = true;
  67. // 在本地进程中,进行存储构建好的CotnentProvider和代理对象
  68. pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
  69. mLocalProviders.put(jBinder, pr);
  70. mLocalProvidersByName.put(cname, pr);
  71. }
  72. retHolder = pr.mHolder;
  73. }
  74. }
  75. return retHolder;
  76. }

installProviderAuthoritiesLocked():

  1. private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider,
  2. ContentProvider localProvider, IActivityManager.ContentProviderHolder holder) {
  3. final String auths[] = holder.info.authority.split(";");
  4. final int userId = UserHandle.getUserId(holder.info.applicationInfo.uid);
  5. final ProviderClientRecord pcr = new ProviderClientRecord(
  6. auths, provider, localProvider, holder);
  7. for (String auth : auths) {
  8. final ProviderKey key = new ProviderKey(auth, userId);
  9. final ProviderClientRecord existing = mProviderMap.get(key);
  10. if (existing != null) {
  11. Slog.w(TAG, "Content provider " + pcr.mHolder.info.name
  12. + " already published as " + auth);
  13. } else {
  14. //存储好相应信息
  15. mProviderMap.put(key, pcr);
  16. }
  17. }
  18. return pcr;
  19. }

ContentProvider获取过程

ContextImpl#getContentResolver()获取到ContentResolver对象。

ContextImpl

  1. @Override
  2. public ContentResolver getContentResolver() {
  3. return mContentResolver;
  4. }

检索全局,发现是在ContextImpl的构造方法中创建ContentResolver对象:

  1. private ContextImpl(ContextImpl container, ActivityThread mainThread,
  2. LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
  3. Display display, Configuration overrideConfiguration, int createDisplayWithId) {
  4. //.... 省略部分源码
  5. mContentResolver = new ApplicationContentResolver(this, mainThread, user);
  6. }

从上可知,应用程序是通过ApplicationContentResolver来与contentprovider进行交互操作的。

接下来 , 查看下ApplicationContentResolver这个静态内部类。

  1. private static final class ApplicationContentResolver extends ContentResolver {
  2. private final ActivityThread mMainThread;
  3. private final UserHandle mUser;
  4. public ApplicationContentResolver(
  5. Context context, ActivityThread mainThread, UserHandle user) {
  6. super(context);
  7. mMainThread = Preconditions.checkNotNull(mainThread);
  8. mUser = Preconditions.checkNotNull(user);
  9. }
  10. @Override
  11. protected IContentProvider acquireProvider(Context context, String auth) {
  12. return mMainThread.acquireProvider(context,
  13. ContentProvider.getAuthorityWithoutUserId(auth),
  14. resolveUserIdFromAuthority(auth), true);
  15. }
  16. //... 省略部分源码
  17. @Override
  18. protected IContentProvider acquireUnstableProvider(Context c, String auth) {
  19. return mMainThread.acquireProvider(c,
  20. ContentProvider.getAuthorityWithoutUserId(auth),
  21. resolveUserIdFromAuthority(auth), false);
  22. }
  23. //... 省略部分源码
  24. }

继续,查看ContentResolver#query()是如何走向的:

ContentResolver

  1. public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
  2. @Nullable String[] projection, @Nullable String selection,
  3. @Nullable String[] selectionArgs, @Nullable String sortOrder,
  4. @Nullable CancellationSignal cancellationSignal) {
  5. Preconditions.checkNotNull(uri, "uri");
  6. // 获取到ContentProvider的代理对象
  7. IContentProvider unstableProvider = acquireUnstableProvider(uri);
  8. if (unstableProvider == null) {
  9. return null;
  10. }
  11. //...
  12. }

继续查看acquireUnstableProvider():

  1. public final IContentProvider acquireUnstableProvider(Uri uri) {
  2. if (!SCHEME_CONTENT.equals(uri.getScheme())) {
  3. return null;
  4. }
  5. String auth = uri.getAuthority();
  6. if (auth != null) {
  7. return acquireUnstableProvider(mContext, uri.getAuthority());
  8. }
  9. return null;
  10. }

发现,会走到ApplicationContentResolver#acquireUnstableProvider():

  1. @Override
  2. protected IContentProvider acquireUnstableProvider(Context c, String auth) {
  3. return mMainThread.acquireProvider(c,
  4. ContentProvider.getAuthorityWithoutUserId(auth),
  5. resolveUserIdFromAuthority(auth), false);
  6. }

发现,会走到ActivityThread对象的acquireProvider():

ActivityThread

  1. public final IContentProvider acquireProvider(
  2. Context c, String auth, int userId, boolean stable) {
  3. // 从启动应用程序进程时,已经安装的ContentProvider中查看
  4. final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
  5. if (provider != null) {
  6. return provider;
  7. }
  8. // 从AMS中去找其他应用程序安装的ContentProvider。
  9. IActivityManager.ContentProviderHolder holder = null;
  10. try {
  11. holder = ActivityManagerNative.getDefault().getContentProvider(
  12. getApplicationThread(), auth, userId, stable);
  13. } catch (RemoteException ex) {
  14. throw ex.rethrowFromSystemServer();
  15. }
  16. if (holder == null) {
  17. Slog.e(TAG, "Failed to find provider info for " + auth);
  18. return null;
  19. }
  20. // 会去新安装ConntentProvider
  21. holder = installProvider(c, holder, holder.info,
  22. true /*noisy*/, holder.noReleaseNeeded, stable);
  23. return holder.provider;
  24. }

从上可知会先从应用程序自身的已经安装的ContentProvider中查找匹配,若是没有找到,则会从AactivityManagerService中去查找其他应用程序安装的ContentProvider进行匹配。若是没有,则会重新安装应用程序的ContentProvider中重新匹配。

从应用程序自身安装的ContentProvider中查找, acquireExistingProvider():

  1. public final IContentProvider acquireExistingProvider(
  2. Context c, String auth, int userId, boolean stable) {
  3. synchronized (mProviderMap) {
  4. final ProviderKey key = new ProviderKey(auth, userId);
  5. final ProviderClientRecord pr = mProviderMap.get(key);
  6. if (pr == null) {
  7. return null;
  8. }
  9. IContentProvider provider = pr.mProvider;
  10. IBinder jBinder = provider.asBinder();
  11. // ContentProvider所在进程已经死亡
  12. if (!jBinder.isBinderAlive()) {
  13. // The hosting process of the provider has died; we can't
  14. // use this one.
  15. Log.i(TAG, "Acquiring provider " + auth + " for user " + userId
  16. + ": existing object's process dead");
  17. handleUnstableProviderDiedLocked(jBinder, true);
  18. return null;
  19. }
  20. // Only increment the ref count if we have one. If we don't then the
  21. // provider is not reference counted and never needs to be released.
  22. ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
  23. if (prc != null) {
  24. // 会累计引用的个数
  25. incProviderRefLocked(prc, stable);
  26. }
  27. return provider;
  28. }
  29. }

从mProviderMap中获取缓存好的ContentProvider信息中查询,若是能匹配到,且能够使用则返回。反之,返回null。


实战案例

思考

  1. 解析出插件中contentprovider
  2. 在Application#attachBaseContext()中安装插件的contentprovidr。
  3. 存在的一个问题,其他应用程序无法访问插件中ContentProvider。因PackageManagerService是无法获取到插件中信息。

在插件中编写一个contentprovoider

  1. public class PluginContentProvider extends ContentProvider {
  2. public static final String AUTHORITY = "com.xingen.plugin.contentprovider.PluginContentProvider";
  3. public static final Uri URI = Uri.parse("content://" + AUTHORITY);
  4. public static final String NAME = "name";
  5. private static final String TABLE_NAME = "developer";
  6. private PluginDB db;
  7. @Override
  8. public boolean onCreate() {
  9. db = new PluginDB(getContext());
  10. return true;
  11. }
  12. @Nullable
  13. @Override
  14. public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
  15. SQLiteDatabase database = db.getReadableDatabase();
  16. return database.query(TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
  17. }
  18. @Nullable
  19. @Override
  20. public String getType(@NonNull Uri uri) {
  21. return null;
  22. }
  23. @Nullable
  24. @Override
  25. public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
  26. SQLiteDatabase database = db.getWritableDatabase();
  27. long rowId = database.insert(TABLE_NAME, null, values);
  28. Uri rowUri = ContentUris.appendId(URI.buildUpon(), rowId).build();
  29. return rowUri;
  30. }
  31. @Override
  32. public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
  33. return 0;
  34. }
  35. @Override
  36. public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
  37. return 0;
  38. }
  39. private static class PluginDB extends SQLiteOpenHelper {
  40. private static final String db_name = "developer.db";
  41. private static final int db_version = 1;
  42. public PluginDB(Context context) {
  43. super(context, db_name, null, db_version);
  44. }
  45. @Override
  46. public void onCreate(SQLiteDatabase db) {
  47. StringBuffer stringBuffer = new StringBuffer();
  48. stringBuffer.append("Create table ");
  49. stringBuffer.append(TABLE_NAME);
  50. stringBuffer.append("( _id INTEGER PRIMARY KEY AUTOINCREMENT, ");
  51. stringBuffer.append(NAME);
  52. stringBuffer.append(" text ");
  53. stringBuffer.append(");");
  54. db.execSQL(stringBuffer.toString());
  55. }
  56. @Override
  57. public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  58. }
  59. }
  60. }

在AndroidManifest.xml中注册:

  1. <provider
  2. android:name="com.xingen.plugin.contentprovider.PluginContentProvider"
  3. android:authorities="com.xingen.plugin.contentprovider.PluginContentProvider">
  4. </provider>

在宿主中编写hook,安装插件中的Contentprovider:

  1. public class ContentProviderHookManager {
  2. private static List<ProviderInfo> providerInfoList=new LinkedList<>();
  3. public static void init(Application context, String apkFilePath){
  4. preloadParseContentProvider(apkFilePath);
  5. // 便于classloader加载,修改
  6. String packageName=context.getBaseContext().getPackageName();
  7. for (ProviderInfo providerInfo:providerInfoList){
  8. providerInfo.applicationInfo.packageName=packageName;
  9. }
  10. installContentProvider(context);
  11. }
  12. /** * 将ContentProvider安装到进程中 */
  13. private static void installContentProvider(Context context){
  14. try {
  15. //获取到ActivityThread
  16. Class<?> ActivityThreadClass = Class.forName("android.app.ActivityThread");
  17. Field sCurrentActivityThreadField = ActivityThreadClass.getDeclaredField("sCurrentActivityThread");
  18. sCurrentActivityThreadField.setAccessible(true);
  19. Object ActivityThread = sCurrentActivityThreadField.get(null);
  20. // 调用 installContentProviders()
  21. Method installContentProvidersMethod=ActivityThreadClass.getDeclaredMethod("installContentProviders",Context.class,List.class);
  22. installContentProvidersMethod.setAccessible(true);
  23. installContentProvidersMethod.invoke(ActivityThread,context,providerInfoList);
  24. }catch (Exception e){
  25. e.printStackTrace();
  26. }
  27. }
  28. /** * 解析插件中的service * * @param apkFilePath */
  29. private static void preloadParseContentProvider(String apkFilePath) {
  30. try {
  31. // 先获取PackageParser对象
  32. Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");
  33. Object packageParser = packageParserClass.newInstance();
  34. //接着获取PackageParser.Package
  35. Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class);
  36. parsePackageMethod.setAccessible(true);
  37. Object packageParser$package = parsePackageMethod.invoke(packageParser, new File(apkFilePath), PackageManager.GET_RECEIVERS);
  38. // 接着获取到Package中的receivers列表
  39. Class<?> packageParser$package_Class = packageParser$package.getClass();
  40. Field providersField = packageParser$package_Class.getDeclaredField("providers");
  41. providersField.setAccessible(true);
  42. List providersList = (List) providersField.get(packageParser$package);
  43. Class<?> packageParser$Provider_Class = Class.forName("android.content.pm.PackageParser$Provider");
  44. // 获取 name
  45. Field infoField = packageParser$Provider_Class.getDeclaredField("info");
  46. infoField.setAccessible(true);
  47. for (Object provider : providersList) {
  48. ProviderInfo info = (ProviderInfo) infoField.get(provider);
  49. providerInfoList.add(info);
  50. }
  51. } catch (Exception e) {
  52. e.printStackTrace();
  53. }
  54. }
  55. }

在宿主的Application中加载:

  1. public class ProxyApplication extends Application {
  2. @Override
  3. protected void attachBaseContext(Context base) {
  4. super.attachBaseContext(base);
  5. loadPluginDex(base);
  6. }
  7. private void loadPluginDex(Context context) {
  8. // 先拷贝assets 下的apk,写入磁盘中。
  9. String zipFilePath = PluginConfig.getZipFilePath(context);
  10. File zipFile = new File(zipFilePath);
  11. final String asset_file_name = "plugin.apk";
  12. Utils.copyFiles(context, asset_file_name, zipFile);
  13. String optimizedDirectory = new File(Utils.getCacheDir(context).getAbsolutePath() + File.separator + "plugin").getAbsolutePath();
  14. // 加载插件dex
  15. ClassLoaderHookManager.init(context, zipFilePath, optimizedDirectory);
  16. // 安装插件中的ContentProvider
  17. ContentProviderHookManager.init(this, zipFilePath);
  18. }
  19. }

最后,测试:

  1. private void useContentProvider(View view) {
  2. final Uri uri = Uri.parse("content://" + PluginConfig.provider_name);
  3. final String column_name = "name";
  4. Button button = (Button) view;
  5. final String text_query = "Hook 使用content_provider 查询";
  6. final String text_insert = "Hook 使用content_provider 插入";
  7. ContentResolver contentResolver = getContentResolver();
  8. if (text_query.equals(button.getText().toString())) { // 查询
  9. Cursor cursor = contentResolver.query(uri, null, null, null, null);
  10. try {
  11. StringBuffer stringBuffer = new StringBuffer();
  12. if (cursor != null && cursor.moveToFirst()) {
  13. do {
  14. stringBuffer.append(cursor.getString(cursor.getColumnIndex(column_name)));
  15. stringBuffer.append(",");
  16. } while (cursor.moveToNext());
  17. }
  18. if (!TextUtils.isEmpty(stringBuffer.toString())) {
  19. Toast.makeText(getApplicationContext(), "查询到的名字:" + stringBuffer.toString(), Toast.LENGTH_SHORT).show();
  20. }
  21. } catch (Exception e) {
  22. e.printStackTrace();
  23. } finally {
  24. if (cursor != null) {
  25. cursor.close();
  26. }
  27. button.setText(text_insert);
  28. }
  29. } else { // 插入
  30. ContentValues contentValues = new ContentValues();
  31. contentValues.put(column_name, "android " + (int) (Math.random() * 100));
  32. contentResolver.insert(uri, contentValues);
  33. button.setText(text_query);
  34. }
  35. }

项目代码: https://github.com/13767004362/HookDemo

发表评论

表情:
评论列表 (有 0 条评论,502人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Android开发

    项目发展到一定程度,就必须进行模块的拆分。模块化是一种指导理念,其核心思想就是分而治之、降低耦合。而在 Android 工程实践,目前有两种途径,一个是组件化,一个是插件化。