当前位置:文档之家› Android Hotfix 新方案——Amigo 源码解读

Android Hotfix 新方案——Amigo 源码解读

Android Hotfix 新方案——Amigo 源码解读首先我们先来看看如何使用这个库。

用法在project 的build.gradle中dependencies {classpath 'me.ele:amigo:0.0.3'}在module 的build.gradle中apply plugin: 'me.ele.amigo'就这样轻松的集成了Amigo。

生效补丁包补丁包生效有两种方式可以选择:∙稍后生效补丁包∙如果不想立即生效而是用户第二次打开App 时才打入补丁包,则可以将新的Apk 放到/data/data/{your pkg}/files/amigo/demo.apk,第二次打开时就会自动生效。

可以通过这个方法∙File hotfixApk = Amigo.getHotfixApk(context);∙获取到新的Apk。

同时,你也可以使用Amigo 提供的工具类将你的补丁包拷贝到指定的目录当中。

∙FileUtils.copyFile(yourApkFile, amigoApkFile);∙∙立即生效补丁包∙如果想要补丁包立即生效,调用以下两个方法之一,App 会立即重启,并且打入补丁包。

∙Amigo.work(context);∙Amigo.work(context, apkFile);∙删除补丁包如果需要删除掉已经下好的补丁包,可以通过这个方法Amigo.clear(context);提示:如果apk 发生了变化,Amigo 会自动清除之前的apk。

自定义界面在热修复的过程中会有一些耗时的操作,这些操作会在一个新的进程中的Activity 中执行,所以你可以通过以下方式来自定义这个Activity。

<meta-dataandroid:name="amigo_layout"android:value="{your-layout-name}" /><meta-dataandroid:name="amigo_theme"android:value="{your-theme-name}" />组件修复Amigo 目前能够支持增加Activity 和BroadcastReceiver。

只需要将新的Activity 和BroadcastReceiver 加到新的Apk 包中就可以了。

Service 和ContentProvider 将会在未来的版本中支持更新。

集成Amigo 十分简单,但是明白Amigo 的实现更加重要。

源码分析在Amigo这个类中实现了主要的修复工作。

我们一起追追看,到底是怎样的实现。

检查补丁包Amigo.java...if (demoAPk.exists() && isSignatureRight(this, demoAPk)) {SharedPreferences sp = getSharedPreferences(SP_NAME,MODE_MULTI_PROCESS);String demoApkChecksum = checksum(demoAPk);boolean isFirstRun = !sp.getString(NEW_APK_SIG,"").equals(demoApkChecksum);...这段代码中,首先检查是否有补丁包,并且签名正确,如果正确,则通过检验校验和是否与之前的检验和相同,不同则为检测到新的补丁包。

释放Apk当这是新的补丁包时,首先第一件事就是释放。

ApkReleaser.work(this, layoutId, themeId)在这个方法中最终会去开启一个ApkReleaseActivity,而这个Activity 的layout 和theme 就是之前从配置中解析出来,在work 方法中传进来的layoutId 和themeId。

ApkReleaseActivity.java@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);...new Thread() {@Overridepublic void run() {super.run();DexReleaser.releaseDexes(demoAPk.getAbsolutePath(),dexDir.getAbsolutePath());NativeLibraryHelperCompat.copyNativeBinaries(demoAPk, nativeLibraryDir);dexOptimization();handler.sendEmptyMessage(WHAT_DEX_OPT_DONE);}}.start();}在ApkReleaseActivity 的onCreate()方法中会开启一个线程去进行一系列的释放操作,这些操作十分耗时,目前在不同的机子上测试,从几秒到二十几秒之间不等,如果就这样黑屏在用户前面未免太不优雅,所以Amigo 开启了一个新的进程,启动这个Activity。

在这个线程中,做了三件微小的事情:∙释放Dex 到指定目录∙拷贝so 文件到Amigo 的指定目录下拷贝so 文件是通过反射去调用NativeLibraryHelper这个类的nativeCopyNativeBinaries()方法,但这个方法在不同版本上有不同的实现。

