Activity的属性注入 - Xiasm/EasyRouter GitHub Wiki

上节讲了为什么需要依赖注入,这节我们就来实现依赖注入。因为依赖注入包含两大块,其一是activity通过intent传参属性的注入,其二是类似ARouter的IPorvider。其实两者的实现方式类似,但是考虑到文章长度的问题,我们先来讲解第一个。

依赖注入实现

先来看一段代码:

public class MainActivity {
  public void startModule1MainActivity(View view) {
    //跳转到Module1MainActivity,并且传入参数msg
    EasyRouter.getsInstance().build("/module1/module1main")
            .withString("msg", "从MainActivity").navigation();
  }

}


@Route(path = "/module1/module1main")
public class Module1MainActivity extends AppCompatActivity {

    @Extra
    String msg;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_module1_main);
        EasyRouter.getsInstance().inject(this);
        Toast.makeText(this, "msg=" + msg, Toast.LENGTH_SHORT).show();

    }
}

我们要通过注解处理器实现MainActivity传入的msg参数直接被Module1MainActivity解析,需要两个要素,其一是需要知道哪个类要被注入,很显然,这里是Module1MainActivity,其二是这个类的哪一个属性要被注入,这里就是msg。
这里我就以这个例子来说明,只要我们在编译期间拿到需要依赖注入的类Module1MainActivity和需要注入的属性msg,那么在Module1MainActivity的onCreate方法里,我们调用EasyRouter.getsInstance().inject(this)方法把intent中的属性注入到msg里面就可以了。
这个属性msg如何拿到呢?还是用注解处理器,我们在msg属性上加上注解@Extra,然后在编译期拿到所有被Extra注解的属性集。
说起来简单,下面我们看代码:

@AutoService(Processor.class)
@SupportedOptions(Constant.ARGUMENTS_NAME)
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes({Constant.ANN_TYPE_EXTRA})
public class ExtraProcessor extends AbstractProcessor {

    /**
     * 节点工具类 (类、函数、属性都是节点)
     */
    private Elements elementUtils;

    /**
     * type(类信息)工具类
     */
    private Types typeUtils;
    /**
     * 类/资源生成器
     */
    private Filer filerUtils;

    /**
     * 记录所有需要注入的属性 key:类节点 value:需要注入的属性节点集合
     */
    private Map<TypeElement, List<Element>> parentAndChild = new HashMap<>();
    private Log log;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        //获得apt的日志输出
        log = Log.newLog(processingEnvironment.getMessager());
        elementUtils = processingEnv.getElementUtils();
        typeUtils = processingEnvironment.getTypeUtils();
        filerUtils = processingEnv.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (!Utils.isEmpty(set)) {
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Extra.class);
            if (!Utils.isEmpty(elements)) {
                try {
                    categories(elements);
                    generateAutoWired();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return true;
            }
        }
        return false;
    }

    private void generateAutoWired() throws IOException {
        TypeMirror typeActivity = elementUtils.getTypeElement(Constant.ACTIVITY).asType();
        TypeElement iExtra = elementUtils.getTypeElement(Constant.IEXTRA);

        if (!Utils.isEmpty(parentAndChild)) {
            // 参数 Object target
            ParameterSpec objectParamSpec = ParameterSpec.builder(TypeName.OBJECT, "target").build();
            for (Map.Entry<TypeElement, List<Element>> entry : parentAndChild.entrySet()) {
                TypeElement rawClassElement = entry.getKey();
                if (!typeUtils.isSubtype(rawClassElement.asType(), typeActivity)) {
                    throw new RuntimeException("just support activity filed: " + rawClassElement);
                }
                //封装的函数生成类
                LoadExtraBuilder loadExtra = new LoadExtraBuilder(objectParamSpec);
                loadExtra.setElementUtils(elementUtils);
                loadExtra.setTypeUtils(typeUtils);
                ClassName className = ClassName.get(rawClassElement);
                loadExtra.injectTarget(className);
                //遍历属性
                for (int i = 0; i < entry.getValue().size(); i++) {
                    Element element = entry.getValue().get(i);
                    loadExtra.buildStatement(element);
                }

                // 生成java类名
                String extraClassName = rawClassElement.getSimpleName() + Constant.NAME_OF_EXTRA;
                // 生成 XX$$Autowired
                JavaFile.builder(className.packageName(), TypeSpec.classBuilder(extraClassName)
                        .addSuperinterface(ClassName.get(iExtra))
                        .addModifiers(PUBLIC).addMethod(loadExtra.build()).build())
                        .build().writeTo(filerUtils);
                log.i("Generated Extra: " + className.packageName() + "." + extraClassName);
            }
        }
    }

    /**
     * 记录需要生成的类与属性
     *
     * @param elements
     * @throws IllegalAccessException
     */
    private void categories(Set<? extends Element> elements) {
        for (Element element : elements) {
            //获得父节点 (类)
            TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
            if (parentAndChild.containsKey(enclosingElement)) {
                parentAndChild.get(enclosingElement).add(element);
            } else {
                List<Element> childs = new ArrayList<>();
                childs.add(element);
                parentAndChild.put(enclosingElement, childs);
            }
        }
    }

}

