Android 插件化和热修复知识梳理

代理模式

P.func InvocationHandler.invoke


Proxy.newProxyInstance

Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

动态代理
动态代理对象P执行方法调用顺序:
P.func==>InvocationHandler.invoke==>目标类实例.func
动态代理实现需要3步:
1 创建目标类接口 及 目标类
2 实现InvocationHandler接口
调用代理对象的每个函数实际最终都是调用了InvocationHandler的invoke函数
3 通过Proxy类新建代理类对象:Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)


InvocationHandler
invoke

Proxy.newProxyInstance      ClassLoader  

Android 插件化和热修复知识梳理
https://www.jianshu.com/p/704cac3eb13d

hook技术实现一键换肤


宿主: 就是当前运行的APP
插件: 相对于插件化技术来说,就是要加载运行的apk类文件
补丁: 相对于热修复技术来说,就是要加载运行的.patch,.dex,*.apk等一系列包含dex修复内容的文件。

.patch       .dex      .apk dex


Android动态加载技术 简单易懂的介绍方式

https://segmentfault.com/a/1190000004062866


ClassLoader Jar

so  dex/jar/apk/zip文件,也就是我们一开始提到的补丁。

data/packagename/


动态加载的大致过程就是:

把可执行文件(.so/dex/jar/apk)拷贝到应用APP内部存储;

加载可执行文件;

调用具体的方法执行业务逻辑;


noexec ->  data/packagename/

.so/dex/jar/apk)拷贝到应用APP内部存储;


ClassLoader System


Android动态加载补充 加载SD卡中的SO库

https://segmentfault.com/a/1190000004062899

  System.loadLibrary("stackblur");
            NativeBlurProcess.isLoadLibraryOk.set(true);

ClassLoader dex

基础知识:类加载器ClassLoader和dex文件


loadClass


tinker

Tinker


dexPathList

dexPathlist

 

 VMStack.getCallingClassLoader
   
   public class BaseDexClassLoader extends ClassLoader {
    public String findLibrary(String name) {
        throw new RuntimeException("Stub!");
    }
}
   
  
BaseDexClassLoader  
   
   
   @Override
    public String findLibrary(String name) {
        return pathList.findLibrary(name);
    }
    
    DexPathList
    Runtime
   doLoad(fileName,loader);
   
   dexClassLoader BaseDexClassLoader 
   dexClassLoader.getLdLibraryPath
   
   return nativeLoad(name, loader, ldLibraryPath);return nativeLoad(name, loader, ldLibraryPath);
   
        if (copyFileFromAssets(this, "libstackblur.so", distFile.getAbsolutePath())){
            //使用load方法加载内部储存的SO库
            System.load(distFile.getAbsolutePath());
            NativeBlurProcess.isLoadLibraryOk.set(true);
        }
   
   
   System.load distFile.getAbsolutePath
   
   
   /storage/emulated/0/libstackblur.so"
   
   
   想要用的话就把可执行文件(so/dex/apk)复制到应用的data目录下再用
  回复  2017-07-30
凸一_一凸: 开发中我们能存储的地方其实不多,一般情况下我们说的外置存储其实都是 /sdcard/目录。内部存储基本上是 /data/data/package/ 目录。当然还有其他可写的目录
   
   /data/data/package/ 
   so/dex/apk)复制到应用的data目录下再用
   
   Android动态加载基础 ClassLoader工作机制
   
   https://segmentfault.com/a/1190000004062880
   
   的ClassLoader实例,用于加载自己dex文件中的类。下面我们在项目里验证看看
   
   ClassLoader dex/apk)复制到应用的data目录下再用
   
   [onCreate] classLoader 1 : dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/me.kaede.anroidclassloadersample-1/base.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]]

[onCreate] classLoader 2 : java.lang.BootClassLoader@14af4e32
   
   
   
   PathClassLoader        BootClassLoader@14af4e32
   
   
   
   
   loadClass
   
