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.amigo
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
class 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中资源等。