Android 组件化注解与注解处理器组件通讯

Posted 初学者-Study

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 组件化注解与注解处理器组件通讯相关的知识,希望对你有一定的参考价值。

android 组件化(二)注解与注解处理器、组件通讯

前言

  在上一篇文章中,我们完成了组件的创建、gradle统一管理、组件模式管理和切换,那么这一篇文章,我们需要做的就是组件之间的通讯了。

正文

  组件化是将原来复杂的App分解成一个个组件,在调试运行的时候各个组件之间可以单独测试,而在打包的时候需要将其他的组件打包在app组件中,作为一个apk时,肯定会有不同组件之前的通讯,举一个简单的例子,我们在app组件中写一个启动页,如果之前用户有登录过,则进入personal组件的PersonalActivity,如果没有登录过则进入login组件的LoginActivity。而LoginActivity登录成功之后要进入personal组件的PersonalActivity,要实现这样一个简单的例子,我们需要做的就是组件之间的相互通讯,而在通讯之前首先要找到通讯的目标。

这里需要用到编译时技术,在之前的学习注解和注解处理器中我提到过,而组件中用到的就是类似于ARouter的路由框架,下面我们简单来写一下。

一、注解

  还是之前的StudyComponent项目,这里我们再创建一个Module,这里要注意创建的是java Module,注意我选择的模式。

创建Java Module的时候需要创建一个默认的类,这里我们改变一下类名为BindPath,稍后还将改成注解类。

① 创建注解类

Module创建好之后修改这个BindPath,代码如下:

@Target(ElementType.TYPE)   
@Retention(RetentionPolicy.CLASS)   
public @interface BindPath 
    
    String value();

这个库里面代码其实就这么一点,那么我们怎么使用这个注解呢?

② 使用注解类

  要使用这个注解类,首先要依赖这个注解库,那么我们之前所写的config.gradle就排上用场了,还记得它的作用吗?管理工程中所有的gradle,那么添加依赖库自然是可以的,何况我们之前还添加过,还记得吗?帮你回忆一下,我们的app、login、personal都需要依赖basic库,之前通过config.gradle中配置可以一步到位,那么这个注解库也是一样的道理,所以我们只需要改动一个地方就可以完成所有组件对于注解库的依赖,

现在Sync Now同步一下就可以了,我们分别在app、login、personal组件中使用这个注解,如下图所示:

  注意看,这里在Activity上面添加注解,然后里面的值就是当前的模块名斜杠再加上当前的类名,好了下面你可以暂且运行一下,看看报不报错,无论报不报错都继续往后走。

二、路由

  这里我们做注解是要标记一个Activity,然后保存到路由中,那么这个路由就负责组件之间通讯,这里的路由,你可以单独创建一个library库,也可以写在basic中,这里我就写在basic模块中,在com.llw.basic包下新建一个router包,router包下新建IRouter接口,代码如下:

public interface IRouter 

    void putActivity();

然后我们再创建一个ARouter类,代码如下:

public class ARouter 

    @SuppressLint("StaticFieldLeak")
    private static final ARouter aRouter = new ARouter();
    private final Map<String, Class<? extends Activity>> map;
    private Context context;

    private ARouter() 
        map = new HashMap<>();
    

    public static ARouter getInstance() 
        return aRouter;
    

    /**
     * 初始化
     */
    public void init(Context context) 
        this.context = context;
        //执行生成的工具类中的方法  将Activity的类对象加入到路由表中
        List<String> classNames = getClassName();
        for (String className : classNames) 
            try 
                Class<?> utilClass = Class.forName(className);
                if (IRouter.class.isAssignableFrom(utilClass)) 
                    IRouter iRouter = (IRouter) utilClass.newInstance();
                    iRouter.putActivity();
                
             catch (Exception e) 
                e.printStackTrace();
            
        
    

    /**
     * 添加Activity
     * @param key 注解中的值 例如  "main/MainActivity"
     * @param clazz 目标Activity
     */
    public void addActivity(String key, Class<? extends Activity> clazz) 
        //如果Key不会空,activity不为空,且map中没有这个key
        if (key != null && clazz != null && !map.containsKey(key)) 
            map.put(key, clazz);
        
    

    /**
     * 跳转Activity
     * @param key 注解中的值 例如  "main/MainActivity"
     */
    public void jumpActivity(String key) 
        jumpActivity(key, null);
    

    /**
     * 跳转Activity 带参数
     * @param key 注解中的值 例如  "main/MainActivity"
     * @param bundle 参数包
     */
    public void jumpActivity(String key, Bundle bundle) 
        Class<? extends Activity> aClass = map.get(key);
        if (aClass == null) 
            return;
        
        Intent intent = new Intent(context, aClass);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if (bundle != null) 
            intent.putExtras(bundle);
        
        context.startActivity(intent);
    

    /**
     * 通过包名获取这个包下面的所有的类名
     */
    private List<String> getClassName() 
        //创建一个class对象的集合
        List<String> classList = new ArrayList<>();
        try 
            DexFile df = new DexFile(context.getPackageCodePath());
            Enumeration<String> entries = df.entries();
            while (entries.hasMoreElements()) 
                String className = entries.nextElement();
                if (className.contains("com.llw.util")) 
                    classList.add(className);
                
            
         catch (IOException e) 
            e.printStackTrace();
        
        return classList;
    

  这里面的代码就是存放和使用Activity、组件之间跳转Activity操作。现在注解和路由都有了,要使我们的注解能够生效,还需要一个注解处理器,顾名思义就是用来处理被注解的类型。

