当前位置: 首页 > 工具软件 > Droid Plugin > 使用案例 >

Android插件化之DroidPlugin原理解析

段晨
2023-12-01

DroidPlugin原理解析

从系统设计的角度,组件和窗口的逻辑实体都存在于系统服务,比如Activity创建后,其逻辑控制主体在AMS,对于窗口,其逻辑控制主体在WMS

android将逻辑主体放置于系统服务,系统就可以对组件和窗口的生命周期,显示状态进行强掌控,这样就能做到在各种状态变更时能做到及时回调通知

所以,创建任何组件,都需要通过RPC通讯到AMS创建 — 第一个hook点

那逻辑主体确定后,AMS就需要创建进程去运行真实的Activity对象(可以认为它是一个提线木偶)

Android进程启动后,JAVA的入口是ActivityThread.main

ActivityThread主要干两件事件

  • 创建IApplicationThread native binder和AMS进行通讯
  • 收到AMS发来的RPC事件后,创建并保存各个组件相关的数据 ---- 第二个hook点

组件相关数据主要包括两个

  • 组件所属包信息和对应的loadedApk - 保存于mPackages
  • 将AMS中逐渐的逻辑主体对象token和真实组件对象一同保存,便于后续跟踪操作 - 比如Activity相关的保存于mActivities,service相关保存于mServices

还有,ActivityThread的设计本身好像就支持加载多个application,多个application会被保存到mAllApplications中

插件包安装

DroidPlugin实现了一个简易的IPluginManagerImpl用于插件APK包的安装和解析,当然这部分代码是参考系统的PMS来实现的,主要职责:

  • 插件APK安装到本地目录
  • 对插件APK的组件等数据进行解析

插件包解析和加载

  1. 插件包的解析,就是对AndroidManifest的解析,主要通过反射系统的PackageParser来完成
  2. 在Activity启动前(hook见下面介绍),会调用
 PluginProcessManager.preLoadApk(mHostContext, targetActivityInfo);
 preLoadApk内部会根据targetActivityInfo包含的包名来判断LoadedApk是
 否创建,如果未创建,则会通过反射调用ActivityThread的函数来创建插件
 LoadedApk并保存到ActivityThread的mPackages中,接着创建
 PluginClassLoader并设置到LoadedApk对象中
  1. 最后通过反射调用LoadedApk的makeApplication创建插件Application对象并调用onCreate

插件Activity启动解析

我们先来看下Android常规Activity的启动流程

  1. 调用Context.startActivity -> ActivityManagerNative -> AMS, AMS通过Intent从PMS拿到ActivityInfo并创建ActivityRecord和token放入前台ActivityStack,接着按需启动Activity所属进程
  2. 进程启动后,马上执行入口ActivityThread.main并调用attachApplication将启动信息反馈到AMS,AMS通过pid找到对应的ProcessRecord并更新其数据
  3. 接着从前台ActivityStack中拿到栈顶的ActivityRecord,如果其proecssrecord为null,并且uid和processname跟新创建的ProcessRecord一致,则正式调用app.thread.scheduleLaunchActivity
  4. ActivityThread在scheduleLaunchActivity中创建ActivityClientRecord,用于跟AMS中的ActivityRecord对应,ActivityClientRecord最重要的两个字段是token和activityinfo,token用于关联ActivityRecord,activityinfo则包含activity的描述和所属包等信息
  5. 在scheduleLaunchActivity内部接着发送LAUNCH_ACTIVITY message到mH这个handler,mH收到LAUNCH_ACTIVITY message后的代码如下:
     ActivityClientRecord r = (ActivityClientRecord)msg.obj;
     //通过activityinfo中包含的application信息创建loaedapk并保存于packageinfo
     r.packageInfo = getPackageInfoNoCheck(
                             r.activityInfo.applicationInfo, r.compatInfo);
     handleLaunchActivity(r, null);
    

