VirtualAPK滴滴插件化框架源码深入解读

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/wangwei708846696/article/details/80670170

初始化部分

PluginManager.getInstance(base).init();

保证单例

private static volatile PluginManager sInstance = null;
public static PluginManager getInstance(Context base) {
    if (sInstance == null) {
        synchronized (PluginManager.class) {
            if (sInstance == null)
                sInstance = new PluginManager(base);
        }
    }
    return sInstance;
}
private PluginManager(Context context) {
    Context app = context.getApplicationContext();
    if (app == null) {
        this.mContext = context;
    } else {
        this.mContext = ((Application)app).getBaseContext();
    }
    prepare();
}

下面仔细按照这个方法的步骤分析初始化过程做了哪些事情

private void prepare() {
    Systems.sHostContext = getHostContext();
    this.hookInstrumentationAndHandler();//对Instrumentation和H类进行hook
    this.hookSystemServices();//对AMS代理也就是AMP对象进行hook
    hookDataBindingUtil();
}
private void hookInstrumentationAndHandler() {
    try {
        Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation(this.mContext);//[1]从Context开始hook
        if (baseInstrumentation.getClass().getName().contains("lbe")) {
            System.exit(0);
        }
        //[2]
        final VAInstrumentation instrumentation = new VAInstrumentation(this, baseInstrumentation);
        Object activityThread = ReflectUtil.getActivityThread(this.mContext);
        ReflectUtil.setInstrumentation(activityThread, instrumentation);
        ReflectUtil.setHandlerCallback(this.mContext, instrumentation);
        this.mInstrumentation = instrumentation;
    } catch (Exception e) {
        e.printStackTrace();
    }
}

其中getActivityThread这个方法中对ActivityThread进行了获取(通过反射),然后从ActivityThread中通过getInstrumentation方法得到Instrumentation对象,这个对象会跟踪应用内application和activity生命周期,该对象的创建在ActivityThread::handleBindApplication函数中:

if (data.instrumentationName !=null) {
    ...
    java.lang.ClassLoadercl = instrContext.getClassLoader();
    mInstrumentation = (Instrumentation)cl.loadClass(data.instrumentationName.getClassName()).newInstance();
    ...
} else {
    mInstrumentation =newInstrumentation();
}

[1]

