Amigo源码分析

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中资源等。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值