理解上面第1和第5步很重要,因为DroidPlugin的Activity hook就是基于这两个点来进行的,原理总结如下:

  1. DroidPlugin首先在host app的AndroidManifest预注册一堆stub
    activity,这里只列出一部分,详细的可查看源码
      .stub.ActivityStub$P00$Standard00
      .stub.ActivityStub$P00$SingleInstance00
      .stub.ActivityStub$P00$SingleInstance01
      .stub.ActivityStub$P00$SingleInstance02
      .stub.ActivityStub$P00$SingleInstance03
      .stub.ActivityStub$P00$SingleTask00
      .stub.ActivityStub$P00$SingleTask01
      .stub.ActivityStub$P00$SingleTask02
      .stub.ActivityStub$P00$SingleTask03
      .stub.ActivityStub$P00$SingleTop00
      .stub.ActivityStub$P00$SingleTop01
      .stub.ActivityStub$P00$SingleTop02
      .stub.ActivityStub$P00$SingleTop03
    
  2. 通过动态代理和反射,hook ActivityManagerNative的接口,这个实现原理网上很多,这里不再赘述
  3. hook startActivity,相关代码在IActivityManagerHookHandle.startActivity中
                 ActivityInfo activityInfo = resolveActivity(intent);
                 if (activityInfo != null && isPackagePlugin(activityInfo.packageName)) {
                     ComponentName component = selectProxyActivity(intent);
                     if (component != null) {
                         Intent newIntent = new Intent();
                         try {
                             ClassLoader pluginClassLoader = PluginProcessManager.getPluginClassLoader(component.getPackageName());
                             setIntentClassLoader(newIntent, pluginClassLoader);
                         } catch (Exception e) {
                             Log.w(TAG, "Set Class Loader to new Intent fail", e);
                         }
                         newIntent.setComponent(component);
                         newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent);
                         newIntent.setFlags(intent.getFlags());
    
                         String callingPackage = (String) args[1];
                         if (TextUtils.equals(mHostContext.getPackageName(), callingPackage)) {
                             newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    
                         args[intentOfArgIndex] = newIntent;
                         args[1] = mHostContext.getPackageName();
                     }
    
    • 根据intent从DroidPlugin的packagemanager中拿到activityinfo(如果已安装插件包中有匹配的activty)
    • 还是根据intent,根据目标activity的属性,去匹配一个最合适的stub activity,并将component信息保存到newIntent,同时将intent作为extra保存到newintent
    • 最后将args中intent替换称newintent达到偷梁换柱的效果


      经过上面的偷梁换柱后,系统实际上拿到的是newintent,进而启动stubactivity;DroidPlugin接下去要做的就是,将stubactivity还原成真正要启动的插件activity,这个是在上面启动流程第5步中完成的
  4. 上面启动流程第五部可以看出,ActivityThread在启动Activity的时候,最重要的两个参数就是ActivityClientRecord里的两个变量intent和activityinfo,activityinfo是用来创建packageinfo(loadedapk), intent是要在创建activity后传入的,所以DroidPlugin必须要在创建Acivity之前,也就是handleLaunchActivity(msg)之前将这两个变量替换成原始的插件intent,这就是DroidPlugin Hook mH的目的,下面是hook 也就是handleLaunchActivity的部分代码
    //PluginCallback.java
    private boolean handleLaunchActivity(Message msg) {
         try {
             Object obj = msg.obj;
             Intent stubIntent = (Intent) FieldUtils.readField(obj, "intent");
             //ActivityInfo activityInfo = (ActivityInfo) FieldUtils.readField(obj, "activityInfo", true);
             stubIntent.setExtrasClassLoader(mHostContext.getClassLoader());
             Intent targetIntent = stubIntent.getParcelableExtra(Env.EXTRA_TARGET_INTENT);
             // 这里多加一个isNotShortcutProxyActivity的判断,因为ShortcutProxyActivity的很特殊,启动它的时候,
             // 也会带上一个EXTRA_TARGET_INTENT的数据,就会导致这里误以为是启动插件Activity,所以这里要先做一个判断。
             // 之前ShortcutProxyActivity错误复用了key,但是为了兼容,所以这里就先这么判断吧。
             if (targetIntent != null && !isShortcutProxyActivity(stubIntent)) {
                 IPackageManagerHook.fixContextPackageManager(mHostContext);
                 ComponentName targetComponentName = targetIntent.resolveActivity(mHostContext.getPackageManager());
                 ActivityInfo targetActivityInfo = PluginManager.getInstance().getActivityInfo(targetComponentName, 0);
                 if (targetActivityInfo != null) {
    
                     if (targetComponentName != null && targetComponentName.getClassName().startsWith(".")) {
                         targetIntent.setClassName(targetComponentName.getPackageName(), targetComponentName.getPackageName() + targetComponentName.getClassName());
                     }
    
                     ResolveInfo resolveInfo = mHostContext.getPackageManager().resolveActivity(stubIntent, 0);
                     ActivityInfo stubActivityInfo = resolveInfo != null ? resolveInfo.activityInfo : null;
                     if (stubActivityInfo != null) {
                         PluginManager.getInstance().reportMyProcessName(stubActivityInfo.processName, targetActivityInfo.processName, targetActivityInfo.packageName);
                     }
                     PluginProcessManager.preLoadApk(mHostContext, targetActivityInfo);
                     ClassLoader pluginClassLoader = PluginProcessManager.getPluginClassLoader(targetComponentName.getPackageName());
                     setIntentClassLoader(targetIntent, pluginClassLoader);
                     setIntentClassLoader(stubIntent, pluginClassLoader);
    
                     boolean success = false;
                     try {
                         targetIntent.putExtra(Env.EXTRA_TARGET_INFO, targetActivityInfo);
                         if (stubActivityInfo != null) {
                             targetIntent.putExtra(Env.EXTRA_STUB_INFO, stubActivityInfo);
                         }
                         success = true;
                     } catch (Exception e) {
                         Log.e(TAG, "putExtra 1 fail", e);
                     }
    
                     if (!success && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
                         try {
                             ClassLoader oldParent = fixedClassLoader(pluginClassLoader);
                             targetIntent.putExtras(targetIntent.getExtras());
    
                             targetIntent.putExtra(Env.EXTRA_TARGET_INFO, targetActivityInfo);
                             if (stubActivityInfo != null) {
                                 targetIntent.putExtra(Env.EXTRA_STUB_INFO, stubActivityInfo);
                             }
                             fixedClassLoader(oldParent);
                             success = true;
                         } catch (Exception e) {
                             Log.e(TAG, "putExtra 2 fail", e);
                         }
                     }
    
                     if (!success) {
                         Intent newTargetIntent = new Intent();
                         newTargetIntent.setComponent(targetIntent.getComponent());
                         newTargetIntent.putExtra(Env.EXTRA_TARGET_INFO, targetActivityInfo);
                         if (stubActivityInfo != null) {
                             newTargetIntent.putExtra(Env.EXTRA_STUB_INFO, stubActivityInfo);
                         }
                         FieldUtils.writeDeclaredField(msg.obj, "intent", newTargetIntent);
                     } else {
                         FieldUtils.writeDeclaredField(msg.obj, "intent", targetIntent);
                     }
                     FieldUtils.writeDeclaredField(msg.obj, "activityInfo", targetActivityInfo);
    
                     Log.i(TAG, "handleLaunchActivity OK");
                 } else {
                     Log.e(TAG, "handleLaunchActivity oldInfo==null");
                 }
             } else {
                 Log.e(TAG, "handleLaunchActivity targetIntent==null");
             }
         } catch (Exception e) {
             Log.e(TAG, "handleLaunchActivity FAIL", e);
         }
    
         if (mCallback != null) {
             return mCallback.handleMessage(msg);
         } else {
             return false;
         }
     }
    
    先用intent中拿出之前保存到extra的插件intent
    Intent targetIntent = stubIntent.getParcelableExtra(Env.EXTRA_TARGET_INTENT);
    
    接着根据targetIntent获取对应的activityinfo
    ComponentName targetComponentName = targetIntent.resolveActivity(mHostContext.getPackageManager());
    ActivityInfo targetActivityInfo = PluginManager.getInstance().getActivityInfo(targetComponentName, 0);
    
    最后将数据写回到ActivityClientRecord,完成最终的替换
     FieldUtils.writeDeclaredField(msg.obj, "intent", targetIntent);
     FieldUtils.writeDeclaredField(msg.obj, "activityInfo", targetActivityInfo);
    

插件service启动分析

同样的,先来看看service的常规启动流程

  • 调用contextimpl.startService/bindService/stopService -> AMS,AMS对应创建ServiceRecord和token后,通知ActivityThread
  • ActivityThread收到startService后,会创建service并保存到mService map,key为token,接着调用oncreate
  • ActivityThread接着收到handleServiceArgs, 根据token拿到service,接着调用onStartCommond并传入intent
  • ActivityThread收到bindservice后,从根据token拿到service,接着调用onbind拿到native binder,接着调用publishService将native binder传到AMS
 ActivityManagerNative.getDefault().publishService(
                               data.token, data.intent, binder);

Service跟Activity还是存在很大的区别的,service非常独立,也就是说,系统创建service后,除了调用规定的那些回调,传递intent外,剩下就是service自己玩自己的,跟系统一毛钱关系都没有了

Activity则不同,因为其涉及到窗口,所以会存在大量的交互,比如WMS,IMS等

对于DroidPlugin来说,插件service的hook,则会简单很多,只需要用一个stub service做为代理,在stubservice内部根据传入的intent去管理插件service对象即可:

.stub.ServiceStub$StubP00$P00

在startservice和bindservice时,只需要把目标sevice缓存stubservice,并将真实的intent作为extra传递到stub service就可以了

  private static ServiceInfo replaceFirstServiceIntentOfArgs(Object[] args) throws RemoteException {
       int intentOfArgIndex = findFirstIntentIndexInArgs(args);
       if (args != null && args.length > 1 && intentOfArgIndex >= 0) {
           Intent intent = (Intent) args[intentOfArgIndex];
           ServiceInfo serviceInfo = resolveService(intent);
           if (serviceInfo != null && isPackagePlugin(serviceInfo.packageName)) {
               ServiceInfo proxyService = selectProxyService(intent);
               if (proxyService != null) {
                   Intent newIntent = new Intent();
                   //FIXBUG:https://github.com/Qihoo360/DroidPlugin/issues/122
                   //如果插件中有两个Service:ServiceA和ServiceB,在bind ServiceA的时候会调用ServiceA的onBind并返回其IBinder对象,
                   // 但是再次bind ServiceA的时候还是会返回ServiceA的IBinder对象,这是因为插件系统对多个Service使用了同一个StubService
                   // 来代理,而系统对StubService的IBinder做了缓存的问题。这里设置一个Action则会穿透这种缓存。
                   newIntent.setAction(proxyService.name + new Random().nextInt());

                   newIntent.setClassName(proxyService.packageName, proxyService.name);
                   newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent);
                   newIntent.setFlags(intent.getFlags());
                   args[intentOfArgIndex] = newIntent;
                   return serviceInfo;
               }
           }
       }
       return null;
   }

