Small框架类加载实现
一、总体流程图:
插件类的加载是Small插件化框架中重要的部分,我们首先列出Small类加载程序的总体流程图:
程序开始,通过逐层调用,首先开启一个LoadBundleThread线程,在LoadBundleThread线程中读取bundle.json字符串配置信息并解析。接下来,获取最新版本插件的so文件路径,生成其对应的BundleParser,并对存在更新的so文件签名进行CRC校验。然后,将使用loadDex方法生成插件对应DexFile对象的过程放入到Runnable线程中,将Runnable线程放入到List sIOActions中。接下来,将List sIOActions中Runnable线程放入到线程池中执行,这样就生成了插件所对应的DexFile对象。最后重要的,利用插件的DexFile对象生成插件Element对象,将插件程序产生的Element数组与宿主程序Element数组融合,这样就最终完成了LoadBundleThread线程。最后响应LoadBundleHandler的MSG_COMPLETE消息,执行Small.OnCompleteListener的onComplete方法,实现跳转到MainActivity。
二、详细讲解
① 开始:
准备工作
Small框架类加载程序的入口在LaunchActivity.java的onStart()方法中。在分析程序的入口之前,我们需要看一下Small的一些准备工作,这些准备工作是在Small框架的Application.java的onCreate()方法中开始的,onCreate()方法调用了Small.java中的preSetUp方法:
Small.preSetUp(this);
调用了small.java的preSetUp方法中的流程如下图:
首先,注册ActivityLauncher、ApkBundleLauncher、WebBundleLauncher三种BundleLauncher,实质上就是生成三种BundleLauncher的对象,并将其添加入List sBundleLauncher中。
获取宿主程序PackageInfo对象,再通过PackageInfo对象获得versionCode,如果宿主程序是第一次安装或者更新,那么这个versionCode与SharedPreferences中保存的versionCode将会不一致,如果这样,sIsNewHostApp设置为true,并将新的versionCode保存入SharedPreferences里。
注:Android系统为我们提供了很多服务管理的类,包括ActivityManager、PowerManager(电源管理)、AudioManager(音频管理)等。除此之外,还提供了一个PackageManger管理类,它的主要职责是管理应用程序包。 通过它,我们就可以获取应用程序信息,比如获取PackageInfo、ApplicationInfo、ActivityInfo等AnroidManifest.xml文件节点信息,它们与AnroidManifest.xml的对应关系示意图如下图:
接下来,获取宿主程序签名信息,存放于byte[][]sHostCertificates数组中。
判断task栈顶的Activity与宿主程序的默认启动Activity是否一致,如果不一致则调用setUp(context, null)方法。这个操作的主要作用是程序处于后台运行,很可能被强杀,当我们回到前台时,由于Activity task栈没有被清空,重新开启之前的Activity,此时调用setUp(context, null)方法,就是重新对程序进行初始化,保证Activity能够正常启动。
至此准备工作就完成,下面分析类加载程序的入口。
类加载程序入口
进入LaunchActivity,首先执行它的onCreate方法,如果sIsNewHostApp为true,即第一次打开应用,LaunchActivity显示“Preparing for first launching…”。
LaunchActivity类的onStart方法调用了Small.java中的setUp方法,并声明net.wequick.small.Small.OnCompleteListener监听器,代码如下:
@Override
protected void onStart() {
super.onStart();
......
//调用setUp
Small.setUp(this, new net.wequick.small.Small.OnCompleteListener() {
@Override
public void onComplete() {
......
//打开main插件
Small.openUri("main", LaunchActivity.this);
finish();
}
});
}
- Small.java中的setUp方法流程图如下:
首先,判断sHasSetUp是否为true,如果sHasSetUp==true,调用net.wequick.small.Small.OnCompleteListener的onComplete方法;如果首次调用setUp方法,sHasSetUp==false,则调用Bundle.java的loadLaunchableBundles()方法,并设置sHasSetUp = true。
接下来我们查看一下Bundle.java的loadLaunchableBundles()方法,loadLaunchableBundles()方法的示意图如下:
② 开启LoadBundleThread
在Bundle.java的loadLaunchableBundles()方法中,因为synchronous为false,sLoading仍然为false;开启LoadBundleThread线程,并声明LoadBundleHandler,因为此时sLoading==false,所以程序不执行while循环。
LoadBundleThread线程的run方法如下:
public void run() {
// Instantiate bundle
loadBundles(mContext);
sLoading = false;
sHandler.obtainMessage(MSG_COMPLETE).sendToTarget();
}
可以看到,在run方法中首先调用了Bundle.java中的loadBundles(Context context)方法,之后就发现:sLoading设置为false,然后响应LoadBundleHandler类MSG_COMPLETE消息,实现net.wequick.small.Small.OnCompleteListener类的onComplete()方法的调用。于是我们的关键点又落在了Bundle.java中的loadBundles(Context context)方法之上。
③ 读取bundle.json字符串并解析
Bundle.java的loadBundles(Context context)方法的目的就是获得插件配置的json字符串,其流程如下:
声明patch目录下的bundle.json文件patchManifestFile,patch目录为/data/data/包名/files/,如果有添加新的插件,应该将新的bundle.json文件放置到此目录。
获得SharedPreferences中缓存的json字符串manifestJson,这个字符串是调用updateManifest方法后生成的。
如果缓存的manifestJson字符串不为空,则将manifestJson字符串写入patchManifestFile文件,并清空SharedPreferences中的存储;如果manifestJson字符串为空,并且patchManifestFile存在,读取patchManifestFile中的json字符串,将其复制给manifestJson;如果manifestJson字符串为空,并且patchManifestFile不存在,那就读取assets文件下的bundle.json文件的内容,将其赋值给manifestJson。
利用json字符串生成JSONObject对象manifestData:
JSONObject manifestData = new JSONObject(manifestJson);
- 最后调用Bundle.java中的loadBundles(manifest.bundles)方法。
注: 此过程使用的Bundle类和Manifest类都是bundle.json字符串对应的JavaBean类,其类图如下:
④ ⑤ ⑥的实现
下面我们来看一下Bundle.java中的loadBundles(List bundles)方法:
- 每个Bundle类对象都执行了Bundle.java中的prepareForLaunch()方法,它为插件类加载做了一些前提准备工作。打开Bundle.java里的prepareForLaunch()方法:
protected void prepareForLaunch() {
if (mIntent != null) return;
if (mApplicableLauncher == null && sBundleLaunchers != null) {
for (BundleLauncher launcher : sBundleLaunchers) {
if (launcher.resolveBundle(this)) {
mApplicableLauncher = launcher;
break;
}
}
}
}
Bundle.java中的prepareForLaunch()方法流程图:
在Bundle.java中的prepareForLaunch()方法中,遍历3种BundleLauncher,判断launcher.resolveBundle(this),如果为true,将launcher复制给mApplicableLauncher,插件对应的BundleLauncher的选定就在resolveBundle方法中实现的,那么我们先看看resolveBundle方法。
在resolveBundle方法中首先执行SoBundleLauncher类中的preloadBundle(bundle)方法进行判断。preloadBundle(bundle)方法的主要作用是,判断插件类型是否支持,如果支持,就更新插件so文件和BundleParser。如果插件文件被更新了,则进行进行签名CRC校验,如果校验成功,更新versionCode和versionName,返回true。
示意图如下:
首先,判断bundle是否支持,如果不支持,preloadBundle(bundle)方法返回false,resolveBundle方法也返回false;如果bundle支持,那么获取extractPath路径的so文件plugin,生成这个路径文件的BundleParser为parser,获取patch路径的so文件patch,生成这个路径的BundleParser为patchParser。然后令plugin和parser都设置为最新的,代码如下:
if (parser == null) {
if (patchParser == null) {
return false;
} else {
parser = patchParser; // use patch
plugin = patch;
}
} else if (patchParser != null) {
if (patchParser.getPackageInfo().versionCode
<=parser.getPackageInfo().versionCode) {
Log.d(TAG, "Patch file should be later than built-in!");
patch.delete();
} else {
parser = patchParser; // use patch
plugin = patch;
}
}
接下来设置bundle的BundleParser为parser。判断插件是否被修改,如果被修改则进行签名的CRC校验,如果校验通过,那么将插件中的C/C++本地.so文件复制到流程图中所示路径,并更新versionCode和versionName;如果校验不通过,则将此bundle设置失能。最后preloadBundle(bundle)方法返回true,resolveBundle方法也返回ture。那么,接下来则执行ApkBundleLauncher类中的loadBundle(bundle)方法。
⑦ 生成DexFile对象
ApkBundleLauncher类中的loadBundle(bundle)方法的主要作用是解析插件程序中的Activity, 将生成DexFile的线程添加到List中,其流程图如下:
获取插件中ActivityInfo信息,将这个插件所有的ActivityInfo信息都加入到List activities中,将List activities赋予mPackageInfo.activities。
接下来,获取LoadedApk的对象apk,LoadedApk类的定义如下:
private static class LoadedApk {
//包名
public String packageName;
//data/data/宿主包名/files/storage/插件包名
public File packagePath;
//Application name
public String applicationName;
//data/app/宿主包名/lib/arm/.so
public String path;
//DexFile
public DexFile dexFile;
//data/data/宿主包名/files/storage/插件包名/bundle.dex
public File optDexFile;
//native library路径
public File libraryPath;
//
public boolean nonResources; /** no resources.arsc */
}
将插件程序so文件路径path和bundle.dex文件路径等,作为LoadedApk的对象apk的成员变量。然后将此插件生成DexFile对象的过程放入到Runnable线程中,将此Runnable线程添加到List sIOActions中,将此pluginInfo.activities放入到ConcurrentHashMap
⑧ 线程池中执行sIOActions中的Runnable线程
接下来将sIOActions中的每个线程放入到线程池中进行执行,实现每个插件程序的DexFile的生成,在loadDex方法执行后,对应插件的类加载到内存。
⑨ Element数组的融合
- 然后,遍历sBundleLauncher中每个元素执行ApkBundleLauncher类中的postSetUp方法,代码如下:
@Override
public void postSetUp() {
super.postSetUp();
Collection<LoadedApk> apks = sLoadedApks.values();
......
ClassLoader cl = app.getClassLoader();
i = 0;
int N = apks.size();
String[] dexPaths = new String[N];
DexFile[] dexFiles = new DexFile[N];
for (LoadedApk apk : apks) {
dexPaths[i] = apk.path;
dexFiles[i] = apk.dexFile;
if (Small.getBundleUpgraded(apk.packageName)) {
// If upgraded, delete the opt dex file for recreating
if (apk.optDexFile.exists()) apk.optDexFile.delete();
Small.setBundleUpgraded(apk.packageName, false);
}
i++;
}
ReflectAccelerator.expandDexPathList(cl, dexPaths, dexFiles);
......
}
- 代码中,首先获得宿主程序的类加载器,然后获得各个插件的so文件路径和对应的DexFile对象,将它们带入了下面方法:
ReflectAccelerator.expandDexPathList(cl, dexPaths, dexFiles)
执行到这里,终于到了我们最关键的地方,也是最激动人心的地方,这个方法中实现的最终目的就是我们能够通过宿主类加载器调用loadClass方法获得插件程序中的类,贴代码:
public static boolean expandDexPathList(ClassLoader cl,
String[] dexPaths, DexFile[] dexFiles) {
try {
int N = dexPaths.length;
Object[] elements = new Object[N];
for (int i = 0; i < N; i++) {
String dexPath = dexPaths[i];
File pkg = new File(dexPath);
DexFile dexFile = dexFiles[i];
elements[i] = makeDexElement(pkg, dexFile);
}
fillDexPathList(cl, elements);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
- 首先调用makeDexElement方法,生成Element对象:
//生成Element对象
private static Object makeDexElement(File pkg, DexFile dexFile) throws Exception {
return makeDexElement(pkg, false, dexFile);
}
//生成Element对象
private static Object makeDexElement(File pkg, boolean isDirectory, DexFile dexFile) throws Exception {
//获取DexPathList类中的Element类
if (sDexElementClass == null) {
sDexElementClass = Class.forName("dalvik.system.DexPathList$Element");
}
//获取Element类的构造方法
if (sDexElementConstructor == null) {
sDexElementConstructor = sDexElementClass.getConstructors()[0];
}
//获取Element类构造方法的参数类型
Class<?>[] types = sDexElementConstructor.getParameterTypes();
//生成Element对象
switch (types.length) {
//当API版本为14_17时,Element类构造方法的参数个数为3
case 3:
if (types[1].equals(ZipFile.class)) {
// Element(File apk, ZipFile zip, DexFile dex)
ZipFile zip;
try {
zip = new ZipFile(pkg);
} catch (IOException e) {
throw e;
}
try {
return sDexElementConstructor.newInstance(pkg, zip, dexFile);
} catch (Exception e) {
zip.close();
throw e;
}
} else {
// Element(File apk, File zip, DexFile dex)
return sDexElementConstructor.newInstance(pkg, pkg, dexFile);
}
//当API版本为18_时,Element类构造方法的参数个数为4
case 4:
default:
// Element(File apk, boolean isDir, File zip, DexFile dex)
if (isDirectory) {
return sDexElementConstructor.newInstance(pkg, true, null, null);
} else {
return sDexElementConstructor.newInstance(pkg, false, pkg, dexFile);
}
}
}
- 接下来,将生成插件的Element数组加入到宿主程序Element数组中,调用ReflectAccelerator.java中的fillDexPathList方法:
private static void fillDexPathList(ClassLoader cl, Object[] elements)
throws NoSuchFieldException, IllegalAccessException {
//获取pathList属性
if (sPathListField == null) {
sPathListField = getDeclaredField(DexClassLoader.class.getSuperclass(), "pathList");
}
//实例化pathList属性
Object pathList = sPathListField.get(cl);
//获取dexElements属性
if (sDexElementsField == null) {
sDexElementsField = getDeclaredField(pathList.getClass(), "dexElements");
}
expandArray(pathList, sDexElementsField, elements, true);
}
ReflectAccelerator.java中的expandArray方法:
private static void expandArray(Object target, Field arrField,
Object[] extraElements, boolean push)
throws IllegalAccessException {
//原来的 Element 数组
Object[] original = (Object[]) arrField.get(target);
//用来存储最终结合后的 Element 数组,长度等于 original 和插件 Element 数组长度之和
Object[] combined = (Object[]) Array.newInstance(
original.getClass().getComponentType(), original.length + extraElements.length);
//插件 Element 数组放在前面
if (push) {
System.arraycopy(extraElements, 0, combined, 0, extraElements.length);
System.arraycopy(original, 0, combined, extraElements.length, original.length);
//插件数组放在后面
} else {
System.arraycopy(original, 0, combined, 0, original.length);
System.arraycopy(extraElements, 0, combined, original.length, extraElements.length);
}
//替换数组
arrField.set(target, combined);
}
- 用插件程序中类生成的 Element 对象就添加到了宿主程序 Element 数组中了,当程序调用 DexPathList 类中 findClass 方法遍历数组的时候,就能够找到插件程序对应的 Element 对象,进而就可以调用loadClass方法实现类的实例化。
⑩ LoadBundleThread线程结束
到此Bundle.java中的loadBundles(bundles)方法就执行完了,Bundle.java中的loadBundles(context)方法也结束。
继续执行LoadBundleThread线程run方法下面的程序,sLoading = false执行如下内容:
if (sUIActions != null) {
for (Runnable action : sUIActions) {
action.run();
}
sUIActions = null;
}
如果第一次启动,因为sUIAction为null,上面代码没有作用。
⑪ 给LoadBundleHandler发送MSG_COMPLETE消息
接下来执行如下代码:
sHandler.obtainMessage(MSG_COMPLETE).sendToTarget();
我们不妨先看一下Bundle.java中的LoadBundleHandler类的定义:
private static class LoadBundleHandler extends Handler {
private Small.OnCompleteListener mListener;
public LoadBundleHandler(Small.OnCompleteListener listener) {
mListener = listener;
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_COMPLETE:
if (mListener != null) {
mListener.onComplete();
}
mListener = null;
sThread = null;
sHandler = null;
break;
}
}
}
LoadBundleHandler类的handleMessage方法中调用了net.wequick.small.Small.OnCompleteListener类的onComplete()方法。
响应LoadBundleHandler的MSG_COMPLETE消息,调用Small.OnCompleteListener的onComplete方法,启动MainActivity。