三、注解处理器

这里我们再创建一个Module,这里要注意创建的是java Module,注意我选择的模式。

这里修改模块名和包名和类名,等待注解处理器这个库创建完成。

① 添加依赖

  这里的注解处理器相较于注解稍稍有一些不同,首先我们改动一下注解处理器模块的build.gradle,添加代码如下:

dependencies 

    implementation 'com.google.auto.service:auto-service:1.0-rc7'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
    implementation 'com.squareup:javapoet:1.13.0'
    implementation project(path: ':annotation')

添加位置如下图所示

  这里前面两句依赖是添加注解处理器,然后就是生成编译时文件需要用到的库,最后就是依赖注解库,这里和之前稍有不同,我们不使用config.gradle中的配置,这也是注解处理器的特殊之处,添加完依赖之后点击Sync Now。

② 注解处理器编写

同步完成之后我们可以编写AnnotationCompiler类的代码,如下所示:

@AutoService(Processor.class)
public class AnnotationCompiler extends AbstractProcessor 

    // 定义用来生成APT目录下面的文件的对象(例如:ActivityRouterUtil1668396026324)
    Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) 
        super.init(processingEnv);
        filer = processingEnv.getFiler();
    

    /**
     * 支持的注解类型
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() 
        Set<String> types = new HashSet<>();
        types.add(BindPath.class.getCanonicalName());
        return types;
    

    /**
     * 支持版本
     */
    @Override
    public SourceVersion getSupportedSourceVersion() 
        return processingEnv.getSourceVersion();
    

    /**
     * 通过注解处理器处理注解,生成代码到build文件夹中
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) 
        //获取注解 例如 :@BindPath("main/MainActivity")
        Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindPath.class);
        Map<String, String> map = new HashMap<>();
        for (Element element : elementsAnnotatedWith) 
            TypeElement typeElement = (TypeElement) element;
            //key为注解的Activity 例如:MainActivity
            String key = typeElement.getQualifiedName().toString() + ".class";
            //value为注解方法中的值 例如:"main/MainActivity"
            String value = typeElement.getAnnotation(BindPath.class).value();
            map.put(key, value);
        
        makefile(map);
        return false;
    

    private void makefile(Map<String, String> map) 
        if (map.size() > 0) 
            //定义编译时类生成时的包名
            String packageName = "com.llw.util";
            //定义处理器的包名
            String routerPackageName = "com.llw.basic.router";
            //获取接口名IRouter
            ClassName interfaceName = ClassName.get(routerPackageName, "IRouter");
            //获取类名 ARouter
            ClassName className = ClassName.get(routerPackageName, "ARouter");
            //创建类构造器,例如ActivityRouterUtil  加上时间戳是为了防止生成的编译时类名重复报错
            TypeSpec.Builder classBuilder = TypeSpec.classBuilder("ActivityRouterUtil" + System.currentTimeMillis())
                    //添加修饰符 public
                    .addModifiers(Modifier.PUBLIC)
                    //添加实现接口,例如 implements IArouter
                    .addSuperinterface(interfaceName);
            //创建方法构造器 方法名putActivity()
            MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("putActivity")
                    //添加注解
                    .addAnnotation(Override.class)
                    //添加修饰符
                    .addModifiers(Modifier.PUBLIC);
            //这里遍历是为了给每一个添加了注解进行代码生成
            for (String activityName : map.keySet()) 
                String value = map.get(activityName);
                //例如 com.llw.arouter.ARouter.getInstance().addActivity("login/LoginActivity",com.llw.login.LoginActivity.class);
                methodBuilder.addStatement("$L.getInstance().addActivity($S, $L)", className, value, activityName);
            
            //在类构造器中添加方法
            classBuilder.addMethod(methodBuilder.build());
            try 
                //最后写入文件
                JavaFile.builder(packageName, classBuilder.build())
                        .build()
                        .writeTo(filer);
             catch (Exception e) 
                e.printStackTrace();
            
        
    

代码中的注解已经很清楚了,就是生成一个编译时类,编译时类的代码如下图:

③ 注解处理器使用

要使这个注解处理器生效,需要分别在app、login、personal的build.gradle中的denpendencies下添加如下所示代码:

annotationProcessor project(path: ':annotation_compiler')

添加的位置如下面三图所示:

添加好之后Sync Now,然后运行一下,运行之后在app模块下会生成一个build文件夹,然后层层打开,最终如下图所示:


我们刚才的AnnotationCompiler中所写的代码就是为了生成这个编译时文件,如果你没有找到这个文件,点击这个刷新按钮,刷新一下项目文件。

  Android Studio有时候文件检查不是很及时,所以手动刷新一下,看有没有生成这个文件。如果文件生成了,那么你再依次检查一下login、personal组件中的build文件夹中有没有生成相关文件。

四、使用路由

  下面要做的就是能够进行组件之间的Activity跳转,例如从app的MainActivity跳转到login的LoginActivity,再从LoginActivity跳转到personal的PersonalActivity,要做到这一步我们需要对路由进行初始化,可以在basic模块中的BaseApplication中完成。

  而为了使BaseApplication生效,我们需要在各自组件中的AndroidManifest.xml进行注册,实际上我们各个组件应该自己写一个Application类继承自BaseApplication,但是目前我们的功能比较简单,所以就不这样写了,直接使用BaseApplication进行注册即可,下面我在app组件的AndroidManifest.xml中注册。

其他的组件自己去注册一下。

① 页面跳转

然后我们在MainActivity中添加这样一行代码。

ARouter.getInstance().jumpActivity("login/LoginActivity");

这里意图很明显,我要跳转到LoginActivity,那么我们在LoginActivity的onCreate()方法中添加

ARouter.getInstance().jumpActivity("personal/PersonalActivity");

我相信你知道怎么添加这行代码,这样就能跳转到PersonalActivity中了,下面我们运行测试一下。

  这里可以看到,直接就进入了PersonalActivity,但是你会发现还有LoginActivity的Toast显示出来,这证明确实是从MainActivity过来的,最终到达PersonalActivity,你要是延时跳转那就会很明显,自行尝试吧。

② 页面带参跳转

修改一下LoginActivity的onCreate()方法,进行传参,代码如下:

	@Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        showMsg("LoginActivity");
        Bundle bundle = new Bundle();
        bundle.putString("data","Very Good!");
        ARouter.getInstance().jumpActivity("personal/PersonalActivity", bundle);
    

然后修改PersonalActivity中的onCreate()方法,接收参数,代码如下:

	@Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_personal);
        String data = getIntent().getExtras().getString("data");
        if (data != null) 
            showMsg(data);
        
    

下面重新运行一下:

OK,现在页面的组件通讯就初步完成了。

五、源码

欢迎 Star 和 Fork

源码地址:StudyComponent

以上是关于Android 组件化注解与注解处理器组件通讯的主要内容,如果未能解决你的问题,请参考以下文章

Android 组件化注解与注解处理器组件通讯

Android 组件化路由组件 ( 注解处理器获取被注解的节点 )

Android 组件化路由组件 ( 注解处理器参数选项设置 )

Android 组件化路由组件 ( 注解处理器参数选项设置 )

Android 组件化路由组件 ( 注解处理器中使用 JavaPoet 生成代码 )

Android 组件化路由组件 ( 注解处理器调试 )