public static Instrumentation getInstrumentation(Context base) {
    if (getActivityThread(base) != null) {
        try {
            sInstrumentation = (Instrumentation) ReflectUtil.invoke(
                    sActivityThread.getClass(), sActivityThread, "getInstrumentation");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    return sInstrumentation;
}

[2]
这一部分代码通过自定义一个Instrumentation将此类hook进ActivityThread中从而达到跟踪目的,同时下面代码的参与是将mH类改变,mH类是AMS操作完成之后通知mH对当前应用进程进行改变的类,比如说Activity的实例产生就要通过改mH进行执行。

//ReflectUtil.setHandlerCallback(this.mContext, instrumentation);
public static void setHandlerCallback(Context base, Handler.Callback callback) {
    try {
        Object activityThread = getActivityThread(base);
        //activityThread对象调用getHandler得到mH对象引用final H mH = new H();
        Handler mainHandler = (Handler) ReflectUtil.invoke(activityThread.getClass(), activityThread, "getHandler", (Object[])null);
        //将Handler中的mCallback进行替换,替换的逻辑处理在VAInstrumentation中
        ReflectUtil.setField(Handler.class, mainHandler, "mCallback", callback);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

我们此时要看一看Handler的mCallback起到什么作用

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            //一般情况下要执行到mCallback的handleMessage方法完了再执行handleMessage方法
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

也就是对应:

获取下启动的Activity,然后判断如果是插件的,则对应下主题

@Override
public boolean handleMessage(Message msg) {
    //当消息是LAUNCH_ACTIVITY的时候机型一些操作
    if (msg.what == LAUNCH_ACTIVITY) {
        // ActivityClientRecord r
        Object r = msg.obj;//获取ActivityClientRecord
        try {
            Intent intent = (Intent) ReflectUtil.getField(r.getClass(), r, "intent");//得到启动意图
            intent.setExtrasClassLoader(VAInstrumentation.class.getClassLoader());//得到对应类加载器
            //得到ActivityInfo
            ActivityInfo activityInfo = (ActivityInfo) ReflectUtil.getField(r.getClass(), r, "activityInfo");
            //如果从插件启动则进行下面更换主题
            if (PluginUtil.isIntentFromPlugin(intent)) {
                int theme = PluginUtil.getTheme(mPluginManager.getHostContext(), intent);
                if (theme != 0) {
                    activityInfo.theme = theme;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    return false;
}

当真正执行LAUNCH_ACTIVITY的时候是这样的

H.java

case LAUNCH_ACTIVITY: {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

    r.loadedApk = getLoadedApkNoCheck(
            r.activityInfo.applicationInfo, r.compatInfo);
    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;

hookSystemServices()

这里是对IActivityManager进行hook,替换的对象是ActivityManagerProxy(插件内的)

private void hookSystemServices() {
    try {
        Singleton<IActivityManager> defaultSingleton;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            //得到ActivityManager中的IActivityManagerSingleton对象
            defaultSingleton = (Singleton<IActivityManager>) ReflectUtil.getField(ActivityManager.class, null, "IActivityManagerSingleton");
        } else {
            defaultSingleton = (Singleton<IActivityManager>) ReflectUtil.getField(ActivityManagerNative.class, null, "gDefault");
        }
        //使用动态代理将动态代理产生的代理对象赋值给activityManagerProxy
        IActivityManager activityManagerProxy = ActivityManagerProxy.newInstance(this, defaultSingleton.get());

        // Hook IActivityManager from ActivityManagerNative
        ReflectUtil.setField(defaultSingleton.getClass().getSuperclass(), defaultSingleton, "mInstance", activityManagerProxy);

        if (defaultSingleton.get() == activityManagerProxy) {
            this.mActivityManager = activityManagerProxy;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

替换的原理是:对ActivityManager进行hook,IActivityManagerSingleton.get()得到的就是IActivityManager的对象然后使用动态代理对其中的方法进行拦截

private static final Singleton<IActivityManager> IActivityManagerSingleton =
    new Singleton<IActivityManager>() {
    @Override
    protected IActivityManager create() {
        final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
        final IActivityManager am = IActivityManager.Stub.asInterface(b);
        return am;
    }
};

public abstract class Singleton<T> {
    private T mInstance;
    protected abstract T create();
    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}

上面这一部分主要是将当前进程通过startActivity,startService等等与AMS进行通信的渠道进行拦截,所以后面的startService等这些操作都需要经过ActivityManagerProxy(插件框架中)进行操作。

下面进入init方法,没啥操作

public void init() {
    mComponentsHandler = new ComponentsHandler(this);
    RunUtil.getThreadPool().execute(new Runnable() {
        @Override
        public void run() {
            doInWorkThread();
        }
    });
}

private void doInWorkThread() {
}

进行加载插件

当在MainActivity中执行

this.loadPlugin(this);



private void loadPlugin(Context base) {
    PluginManager pluginManager = PluginManager.getInstance(base);
    File apk = new File(Environment.getExternalStorageDirectory(), "Test.apk");
    if (apk.exists()) {
        try {
            pluginManager.loadPlugin(apk);
            Log.i(TAG, "Loaded plugin from apk: " + apk);
        } catch (Exception e) {
            e.printStackTrace();
        }
    } else {
        ...
    }
}

通过pluginManager.loadPlugin(apk);进行装载插件

public void loadPlugin(File apk) throws Exception {
    //如果apk存在的话,创建加载插件类
    LoadedPlugin plugin = LoadedPlugin.create(this, this.mContext, apk);
    if (null != plugin) {
        this.mPlugins.put(plugin.getPackageName(), plugin);
        synchronized (mCallbacks) {
            for (int i = 0; i < mCallbacks.size(); i++) {
                mCallbacks.get(i).onAddedLoadedPlugin(plugin);
            }
        }
        plugin.invokeApplication();
    } else {
        throw  new RuntimeException("Can't load plugin which is invalid: " + apk.getAbsolutePath());
    }
}

进行创建

LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception {
    this.mPluginManager = pluginManager;
    this.mHostContext = context;
    this.mLocation = apk.getAbsolutePath();//得到apk的路径
    //将APK进行解析并执行collectCertificates进行证书操作
    this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK);
    this.mPackage.applicationInfo.metaData = this.mPackage.mAppMetaData;//meta-data对应的操作
    this.mPackageInfo = new PackageInfo();
    this.mPackageInfo.applicationInfo = this.mPackage.applicationInfo;
    this.mPackageInfo.applicationInfo.sourceDir = apk.getAbsolutePath();

    if (Build.VERSION.SDK_INT == 27 && Build.VERSION.PREVIEW_SDK_INT != 0) { // Android P Preview
        this.mPackageInfo.signatures = this.mPackage.mSigningDetails.signatures;
    } else {
        this.mPackageInfo.signatures = this.mPackage.mSignatures;
    }
    this.mPackageInfo.packageName = this.mPackage.packageName;
    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 = new PluginPackageManager();
    this.mPluginContext = new PluginContext(this);
    this.mNativeLibDir = context.getDir(Constants.NATIVE_DIR, Context.MODE_PRIVATE);
    this.mResources = createResources(context, apk);//生成了新的Resources对象,这个要单独分析
    //创建类加载器,也要单独分析
    this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader());

    tryToCopyNativeLib(apk);//赋值native的lib库

    // 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
    Map<ComponentName, ActivityInfo> activityInfos = new HashMap<ComponentName, ActivityInfo>();
    for (PackageParser.Activity activity : this.mPackage.activities) {
        activityInfos.put(activity.getComponentName(), activity.info);
    }
    this.mActivityInfos = Collections.unmodifiableMap(activityInfos);
    this.mPackageInfo.activities = activityInfos.values().toArray(new ActivityInfo[activityInfos.size()]);

    // Cache services
    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
    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()]);

    // Register broadcast receivers dynamically
    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()]);
}

上面这个构造很重要,很重要很重要,这个负责解析apk里面内容我们一步步分析

第一步:

this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK);
public static final PackageParser.Package parsePackage(final Context context, final File apk, final int flags) throws PackageParser.PackageParserException {
    if (Build.VERSION.SDK_INT >= 24) {
        if (Build.VERSION.PREVIEW_SDK_INT == 0) {
            return PackageParserV24.parsePackage(context, apk, flags);
        } else {
            return PackageParserPPreview.parsePackage(context, apk, flags);
        }
    } else if (Build.VERSION.SDK_INT >= 21) {
        return PackageParserLollipop.parsePackage(context, apk, flags);
    } else {
        return PackageParserLegacy.parsePackage(context, apk, flags);
    }
}

主要进去一个,就知道原理

static final PackageParser.Package parsePackage(Context context, File apk, int flags) throws PackageParser.PackageParserException {
    PackageParser parser = new PackageParser();
    PackageParser.Package pkg = parser.parsePackage(apk, flags);
    ReflectUtil.invokeNoException(PackageParser.class, null, "collectCertificates",
            new Class[]{PackageParser.Package.class, int.class}, pkg, flags);
    return pkg;
}

当你点击进去PackageParser的构造你会发现抛出一个异常,其实这里只是使用一个假的PackageParser,只是包名和类名与系统中的一样而已,所以当apk运行在系统中时候由于类的加载机制是双亲委托模型,优先使用父节点的加载器,所以优先加载系统中的PackageParser。这里调用parsePackage主要目的是解析AndroidManifest.xml,最后通过collectCertificates方法获取应用的签名信息,最后将解析完成的PackageParser.Package返回


第二步:

this.mPackage.applicationInfo.metaData = this.mPackage.mAppMetaData;//meta-data
this.mPackageInfo = new PackageInfo();
this.mPackageInfo.applicationInfo = this.mPackage.applicationInfo;
this.mPackageInfo.applicationInfo.sourceDir = apk.getAbsolutePath();

if (Build.VERSION.SDK_INT == 27 && Build.VERSION.PREVIEW_SDK_INT != 0) { // Android P Preview
    this.mPackageInfo.signatures = this.mPackage.mSigningDetails.signatures;
} else {
    this.mPackageInfo.signatures = this.mPackage.mSignatures;
}
this.mPackageInfo.packageName = this.mPackage.packageName;
//此插件一定没加载过才可以
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];

复制各种标签的信息,有meta-data,版本号,版本名称等等


第三步:

this.mPackageManager = new PluginPackageManager();
this.mPluginContext = new PluginContext(this);
this.mNativeLibDir = context.getDir(Constants.NATIVE_DIR, Context.MODE_PRIVATE);
this.mResources = createResources(context, apk);
this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader());
tryToCopyNativeLib(apk);
@WorkerThread
private static Resources createResources(Context context, File apk) {
    if (Constants.COMBINE_RESOURCES) {
        //创建Resources对象
        Resources resources = ResourcesManager.createResources(context, apk.getAbsolutePath());
        ResourcesManager.hookResources(context, resources);
        return resources;
    } else {
        Resources hostResources = context.getResources();
        AssetManager assetManager = createAssetManager(context, apk);
        return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
    }
}
public static synchronized Resources createResources(Context hostContext, String apk) {
    Resources hostResources = hostContext.getResources();//得到应用原来的Resources
    Resources newResources = null;
    AssetManager assetManager;
    try {
        //得到AssetManager
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            assetManager = AssetManager.class.newInstance();
            ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", hostContext.getApplicationInfo().sourceDir);
        } else {
            assetManager = hostResources.getAssets();
        }
        //将当前apk执行AssetManager的addAssetPath添加到搜索资源的路径中
        ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", apk);
        List<LoadedPlugin> pluginList = PluginManager.getInstance(hostContext).getAllLoadedPlugins();
        for (LoadedPlugin plugin : pluginList) {
            ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", plugin.getLocation());
        }
        //创建一个新的Resources
        if (isMiUi(hostResources)) {
            newResources = MiUiResourcesCompat.createResources(hostResources, assetManager);
        } else if (isVivo(hostResources)) {
            newResources = VivoResourcesCompat.createResources(hostContext, hostResources, assetManager);
        } else if (isNubia(hostResources)) {
            newResources = NubiaResourcesCompat.createResources(hostResources, assetManager);
        } else if (isNotRawResources(hostResources)) {
            newResources = AdaptationResourcesCompat.createResources(hostResources, assetManager);
        } else {
            // is raw android resources
            newResources = new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
        }
        // lastly, sync all LoadedPlugin to newResources
        for (LoadedPlugin plugin : pluginList) {
            plugin.updateResources(newResources);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return newResources;
}

要想获得资源文件必须得到一个Resource对象,想要获得插件的资源文件,必须得到一个插件的Resource对象,在android.content.res.AssetManager.java中包含一个私有方法addAssetPath。只需要将apk的路径作为参数传入,就可以获得对应的AssetsManager对象,从而创建一个Resources对象,然后就可以从Resource对象中访问apk中的资源了。

既然现在可以加载插件中资源的Resource对象得到了,下一步就是需要将Resource对象hook进去。看代码

public static void hookResources(Context base, Resources resources) {
    try {
        ReflectUtil.setField(base.getClass(), base, "mResources", resources);
        Object loadedApk = ReflectUtil.getPackageInfo(base);
        ReflectUtil.setField(loadedApk.getClass(), loadedApk, "mResources", resources);

        Object activityThread = ReflectUtil.getActivityThread(base);
        Object resManager = ReflectUtil.getField(activityThread.getClass(), activityThread, "mResourcesManager");
        if (Build.VERSION.SDK_INT < 24) {
            Map<Object, WeakReference<Resources>> map = (Map<Object, WeakReference<Resources>>) ReflectUtil.getField(resManager.getClass(), resManager, "mActiveResources");
            Object key = map.keySet().iterator().next();
            map.put(key, new WeakReference<>(resources));
        } else {
            // still hook Android N Resources, even though it's unnecessary, then nobody will be strange.
            Map map = (Map) ReflectUtil.getFieldNoException(resManager.getClass(), resManager, "mResourceImpls");
            Object key = map.keySet().iterator().next();
            Object resourcesImpl = ReflectUtil.getFieldNoException(Resources.class, resources, "mResourcesImpl");
            map.put(key, new WeakReference<>(resourcesImpl));
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

代码可以看出来需要hook的有两个地方一个是Context中的mResources(定义在ContextImpl中的private @NonNull Resources mResources;)一个是PackageInfo中的mResources,但是这里我们设置PackageInfo这个有问题,老版本中ContextImpl中是有这个的,但是8.0之后没有这个属性。取而代之的是:

final @NonNull LoadedApk mLoadedApk;

所以这两句代码应该加以区分版本

Object loadedApk = ReflectUtil.getPackageInfo(base);
ReflectUtil.setField(loadedApk.getClass(), loadedApk, "mResources", resources);

之后就是把mResourcesImpl也hook进Resources去


第四步

this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader());
private static ClassLoader createClassLoader(Context context, File apk, File libsDir, ClassLoader parent) {
    //获取dex目录
    File dexOutputDir = context.getDir(Constants.OPTIMIZE_DIR, Context.MODE_PRIVATE);
    //得到绝对路径
    String dexOutputPath = dexOutputDir.getAbsolutePath();
    DexClassLoader loader = new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent);
    if (Constants.COMBINE_CLASSLOADER) {
        try {
            DexUtil.insertDex(loader);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    return loader;
}

DexClassLoader先解释下参数:
- dexpath为jar或apk文件目录。
- optimizedDirectory为优化dex缓存目录。
- libraryPath包含native lib的目录路径。
- parent父类加载器。

不熟悉类加载器的,这里我从源码角度分析一下类DexClassLoader:

public classDexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}
public BaseDexClassLoader(String dexPath, File optimizedDirectory,String libraryPath, ClassLoader parent) {
    super(parent);
    this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
protected ClassLoader(ClassLoader parentLoader) {
    this(parentLoader, false);
}

ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
    if (parentLoader == null && !nullAllowed) {
        throw new NullPointerException(“parentLoader == null && !nullAllowed”);
    }
    parent = parentLoader;//将父parent保存
}

继续观察this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);

public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) {
    this.definingContext = definingContext;
    ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
    this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);
    this.nativeLibraryDirectories = splitPaths(libraryPath, false);
    this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true);
    List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
    allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
    this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null, suppressedExceptions);
    if (suppressedExceptions.size() > 0) {
        this.dexElementsSuppressedExceptions = suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
    } else {
        dexElementsSuppressedExceptions = null;
    }
}

