由VirtualAPK了解插件化原理(一)

上篇了解了热修复原理,热修复技术主要用来修复 bug,插件化则主要解决应用越来越庞大以及功能模块的解耦。插件化处理两部分业务,一种是自身的业务模块,一种是接入其他的应用业务。这种方式在淘宝、支付宝、美团、滴滴、360 等航母应用上十分常见。代表方案有阿里的 Atlas、360 的 RePlugin、滴滴的 VirtualAPK 等。

插件化的 apk 分为宿主和插件两部分组成,先被安装到手机中的 apk 就是宿主,经过处理的 apk、so 和 dex 等文件为宿主。

插件化中四大组件是 android 插件化的重中之重,这篇以 VirtualAPK 了解插件化原理。在说四大组件之前,必须先解析插件的 apk,这里不是重点,暂不细说。

插件解析

插件化需要用到反射、Hook,先看下 VirtualAPK 中反射的工具类,以便源码阅读方便。

反射:比如 Reflector.with(activityThread).field("mInstrumentation").set(instrumentation);

Reflector 的 with() 方法,调用 on 和 bind 方法。

    protected Reflector() {
    
    }

    public static Reflector with(@NonNull Object caller) throws ReflectedException {
        return on(caller.getClass()).bind(caller);
    }

Reflector 的 on() 方法实例化一个 Reflector 对象,将传入的 Class 赋值给 mType,这里的 mType 就是 ActivityThread 的 getClass。

    protected Class<?> mType;
    public static Reflector on(@NonNull Class<?> type) {
        Reflector reflector = new Reflector();
        reflector.mType = type;
        return reflector;
    }

Reflector 的 bind() 方法调用 checked 方法创建 mCaller 成员。

    protected Object mCaller;
    public Reflector bind(@Nullable Object caller) throws ReflectedException {
        mCaller = checked(caller);
        return this;
    }

Reflector 的 checked() 方法调用 class 的 mType 与传入的 caller 进行 isInstance 判断,检查完毕将 caller 返回。

    protected Object checked(@Nullable Object caller) throws ReflectedException {
        if (caller == null || mType.isInstance(caller)) {
            return caller;
        }
        throw new ReflectedException("Caller [" + caller + "] is not a instance of type [" + mType + "]!");
    }

class.inInstance(obj) 表示这个对象能不能被转化为这个类,而 obj.instanceof(class) 表示这个对象是不是这种类型。

接下来 Reflector 的 field() 方法开始反射处理。

    public Reflector field(@NonNull String name) throws ReflectedException {
        try {
            mField = findField(name);
            mField.setAccessible(true);
            mConstructor = null;
            mMethod = null;
            return this;
        } catch (Throwable e) {
            throw new ReflectedException("Oops!", e);
        }
    }

field 中调用 findField() 方法通过两种反射的方法得到 Field 实例。

    protected Field findField(@NonNull String name) throws NoSuchFieldException {
        try {
            return mType.getField(name);
        } catch (NoSuchFieldException e) {
            for (Class<?> cls = mType; cls != null; cls = cls.getSuperclass()) {
                try {
                    return cls.getDeclaredField(name);
                } catch (NoSuchFieldException ex) {
                    // Ignored
                }
            }
            throw e;
        }
    }

getField:仅能获取类(及其父类可以自己测试)  public 属性成员;

getDeclaredField:仅能获取类本身的属性成员(包括私有、共有、保护) ;

之后将 findField 返回值赋值给成员 mField,调用 setAccessible 修改权限,这时 activityThread 的成员变量 mInstrumentation 我们就拿到了。

Reflector 的 set() 方法

    public Reflector set(@Nullable Object caller, @Nullable Object value) throws ReflectedException {
        check(caller, mField, "Field");
        try {
            mField.set(caller, value);
            return this;
        } catch (Throwable e) {
            throw new ReflectedException("Oops!", e);
        }
    }