findLoaderClass
   
   parent.loadClass 
   
  clazz =  findClass
   
   同一个Class = 相同的 ClassName + PackageName + ClassLoader
   
   makeDexElements   optimizedDirectory
   
   loadDexFile
      File result = new File(optimizedDirectory, fileName);
   
   
   pathList.findClass 
   
   我们可以通过重写loadClass方法避开双亲代理的框架
   
   
   
   DexClassLoader BaseDexClassLoader
   
   
   PathClassLoader        
   
   
   看到这里我们明白了,optimizedDirectory是用来缓存我们需要加载的dex文件的,并创建一个DexFile对象,如果它为null,那么会直接使用dex文件原有的路径来创建DexFile
对象。
   
   。DexClassLoader可以指定自己的optimizedDirectory,所以它可以加载外部的dex,因为这个dex会被复制到内部路径的optimizedDirectory;
   
   optimizedDirectory内部路径
   
   
   
   Android动态加载入门 简单加载模式
   
   https://segmentfault.com/a/1190000004062952
   
   
   .class .jar .dex
   
   jar/apk/dex,可以从SD卡中加载未安装的apk;
   
   
   File optimizedDexOutputPath = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "test_dexloader.jar");// 外部路径
File dexOutputDir = this.getDir("dex", 0);// 无法直接从外部路径加载.dex文件,需要指定APP内部路径作为缓存目录(.dex文件会被解压到此目录)
DexClassLoader dexClassLoader = new DexClassLoader(optimizedDexOutputPath.getAbsolutePath(),dexOutputDir.getAbsolutePath(), null, getClassLoader());
   
   
   getDir
   
   Android动态加载黑科技 动态创建Activity模式
   
   https://segmentfault.com/a/1190000004077469
   
   Android 动态创建Activity模式
   
   TypeId
   
   ClassLoader loader = dexMaker.generateAndLoad(this.getClassLoader(), outputDir);
            Class<?> helloWorldClass = loader.loadClass("HelloWorld");
            // Execute our newly-generated code in-process.
            helloWorldClass.getMethod("hello").invoke(null);
            
            
            generateAndLoad 
            loader.loadClass   getMethod      .invoke
            
    在Android,虚拟机加载类的时候,是通过ClassLoader的loadClass方法,而loadClass方法并不是final类型的,这意味着我们可以创建自己的类去继承ClassLoader,以重载loadClass方法并改写类的加载逻辑,在需要加载PlugActivity的时候,偷偷把其换成TargetActivity。        
            
            
    PlugActivity TargetActivity


    
   CJClassLoader ClassLoader
   
   
   loadClass 
   
   
   ClassLoader类加载Class的时候,会先使用Parent的ClassLoader,但Parent不能完成加载工作时,才会调用Child的ClassLoader去完成工作。
   
   
   
   [2] 为了方便区分概念,阐述一些术语:
宿主:Host,主项目APK、主APK,也就是我们希望采用动态加载技术的主项目;
插件:Plugin,可以是dex、jar或者apk文件,从主项目分离开来,我们能通过动态加载加载到主项目里面来的模块,一个主APK可以同时加载多个插件APK;
   
   Host,主项目APK、主APK,也就是我们希望采用动态加载技术的主项目;
   Plugin,可以是dex、jar或者apk文件,从主项目分离开来,我们能通过动态加载加载到主项目里面来的模块,一个主APK可以同时加载多个插件APK;
   
   dex,jar apk
   
   

PathClassLoader:只能加载已经安装到Android系统中的apk文件(/data/app目录),是Android默认使用的类加载器。


/data/app目录),是Android默认使用的类加载器。


DexClassLoader     dex/jar/apk/zip文件,也就是我们一开始提到的补丁。


apk zip文件,也就是我们一开始提到的补丁。


BaseDexClassLoader  optimizedDirectory
dexPath