ExtraProcessor是处理Activity参数注入的注解处理器,关于注解处理器前面已经讲解过,我们就不再说了。直接看process()方法,我们拿到所有被Extra注解的属性集合,然后调用categories(elements)方法,categories()方法里会将属性所在类和属性集合保存起来,接着调用generateAutoWired()方法。generateAutoWired()方法的作用就是将要依赖的类和依赖的属性信息以java文件的形式存起来,这些利用javapoet生成Java类的api我们以前已经讲过了,这里为了保持代码的美观把这些生成过程封装了起来,还是以上面的例子,将会生成关系文件如下:

public class Module1MainActivity_Extra implements IExtra {
  @Override
  public void loadExtra(Object target) {
    Module1MainActivity t = (Module1MainActivity)target;
    t.msg = t.getIntent().getStringExtra("msg");
  }
}

我们生成的类文件以Module1MainActivity开头,具有一定的规则,并且实现了IExtra接口的loadExtra(Object target)方法,只要有了这个方法,我们便可以在程序运行期间让Module1MainActivity调用此方法,将intent里面的属性注入到msg属性上。

api设计

上面我说过,依赖注入需要两个要素,其一是需要知道哪个类要被注入,其二是这个类的哪一个属性要被注入。
我们通过注解处理器解析@Extra注解并生成文件,这些文件记录了需要被注入的类和属性,那么如何注入以及在什么时期注入呢? 在什么时期注入是根据具体业务而定的,那么框架只需要提供一个注入的方法,将在何时注入交给需要被注入的类就可以了。
在Module1MainActivity的onCreate方法里,我调用了一下代码:

EasyRouter.getsInstance().inject(this);

这个就是注入的方法,注入的时期Activity的onCreate时期。下面看inject()方法:

public void inject(Activity instance) {
    ExtraManager.getInstance().loadExtras(instance);
}

public class ExtraManager {
  public void loadExtras(Activity instance) {
    //查找对应activity的缓存
    String className = instance.getClass().getName();
    IExtra iExtra = classCache.get(className);
    try {
        if (null == iExtra) {
            iExtra = (IExtra) Class.forName(instance.getClass().getName() +
                    SUFFIX_AUTOWIRED).getConstructor().newInstance();
        }
        iExtra.loadExtra(instance);
        classCache.put(className, iExtra);
    } catch (Exception e) {
        e.printStackTrace();
    }
  }
}

首先我们拿到类名,然后从classCache里取出生成Module1MainActivity映射关系的类Module1MainActivity_Extra,这里classCache就是一个缓存,为的是如果打开过Module1MainActivity后再次打开就不需要重新创建Module1MainActivity_Extra去拿映射信息。
接着看,我们通过IExtra iExtra = classCache.get(className)拿到iExtra,这里的iExtra就是Module1MainActivity_Extra,如果拿到的是空,就去通过反射创建Module1MainActivity_Extra实例,然后调用loadExtra(instance)方法将msg从intent里面拿出来赋给msg属性。
好了,到这里,Activity的属性注入就讲完了,其实很简单,如果有读者还是不理解,我建议你将源码中的每一个流程再详细看一遍。

⚠️ **GitHub.com Fallback** ⚠️