主要是makeDexElements这个方法,这个方法就是得到一个装有dex文件的数组Element[],每个Element对象里面包含一个DexFile对象成员,它对应的就是dex文件

private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
                                         ArrayList<IOException> suppressedExceptions) {
    ArrayList<Element> elements = new ArrayList<Element>();
    for (File file : files) {
        File zip = null;
        DexFile dex = null;
        String name = file.getName();

        // 如果是一个dex文件
        if (name.endsWith(DEX_SUFFIX)) {
            // Raw dex file (not inside a zip/jar).
            try {
                dex = loadDexFile(file, optimizedDirectory);
            } catch (IOException ex) {
                System.logE("Unable to load dex file: " + file, ex);
            }
        // 如果是一个apk或者jar或者zip文件
        } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
                || name.endsWith(ZIP_SUFFIX)) {
            zip = file;

            try {
                dex = loadDexFile(file, optimizedDirectory);
            } catch (IOException suppressed) {
                suppressedExceptions.add(suppressed);
            }
        } else if (file.isDirectory()) {
            elements.add(new Element(file, true, null, null));
        } else {
            System.logW("Unknown file type for: " + file);
        }

        //最后封装成Element
        if ((zip != null) || (dex != null)) {
            elements.add(new Element(file, false, zip, dex));
        }
    }

    return elements.toArray(new Element[elements.size()]);
}

