Amigo源码分析
来源:互联网 发布:程序员面试书籍推荐 编辑:程序博客网 时间:2024/05/02 21:21
Amigo通过使用新的apk来更新原来的apk,来达到修复的目地
amigo会在build的过程中替换原来的Application,把它替换成Amigo,并把原来的Application的名字保存在一个acd.java中的一个n变量中,具体是通过gradle插件生成的
GenerateCodeTask generateCodeTask = project.tasks.create( name: "generate${variant.name.capitalize()}ApplicationInfo", type: GenerateCodeTask) { variantDirName variant.dirName appName applicationName }
AmigoPlugin会调用类似的代码,穿进去application的名字
package me.ele.amigoimport org.gradle.api.DefaultTaskimport org.gradle.api.tasks.Inputimport org.gradle.api.tasks.OutputDirectoryimport org.gradle.api.tasks.OutputFileimport org.gradle.api.tasks.TaskActionclass GenerateCodeTask extends DefaultTask { @Input String appName @Input String variantDirName @OutputDirectory File outputDir() { project.file("${project.buildDir}/generated/source/amigo/${variantDirName}") } @OutputFile File outputFile() { project.file("${outputDir().absolutePath}/me/ele/amigo/acd.java") } @TaskAction def taskAction() { def source = new JavaFileTemplate(['appName': appName]).getContent() def outputFile = outputFile() if (!outputFile.isFile()) { outputFile.delete() outputFile.parentFile.mkdirs() } outputFile.text = source }}
这里是生成acd.java
所以我们程序的入口就变成了Amigo.java,先看下它的onCreate
public void onCreate() { super.onCreate(); Log.e(TAG, "onCreate"); try { directory = new File(getFilesDir(), "amigo");///data/data/me.ele.amigo.demo/files/amigo if (!directory.exists()) { directory.mkdirs(); } demoAPk = new File(directory, "demo.apk"); optimizedDir = new File(directory, "dex_opt");///data/data/me.ele.amigo.demo/files/amigo/demo.apk if (!optimizedDir.exists()) { optimizedDir.mkdir(); } dexDir = new File(directory, "dex"); if (!dexDir.exists()) {///data/data/me.ele.amigo.demo/files/amigo/dex dexDir.mkdir(); } nativeLibraryDir = new File(directory, "lib"); if (!nativeLibraryDir.exists()) {///data/data/me.ele.amigo.demo/files/amigo/lib nativeLibraryDir.mkdir(); } Log.e(TAG, "demoAPk.exists-->" + demoAPk.exists() + ", this--->" + this); ClassLoader classLoader = getClassLoader();//图classloader.png SharedPreferences sp = getSharedPreferences(SP_NAME, MODE_MULTI_PROCESS); if (checkUpgrade(sp)) {//版本是否有升级 Log.e(TAG, "upgraded host app"); clear(this); runOriginalApplication(classLoader);//运行主进程 return; } if (!demoAPk.exists()) {//demoAPk是否存在 Log.e(TAG, "demoApk not exist"); clear(this); runOriginalApplication(classLoader); return; } if (!isSignatureRight(this, demoAPk)) {//签名是否一致 Log.e(TAG, "signature is illegal"); clear(this); runOriginalApplication(classLoader); return; } if (!checkPatchApkVersion(this, demoAPk)) {//版本校验 Log.e(TAG, "patch apk version cannot be less than host apk"); clear(this); runOriginalApplication(classLoader); return; } if (!ProcessUtils.isMainProcess(this) && isPatchApkFirstRun(sp)) {//是否是第一次运行 Log.e(TAG, "none main process and patch apk is not released yet"); runOriginalApplication(classLoader); return; } // only release loaded apk in the main process runPatchApk(sp);//打包 } catch (Throwable e) { e.printStackTrace(); throw new RuntimeException(e); } }
代码中注释比较详细,主要是一些初始化,然后:
1、检查是否有版本升级,如果有的话清除插件包,直接运行主进程
2、检测插件包是否存在(demo.apk,上次热修复保存的),如果不存在清除目录,直接运行主进程
3、说明有上次热修复过后的插件包,则进行一些校验调用runPatchApk解析插件包
1跟2的流程是一样的,启动主线程,我们先看这种情况
private void runOriginalApplication(ClassLoader classLoader) throws Throwable { Class acd = classLoader.loadClass("me.ele.amigo.acd");//应用程序的applicationName会保存到me.ele.amigo.acd的n成员 String applicationName = (String) readStaticField(acd, "n");//me.ele.amigo.demo.ApplicationContext Application application = (Application) classLoader.loadClass(applicationName).newInstance();//获取application,如demo中me.ele.amigo.demo.ApplicationContext Method attach = getDeclaredMethod(Application.class, "attach", Context.class);//获取attach方法 attach.setAccessible(true);//设置为可访问 attach.invoke(application, getBaseContext());//调用该attatch方法 setAPKApplication(application);//重新设置该apk的application application.onCreate();//调用application的onCreate }这里获取真正Application的名字,调用setAPKApplication把他设置为apk的Application,最后调用它的onCreate
//重新设置apk的aplication private void setAPKApplication(Application application) throws InvocationTargetException, NoSuchMethodException, ClassNotFoundException, IllegalAccessException { Object apk = getLoadedApk();// 这里的application还是Amigo writeField(apk, "mApplication", application);//替换为当前apk实际的application,替换后4. }
替换后
看看demo中的onCreate
public void onCreate() { super.onCreate(); Log.e(TAG, "onCreate: " + this); if (ProcessUtils.isMainProcess(this)) {//是否在主线程 File fixedApkFile = new File(Environment.getExternalStorageDirectory(), "demo.apk");///storage/sdcard0/demo.apk File amigoApkFile = Amigo.getHotfixApk(this);///data/data/me.ele.amigo.demo/files/amigo/demo.apk if (fixedApkFile.exists() && !amigoApkFile.exists()) {//SD卡下存在 且 app安装目录下不存在 Amigo.work(this, fixedApkFile);//修复// Amigo.workLater(this, fixedApkFile); } } }
检查sd卡上是否有 demo.apk,进行一些检查后,然后调用Amigo.work进行修复
// auto restart the whole app自动重启整个app public static void work(Context context, File apkFile) { if (context == null) { throw new NullPointerException("param context cannot be null"); } if (apkFile == null) {///storage/sdcard0/demo.apk throw new NullPointerException("param apkFile cannot be null"); } if (!apkFile.exists()) { throw new IllegalArgumentException("param apkFile doesn't exist"); } if (!apkFile.canRead()) {//是否可读 throw new IllegalArgumentException("param apkFile cannot be read"); } if (!isSignatureRight(context, apkFile)) {//签名是否正确 Log.e(TAG, "no valid apk"); return; } if (!checkPatchApkVersion(context, apkFile)) {//插件版本 Log.e(TAG, "patch apk version cannot be less than host apk"); return; } File directory = new File(context.getFilesDir(), "amigo");///data/data/me.ele.amigo.demo/files/amigo File demoAPk = new File(directory, "demo.apk");///data/data/me.ele.amigo.demo/files/amigo/demo.apk if (!apkFile.getAbsolutePath().equals(demoAPk.getAbsolutePath())) {//不相等,则拷贝 copyFile(apkFile, demoAPk); } AmigoService.start(context, false);//启动AmigoService,workLater为false System.exit(0);//结束当前进程 Process.killProcess(Process.myPid()); }
这里主要是做了3件事:
1、对插件包进行一系列的检查,把文件拷贝到/data/data/me.ele.amigo.demo/files/amigo/demo.apk
2、调用AmigoService.start启动AmigoService
3、结束当前进程
我们看下AmigoService的启动
public static void start(Context context, boolean workLater) {//启动Service Intent intent = new Intent(context, AmigoService.class); intent.putExtra(WORK_LATER, workLater); context.startService(intent); }
@Override public int onStartCommand(Intent intent, int flags, int startId) { try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } if (intent != null) { boolean workLater = intent.getBooleanExtra(WORK_LATER, false); if (workLater) {//是否立即修复 ApkReleaser.getInstance(this).release(); } else { handler.sendEmptyMessage(WHAT); } } return super.onStartCommand(intent, flags, startId); }
这里的sleep是我为了方便调试加的
如果不是立即修复,则调用ApkReleaser.getInstance(this).release();否则发送一个WHAT消息
这里workLater为false,发送一个WHAT消息
private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case WHAT: Context context = AmigoService.this; if (!isMainProcessRunning(context)) {//启动主Activity Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName()); launchIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(launchIntent); Log.e(TAG, "start launchIntent"); stopSelf(); System.exit(0); Process.killProcess(Process.myPid()); return; } sendEmptyMessageDelayed(WHAT, DELAY); break; default: break; } } };这里如果是主线程,则获取当前启动的Activity,并启动他,然后结束当前进程,这样第二次启动app的时候,在Amigo的onCreate中,最终会调用runPatchApk
private void runPatchApk(SharedPreferences sp) throws Throwable { String demoApkChecksum = getCrc(demoAPk); boolean isFirstRun = isPatchApkFirstRun(sp); Log.e(TAG, "demoApkChecksum-->" + demoApkChecksum + ", sig--->" + sp.getString(NEW_APK_SIG, "")); if (isFirstRun) {//第一次运行 //clear previous working dir Amigo.clearWithoutApk(this);//清除/data/data/me.ele.amigo.demo/files/amigo目录文件 //start a new process to handle time-tense operation ApplicationInfo appInfo = getPackageManager().getApplicationInfo(getPackageName(), GET_META_DATA);//获取meta数据 String layoutName = appInfo.metaData.getString("amigo_layout");//amigo_layout_demo String themeName = appInfo.metaData.getString("amigo_theme");//amigoThemeDemo int layoutId = 0; int themeId = 0; if (!TextUtils.isEmpty(layoutName)) {//获取布局id layoutId = (int) readStaticField(Class.forName(getPackageName() + ".R$layout"), layoutName); } if (!TextUtils.isEmpty(themeName)) {//获取主题id themeId = (int) readStaticField(Class.forName(getPackageName() + ".R$style"), themeName); } Log.e(TAG, String.format("layoutName-->%s, themeName-->%s", layoutName, themeName)); Log.e(TAG, String.format("layoutId-->%d, themeId-->%d", layoutId, themeId)); ApkReleaser.work(this, layoutId, themeId); Log.e(TAG, "release apk once"); } else { checkDexAndSoChecksum(); } AmigoClassLoader amigoClassLoader = new AmigoClassLoader(demoAPk.getAbsolutePath(), getRootClassLoader()); setAPKClassLoader(amigoClassLoader);//设置LoadedApk为该classLoader setDexElements(amigoClassLoader);//重新设置dex文件 setNativeLibraryDirectories(amigoClassLoader);//重新设置so文件目录 AssetManager assetManager = AssetManager.class.newInstance(); Method addAssetPath = getDeclaredMethod(AssetManager.class, "addAssetPath", String.class);//获取addAssetPath方法 addAssetPath.setAccessible(true); addAssetPath.invoke(assetManager, demoAPk.getAbsolutePath());//添加/data/data/me.ele.amigo.demo/files/amigo/demo.apk setAPKResources(assetManager);//重新设置资源目录 runOriginalApplication(amigoClassLoader);//运行主进程 }这里isFirstRun为true,获取amigo_layout和amigo_theme,然后调用ApkReleaser.work,传递前面的两个参数
public static void work(Context context, int layoutId, int themeId) { if (!ProcessUtils.isLoadDexProcess(context)) {//不是LoadDex进程 if (!isDexOptDone(context)) {//dex优化是否完成 waitDexOptDone(context, layoutId, themeId);//等待dex优化 } } }
//等待dex优化完成 private static void waitDexOptDone(Context context, int layoutId, int themeId) { new Launcher(context, layoutId).themeId(themeId).launch();//启动ApkReleaseActivity释放apk文件 while (!isDexOptDone(context)) {//等待Dex释放优化完成 try { Thread.sleep(SLEEP_DURATION); } catch (InterruptedException e) { e.printStackTrace(); } } }
启动Launcher的launch方法,并等待优化完成
public void launch() {//启动ApkReleaseActivity Intent intent = new Intent(context, ApkReleaseActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(ApkReleaseActivity.LAYOUT_ID, layoutId); intent.putExtra(ApkReleaseActivity.THEME_ID, themeId); context.startActivity(intent); }这样就进入ApkReleaseActivity
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); overridePendingTransition(0, 0); layoutId = getIntent().getIntExtra(LAYOUT_ID, 0);//获取布局id themeId = getIntent().getIntExtra(THEME_ID, 0);//获取主题id if (themeId != 0) { setTheme(themeId); } if (layoutId != 0) { setContentView(layoutId); } ApkReleaser.getInstance(this).release();////释放apk文件 }
先看下ApkReleaser的生成
//构造ApkReleaser private ApkReleaser(Context context) { this.context = context; directory = new File(context.getFilesDir(), "amigo");///data/data/me.ele.amigo.demo/files/amigo if (!directory.exists()) { directory.mkdirs(); } demoAPk = new File(directory, "demo.apk");///data/data/me.ele.amigo.demo/files/amigo/demo.apk optimizedDir = new File(directory, "dex_opt");///data/data/me.ele.amigo.demo/files/amigo/dex_opt if (!optimizedDir.exists()) { optimizedDir.mkdir(); } dexDir = new File(directory, "dex");///data/data/me.ele.amigo.demo/files/amigo/dex if (!dexDir.exists()) { dexDir.mkdir(); } nativeLibraryDir = new File(directory, "lib");///data/data/me.ele.amigo.demo/files/amigo/lib if (!nativeLibraryDir.exists()) { nativeLibraryDir.mkdir(); } service = Executors.newFixedThreadPool(3);//3个线程池 }
看下directory
然后是release方法
//释放apk文件 public void release() { if (isReleasing) {//是否已经在释放 return; } Log.e(TAG, "release doing--->" + isReleasing); service.submit(new Runnable() { @Override public void run() { isReleasing = true; DexReleaser.releaseDexes(demoAPk.getAbsolutePath(), dexDir.getAbsolutePath());//释放dex NativeLibraryHelperCompat.copyNativeBinaries(demoAPk, nativeLibraryDir);//拷贝动态库 dexOptimization();//优化dex } }); }
先看releaseDexes函数
public static void releaseDexes(String zipFile, String outputFolder) {///data/data/me.ele.amigo.demo/files/amigo/demo.apk /data/data/me.ele.amigo.demo/files/amigo/dex byte[] buffer = new byte[1024]; try { File folder = new File(outputFolder); if (!folder.exists()) { folder.mkdir(); } ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile)); ZipEntry ze = zis.getNextEntry(); while (ze != null) { String fileName = ze.getName(); if (!fileName.startsWith("classes") || !fileName.endsWith(".dex")) {//查找classes.dex ze = zis.getNextEntry(); continue; } File newFile = new File(outputFolder + File.separator + fileName);///data/data/me.ele.amigo.demo/files/amigo/dex/classes.dex new File(newFile.getParent()).mkdirs(); FileOutputStream fos = new FileOutputStream(newFile); int len; while ((len = zis.read(buffer)) > 0) {//把dex文件拷贝过来 fos.write(buffer, 0, len); } fos.close(); ze = zis.getNextEntry(); } zis.closeEntry(); zis.close(); } catch (IOException ex) { ex.printStackTrace(); } }
这里主要是把dex文件从apk zip包中拷贝到目标目录
然后是copyNativeBinaries
public static final int copyNativeBinaries(File apkFile, File sharedLibraryDir) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {//版本大于21 return copyNativeBinariesAfterL(apkFile, sharedLibraryDir); } else { return copyNativeBinariesBeforeL(apkFile, sharedLibraryDir); } }
这里区分当前系统的版本号
@TargetApi(Build.VERSION_CODES.LOLLIPOP) private static int copyNativeBinariesAfterL(File apkFile, File sharedLibraryDir) { try { Object handleInstance = MethodUtils.invokeStaticMethod(handleClass(), "create", apkFile);//调用Handle的类的create if (handleInstance == null) { return -1; } String abi = null; if (isVM64()) { if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { Set<String> abis = getAbisFromApk(apkFile.getAbsolutePath()); if (abis == null || abis.isEmpty()) { return 0; } int abiIndex = (int) MethodUtils.invokeStaticMethod(nativeLibraryHelperClass(), "findSupportedAbi", handleInstance, Build.SUPPORTED_64_BIT_ABIS); if (abiIndex >= 0) { abi = Build.SUPPORTED_64_BIT_ABIS[abiIndex]; } } } else { if (Build.SUPPORTED_32_BIT_ABIS.length > 0) { Set<String> abis = getAbisFromApk(apkFile.getAbsolutePath()); if (abis == null || abis.isEmpty()) { return 0; } int abiIndex = (int) MethodUtils.invokeStaticMethod(nativeLibraryHelperClass(), "findSupportedAbi", handleInstance, Build.SUPPORTED_32_BIT_ABIS); if (abiIndex >= 0) { abi = Build.SUPPORTED_32_BIT_ABIS[abiIndex]; } } } if (abi == null) { return -1; } Object[] args = new Object[3]; args[0] = handleInstance; args[1] = sharedLibraryDir; args[2] = abi; return (int) MethodUtils.invokeStaticMethod(nativeLibraryHelperClass(), "copyNativeBinaries", args); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return -1; }
看下handleInstance
isVM64判断当前是不是64位系统
@TargetApi(Build.VERSION_CODES.LOLLIPOP)//是不是64位 private static boolean isVM64() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Set<String> supportedAbis = getAbisFromApk(getHostApk()); if (Build.SUPPORTED_64_BIT_ABIS.length == 0) {//20.png return false; } if (supportedAbis == null || supportedAbis.isEmpty()) { return true; } for (String supportedAbi : supportedAbis) {//是否支持 if ("arm64-v8a".endsWith(supportedAbi) || "x86_64".equals(supportedAbi) || "mips64".equals(supportedAbi)) { return true; } } return false; }
getAbisFromApk获取so文件
//获取apk中的so文件 private static Set<String> getAbisFromApk(String apk) {///data/app/me.ele.amigo.demo-1/base.apk try { ZipFile apkFile = new ZipFile(apk); Enumeration<? extends ZipEntry> entries = apkFile.entries(); Set<String> supportedAbis = new HashSet<>(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); String name = entry.getName(); if (name.contains("../")) { continue; } if (name.startsWith("lib/") && !entry.isDirectory() && name.endsWith(".so")) {//查找支持的平台 String supportedAbi = name.substring(name.indexOf("/") + 1, name.lastIndexOf("/")); supportedAbis.add(supportedAbi); } } Log.d(TAG, "supportedAbis : " + supportedAbis); return supportedAbis; } catch (Exception e) { Log.e(TAG, "get supportedAbis failure", e); } return null; }
根据平台的不一样调用不同的函数,最终是通过invokeStaticMethod调用的
//调用静态方法//class com.android.internal.content.NativeLibraryHelper$Handle create /data/data/me.ele.amigo.demo/files/amigo/demo.apk public static Object invokeStaticMethod(Class clazz, String methodName, Object... args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { args = Utils.nullToEmpty(args); Class<?>[] parameterTypes = Utils.toClass(args); return invokeStaticMethod(clazz, methodName, args, parameterTypes); }
另外一个copyNativeBinariesBeforeL则相对简单些
回到run继续调用dexOptimization进行优化dex
//优化dex private void dexOptimization() { Log.e(TAG, "dexOptimization"); File[] listFiles = dexDir.listFiles();///data/data/me.ele.amigo.demo/files/amigo/dex/classes.dex final List<File> validDexes = new ArrayList<>(); for (File listFile : listFiles) { if (listFile.getName().endsWith(".dex")) { validDexes.add(listFile); } } final CountDownLatch countDownLatch = new CountDownLatch(validDexes.size()); for (final File dex : validDexes) { service.submit(new Runnable() { @Override public void run() { long startTime = System.currentTimeMillis(); String optimizedPath = optimizedPathFor(dex, optimizedDir); DexFile dexFile = null; try { dexFile = DexFile.loadDex(dex.getPath(), optimizedPath, 0);//加载dex } catch (IOException e) { e.printStackTrace(); } finally { if (dexFile != null) { try { dexFile.close(); } catch (IOException e) { e.printStackTrace(); } } } Log.e(TAG, String.format("dex %s consume %d ms", dex.getAbsolutePath(), System.currentTimeMillis() - startTime)); countDownLatch.countDown(); } }); } try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } Log.e(TAG, "dex opt done"); handler.sendEmptyMessage(WHAT_DEX_OPT_DONE); }
把所有的dex文件添加到validDexes
调用optimizedPathFor获取优化后的存放目录
调用loadDex加载dex
调用 handler.sendEmptyMessage(WHAT_DEX_OPT_DONE);发送dex优化完成消息
///data/data/me.ele.amigo.demo/files/amigo/dex/classes.dex /data/data/me.ele.amigo.demo/files/amigo/dex_opt private String optimizedPathFor(File path, File optimizedDirectory) {//获取dex优化后的目录 String fileName = path.getName(); if (!fileName.endsWith(DEX_SUFFIX)) { int lastDot = fileName.lastIndexOf("."); if (lastDot < 0) { fileName += DEX_SUFFIX; } else { StringBuilder sb = new StringBuilder(lastDot + 4); sb.append(fileName, 0, lastDot); sb.append(DEX_SUFFIX); fileName = sb.toString(); } } File result = new File(optimizedDirectory, fileName); return result.getPath();///data/data/me.ele.amigo.demo/files/amigo/dex_opt/classes.dex }
private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case WHAT_DEX_OPT_DONE://优化完成 isReleasing = false; ApkReleaser.doneDexOpt(context);//设置完成 saveDexAndSoChecksum(); SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_MULTI_PROCESS); String demoApkChecksum = getCrc(demoAPk);///data/data/me.ele.amigo.demo/files/amigo/demo.apk sp.edit().putString(Amigo.NEW_APK_SIG, demoApkChecksum).commit(); handler.sendEmptyMessageDelayed(WHAT_FINISH, DELAY_FINISH_TIME);//结束消息 break; case WHAT_FINISH: System.exit(0); Process.killProcess(Process.myPid()); break; default: break; } } };优化完成后
调用
//dex 优化完成 public static void doneDexOpt(Context context) { context.getSharedPreferences(SP_NAME, Context.MODE_MULTI_PROCESS) .edit() .putBoolean(getUniqueKey(context), true) .apply(); }
设置已经完成,这样waitDexOptDone将会继续执行
saveDexAndSoChecksum保存校验和
发送WHAT_FINISH消息,WHAT_FINISH消息会结束当前进程。
回到waitDexOptDone继续执行,也即ApkReleaser.work(this, layoutId, themeId);执行完毕,回到runPatchApk,执行如下代码
AmigoClassLoader amigoClassLoader = new AmigoClassLoader(demoAPk.getAbsolutePath(), getRootClassLoader()); setAPKClassLoader(amigoClassLoader);//设置LoadedApk为该classLoader setDexElements(amigoClassLoader);//重新设置dex文件 setNativeLibraryDirectories(amigoClassLoader);//重新设置so文件目录 AssetManager assetManager = AssetManager.class.newInstance(); Method addAssetPath = getDeclaredMethod(AssetManager.class, "addAssetPath", String.class);//获取addAssetPath方法 addAssetPath.setAccessible(true); addAssetPath.invoke(assetManager, demoAPk.getAbsolutePath());//添加/data/data/me.ele.amigo.demo/files/amigo/demo.apk setAPKResources(assetManager);//重新设置资源目录 runOriginalApplication(amigoClassLoader);//运行主进程
首先是新建了一个AmigoClassLoader
public class AmigoClassLoader extends PathClassLoader { public AmigoClassLoader(String dexPath, String libraryPath, ClassLoader parent) { super(dexPath, libraryPath, parent); } public AmigoClassLoader(String dexPath, ClassLoader parent) { super(dexPath, parent); }}新的ClassLoader主要是修改了dexPath,使用我们这个新的apk中的dex
setAPKClassLoader设置mClassLoader
//设置apk的classLoader private void setAPKClassLoader(ClassLoader classLoader) throws IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InvocationTargetException { writeField(getLoadedApk(), "mClassLoader", classLoader); }然后回到runPatchApk 调用setDexElements
private void setDexElements(ClassLoader classLoader) throws NoSuchFieldException, IllegalAccessException { Object dexPathList = getPathList(classLoader);// File[] listFiles = dexDir.listFiles();///data/data/me.ele.amigo.demo/files/amigo/dex 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()]);///data/data/me.ele.amigo.demo/files/amigo/dex/classes.dex Object originDexElements = readField(dexPathList, "dexElements");// Class<?> localClass = originDexElements.getClass().getComponentType();//dalvik.system.DexPathList$Element 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);//重新赋值dexElements }
这里主要是重新设置它的dexElements
同样setNativeLibraryDirectories设置so的目录
//重新设置so库的目录 private void setNativeLibraryDirectories(AmigoClassLoader hackClassLoader) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, NoSuchFieldException { injectSoAtFirst(hackClassLoader, nativeLibraryDir.getAbsolutePath());///data/data/me.ele.amigo.demo/files/amigo/lib nativeLibraryDir.setReadOnly(); File[] libs = nativeLibraryDir.listFiles(); if (libs != null && libs.length > 0) { for (File lib : libs) { lib.setReadOnly(); } } }
//soPath = /data/data/me.ele.amigo.demo/files/amigo/lib 重新设置so目录 public static void injectSoAtFirst(ClassLoader hackClassLoader, String soPath) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException { Object[] baseDexElements = getNativeLibraryDirectories(hackClassLoader); Object newElement; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { Constructor constructor = baseDexElements[0].getClass().getConstructors()[0]; constructor.setAccessible(true); Class<?>[] parameterTypes = constructor.getParameterTypes(); Object[] args = new Object[parameterTypes.length]; for (int i = 0; i < parameterTypes.length; i++) { if (parameterTypes[i] == File.class) { args[i] = new File(soPath); } else if (parameterTypes[i] == boolean.class) { args[i] = true; } } newElement = constructor.newInstance(args); } else { newElement = new File(soPath); } Object newDexElements = Array.newInstance(baseDexElements[0].getClass(), 1); Array.set(newDexElements, 0, newElement);//新的目录添加到数组前面 Object allDexElements = combineArray(newDexElements, baseDexElements);//合并两个目录 Object pathList = getPathList(hackClassLoader);//获取classLoader的pathList if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//合并后的目录写回 writeField(pathList, "nativeLibraryPathElements", allDexElements); } else { writeField(pathList, "nativeLibraryDirectories", allDexElements); } }
//获取DexPathList[[dex file "dalvik.system.DexFile@43eb75a8"],nativeLibraryDirectories=[/vendor/lib, /system/lib, /data/datalib]] so库目录 public static Object[] getNativeLibraryDirectories(ClassLoader hackClassLoader) throws NoSuchFieldException, IllegalAccessException { Object pathList = getPathList(hackClassLoader); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return (Object[]) readField(pathList, "nativeLibraryPathElements"); } else { return (Object[]) readField(pathList, "nativeLibraryDirectories"); } }
回到runPatchApk
AssetManager assetManager = AssetManager.class.newInstance(); Method addAssetPath = getDeclaredMethod(AssetManager.class, "addAssetPath", String.class);//获取addAssetPath方法 addAssetPath.setAccessible(true); addAssetPath.invoke(assetManager, demoAPk.getAbsolutePath());//添加/data/data/me.ele.amigo.demo/files/amigo/demo.apk setAPKResources(assetManager);//重新设置资源目录
重新设置资源,添加新apk到资源目录
private void setAPKResources(AssetManager newAssetManager) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ClassNotFoundException { invokeMethod(newAssetManager, "ensureStringBlocks");//创建字符串资源池 Collection<WeakReference<Resources>> references; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { Class<?> resourcesManagerClass = Class.forName("android.app.ResourcesManager");//获取ResourcesManager Object resourcesManager = invokeStaticMethod(resourcesManagerClass, "getInstance"); if (getField(resourcesManagerClass, "mActiveResources") != null) {//获取mActiveResources成员 ArrayMap<?, WeakReference<Resources>> arrayMap = (ArrayMap) readField(resourcesManager, "mActiveResources", true);// references = arrayMap.values(); } else { references = (Collection) readField(resourcesManager, "mResourceReferences", true); } } else { HashMap<?, WeakReference<Resources>> map = (HashMap) readField(instance(), "mActiveResources", true); references = map.values(); } for (WeakReference<Resources> wr : references) { Resources resources = wr.get(); if (resources == null) continue; try { writeField(resources, "mAssets", newAssetManager);//重新赋值AssetManager } catch (Throwable ignore) { Object resourceImpl = readField(resources, "mResourcesImpl", true); writeField(resourceImpl, "mAssets", newAssetManager); } resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics()); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { for (WeakReference<Resources> wr : references) { Resources resources = wr.get(); if (resources == null) continue; // android.util.Pools$SynchronizedPool<TypedArray> Object typedArrayPool = readField(resources, "mTypedArrayPool", true); // Clear all the pools while (invokeMethod(typedArrayPool, "acquire") != null) ; } } }
最后调用runOriginalApplication启动主线程
这样,我们每次启动app,都重新设置了classloader等相关变量 ,使用新的apk中资源等。
- Amigo源码分析
- Amigo 源码解读
- Android Hotfix 新方案——Amigo 源码解读
- Amigo---Android hotfix terminator
- 插件化Amigo-1
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析:SparseArray分析
- 源码- Spark Broadcast源码分析
- Android源码/框架源码分析
- 【Android应用源码分析】HandlerThread 源码分析
- 【Android应用源码分析】IntentService 源码分析
- svn开一个新项目
- [计算机动画] 线性插值 矢量线性插值
- C++精讲3构造函数与析构函数
- 如何本地安装dedecms网站
- python 中文切词并计算相似度
- Amigo源码分析
- PHP你所不知道的事--empty
- java发送http的get、post请求
- ajax超时
- AndroidStudio修改PackName
- 福昕阅读器常用快捷键
- HTML基础知识自结
- hdoj2112-HDU Today
- scikit learning——用k邻近算法进行分类实例