1 . 前言

Virtual APK是滴滴出行自研的一款优良的插件化框架,其次要开发人员有任玉刚老师

说到任玉刚老师,他能够说是我Android FrameWork层的启蒙老师。刚接触Android的时候,在拖了几年控件、写了一些CURD操作后,就得出了这样的论断:客户端太无聊了,当初曾经齐全精通安卓开发了。直到有一天看了一本叫做《Android开发艺术摸索》的书,不禁感叹:原来Android开发居然还能这么玩,之前的认知切实是肤浅

言归正传,Virtual APK的个性和应用办法不是本文重点,如有须要理解更多请移步VirtualAPK的个性和应用办法。本文次要针对Virtual APK的实现做解说。

2 . 重要的知识点

  • Activity启动流程(AMS)
  • DexClassLoader
  • 动静代理
  • 反射
  • 播送的动静注册

3 . 宿主App的实现

中心思想:

  • 对插件APK进行解析,获取插件APK的信息
  • 在框架初始化时,对一系列零碎组件和接口进行替换,从而对Activity、Service、ContentProvider的启动和生命周期进行批改和监控,达到欺瞒零碎或者劫持零碎的目标来启动插件Apk的对应组件。

3.1 插件Apk的解析和加载

插件Apk的加载在PluginManager#loadPlugin办法,在加载实现后,会生成一个LoadedPlugin对象并保留在Map中。LoadedPlugin里保留里插件Apk里绝大多数的重要信息和一个DexClassLoader,这个DexClassLoader是作为插件Apk的类加载器应用。

看下LoadedPlugin的具体实现,正文表明了各个属性的含意:

public LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception {        // PluginManager        this.mPluginManager = pluginManager;        // 宿主Context        this.mHostContext = context;        // 插件apk门路        this.mLocation = apk.getAbsolutePath();        this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK);        // 插件apk metadata        this.mPackage.applicationInfo.metaData = this.mPackage.mAppMetaData;        // 插件apk package信息        this.mPackageInfo = new PackageInfo();        this.mPackageInfo.applicationInfo = this.mPackage.applicationInfo;        this.mPackageInfo.applicationInfo.sourceDir = apk.getAbsolutePath();        // 插件apk 签名信息        if (Build.VERSION.SDK_INT >= 28            || (Build.VERSION.SDK_INT == 27 && Build.VERSION.PREVIEW_SDK_INT != 0)) { // Android P Preview            try {                this.mPackageInfo.signatures = this.mPackage.mSigningDetails.signatures;            } catch (Throwable e) {                PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);                this.mPackageInfo.signatures = info.signatures;            }        } else {            this.mPackageInfo.signatures = this.mPackage.mSignatures;        }        // 插件apk 包名        this.mPackageInfo.packageName = this.mPackage.packageName;        // 如果曾经加载过雷同的apk, 抛出异样        if (pluginManager.getLoadedPlugin(mPackageInfo.packageName) != null) {            throw new RuntimeException("plugin has already been loaded : " + mPackageInfo.packageName);        }        this.mPackageInfo.versionCode = this.mPackage.mVersionCode;        this.mPackageInfo.versionName = this.mPackage.mVersionName;        this.mPackageInfo.permissions = new PermissionInfo[0];        this.mPackageManager = createPluginPackageManager();        this.mPluginContext = createPluginContext(null);        this.mNativeLibDir = getDir(context, Constants.NATIVE_DIR);        this.mPackage.applicationInfo.nativeLibraryDir = this.mNativeLibDir.getAbsolutePath();        // 创立插件的资源管理器        this.mResources = createResources(context, getPackageName(), apk);        // 创立 一个dexClassLoader        this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader());        tryToCopyNativeLib(apk);        // Cache instrumentations        Map<ComponentName, InstrumentationInfo> instrumentations = new HashMap<ComponentName, InstrumentationInfo>();        for (PackageParser.Instrumentation instrumentation : this.mPackage.instrumentation) {            instrumentations.put(instrumentation.getComponentName(), instrumentation.info);        }        this.mInstrumentationInfos = Collections.unmodifiableMap(instrumentations);        this.mPackageInfo.instrumentation = instrumentations.values().toArray(new InstrumentationInfo[instrumentations.size()]);        // Cache activities        // 保留插件apk的Activity信息        Map<ComponentName, ActivityInfo> activityInfos = new HashMap<ComponentName, ActivityInfo>();        for (PackageParser.Activity activity : this.mPackage.activities) {            activity.info.metaData = activity.metaData;            activityInfos.put(activity.getComponentName(), activity.info);        }        this.mActivityInfos = Collections.unmodifiableMap(activityInfos);        this.mPackageInfo.activities = activityInfos.values().toArray(new ActivityInfo[activityInfos.size()]);        // Cache services        // 保留插件apk的Service信息        Map<ComponentName, ServiceInfo> serviceInfos = new HashMap<ComponentName, ServiceInfo>();        for (PackageParser.Service service : this.mPackage.services) {            serviceInfos.put(service.getComponentName(), service.info);        }        this.mServiceInfos = Collections.unmodifiableMap(serviceInfos);        this.mPackageInfo.services = serviceInfos.values().toArray(new ServiceInfo[serviceInfos.size()]);        // Cache providers        // 保留插件apk的ContentProvider信息        Map<String, ProviderInfo> providers = new HashMap<String, ProviderInfo>();        Map<ComponentName, ProviderInfo> providerInfos = new HashMap<ComponentName, ProviderInfo>();        for (PackageParser.Provider provider : this.mPackage.providers) {            providers.put(provider.info.authority, provider.info);            providerInfos.put(provider.getComponentName(), provider.info);        }        this.mProviders = Collections.unmodifiableMap(providers);        this.mProviderInfos = Collections.unmodifiableMap(providerInfos);        this.mPackageInfo.providers = providerInfos.values().toArray(new ProviderInfo[providerInfos.size()]);        // 将所有动态注册的播送全副改为动静注册        Map<ComponentName, ActivityInfo> receivers = new HashMap<ComponentName, ActivityInfo>();        for (PackageParser.Activity receiver : this.mPackage.receivers) {            receivers.put(receiver.getComponentName(), receiver.info);            BroadcastReceiver br = BroadcastReceiver.class.cast(getClassLoader().loadClass(receiver.getComponentName().getClassName()).newInstance());            for (PackageParser.ActivityIntentInfo aii : receiver.intents) {                this.mHostContext.registerReceiver(br, aii);            }        }        this.mReceiverInfos = Collections.unmodifiableMap(receivers);        this.mPackageInfo.receivers = receivers.values().toArray(new ActivityInfo[receivers.size()]);        // try to invoke plugin's application        // 创立插件apk的Application对象        invokeApplication();    }

3.2 Activity的启动解决及生命周期治理

Virtual APK启动插件APK中Activity的整体计划:

  1. Hook Instrumentaion 和主线程Halder的callback,在重要启动过程节点对Intent或Activity进行替换
  2. 在宿主APP中事后设置一些插桩Activity,这些插桩Activity并不会真正的启动,而是对AMS进行坑骗。如果启动的Activity是插件APK中的,则依据该Actiivty的启动模式抉择适合的插桩Activity, AMS在启动阶段对插桩Activity解决后,在创立Activity实例阶段,理论创立插件APK中要启动的Activity。

3.2.1 插桩Activity的申明:

插桩Activity有很多个,挑一些看一下:

 <!-- Stub Activities -->        <activity android:exported="false" android:name=".A$1" android:launchMode="standard"/>        <activity android:exported="false" android:name=".A$2" android:launchMode="standard"            android:theme="@android:style/Theme.Translucent" />        <!-- Stub Activities -->        <activity android:exported="false" android:name=".B$1" android:launchMode="singleTop"/>        <activity android:exported="false" android:name=".B$2" android:launchMode="singleTop"/>        <activity android:exported="false" android:name=".B$3" 

3.2.2 hook Instrumentation

  1. 将零碎提供的Instrumentation替换为自定义的VAInstrumentation,将主线程Handler的Callback也替换为VAInstrumentation(VAInstrumentation 实现了Handler.Callback接口)
     protected void hookInstrumentationAndHandler() {        try {            // 获取以后过程的activityThread            ActivityThread activityThread = ActivityThread.currentActivityThread();            // 获取以后过程的Instrumentation            Instrumentation baseInstrumentation = activityThread.getInstrumentation();            // 创立自定义Instrumentation            final VAInstrumentation instrumentation = createInstrumentation(baseInstrumentation);            // 将以后过程原有的Instrumentation对象替换为自定义的            Reflector.with(activityThread).field("mInstrumentation").set(instrumentation);            // 将以后过程原有的主线程Hander的callback替换为自定义的            Handler mainHandler = Reflector.with(activityThread).method("getHandler").call();            Reflector.with(mainHandler).field("mCallback").set(instrumentation);            this.mInstrumentation = instrumentation;            Log.d(TAG, "hookInstrumentationAndHandler succeed : " + mInstrumentation);        } catch (Exception e) {            Log.w(TAG, e);        }    }

3.2.3 启动Activity时对AMS进行坑骗

如果咱们相熟Activity启动流程的话,咱们肯定晓得Activity的启动和生命周期治理,都间接通过Instrumentation进行治理的。--如果不相熟也没关系,能够看我之前写的AMS系列文章,看完保障秒懂(雾)。VAInstrumentation重写了这个类的一些重要办法,咱们依据Activity启动流程一个一个说

3.2.3.1 execStartActivity

这个办法有很多个重载,挑其中一个:

    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode) {        // 对原始Intent进行解决        injectIntent(intent);        return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode);    }

injectIntent办法对Intent的解决在ComponentsHandler#markIntentIfNeeded办法,对原始Intent进行解析,获取指标Actiivty的包名和类名,如果指标Activity的包名和以后过程不同且该包名对应的LoadedPlugin对象存在,则阐明它是咱们加载过的插件APK中的Activity,则对该Intent的指标进行替换:

   public void markIntentIfNeeded(Intent intent) {        ...        String targetPackageName = intent.getComponent().getPackageName();        String targetClassName = intent.getComponent().getClassName();        // 判断是否须要启动的是插件Apk的Activity        if (!targetPackageName.equals(mContext.getPackageName()) && mPluginManager.getLoadedPlugin(targetPackageName) != null) {            ...            // 将原始Intent的指标Acitivy替换为预设的插桩Activity中的一个            dispatchStubActivity(intent);        }    }

dispatchStubActivity办法依据原始Intent的启动模式抉择适合的插桩Activity,将原始Intent中的类名批改为插桩Activity的类名,示例代码:

 case ActivityInfo.LAUNCH_SINGLE_TOP: {                usedSingleTopStubActivity = usedSingleTopStubActivity % MAX_COUNT_SINGLETOP + 1;                stubActivity = String.format(STUB_ACTIVITY_SINGLETOP, corePackage, usedSingleTopStubActivity);                break;            }            case ActivityInfo.LAUNCH_SINGLE_TASK: {                usedSingleTaskStubActivity = usedSingleTaskStubActivity % MAX_COUNT_SINGLETASK + 1;                stubActivity = String.format(STUB_ACTIVITY_SINGLETASK, corePackage, usedSingleTaskStubActivity);                break;            }            case ActivityInfo.LAUNCH_SINGLE_INSTANCE: {                usedSingleInstanceStubActivity = usedSingleInstanceStubActivity % MAX_COUNT_SINGLEINSTANCE + 1;                stubActivity = String.format(STUB_ACTIVITY_SINGLEINSTANCE, corePackage, usedSingleInstanceStubActivity);                break;            }
3.2.3.2 newActivity

如果只是对原始Intent进行替换,那么最终启动的会是插桩Activity,这显然达不到启动插件Apk中Acitivty的目标,在Activity实例创立阶段,还须要对理论创立的Actiivty进行替换,办法在VAInstrumentation#newActivity:

@Override    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {        try {            cl.loadClass(className);            Log.i(TAG, String.format("newActivity[%s]", className));        } catch (ClassNotFoundException e) {            ComponentName component = PluginUtil.getComponent(intent);            String targetClassName = component.getClassName();            Log.i(TAG, String.format("newActivity[%s : %s/%s]", className, component.getPackageName(), targetClassName));            LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(component);            // 应用在LoadedPlugin对象中创立的DexClassLoader进行类加载,该ClassLoader指向插件APK所在门路            Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);            activity.setIntent(intent);            // 插件Activity实例创立后,将Resource替换为插件APK的资源            Reflector.QuietReflector.with(activity).field("mResources").set(plugin.getResources());            return newActivity(activity);        }        return newActivity(mBase.newActivity(cl, className, intent));    }

如果咱们启动的是插件APK里的Activity,这个办法的Catch语句块是肯定会被执行的,因为入参className曾经被替换为插桩Activity的,然而咱们只是在宿主App的AndroidManifest.xml中定义了这些Actiivty,并没有真正的实现。在进入Catch语句块后,应用LoadedPlugin中保留的DexClassloader进行Activity的创立。

3.2.3.3 AMS对插件APK中的Activity治理

看到这里,可能就会有同学有问题了,你把要启动的Activity给替换了,然而AMS中不是还记录的是插桩Actiivty么,那么这个Activity实例后续跟AMS的交互怎么办?那岂不是在AMS中的记录找不到了?释怀,不会呈现这个问题的。温习之前AMS系列文章咱们就会晓得,AMS中对Activity治理的根据是一个叫appToken的Binder实例,在客户端对应的token会在Instrumentation#newActivity执行实现后调用Activity#attach办法传递给Actiivty。

这也是为什么对AMS进行坑骗这种插件化计划可行的起因,因为后续治理是应用的token,如果Android应用className之类的来治理的话,恐怕这种计划就不太好实现了。

3.2.3.4 替换Context、applicaiton、Resources

在零碎创立插件Activity的Context创立实现之后,须要将其替换为PluginContext,PluginContext和Context的区别是其外部保留有一个LoadedPlugin对象,不便对Context中的资源进行替换。代码在VAInstrumentaiton#injectActivity,调用处在VAInstrumentaiton#callActivityOnCreate

protected void injectActivity(Activity activity) {        final Intent intent = activity.getIntent();        if (PluginUtil.isIntentFromPlugin(intent)) {            Context base = activity.getBaseContext();            try {                LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);                Reflector.with(base).field("mResources").set(plugin.getResources());                Reflector reflector = Reflector.with(activity);                reflector.field("mBase").set(plugin.createPluginContext(activity.getBaseContext()));                reflector.field("mApplication").set(plugin.getApplication());                // set screenOrientation                ActivityInfo activityInfo = plugin.getActivityInfo(PluginUtil.getComponent(intent));                if (activityInfo.screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {                    activity.setRequestedOrientation(activityInfo.screenOrientation);                }                // for native activity                ComponentName component = PluginUtil.getComponent(intent);                Intent wrapperIntent = new Intent(intent);                wrapperIntent.setClassName(component.getPackageName(), component.getClassName());                wrapperIntent.setExtrasClassLoader(activity.getClassLoader());                activity.setIntent(wrapperIntent);            } catch (Exception e) {                Log.w(TAG, e);            }        }    }

3.3 Service的解决

Virtual APK启动插件APK中Activity的整体计划:

  1. 应用动静代理代理宿主APP中所有对于Service的申请
  2. 判断是否为插件APK中的Service,如果不是,则阐明为宿主 APP中的,间接关上即可
  3. 如果是插件APK中的Service,则判断是否为远端Service,如果是远端Service,则启动RemoteService,并在其StartCommand办法中依据所代理的生命周期办法进行解决
  4. 如果是本地Service,则启动LocalService,并在其StartCommand办法中依据所代理的生命周期办法进行解决

3.3.1 插件化框架初始化时代理零碎的IActivityManager

IActivityManager是AMS的实现接口,它的实现类别离是ActivityManagerService和其proxy

这里咱们须要代理的是Proxy,实现办法在PluginManager#hookSystemServices

 protected void hookSystemServices() {        try {            Singleton<IActivityManager对象> defaultSingleton;            // 获取IActivityManager对象            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {                defaultSingleton = Reflector.on(ActivityManager.class).field("IActivityManagerSingleton").get();            } else {                defaultSingleton = Reflector.on(ActivityManagerNative.class).field("gDefault").get();            }            IActivityManager origin = defaultSingleton.get();            // 创立activityManager对象的动静代理            IActivityManager activityManager对象的动静代理 = (IActivityManager) Proxy.newProxyInstance(mContext.getClassLoader(), new Class[] { IActivityManager.class },                createActivityManagerProxy(origin));            // 应用动静代理替换之前的IActivityManager对象实例            Reflector.with(defaultSingleton).field("mInstance").set(activityManagerProxy);            if (defaultSingleton.get() == activityManagerProxy) {                this.mActivityManager = activityManagerProxy;                Log.d(TAG, "hookSystemServices succeed : " + mActivityManager);            }        } catch (Exception e) {            Log.w(TAG, e);        }    }

通过将动静代理对系统创立的ActivityManager的proxy进行替换,这样,调用AMS办法时,会转到ActivityManagerProxy的invoke办法,并依据办法名对Service的生命周期进行治理,生命周期办法较多,筛选其中一个:

@Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        if ("startService".equals(method.getName())) {            try {                return startService(proxy, method, args);            } catch (Throwable e) {                Log.e(TAG, "Start service error", e);            }        }

startService:

 protected Object startService(Object proxy, Method method, Object[] args) throws Throwable {        IApplicationThread appThread = (IApplicationThread) args[0];        Intent target = (Intent) args[1];        ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);        if (null == resolveInfo || null == resolveInfo.serviceInfo) {            //  插件中没找到,阐明是宿主APP本人的Service            return method.invoke(this.mActivityManager, args);        }        // 启动插件APK中的Service        return startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_START_SERVICE);    }

startDelegateServiceForTarget中会调用wrapperTargetIntent解决,最终在RemoteService或者LocalService的onStartCommand中对Service的各生命周期解决。

须要留神的是,在RemoteService中须要从新对APK进行解析和装载,生成LoadedPlugin,因为它运行在另一个过程中。

这也阐明插件APK的Service过程如果申明了多个是有效的,因为他们最终都会运行在宿主RemoteService所在过程。

3.4 ContentProvider的解决

ContentProvicer的解决和Service是相似的,不多说了。

4 . 插件App的实现

插件APP实践上并不需要做什么非凡解决,惟一须要留神的是资源文件的抵触问题,因而,须要在插件工程app目录下的build.gradle中增加如下代码:

virtualApk {    packageId = 0x6f // the package id of Resources.    targetHost = '../../VirtualAPK/app' // the path of application module in host project.    applyHostMapping = true //optional, default value: true.}

它的作用是在插件APK编译时对资源ID进行重写,解决办法在ResourceCollector.groovy文件的collect办法:

 def collect() {        //1、First, collect all resources by parsing the R symbol file.        parseResEntries(allRSymbolFile, allResources, allStyleables)        //2、Then, collect host resources by parsing the host apk R symbol file, should be stripped.        parseResEntries(hostRSymbolFile, hostResources, hostStyleables)        //3、Compute the resources that should be retained in the plugin apk.        filterPluginResources()        //4、Reassign the resource ID. If the resource entry exists in host apk, the reassign ID        //   should be same with value in host apk; If the resource entry is owned by plugin project,        //   then we should recalculate the ID value.        reassignPluginResourceId()        //5、Collect all the resources in the retained AARs, to regenerate the R java file that uses the new resource ID        vaContext.retainedAarLibs.each {            gatherReservedAarResources(it)        }    }

首先获取插件app和宿主app的资源汇合,而后寻找其中抵触的资源id进行批改,批改id是 reassignPluginResourceId办法:

private void reassignPluginResourceId() {        // 对资源ID依据typeId进行排序        resourceIdList.sort { t1, t2 ->            t1.typeId - t2.typeId        }        int lastType = 1        // 重写资源ID        resourceIdList.each {            if (it.typeId < 0) {                return            }            def typeId = 0            def entryId = 0            typeId = lastType++            pluginResources.get(it.resType).each {                it.setNewResourceId(virtualApk.packageId, typeId, entryId++)            }        }    }

这里要说一下资源ID的组成:

资源ID是一个32位的16进制整数,前8位代表app, 接下来8位代表typeId(string、layout、id等),从01开始累加,前面四位为资源id,从0000开始累加。轻易反编译了一个apk,看一下其中一部分的构造:

对资源ID的遍历应用了双重循环,外层循环从01开始对typeId进行遍历,内层循环从0000开始对typeId对应的资源ID进行遍历,并且在内层循环调用setNewResourceId进行重写:

  public void setNewResourceId(packageId, typeId, entryId) {        newResourceId = packageId << 24 | typeId << 16 | entryId    }

packageId是咱们在build.gradle中定义的virtualApk.packageId,将其左移24位,与资源id的前8位对应,typeId与第9-16位对应,前面是资源id

这样,在插件app编译过程中就实现了抵触资源id的替换,前面也不会有抵触的问题了

5 . 总结

回顾整个Virtual APK的实现,其实逻辑并不是特地简单,然而能够看到作者们对AMS以及资源加载、类加载器等API的相熟水平,如果不是对这些常识体系特地精通的话,是很难实现的,甚至连思路都不可能有,这也是咱们学习源码的意义所在。

本文转自 https://www.androidos.net.cn/doc/2021/10/13/1024.html,如有侵权,请分割删除。

相干视频举荐

【B站超具体解说】Android开源库最新完整版课程!深刻底层原理,案例+我的项目实战解说通俗易懂

【合集】全网超具体的Android开源框架应用学习(组件化/集成化/RxJava/IOC/AOP/MVP/网络架构/Google标准化架构/热修复设计)

Android外围开源框架我的项目实战解析选集/热修复/类加载/插件化/组件化/AMS/路由框架......

Android开发零基础教程网络框架篇/OKHTTP/Retrofit