DexPathList    Element dexElements
ClassLoaer 的加载机制是一种特别聪明的方式,双亲委托机制,在这种机制下,一个Class只会被加载一次。

Android解析ClassLoader(一)Java中的ClassLoader

http://liuwangshu.cn/application/classloader/1-java-classloader-.html


ClassLoader getParent ClassLoader

ExtClassLoader和AppClassLoader都继承自URLClassLoader,它们都是Launcher 的内部类,Launcher 是Java虚拟机的入口应用,ExtClassLoader和AppClassLoader都是在Launcher中进行初始化的。

3.1 双亲委托模式的特点
向上委托 向下查找
类加载器查找Class所采用的是双亲委托模式,所谓双亲委托模式就是首先判断该Class是否已经加载,如果没有则不是自身去查找而是委托给父加载器进行查找,这样依次的进行递归,直到委托到最顶层的Bootstrap ClassLoader,如果Bootstrap ClassLoader找到了该Class,就会直接返回,如果没找到,则继续依次向下查找,如果还没找到则最后会交由自身去查找。
这样讲可能会有些抽象,来看下面的图。

Bootstrap ClassLoader


Extensions ClassLoader 

App ClassLoader 

Custom ClassLoader

protected Class<?> More ...loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            Class<?> c = findLoadedClass(name);//1
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);//2
                    } else {
                        c = findBootstrapClassOrNull(name);//3
                    }
                } catch (ClassNotFoundException e) {            
                }
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);//4
                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
           if (resolve) {
                resolveClass(c);
            }
           return c;
        }
    }

findLoadedClass 

parent.loadClass

loadClass

findClass defineClass