接着在stubservice会创建ServcesManager用于插件service管理,所有的stub service回调会同步到ServcesManager里:

   public int onStart(Context context, Intent intent, int flags, int startId) throws Exception {
       Intent targetIntent = intent.getParcelableExtra(Env.EXTRA_TARGET_INTENT);
       if (targetIntent != null) {
           ServiceInfo targetInfo = PluginManager.getInstance().resolveServiceInfo(targetIntent, 0);
           if (targetInfo != null) {
               Service service = mNameService.get(targetInfo.name);
               if (service == null) {

                   handleCreateServiceOne(context, intent, targetInfo);
               }
               handleOnStartOne(targetIntent, flags, startId);
           }
       }
       return -1;
   }

看到没,ServcesManager自己管理mNameService map,service信息则是通过extr中中真实的插件intent来获得,onbind函数同样:

 public IBinder onBind(Context context, Intent intent) throws Exception {
        Intent targetIntent = intent.getParcelableExtra(Env.EXTRA_TARGET_INTENT);
        if (targetIntent != null) {
            ServiceInfo info = PluginManager.getInstance().resolveServiceInfo(targetIntent, 0);
            Service service = mNameService.get(info.name);
            if (service == null) {
                handleCreateServiceOne(context, intent, info);
            }
            return handleOnBindOne(targetIntent);
        }
        return null;
    }