先调用 check 检查参数:

    protected void check(@Nullable Object caller, @Nullable Member member, @NonNull String name) throws ReflectedException {
        if (member == null) {
            throw new ReflectedException(name + " was null!");
        }
        if (caller == null && !Modifier.isStatic(member.getModifiers())) {
            throw new ReflectedException("Need a caller!");
        }
        checked(caller);
    }

getModifiers 返回 member 所表示的成员或构造方法的 java 语言修饰符,这里得到 mInstrumentation 的修饰符;

Modifier.isStatic 判断如果给定参数是否包含 static 修饰符,有则为 true,这里判断不是 static 方法可通过;

之后再调用上文的 checked 进行判断。

最后调用 set 方法将 activityThread 中 mInstrumentation 对象更换为我们创建的 instrumentation 对象( set() 指定对象变量上此 Field 对象表示的字段设置为指定的新值)。

当然也有 get() 方法,返回指定对象上由此 Filed 表示的字段的值。如果该对象具有原始类型,则该值将自动包装在对象中。

VirtualAPK 中还有 method(),findMethod(),call() 等对方法,当然对应的是 getMethod(),getDeclaredMethod(),Method的Invoke方法这里篇幅有限,不多说了。

动态代理

参考android源码中的反射、代理的应用,VirtualAPK 实现与链接中介绍的一致。

IActivityManager activityManagerProxy = (IActivityManager) Proxy.newProxyInstance(mContext.getClassLoader(), new Class[] { IActivityManager.class },
                createActivityManagerProxy(origin));

隐藏API

另外 VirtualAPK 中创建了很多针对 Framework 中隐藏API的库,VirtualAPK 解释是

该库主要是针对在 app 中无法直接调用 Android Framework 中很多隐藏的 API 而创造的一系列 stub 类用来欺骗编译器,从而避免了使用反射去调用造成性能损失

从 Android P 开始,对应用能使用的非 SDK 接口实施了限制,它会在所有通过反射方式和 JNI 方式获取 Method 和 Field 的地方判断是否用户代码调用了系统的隐藏 API。参考突破Android P(Preview 1)对调用隐藏API限制的方法

安卓系统要实现限制用户代码调用系统隐藏 API,至少要做以下两个区分

1、必须区分一个 Method(或Field) 对用户代码是隐藏的还是公开的。只有隐藏的才需要进行限制。

2、必须区分调用者的身份:是用户代码调用的还是系统代码(例如 Activity 类)调用的。只有用户代码调用时才需要进行限制。

那么只有在通过反射方式和 JNI 方式获取 Method 和 Field 时,系统才有可能拦截对隐藏 API 的获取,也就是说直接调用是可以的。因此一种方法的核心思想就是想方设法直接调用系统隐藏 API。具体实现时需要用 Provided 方式提供 Module 或自定义 android.jar。

以 ActivityThread activityThread = ActivityThread.currentActivityThread(); 为例

直接调用发现 IDE 提示找不到类 ActivityThread,这是因为在 sdk 的 android.jar(位于SDK/platforms/android-XX 目录下)中并没有这个类的声明,但是在实际运行时这个类是存在于系统中的。我们的解决方法是以 Provided 方式提供一个 Module,在此 Module 中提供需要的类(Provided 方式是为了编译通过,这样的 Module 的代码并不会编译到最终的 apk 中)。

VirtualAPK 中实现

在 AndroidStub 库中自定义 ActivityThread

package android.app;

import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;

/**
 * @author johnsonlee
 */
public final class ActivityThread {

    public static ActivityThread currentActivityThread() {
        throw new RuntimeException("Stub!");
    }

    public static boolean isSystem() {
        throw new RuntimeException("Stub!");
    }

    public static String currentOpPackageName() {
        throw new RuntimeException("Stub!");
    }

    public static String currentPackageName() {
        throw new RuntimeException("Stub!");
    }