Javac .java     .class  classloader

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ClassLoaderTest {
    public static void main(String[] args) {
        DiskClassLoader diskClassLoader = new DiskClassLoader("D:\\lib");//1
        try {
            Class c = diskClassLoader.loadClass("com.example.Jobs");//2
            if (c != null) {
                try {
                    Object obj = c.newInstance();
                    System.out.println(obj.getClass().getClassLoader());
                    Method method = c.getDeclaredMethod("say", null);
                    method.invoke(obj, null);//3
                } catch (InstantiationException | IllegalAccessException
                        | NoSuchMethodException
                        | SecurityException |
                        IllegalArgumentException |
                        InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

loadClass newins    

method.invoke

热修复——深入浅出原理与实现

https://juejin.im/post/6844903511243620366


apk 中的 classes.dex

 Class c = pathList.findClass(name, suppressedExceptions);

DexPathList
makeDexElements dexElementsSuppressedExceptions


dexPath是一个用冒号(":")作为分隔符把多个程序文件目录拼接起来的字符串(如:/data/dexdir1:/data/dexdir2:...)。
loadDexFile

Element file false zip  dex


DexPathList findClass

//在dex文件中查找类名与name相同的类
dex.loadClassBinaryName(definingContext);


身DexPathList对象中的Element数组中获取(Element[] dexElements)到对应的类,之后再加载。采用的是数组遍历的方式,不过注意,遍历出来的是一个个的dex文件。

.java .class

dex/jar/apk/zip文件,也就是我们一开始提到的补丁。   

dex

apk  jar  zip 


 private static HashSet<File> loadedDex = new HashSet<>();

     String optimizeDir = appContext.getFilesDir().getAbsolutePath() +


     File.separator + OPTIMIZE_DEX_DIR;// data/data/包名/files/optimize_dex(这个必须是自己程序下的目录)


getPathList dexLoader


DexClassLoader ->PathClassLoader 

     String optimizeDir = appContext.getFilesDir().getAbsolutePath() + File.separator + OPTIMIZE_DEX_DIR;// data/data/包名/files/optimize_dex(这个必须是自己程序下的目录)


DexClassLoader dexLoader = new DexClassLoader(
        dex.getAbsolutePath(),// 修复好的dex(补丁)所在目录
        fopt.getAbsolutePath(),// 存放dex的解压目录(用于jar、zip、apk格式的补丁)
        null,// 加载dex时需要的库
        pathLoader// 父类加载器


        DexClassLoader dexLoader = new DexClassLoader 
        
        dex.getAbsolutePath, fopt.getAbsolutePath  dex   jar  ,zip ,apk  
        

dexPath是一个用冒号(":")作为分隔符把多个程序文件目录拼接起来的字符串(如:/data/dexdir1:/data/dexdir2:...)。
loadDexFile

参数1是dexPath,指的是补丁所有目录,可以是多个目录(用冒号拼接),而且可以是任意目录,比如说SD卡。
参数2是optimizedDirectory,就是存放从压缩包时解压出来的dex文件的目录,但不能是任意目录,它必须是程序所属的目录才行,比如:data/data/包名/xxx。

dexPath  optimizedDirectory


dex jar apk zip 

DexFile 


classes.dex 
大功告成,压缩格式的补丁跟dex格式的补丁一样,直接丢掉SD卡目录下就行了,但一定要注意,压缩格式的补丁中的文件一定是classes.dex!!!
Android热修复技术——QQ空间补丁方案解析(2)

https://developer.aliyun.com/article/70321


jar -cvf patch.jar


dx  -- dex  --output = 

Class.forName   dalvik.system.BaseDexClassLoader

pathList

dexElements


dexopt   

getDir("dexopt",0).getAbsolutePath

Class.forName dalvik.system.BaseDexClassLoader

pathList  
dexElements


DexClassLoader path dexopt       

pathList    dexClassLoader

dexElements pathList       combineElements

dex odex -> dexElements()

Android 热修复 Tinker接入及源码浅析

https://blog.csdn.net/lmj623565791/article/details/54882693


Application#onCreate 

path mapping

TinkerInstaller(this);

Android 如何编写基于编译时注解的项目
https://blog.csdn.net/lmj623565791/article/details/51931859


Retention  Target BindView


MainActivity$$ViewInjector


javapoet


   Class<?> proxyClazz = Class.forName(proxyClassFullName);
        ViewInjector viewInjector = (com.zhy.ioc.ViewInjector) proxyClazz.newInstance();

proxyClazz  = CLASS.forName   

proxyClazz.newInstance     


patch_signed.apk


java -jar tinker-patch-cli-1.7.7.jar

-old old.apk    -new new.apk       
-config tinker_config.xml -out output


Tinker -- 微信Android热补丁方案
https://github.com/Tencent/tinker/wiki

Android之通过配置Flavor实现一个项目打包成多个apk
https://www.cnblogs.com/zhujiabin/p/7650924.html


productFlavors

flavor1 

flavor2    

productFlavors {
    resValue  "string" ,"app_name","版本1"

}

Android Gradle(1) 概念及基础

https://blog.csdn.net/EnjoyEDU/article/details/102966714

Android Gradle(2) productFlavor多风味打包

Gradle(2) productFlavors

https://blog.csdn.net/EnjoyEDU/article/details/102966975


Hook

     //Tinker在完成补丁后会尝试kill掉patch进程,如果不unbind会导致patch进程重启


ERROR_PATCH_INSERVICE    -4    不能在:patch补丁合成进程,发起补丁的合成请求。

ERROR_PATCH_INSERVICE  patch


    private void restartProcess() {
        TinkerLog.i(TAG, "app is background now, i can kill quietly");
        //you can send service or broadcast intent to restart your process
        android.os.Process.killProcess(android.os.Process.myPid());
    }

    
    killProcess(android.os.Process.myPid()); 
    
    PatchResult
    
    将你的实现的类以及它用到的所有类都加入到dex.loader中;
    保证上述的类都在main dex中。

    
    dex.loader  main dex
Tinker.with(getApplicationContext()).cleanPatch();

ShareTinkerInternals.getManifestTinkerID(getApplicationContext()))

getManifestTinkerID

loadArmV7aLibrary
 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值