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属性上。
上面我说过,依赖注入需要两个要素,其一是需要知道哪个类要被注入,其二是这个类的哪一个属性要被注入。
我们通过注解处理器解析@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的属性注入就讲完了,其实很简单,如果有读者还是不理解,我建议你将源码中的每一个流程再详细看一遍。