    public static String currentProcessName() {
        throw new RuntimeException("Stub!");
    }

    public static Application currentApplication() {
        throw new RuntimeException("Stub!");
    }

    public ApplicationThread getApplicationThread() {
        throw new RuntimeException("Stub!");
    }

    public Instrumentation getInstrumentation() {
        throw new RuntimeException("Stub!");
    }

    public Looper getLooper() {
        throw new RuntimeException("Stub!");
    }

    public Application getApplication() {
        throw new RuntimeException("Stub!");
    }

    public String getProcessName() {
        throw new RuntimeException("Stub!");
    }

    public final ActivityInfo resolveActivityInfo(final Intent intent) {
        throw new RuntimeException("Stub!");
    }

    public final Activity getActivity(final IBinder token) {
        throw new RuntimeException("Stub!");
    }

    final Handler getHandler() {
        throw new RuntimeException("Stub!");
    }

    private class ApplicationThread extends ApplicationThreadNative {


    }
}

Android Library 引用

final String projectAndroidStub = ':AndroidStub'

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])

    provided project(projectAndroidStub)

    testCompile 'junit:junit:4.12'
}

这样可以实现了对系统 API 的调用。

VirtualAPK 中还实现了很多系统 API,如 ApplicationThreadNative、ActivityManagerNative 等,也对相应的 aidl 也实现。

解析插件apk

创建 LoadedPlugin 类将插件 apk 的文件传入

    protected LoadedPlugin createLoadedPlugin(File apk) throws Exception {
        return new LoadedPlugin(this, this.mContext, apk);
    }
    public LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception {
        //宿主pluginManager传入
        this.mPluginManager = pluginManager;
        宿主的context传入
        this.mHostContext = context;
        //apk路径
        this.mLocation = apk.getAbsolutePath();///storage/emulated/0/App-release.apk
        //解析apk,并且验证签名
        this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK);//PARSE_MUST_BE_APK
        // 独立存储应用程序元数据,以避免多个不需要的引用
        this.mPackage.applicationInfo.metaData = this.mPackage.mAppMetaData;
        //创建PackageInfo
        this.mPackageInfo = new PackageInfo();
        //对应ApplicationInfo对象 对应AndroidManifest里面的Application
        this.mPackageInfo.applicationInfo = this.mPackage.applicationInfo;
        this.mPackageInfo.applicationInfo.sourceDir = apk.getAbsolutePath();///storage/emulated/0/App-release.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;
        }
        //表示包名
        this.mPackageInfo.packageName = this.mPackage.packageName;//com.kotlin.mall
        if (pluginManager.getLoadedPlugin(mPackageInfo.packageName) != null) {
            throw new RuntimeException("plugin has already been loaded : " + mPackageInfo.packageName);
        }
        //  版本号
        this.mPackageInfo.versionCode = this.mPackage.mVersionCode;//1
        // 版本名
        this.mPackageInfo.versionName = this.mPackage.mVersionName;//1.0.4
        //APK安装包中 AndroidManifest中对应"adopt-permissions"集合
        this.mPackageInfo.permissions = new PermissionInfo[0];
        /**
         * PackageManager这个类是检测当前已经安装在当前设备上的应用程序包的信息。
         * 可以调用Context类的getPackageManager()方法来获取PackageManager方法。
         */
        this.mPackageManager = createPluginPackageManager();
        // 创建插件的context
        this.mPluginContext = createPluginContext(null);//创建ContextWrapper
        //创建私有数据文件,只能被应用本身访问
        this.mNativeLibDir = getDir(context, Constants.NATIVE_DIR);//"valibs"
        this.mPackage.applicationInfo.nativeLibraryDir = this.mNativeLibDir.getAbsolutePath();///data/user/0/com.didi.virtualapk/app_valibs
        //创建Resources,和上文热修复类似,创建和源码中基本一致
        //这里创建AssetManager,再反射调用AssetManager的addAssetPath方法加载插件,这里的AssetManager只包含了插件资源,因此createResources创建出来的时插件的资源。
        this.mResources = createResources(context, getPackageName(), apk);
        //创建ClassLoader,和上文一致,通过反射,这里没有先后顺序
        this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader());
        //拷贝so
        tryToCopyNativeLib(apk);

        // APK安装包中 AndroidManifest里面的<Instrumentation>,这里面的Instrumentation是不是我们通常说的Instrumentation,而是PackageParse的内部类Instrumentation
        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()]);

        // APK安装包中 AndroidManifest里面的<Activity>,这里面的Activity是不是我们通常说的Activity,而是PackageParse的内部类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()]);

        // APK安装包中 AndroidManifest里面的<Service>,这里面的Service是不是我们通常说的Service,而是PackageParse的内部类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()]);

        // APK安装包中 AndroidManifest里面的<Provider>,这里面的Provider是不是我们通常说的Provider,而是PackageParse的内部类Provider
        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()]);

        // APK安装包中 AndroidManifest里面的<Receiver>,这里面的Activity是不是我们通常说的Activity,而是PackageParse的内部类Activity
        Map<ComponentName, ActivityInfo> receivers = new HashMap<ComponentName, ActivityInfo>();
        for (PackageParser.Activity receiver : this.mPackage.receivers) {
            receivers.put(receiver.getComponentName(), receiver.info);
            //知道BroadcastReceiver类名,由ClassLoader创建实例,并调用宿主的Context的registerReceiver方法完成插件的注册。
            //动态注册广播,对于广播只能如此
            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()]);

        //初始化插件的Application
        invokeApplication();
    }