这两个函数在mNameService未包含该service实例的时候,都会调用handleCreateServiceOne,通过反射调用ActivityThrea的方法创建service,从而达到调用oncreate的目地

插件receiver分析

在插件apk被启动的时候,会通过分析查看apk的receiver组件信息,然后动态注册

插件provider分析

先介绍ContentProvider的实现原理

  • 本质肯定是基于binder,所以每一个ContentProvider都会实现Transport native binder
  • 当我们调用getContentResolve.insert/delete等操作时,前提肯定是需要根据authority来拿到对应ContentProvider绑定的Transport对应binder proxy
  • 拿到binder proxy后,数据连接建立

数据连接建立后,后续跟系统也没一毛钱关系了,那理论上provider跟service是一样的,只要能hook数据发送端,接收端用一个stubprovider做代理就可以搞定了

DroidPlugin定义的stubprovider

.stub.ContentProviderStub$StubP00

发送端hook,就是替换binder proxy的过程,看DroidPlugin的getContentProvider的hook代码:

  @Override
        protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
            if (args != null) {
                final int index = 1;
                if (args.length > index && args[index] instanceof String) {
                    String name = (String) args[index];
                    mStubProvider = null;
                    mTargetProvider = null;

                    ProviderInfo info = mHostContext.getPackageManager().resolveContentProvider(name, 0);
                    mTargetProvider = PluginManager.getInstance().resolveContentProvider(name, 0);
                    //这里有个很坑爹的事情,就是当插件的contentprovider和host的名称一样,冲突的时候处理方式。
                    //在Android系统上,是不会出现这种事情的,因为系统在安装的时候做了处理。而我们目前没做处理。so,在出现冲突时候的时候优先用host的。
                    if (mTargetProvider != null && info != null && TextUtils.equals(mTargetProvider.packageName, info.packageName)) {
                        mStubProvider = PluginManager.getInstance().selectStubProviderInfo(name);
//                        PluginManager.getInstance().reportMyProcessName(mStubProvider.processName, mTargetProvider.processName);
//                        PluginProcessManager.preLoadApk(mHostContext, mTargetProvider);
                        if (mStubProvider != null) {
                            args[index] = mStubProvider.authority;
                        } else {
                            Log.w(TAG, "getContentProvider,fake fail 1");
                        }
                    } else {
                        mTargetProvider = null;
                        Log.w(TAG, "getContentProvider,fake fail 2=%s", name);
                    }
                }
            }
            return super.beforeInvoke(receiver, method, args);
        }