∙o如果版本号在21以下oNativeLibraryHelperopublic static int copyNativeBinariesIfNeededLI(File apkFile, File sharedLibraryDir) {final String cpuAbi = Build.CPU_ABI;final String cpuAbi2 = Build.CPU_ABI2;return nativeCopyNativeBinaries(apkFile.getPath(), sharedLibraryDir.getPath(), cpuAbi,cpuAbi2);}会去反射调用这个方法,其中系统会自动判断出primaryAbi 和secondAbi。

o如果版本号在21以上copyNativeBinariesIfNeededLI(file, file)这个方法已经被废弃了,需要去反射调用这个方法NativeLibraryHelperpublic static int copyNativeBinaries(Handle handle, File sharedLibraryDir, String abi) {for (long apkHandle : handle.apkHandles) {int res = nativeCopyNativeBinaries(apkHandle, sharedLibraryDir.getPath(), abi,handle.extractNativeLibs, HAS_NATIVE_BRIDGE);if (res != I NSTALL_SUCCEEDED) {return res;}}return I NSTALL_SUCCEEDED;}所以首先得去获得一个NativeLibraryHelper$Handle类的实例。

之后就是找primaryAbi。

Amigo 先对机器的位数做了判断,如果是64位的机子,就只找64位的abi,如果是32位的,就只找32位的abi。

然后将Handle 实例当做参数去调用NativeLibraryHelper的findSupportedAbi来获得primaryAbi。

最后再去调用copyNativeBinaries去拷贝so 文件。

∙优化dex 文件∙ApkReleaseActivity.java∙private void dexOptimization() {...for (File dex : validDexes) {new DexClassLoader(dex.getAbsolutePath(), optimizedDir.getAbsolutePath(), null,DexUtils.getPathClassLoader());Log.e(TAG, "dexOptimization finished-->" + dex);}}∙DexClassLoader 没有做什么事情,只是调用了父类构造器,他的父类是BaseDexClassLoader。

在BaseDexClassLoader 的构造器中又去构造了一个DexPathList 对象。

在DexPathList类中,有一个Element 数组∙DexPathList∙/** list of dex/resource (class path) elements */private final Element[] dexElements;∙Element 就是对Dex 的封装。

所以一个Element 对应一个Dex。

这个Element 在后文中会提到。

∙优化dex 只需要在构造DexClassLoader 对象的时候将dex 的路径传进去,系统会在最后会通过DexFile的∙DexFile.java∙native private static int openDexFile(String sourceName, String outputName,int flags) throws IOException;∙来这个方法来加载dex,加载的同时会对其做优化处理。

∙这三项操作完成之后,通知优化完毕,之后就关闭这个进程,将补丁包的校验和保存下来。

这样第一步释放Apk 就完成了。

之后就是重头戏替换修复。

替换修复替换classLoaderAmigo 先行构造一个AmigoClassLoader对象,这个AmigoClassLoader是一个继承于PathClassLoader的类,把补丁包的Apk 路径作为参数来构造AmigoClassLoader对象,之后通过反射替换掉LoadedApk 的ClassLoader。

这一步是Amigo 的关键所在。

替换Dex之前提到,每个dex 文件对应于一个PathClassLoader,其中有一个Element[],Element 是对于dex 的封装。

Amigo.javaprivate void setDexElements(ClassLoader classLoader) throws NoSuchFieldException, IllegalAccessException {Object dexPathList = getPathList(classLoader);File[] listFiles = dexDir.listFiles();List<File> validDexes = new ArrayList<>();for (File listFile : listFiles) {if (listFile.getName().endsWith(".dex")) {validDexes.add(listFile);}}File[] dexes = validDexes.toArray(new File[validDexes.size()]);Object originDexElements = readField(dexPathList, "dexElements"); Class<?> localClass =originDexElements.getClass().getComponentType();int length = dexes.length;Object dexElements = Array.newInstance(localClass, length);for (int k = 0; k < length; k++) {Array.set(dexElements, k, getElementWithDex(dexes[k], optimizedDir));}writeField(dexPathList, "dexElements", dexElements);}在替换dex时,Amigo 将补丁包中每个dex 对应的Element 对象拿出来,之后组成新的Element[],通过反射,将现有的Element[] 数组替换掉。

相关主题