在构造函数中进行 apk 的解析、缓存,以及创建广播、Application 等。

注意这里完成了 BroadcastReceiver 的插件化、资源的插件化、so的插件化。

签名解析比较麻烦,我以24为例

    private static final class PackageParserV24 {

        static final PackageParser.Package parsePackage(Context context, File apk, int flags) throws Throwable {
            /**
             * 解析指定位置的安装包。它自动会检测安装包的模式的是单一APK或者集群APK模式。
             * 这样就可以对"集群APK"的安装包进行理性的检查,比如会检查"base APK"和"拆分APK"是否具有相同的包名和版本号。
             * 请注意,这里面是不执行签名验证的,所以必须要单独执行collectCertificates(Package,int)这个方法
             */
            //Android 安装一个APK的时候首先会解析APK,而解析APK则需要用到一个工具类,这个工具类就是PackageParser
            PackageParser parser = new PackageParser();
            PackageParser.Package pkg = parser.parsePackage(apk, flags);
            //调用PackageParser中collectCertificates方法,传入pkg、flags,
            /**
             *      从给定包中描述的所有apk收集证书,
             *     public static void collectCertificates(Package pkg, int parseFlags)
             *             throws PackageParserException {
             *         collectCertificatesInternal(pkg, parseFlags);
             *         final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
             *         for (int i = 0; i < childCount; i++) {
             *             Package childPkg = pkg.childPackages.get(i);
             *             childPkg.mCertificates = pkg.mCertificates;
             *             childPkg.mSignatures = pkg.mSignatures;
             *             childPkg.mSigningKeys = pkg.mSigningKeys;
             *         }
             *     }
             */
            Reflector.with(parser)
                .method("collectCertificates", PackageParser.Package.class, int.class)
                .call(pkg, flags);
            return pkg;
        }
    }

invokeApplication 则模拟了 Android Framework 的过程 mApplication = makeApplication(false, mPluginManager.getInstrumentation()); 这里不多说了。

总结,这篇介绍了

VirtualAPK 中应用反射、动态代理的相关使用;

VirtualAPK 中如何隐藏系统 API;

VirtualAPK 中如何解析插件 apk;

VirtualAPK 中广播、资源、so 的插件化;

下文介绍其余三大组件的插件化过程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值