这里不管是什么请求,authority都会被改成stub provider的authority,在请求结束后,在将authority关联contentprovider对应的binder proxy设置成DroidPlugin自己的

Object provider = FieldUtils.readField(invokeResult, "provider");
                    if (provider != null) {
                        boolean localProvider = FieldUtils.readField(toObj, "provider") == null;
                        IContentProviderHook invocationHandler = new IContentProviderHook(mHostContext, provider, mStubProvider, mTargetProvider, localProvider);
                        invocationHandler.setEnable(true);
                        Class<?> clazz = provider.getClass();
                        List<Class<?>> interfaces = Utils.getAllInterfaces(clazz);
                        Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0];
                        Object proxyprovider = MyProxy.newProxyInstance(clazz.getClassLoader(), ifs, invocationHandler);
                        FieldUtils.writeField(invokeResult, "provider", proxyprovider);
                        FieldUtils.writeField(toObj, "provider", proxyprovider);
}

接着在IContentProviderHook对发送uri做替换

  if (!mLocalProvider && mStubProvider != null) {
               final int index = indexFirstUri(args);
               if (index >= 0) {
                   Uri uri = (Uri) args[index];
                   String authority = uri.getAuthority();
                   if (!TextUtils.equals(authority, mStubProvider.authority)) {
                       Uri.Builder b = new Builder();
                       b.scheme(uri.getScheme());
                       b.authority(mStubProvider.authority);
                       b.path(uri.getPath());
                       b.query(uri.getQuery());
                       b.appendQueryParameter(Env.EXTRA_TARGET_AUTHORITY, authority);
                       b.fragment(uri.getFragment());
                       args[index] = b.build();
                   }
               }
           }

将uri的authority替换成stub provider的,将插件provider的authority保存到Env.EXTRA_TARGET_AUTHORITY这个parameter中

stubprovider实现就很简单了,根据Env.EXTRA_TARGET_AUTHORITY的值来创建插件provider,接着做代理就好了,这里不就贴代码了


下面是contentprovider常规初始化流程,大家可以了解下

  • ContextImpl.getContentResolver.insert->ApplicationContentResolver.acquireProvider->ActivityThread.acquireProvider->ActivityManagerNative.getContentProvider->AMS.getContentProvider
  • 接着ActivityThread.scheduleInstallProvider->ActivityThread.installProvider
  • 接着创建ContextProvider实例并获取内部native binder
  try {
               final java.lang.ClassLoader cl = c.getClassLoader();
               localProvider = (ContentProvider)cl.
                   loadClass(info.name).newInstance();
               provider = localProvider.getIContentProvider();
               if (provider == null) {
                   Slog.e(TAG, "Failed to instantiate class " +
                         info.name + " from sourceDir " +
                         info.applicationInfo.sourceDir);
                   return null;
               }
               if (DEBUG_PROVIDER) Slog.v(
                   TAG, "Instantiating local provider " + info.name);
               // XXX Need to create the correct context for this provider.
               localProvider.attachInfo(c, info);
           } catch (java.lang.Exception e) {
               if (!mInstrumentation.onException(null, e)) {
                   throw new RuntimeException(
                           "Unable to get provider " + info.name
                           + ": " + e.toString(), e);
               }
               return null;
           }

