代理模式
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