diff --git a/blogs/Android/APT.md b/blogs/Android/APT.md index c2af177..fae1ec6 100644 --- a/blogs/Android/APT.md +++ b/blogs/Android/APT.md @@ -14,6 +14,8 @@ APT、JavaPoet 实现 ButterKnife #### 思维导图 +![](https://i.loli.net/2019/01/26/5c4c00a69d7a5.png) + #### 概述 用 APT、JavaPoet、AutoService 实现简单的 ButterKnife,APT 负责处理编译时注解,JavaPoet 用于生成 Java 代码,AutoService 负责注册注解处理器。 @@ -150,6 +152,152 @@ JavaFile:包含一个顶级类的 Java 文件; ##### 定义注解 +```java +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.FIELD) +public @interface BindView { + int value(); +} +``` + +##### 注解处理器处理注解、生成 Java 文件 + +```java +@AutoService(Processor.class) +public class BindViewProcessor extends AbstractProcessor { + + private Elements mElementsUtils; + private Types mTypesUtils; + private Filter mFilter; + private Messager mMessager; + + /** + * 初始化方法 + * 可以初始化一些给注解处理器使用的工具类 + */ + @Override + public synchronized void init(ProcessingEnvironment processingEnvironment) { + super.init(processingEnvironment); + mElementsUtils = processingEnvironment.getElementUtils(); + } + + /** + * 指定目标注解对象 + */ + @Override + public Set getSupportedAnnotationTypes() { + Set hashSet = new HashSet<>(); + hashSet.add(BindView.class.getCanonicalName()); + return hashSet; + } + + /** + * 指定使用的 Java 版本 + */ + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + /** + * 处理注解 + */ + @Override + public boolean process(Set set, RoundEnvironment roundEnvironment) { + //获取所有包含 BindView 注解的元素 + Set elementSet = roundEnvironment.getElementsAnnotatedWith(BindView.class); + Map> typeElementMapHashMap = new HashMap<>(); + for (Element element : elementSet) { + //因为 BindView 的作用对象是 FIELD,因此 element 可以直接转化为 VariableElement + VariableElement variableElement = (VariableElement) element; + //getEnclosingElement 方法返回封装此 Element 的最里层元素 + //如果 Element 直接封装在另一个元素的声明中,则返回该封装元素 + //此处表示的即是 Activity 类对象 + TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement(); + Map variableElementMap = typeElementMapHashMap.get(typeElement); + if (variableElementMap == null) { + variableElementMap = new HashMap<>(); + typeElementMapHashMap.put(typeElement, variableElementMap); + } + //获取注解的值,即 ViewId + BindView bindAnnotation = variableElement.getAnnotation(BindView.class); + int viewId = bindAnnotation.value(); + variableElementMap.put(viewId, variableElement); + } + for (TypeElement key : typeElementMapHashMap.keySet()) { + Map elementMap = typeElementMapHashMap.get(key); + String packageName = ElementUtil.getPackageName(mElementsUtils, key); + JavaFile javaFile = JavaFile.builder(packageName, generateCodeByPoet(key, elementMap)).build(); + try { + javaFile.writeTo(processingEnv.getFiler()); + } catch (IOException e) { + e.printStackTrace(); + } + } + return true; + } + + /** + * 生成 Java 类 + * + * @param typeElement 注解对象的上层元素对象,即 Activity 对象 + * @param variableElementMap Activity 包含的注解对象以及注解的目标对象 + * @return + */ + private TypeSpec generateCodeByPoet(TypeElement typeElement, Map variableElementMap) { + //自动生成的文件以 Activity 名 + ViewBinding 进行命名 + return TypeSpec.classBuilder(ElementUtil.getEnclosingClassName(typeElement) + "ViewBinding") + .addModifiers(Modifier.PUBLIC) + .addMethod(generateMethodByPoet(typeElement, variableElementMap)) + .build(); + } + + /** + * 生成方法 + * + * @param typeElement 注解对象上层元素对象,即 Activity 对象 + * @param variableElementMap Activity 包含的注解对象以及注解的目标对象 + * @return + */ + private MethodSpec generateMethodByPoet(TypeElement typeElement, Map variableElementMap) { + ClassName className = ClassName.bestGuess(typeElement.getQualifiedName().toString()); + //方法参数名 + String parameter = "_" + StringUtil.toLowerCaseFirstChar(className.simpleName()); + MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .returns(void.class) + .addParameter(className, parameter); + for (int viewId : variableElementMap.keySet()) { + VariableElement element = variableElementMap.get(viewId); + //被注解的字段名 + String name = element.getSimpleName().toString(); + //被注解的字段的对象类型的全名称 + String type = element.asType().toString(); + String text = "{0}.{1}=({2})({3}.findViewById({4}));\n"; + methodBuilder.addCode(MessageFormat.format(text, parameter, name, type, parameter, String.valueOf(viewId))); + } + return methodBuilder.build(); + } +} +``` + +##### 引入 + +```java +public class ButterKnife { + public static void bind(Activity activity) { + Class clazz = activity.getClass(); + try { + Class bindViewClass = Class.forName(clazz.getName() + "ViewBinding"); + Method method = bindViewClass.getMethod("bind", clazz); + method.invoke(null, activity); + } catch (Exception e) { + e.printStackTrace(); + } + } +} +``` + #### 参考 diff --git a/blogs/Android/热修复.md b/blogs/Android/热修复.md new file mode 100644 index 0000000..bb0a60e --- /dev/null +++ b/blogs/Android/热修复.md @@ -0,0 +1,266 @@ +--- +热修复 +--- + +#### 目录 + +1. 思维导图 +2. 概述 +3. 原理浅析 + - BaseDexClassLoader + - PathClassLoader + - DexClassLoader + - DexPathList +4. 实战 +5. 参考 + +#### 思维导图 + +![](https://i.loli.net/2019/01/26/5c4c5bc8bd779.png) + +#### 概述 + +一句话来说就是 dex 插桩。 + +在加载 dex 文件的时候,是放在一个 Element[] 数组里的,我们只要把修复好的 classes.dex 文件放在数组最前面被优先加载,那在加载有 bug 的 dex 文件里的 class 类的时候已经被加载过了,系统就不会加载,这时候带有 bug 的 class 就算被 “修复” 了。 + +#### 原理浅析 + +热修复涉及的就是 dex 的加载流程。涉及的类有 BaseDexClassLoader、DexClassLoader、PathClassLoader、DexPathList 等等。 + +##### BaseDexClassLoader + +```java +public class BaseDexClassLoader extends ClassLoader { + + private final DexPathList pathList; + + /** + * dexPath: 要加载的程序文件,一般是 dex 文件 + * optimizedDirectory: dex 文件解压目录 + * librarySearchPath: 加载程序文件时需要用到的库路径 + * parent: 父加载器 + */ + public BaseDexClassLoader(String dexPath, File optimizedDirectory, + String librarySearchPath, ClassLoader parent) { + super(parent); + this.pathList = new DexPathList(this, dexPath, librarySearchPath, null); + + if (reporter != null) { + reporter.report(this.pathList.getDexPaths()); + } + } + + //... + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + List suppressedExceptions = new ArrayList(); + Class c = pathList.findClass(name, suppressedExceptions); + if (c == null) { + ClassNotFoundException cnfe = new ClassNotFoundException( + "Didn't find class \"" + name + "\" on path: " + pathList); + for (Throwable t : suppressedExceptions) { + cnfe.addSuppressed(t); + } + throw cnfe; + } + return c; + } + //... +} +``` + +其实 BaseDexClassLoader 的 findClass 是调用 DexPathList 的 findClass 方法,如果为 null 就抛出 ClassNotFoundException。 + +##### DexClassLoader + +##### PathClassLoader + +```java +public class PathClassLoader extends BaseDexClassLoader { + + public PathClassLoader(String dexPath, ClassLoader parent) { + super(dexPath, null, null, parent); + } + + public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) { + super(dexPath, null, librarySearchPath, parent); + } +} +``` + +##### DexClassLoader + +```java +public class DexClassLoader extends BaseDexClassLoader { + + public DexClassLoader(String dexPath, String optimizedDirectory, + String librarySearchPath, ClassLoader parent) { + super(dexPath, new File(optimizedDirectory), librarySearchPath, parent); + } +} +``` + +可以看到,其实 DexClassLoader 和 PathClassLoader 都没有做什么,都是调用父类方法,只不过 DexClassLoader 多传了一个optimizedDirectory,它们的使用场景不一样: + +PathClassLoader:只能加载已经安装到 Android 系统中的 apk 文件,是 Android 默认使用的类加载器。 + +DexClassLoader:可以加载任意目录下的 dex/jar/apk/zip 文件,比 PathClassLoader,是实现热修复的重点。 + +##### DexPathList + +```java +final class DexPathList { + private static final String DEX_SUFFIX = ".dex"; + private static final String zipSeparator = "!/"; + + private final ClassLoader definingContext; + + /** + * dex 文件数组 + */ + private Element[] dexElements; + + public DexPathList(ClassLoader definingContext, ByteBuffer[] dexFiles) { + //... + } + + /** + * BaseDexClassLoader 中实例化的是这个构造方法 + */ + public DexPathList(ClassLoader definingContext, String dexPath, + String librarySearchPath, File optimizedDirectory) { + + //... + + this.definingContext = definingContext; + + this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, + suppressedExceptions, definingContext); + //... + } + + /** + * 生成 Element 集合,Element 里保存着 DexFile 和 文件路径 + * 遍历该 APP 私有目录所有文件,拿到以 .dex 结尾的文件 + */ + private static Element[] makeDexElements(List files, File optimizedDirectory, + List suppressedExceptions, ClassLoader loader) { + Element[] elements = new Element[files.size()]; + int elementsPos = 0; + for (File file : files) { + if (file.isDirectory()) { + elements[elementsPos++] = new Element(file); + } else if (file.isFile()) { + String name = file.getName(); + + if (name.endsWith(DEX_SUFFIX)) { + // Raw dex file (not inside a zip/jar). + try { + DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements); + if (dex != null) { + elements[elementsPos++] = new Element(dex, null); + } + } catch (IOException suppressed) { + System.logE("Unable to load dex file: " + file, suppressed); + suppressedExceptions.add(suppressed); + } + } else { + DexFile dex = null; + try { + dex = loadDexFile(file, optimizedDirectory, loader, elements); + } catch (IOException suppressed) { + suppressedExceptions.add(suppressed); + } + + if (dex == null) { + elements[elementsPos++] = new Element(file); + } else { + elements[elementsPos++] = new Element(dex, file); + } + } + } else { + System.logW("ClassLoader referenced unknown path: " + file); + } + } + if (elementsPos != elements.length) { + elements = Arrays.copyOf(elements, elementsPos); + } + return elements; + } + + /** + * 加载 dex 文件 + */ + private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader, + Element[] elements) + throws IOException { + if (optimizedDirectory == null) { + return new DexFile(file, loader, elements); + } else { + String optimizedPath = optimizedPathFor(file, optimizedDirectory); + return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements); + } + } + + /** + * + */ + public Class findClass(String name, List suppressed) { + for (Element element : dexElements) { + Class clazz = element.findClass(name, definingContext, suppressed); + if (clazz != null) { + return clazz; + } + } + + if (dexElementsSuppressedExceptions != null) { + suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); + } + return null; + } + + /** + * dex 文件路径集合 + */ + /*package*/ List getDexPaths() { + List dexPaths = new ArrayList(); + for (Element e : dexElements) { + String dexPath = e.getDexPath(); + if (dexPath != null) { + dexPaths.add(dexPath); + } + } + return dexPaths; + } + + static class Element { + + private final File path; + + private final DexFile dexFile; + + //... + + public Class findClass(String name, ClassLoader definingContext, + List suppressed) { + return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed) + : null; + } + } +} +``` + +#### 实战 + + + +#### 参考 + +[热修复——深入浅出原理与实现](https://juejin.im/post/5a0ad2b551882531ba1077a2) + +[https://github.com/GitLqr/HotFixDemo](https://github.com/GitLqr/HotFixDemo) + +[网易云课堂]( +https://study.163.com/course/courseLearn.htm?courseId=1208968811#/learn/live?lessonId=1278455246&courseId=1208968811) \ No newline at end of file diff --git a/images/Android/ButterKnife.png b/images/Android/ButterKnife.png new file mode 100644 index 0000000..1a891ba Binary files /dev/null and b/images/Android/ButterKnife.png differ diff --git a/images/Android/热修复.png b/images/Android/热修复.png new file mode 100644 index 0000000..b961160 Binary files /dev/null and b/images/Android/热修复.png differ