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



0 0