从代码里可以看出getIContentProvider返回的native binder才是contentprovider数据传输的核心

  • 接着调用ActivityManagerNative.publishContentProviders将新创建的provider同步到AMS

还有一点很重要,通过AMS.getContentProvider->ActivityThread.acquireProvider,由于ActivityThread处理都是发送消息到mH,所以它是异步的,AMS.getContentProvider如果立即返回,肯定是空的,所以它必须要等待后续ActivityManagerNative.publishContentProviders执行完成后才返回,看AMS.getContentProviderImpl部分代码:

 //ActivityManagerService.getContentProviderImpl
 //.....前面代码没贴
 // Wait for the provider to be published...
        synchronized (cpr) {
            while (cpr.provider == null) {
                if (cpr.launchingApp == null) {
                    return null;
                }
                try {
                    if (conn != null) {
                        conn.waiting = true;
                    }
                    cpr.wait();
                } catch (InterruptedException ex) {
                } finally {
                    if (conn != null) {
                        conn.waiting = false;
                    }
                }
            }
        }
  return cpr != null ? cpr.newHolder(conn) : null;

插件加载独立性

如果插件都在主进程启动运行,可能有人会有疑问,LoadedApk会不会乱掉?答案肯定是不会的,因为这个是DroidPlugin这个实现方案的前提,咱们看LoadedApk的生成代码

 private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
            ClassLoader baseLoader, boolean securityViolation, boolean includeCode) {
        synchronized (mResourcesManager) {
            WeakReference<LoadedApk> ref;
            if (includeCode) {
                ref = mPackages.get(aInfo.packageName);
            } else {
                ref = mResourcePackages.get(aInfo.packageName);
            }
            LoadedApk packageInfo = ref != null ? ref.get() : null;
            if (packageInfo == null || (packageInfo.mResources != null
                    && !packageInfo.mResources.getAssets().isUpToDate())) {
                if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
                        : "Loading resource-only package ") + aInfo.packageName
                        + " (in " + (mBoundApplication != null
                                ? mBoundApplication.processName : null)
                        + ")");
                packageInfo =
                    new LoadedApk(this, aInfo, compatInfo, this, baseLoader,
                            securityViolation, includeCode &&
                            (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0);
                if (includeCode) {
                    mPackages.put(aInfo.packageName,
                            new WeakReference<LoadedApk>(packageInfo));
                } else {
                    mResourcePackages.put(aInfo.packageName,
                            new WeakReference<LoadedApk>(packageInfo));
                }
            }
            return packageInfo;
        }
    }

ActivityThread会保存LoadeApk的map,key就是package name,所以各个插件的LoadedApk可以独立的存在ActivityThread中

插件resource获取

Android资源获取依赖

  • resource id,即开发中用到R..
  • 还有就是context.getResource()

由于四大组件和Application这五个入口类的创建使用的是插件的class loader,那他们使用过程中用到的R.java肯定是对应插件的,这个不会有任何问题

不过context本质是ContextImpl对象实例,这个对象不是基于插件的class loader创建的,这个要注意,但是它对插件resource独立获取没任何影响,因为

  1. context实例跟组件和Application都是一对一创建的,这就导致它不可能跟其他插件混淆
  2. context.getresource本质还是使用插件package res info创建AssertManager,它跟插件也是一对一绑定的

所以,只要完成了插件LoadedApk的创建,组件运行过程中的resource就可以正常获取

总结

DroidPlugin的设计真的很巧妙,作者能构思出这种方案,对组件的初始化肯定是非常熟悉的,这套插件化方案出来也很多年了,最近看一遍,主要还是想学习作者的实现思路,同时也加深自己对组件初始化相关代码的理解

组件实现能被偷天换日是基于Android这么一个设计前提,AMS只是保存组件的逻辑对象主体,ActivityThread只是基于逻辑主体token来创建本地组件对象并做后续跟踪,这就为修改本地组件对象提供了可能

不过这种方式对系统潜入太大了,兼容性会比较差

 类似资料: