赞
踩
阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680
本篇文章将继续从以下两个内容来介绍组件化框架设计:
第一步
新建一个android项目。
第二步
新建立一个java的Module。注意是javalib。这个lib用来专门写注解就好。
这个lib里面就先放一个注解,叫TestAnno。
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
-
-
- @Retention(RetentionPolicy.CLASS)
- @Target(ElementType.TYPE)
- public @interface TestAnno {
- }
RetentionPolicy.CLASS表示编译时候注解。你需要关系的就是@Target(ElementType.TYPE)这个type是类的注解,可以有方法的,属性的等等。
然后这个javalib的gradle文件要这么写。
- apply plugin: 'java'
-
- dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
- }
- sourceCompatibility = "1.7"
- targetCompatibility = "1.7"
注解库弄好了,在弄新建一个java lib 叫inject_comiler。这个是就是核心代码了,在编译时候,执行这个个库里面的代码,然后 生成代码。这个工程 三个类。一个是注解注解处理器的核心。一个是定义生成java文件的,方法拼接。还有一个就是常量包名类名了。
先看这个的gradle文件。
- apply plugin: 'java'
- sourceCompatibility = "1.7"
- targetCompatibility = "1.7"
- dependencies {
- compile fileTree(include: ['*.jar'], dir: 'libs')
- compile 'com.google.auto.service:auto-service:1.0-rc2'//谷歌的帮助我们快速实现注解处理器
- compile project(':inject_annotation')//自己定义的注解的java lib
- compile 'com.squareup:javapoet:1.7.0'//用来生成java文件的,避免字符串拼接的尴尬
- }
- //这个注解是谷歌提供了,快速实现注解处理器,会帮你生成配置文件啥的 。直接用就好
- @AutoService(Processor.class)
- public class ActivityInjectProcesser extends AbstractProcessor {
- private Filer mFiler; //文件相关的辅助类
- private Elements mElementUtils; //元素相关的辅助类 许多元素
- private Messager mMessager; //日志相关的辅助类
-
- private Map<String, AnnotatedClass> mAnnotatedClassMap;
-
- @Override
- public synchronized void init(ProcessingEnvironment processingEnv) {
- super.init(processingEnv);
- mFiler = processingEnv.getFiler();
- mElementUtils = processingEnv.getElementUtils();
- mMessager = processingEnv.getMessager();
- mAnnotatedClassMap = new TreeMap<>();
- }
-
- //这个方法是核心方法,在这里处理的你的业务。检测类别参数,生成java文件等
- @Override
- public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
- mAnnotatedClassMap.clear();
-
- try {
- processActivityCheck(roundEnv);
- } catch (Exception e) {
- e.printStackTrace();
- error(e.getMessage());
- }
-
- for (AnnotatedClass annotatedClass : mAnnotatedClassMap.values()) {
- try {
- annotatedClass.generateActivityFile().writeTo(mFiler);
- } catch (Exception e) {
- error("Generate file failed, reason: %s", e.getMessage());
- }
- }
- return true;
- }
-
-
- private void processActivityCheck(RoundEnvironment roundEnv) throws IllegalArgumentException, ClassNotFoundException {
- //check ruleslass forName(String className
- for (Element element : roundEnv.getElementsAnnotatedWith((Class<? extends Annotation>) Class.forName(TypeUtil.ANNOTATION_PATH))) {
- if (element.getKind() == ElementKind.CLASS) {
- getAnnotatedClass(element);
- } else
- error("ActivityInject only can use in ElementKind.CLASS");
- }
- }
-
- private AnnotatedClass getAnnotatedClass(Element element) {
- // tipe . can not use chines so ....
- // get TypeElement element is class's --->class TypeElement typeElement = (TypeElement) element
- // get TypeElement element is method's ---> TypeElement typeElement = (TypeElement) element.getEnclosingElement();
- TypeElement typeElement = (TypeElement) element;
- String fullName = typeElement.getQualifiedName().toString();
- AnnotatedClass annotatedClass = mAnnotatedClassMap.get(fullName);
- if (annotatedClass == null) {
- annotatedClass = new AnnotatedClass(typeElement, mElementUtils, mMessager);
- mAnnotatedClassMap.put(fullName, annotatedClass);
- }
- return annotatedClass;
- }
-
-
- @Override
- public SourceVersion getSupportedSourceVersion() {
- return SourceVersion.latestSupported();
- }
-
-
- //这个个方法返回你要处理注解的类型
- @Override
- public Set<String> getSupportedAnnotationTypes() {
- Set<String> types = new LinkedHashSet<>();
- types.add(TypeUtil.ANNOTATION_PATH);
- return types;
- }
-
- private void error(String msg, Object... args) {
- mMessager.printMessage(Diagnostic.Kind.ERROR, String.format(msg, args));
- }
-
- private void log(String msg, Object... args) {
- mMessager.printMessage(Diagnostic.Kind.NOTE, String.format(msg, args));
- }
- }
然后是生成java文件的辅助类。
- public class AnnotatedClass {
-
- private TypeElement mTypeElement;//activity //fragmemt
- private Elements mElements;
- private Messager mMessager;//日志打印
-
- public AnnotatedClass(TypeElement typeElement, Elements elements, Messager messager) {
- mTypeElement = typeElement;
- mElements = elements;
- this.mMessager = messager;
- }
-
-
- public JavaFile generateActivityFile() {
- // build inject method
- MethodSpec.Builder injectMethod = MethodSpec.methodBuilder(TypeUtil.METHOD_NAME)
- .addModifiers(Modifier.PUBLIC)
- .addParameter(TypeName.get(mTypeElement.asType()), "activity", Modifier.FINAL);
- injectMethod.addStatement("android.widget.Toast.makeText" +"(activity, $S,android.widget.Toast.LENGTH_SHORT).show();", "from build");
- //generaClass
- TypeSpec injectClass = TypeSpec.classBuilder(mTypeElement.getSimpleName() + "$$InjectActivity")
- .addModifiers(Modifier.PUBLIC)
- .addMethod(injectMethod.build())
- .build();
- String packgeName = mElements.getPackageOf(mTypeElement).getQualifiedName().toString();
- return JavaFile.builder(packgeName, injectClass).build();
- }
-
- }
这里就生成了一个 类名+$$的类,有一个方法叫inject参数是这个类本身,弹出一个toast。最后一个就是一个字符常量类。
- public class TypeUtil {
- public static final String METHOD_NAME = "inject";
- public static final String ANNOTATION_PATH = "com.example.TestAnno";
- }
好了lib工程完毕。然后是主工程引用。
首先是在工程的gradle里面配置下apt。
- dependencies {
- classpath 'com.android.tools.build:gradle:2.3.2'
- classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
- }
然后在app的gradle里面配置如下
- apply plugin: 'com.neenbedankt.android-apt'
- ……
- compile project(':inject_annotation')
- apt project(':inject_comiler')
这样就行了。注意是apt 。为什么要新建立javalib。因为javalib不能在引用adnroidlib,。而注解处理器是javalib来完成,app里面,可以这引用这2个,apt如果换成complie 会提示错误,但不会影响啥。配置都好了,就是最后的使用了。
总结构
在我们MainActivity 上面加上注解,使用下。
- @TestAnno
- public class MainActivity extends AppCompatActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- InjectActivity.inject(this);//调用build生成的类
- }
- }
最后一个类就是我们的使用这个在运行时,调用生成build的类
- public class InjectActivity {
- private static final ArrayMap<String, Object> injectMap = new ArrayMap<>();
-
- public static void inject(AppCompatActivity activity) {
- String className = activity.getClass().getName();
- try {
-
- Object inject = injectMap.get(className);
-
- if (inject == null) {
- //加载build生成的类
- Class<?> aClass = Class.forName(className + "$$InjectActivity");
- inject = aClass.newInstance();
- injectMap.put(className, inject);
- }
- //反射执行方法
- Method m1 = inject.getClass().getDeclaredMethod("inject", activity.getClass());
- m1.invoke(inject, activity);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
-
这个类用了反射的方式查找了指定类执行了指定方法。有一个map来缓存,不用每次都重新反射。
当然你可可以不用反射,在创建build生成类的时候,实现一个接口,这里直接强转为接口,直接调用接口的方法也可以。这里简单一些用的反射
通过一个注解,就自动生成弹出toast的代码看下自动 生成的代码
- public class MainActivity$$InjectActivity {
- public void inject(final MainActivity activity) {
- android.widget.Toast.makeText(activity, "from build",android.widget.Toast.LENGTH_SHORT).show();;
- }
- }
最后看下运行效果。
虚拟机类加载过程分为加载,验证,准备,解析,初始化,使用,卸载七个阶段。其中验证,准备,解析三个部分成为连接阶段。
一般来说,在遇到了以下四种情况的时候,虚拟机会开始进行类的加载:
这一阶段是为了确保class文件的字节流包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。主要包括以下几个过程:
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。这个阶段中进行内存分配的变量只有被static修饰的变量,并将其设置为默认值,而真正的赋值则在初始化阶段。另外,被final static字段修饰的常量在编译器就已经被赋值。
解析阶段主要是虚拟机将常量池内的符号引用替换为直接引用的过程。
初始化阶段是执行类构造器<cinit>()方法的过程。
<cinit>()与类的构造方法不同,<cinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并而成的。编译器收集的顺序是按语句在源文件中出现的顺序决定的,静态语句块中只能访问定义在它之前的静态变量,定义在它之后的静态变量,只可以赋值,不可以访问。
虚拟机会保证子类的<cinit>()方法执行之前,其父类的<cinit>()方法一定被执行(父类先与子类完成加载过程)
类加载阶段中,实现“通过一个类的全限定名获取定义类的二进制字节流”的动作被放在了虚拟机外部去实现,以便应用程序决定如何去加载所需要的类,实现这个动作的代码模块被称为“类加载器”
从Java虚拟机的角度上讲,只存在两种不同的类加载器:
这些类加载器之间的关系如下:
双亲委派模型中,除了顶层的BootstrapClassLoader,其他类加载器都要求有自己的父类加载器,这里的类加载器一般以组合的方式实现。
Android中的ClassLoader根据用途可分为一下几种:
- protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
- Class<?> clazz = findLoadedClass(className);
- if (clazz == null) {
- ClassNotFoundException suppressed = null;
- try {
- clazz = parent.loadClass(className, false);
- } catch (ClassNotFoundException e) {
- suppressed = e;
- }
- if (clazz == null) {
- try {
- clazz = findClass(className);
- } catch (ClassNotFoundException e) {
- e.addSuppressed(suppressed);
- throw e;
- }
- }
- }
- return clazz;
- }
-
DexClassLoader和PathClassLoader都是BaseDexClassLoader的子类,他们的实现也很简单,只是构造方法传入了不同的参数而已:
DexClassLoader :
PathClassLoader:
可以看到,DexClassLoader和PathClassLoader的区别就是,PathClassLoader的第二个参数传为NULL,回到BaseDexClassLoader中可以看到:
根据注释可以看到optimizedDirectory参数是用来放置DexFile的,那么具体是怎么回事呢,再进入DexPathList
又被传进了loadDexFile
可以看到,如果optimizedDirectory为NULL,则会以原来的路径创建DexFile,否则会以optimizedDirectory为路径创建DexFile
其实optimizedDirectory是要求一个内部路径的,因为动态加载去加载的可执行文件一定要存放在内部存储。而DexClassLoader可以指定optimizedDirectory,所以它可以加载外部的dex,并且这个dex会被复制到内部路径的optimizedDirectory;而PathClassLoader没有optimizedDirectory,所以它只能加载内部路径的dex,也就是存在于已经安装过的apk里面的。
参考https://www.jianshu.com/p/144df8826d15
https://blog.csdn.net/spinchao/article/details/72972185
阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。