对应的数据结构:

static class Element {
    private final File file;  // 它对应的就是需要加载的apk/dex/jar文件
    private final boolean isDirectory; // 第一个参数file是否为一个目录,一般为false,因为我们传入的是要加载的文件
    private final File zip;  // 如果加载的是一个apk或者jar或者zip文件,该对象对应的就是该apk或者jar或者zip文件
    private final DexFile dexFile; // 它是得到的dex文件
    ......
}

目前我们也只能是说将dex封装到Element并有个数组dexElements存起来,但是必须知道类加载的使用才能知道这些存起来有什么,所以我们继续看类加载的使用

public Class<?> loadClass(String className) throws ClassNotFoundException {
    return loadClass(className, false);
}
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
    Class<?> clazz = findLoadedClass(className);
    if (clazz == null) {
        ClassNotFoundException suppressed = null;
        try {
            clazz = parent.loadClass(className, false);
        } catch (ClassNotFoundException e) {
            suppressed = e;
        }
        if (clazz == null) {
            try {
                clazz = findClass(className);
            } catch (ClassNotFoundException e) {
                e.addSuppressed(suppressed);
                throw e;
            }
        }
    }
    return clazz;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
    Class c = pathList.findClass(name, suppressedExceptions);
    if (c == null) {
        ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
        for (Throwable t : suppressedExceptions) {
            cnfe.addSuppressed(t);
        }
        throw cnfe;
    }
    return c;
}

pathList就是前面创建的DexPathList对象,从上面我们知道,我们加载的dex文件都存放在它的exElements成员变量上面,dexElements就是Element[]数组,所以可以看到BaseDexClassLoader的findClass方法调用的是pathList的findClass方法

BaseDexClassLoader

public Class findClass(String name, List<Throwable> suppressed) {
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;

        if (dex != null) {
            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }
    }
    if (dexElementsSuppressedExceptions != null) {
        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    }
    return null;
}

可以看到它就是遍历dexElements数组,从每个Element对象中拿到DexFile类型的dex文件,然后就是从dex去加载所需要的class文件,直到找到为止。

一个ClassLoader可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找类则返回,如果找不到从下一个dex文件继续查找。

所以那些插件化的类如何加载到内存中呢?

基本原理:

1、除了第一个dex文件(即正常apk包唯一包含的Dex文件),其它dex文件都以资源的方式放在安装包中。所以我们需要将其他dex文件并在Application的onCreate回调中注入到系统的ClassLoader。并且对于那些在注入之前已经引用到的类(以及它们所在的jar),必须放入第一个Dex文件中。

2、PathClassLoader作为默认的类加载器,在打开应用程序的时候PathClassLoader就去加载指定的apk(解压成dex,然后在优化成odex),也就是第一个dex文件是PathClassLoader自动加载的。所以,我们需要做的就是将其他的dex文件注入到这个PathClassLoader中去。

3、因为PathClassLoader和DexClassLoader的原理基本一致,从前面的分析来看,我们知道PathClassLoader里面的dex文件是放在一个Element数组里面,可以包含多个dex文件,每个dex文件是一个Element,所以我们只需要将其他的dex文件放到这个数组中去就可以了。

实现:

1、通过反射获取PathClassLoader中的DexPathList中的Element数组(已加载了第一个dex包,由系统加载)
2、通过反射获取DexClassLoader中的DexPathList中的Element数组(将第二个dex包加载进去)
3、将两个Element数组合并之后,再将其赋值给PathClassLoader的Element数组

我们回归滴滴的插件中如何实现的:

DexClassLoader loader = new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent);

if (Constants.COMBINE_CLASSLOADER) {
    try {
        DexUtil.insertDex(loader);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

所以实现方法在于DexUtil.insertDex(loader);

public static void insertDex(DexClassLoader dexClassLoader) throws Exception {
    Object baseDexElements = getDexElements(getPathList(getPathClassLoader()));
    Object newDexElements = getDexElements(getPathList(dexClassLoader));
    Object allDexElements = combineArray(baseDexElements, newDexElements);
    Object pathList = getPathList(getPathClassLoader());
    ReflectUtil.setField(pathList.getClass(), pathList, "dexElements", allDexElements);
    insertNativeLibrary(dexClassLoader);
}
将主dex中的类加载器中的pathList得到,然后得到其中的dexElements

```java
private static Object getPathList(Object baseDexClassLoader) throws Exception {
    return ReflectUtil.getField(Class.forName("dalvik.system.BaseDexClassLoader"), baseDexClassLoader, "pathList");
}

private static Object getDexElements(Object pathList) throws Exception {
    return ReflectUtil.getField(pathList.getClass(), pathList, "dexElements");
}




<div class="se-preview-section-delimiter"></div>
Object newDexElements = getDexElements(getPathList(dexClassLoader));




<div class="se-preview-section-delimiter"></div>

然后拿到新的类加载器中的dex,因为这个里面装了插件apk的dex。下一步将两个进行混合

Object allDexElements = combineArray(baseDexElements, newDexElements);
private static Object combineArray(Object firstArray, Object secondArray) {
    Class<?> localClass = firstArray.getClass().getComponentType();
    int firstArrayLength = Array.getLength(firstArray);
    int allLength = firstArrayLength + Array.getLength(secondArray);
    Object result = Array.newInstance(localClass, allLength);
    for (int k = 0; k < allLength; ++k) {
        if (k < firstArrayLength) {
            Array.set(result, k, Array.get(firstArray, k));
        } else {
            Array.set(result, k, Array.get(secondArray, k - firstArrayLength));
        }
    }
    return result;
}




<div class="se-preview-section-delimiter"></div>

然后再将老的pathList反射出来,将新融合的dex数组塞进去这样老的里面就融合了新的dex文件,下一步代码是对native的lib包进行融合,具体看插件代码


第五步

将清单文件中的所有instrumentations,activity,services,providers,broadcast都添加到列表中。

到了这里LoadedPlugin类中存在的信息真多啊,把apk的资源,清单文件里面的资源都整合了。

此时apk里面的信息已经都获取到了并且添加到资源与类加载器中了,继续看

LoadedPlugin plugin = LoadedPlugin.create(this, this.mContext, apk);
if (null != plugin) {
this.mPlugins.put(plugin.getPackageName(), plugin);
synchronized (mCallbacks) {
    for (int i = 0; i < mCallbacks.size(); i++) {
        mCallbacks.get(i).onAddedLoadedPlugin(plugin);
    }
}
// try to invoke plugin's application
plugin.invokeApplication();




<div class="se-preview-section-delimiter"></div>
public void invokeApplication() {
    if (mApplication != null) {
        return;
    }

    // make sure application's callback is run on ui thread.
    RunUtil.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            mApplication = makeApplication(false, mPluginManager.getInstrumentation());
        }
    }, true);
}

private Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
    if (null != this.mApplication) {
        return this.mApplication;
    }

    String appClass = this.mPackage.applicationInfo.className;
    if (forceDefaultAppClass || null == appClass) {
        appClass = "android.app.Application";
    }

    try {
        this.mApplication = instrumentation.newApplication(this.mClassLoader, appClass, this.getPluginContext());
        instrumentation.callApplicationOnCreate(this.mApplication);
        return this.mApplication;
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

执行插件的Application.OnCreate

到此为止我们插件的初始化部分就完成了,下一篇我们继续跟踪启动插件apk内的activity


然后再将老的pathList反射出来,将新融合的dex数组塞进去这样老的里面就融合了新的dex文件,下一步代码是对native的lib包进行融合,具体看插件代码

---


第五步
----

将清单文件中的所有instrumentations,activity,services,providers,broadcast都添加到列表中。
到了这里LoadedPlugin类中存在的信息真多啊,把apk的资源,清单文件里面的资源都整合了。
此时apk里面的信息已经都获取到了并且添加到资源与类加载器中了,继续看

LoadedPlugin plugin = LoadedPlugin.create(this, this.mContext, apk);
if (null != plugin) {
this.mPlugins.put(plugin.getPackageName(), plugin);
synchronized (mCallbacks) {
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onAddedLoadedPlugin(plugin);
}
}
plugin.invokeApplication();



```java
public void invokeApplication() {
    if (mApplication != null) {
        return;
    }

    // make sure application's callback is run on ui thread.
    RunUtil.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            mApplication = makeApplication(false, mPluginManager.getInstrumentation());
        }
    }, true);
}

private Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
    if (null != this.mApplication) {
        return this.mApplication;
    }

    String appClass = this.mPackage.applicationInfo.className;
    if (forceDefaultAppClass || null == appClass) {
        appClass = "android.app.Application";
    }

    try {
        this.mApplication = instrumentation.newApplication(this.mClassLoader, appClass, this.getPluginContext());
        instrumentation.callApplicationOnCreate(this.mApplication);
        return this.mApplication;
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

执行插件的Application.OnCreate

到此为止我们插件的初始化部分就完成了,下一篇我们继续跟踪启动插件apk内的activity

猜你喜欢

转载自blog.csdn.net/wangwei708846696/article/details/80670170