美文网首页
从0开始,编写一个组件化开发框架<一>

从0开始,编写一个组件化开发框架<一>

作者: 爱吃豆腐面 | 来源:发表于2020-04-22 19:45 被阅读0次

    本文主要内容:
    组件之间如何进行通信?暂不涉及组件单独编译的动态配置。


    一、新建一个项目

    app模块之外,再新建四个模块,分别为usercenter(用户中心),order(订单),payment(支付)和common(统一工具);由于本框架考虑使用注解+APT技术,所以再新建两个java模块,分别为router(注解),router_compiler(注解处理器)。项目结构大致如下:

    项目目录结构

    二、模块分工

    app: 主模块,负责展示商品;
    common:基础工具层,提供网络、统一封装;
    usercenter:用户中心,用户信息操作相关;
    order:订单模块,用户订单相关操作;
    payment:支付模块,用户支付订单相关操作;
    router:定义组件化相关注解;
    router_compiler:注解处理器模块;
    app的主要流程:用户打开app,进入到app模块,进行商品的选购;选购完成之后,下单进入order模块,order模块操作完成之后,进入支付模块进行付款;付款完成之后返回订单模块。
    app模块可以进入到个人中心,个人中心可以查看订单;

    三、开始代码编写

    组件与组件之间是相互没有依赖的,那么,我们组件之间如何进行通信呢?
    一般有两种思路:

    1. 像EventBus那样,做一个事件总线,使用发布-订阅的模式,各组件向总线订阅消息,有消息时进行处理。
    2. 做一个路由框架,用一个路由表来一一对应各组件对外公开的业务,其他组件可以通过这个路由表与相应的组件进行通信。

    这里我们选择第二种做法。
    一种比较简单的实现就是在common模块,手动维护一个路由表,每个模块有对外的业务就要这个路由表里增加一条路由。当然,这是可以实现组件之间的通信的,不过每个模块都去手动修改同一个路由表,比较麻烦,也容易造成冲突,不易维护。
    这里我们使用注解+注解处理器的方式,自动的生成路由,可以避免这种情况。

    首先,定义注解

    router模块里,定义一个注解:

    @Target(ElementType.TYPE) //只作用于类
    @Retention(RetentionPolicy.CLASS) //编译期保留
    public @interface Router {
        /**
         * 获取路径
         *
         * @return 路径,做为路由表的key
         */
        String path();
    }
    

    需要提供服务的页面就加上这个注解,如payment模块需要对外提供支付服务,那么我们在payment模块里的PaymentActivity上加上Router注解,并传入一个path:

    /**
     * 支付组件首页,展示支付方式,调起具体支付流程,并返回给调用方支付结果
     */
    @Router(path = "com.example.payment.PaymentActivity")
    public class PaymentActivity extends BaseActivity {
        //...
    }
    

    这里的path我们直接使用类的全类名。

    那么,Router注解标记的类,我们应该怎么处理呢?
    我们应该拿到注解标记的类,对应的path,生成一个把这些信息添加进路由表,注解处理器就可以做到这个工作。

    注解处理器,处理注解

    @AutoService(Processor.class) //注册
    @SupportedAnnotationTypes(ProcessorConfig.ROUTER_PACKAGE) //指明需要处理的注解
    @SupportedSourceVersion(SourceVersion.RELEASE_7)
    // 注解处理器接收的参数
    @SupportedOptions({ProcessorConfig.OPTIONS, ProcessorConfig.APT_PACKAGE})
    public class RouterProcessor extends AbstractProcessor {
    
        // 操作Element的工具类(类,函数,属性,其实都是Element)
        private Elements elementTool;
    
        // type(类信息)的工具类,包含用于操作TypeMirror的工具方法
        private Types typeTool;
    
        // Message用来打印 日志相关信息
        private Messager messager;
    
        // 文件生成器, 类 资源 等,就是最终要生成的文件 是需要Filer来完成的
        private Filer filer;
        //
        private String options;  // (模块传递过来的)模块名  app,personal
        private String aptPackage; // (模块传递过来的) 包名
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            this.elementTool = processingEnvironment.getElementUtils();
            this.typeTool = processingEnvironment.getTypeUtils();
            this.filer = processingEnvironment.getFiler();
            this.messager = processingEnvironment.getMessager();
            print("RouterProcessor init");
            // 只有接受到 App壳 传递过来的书籍,才能证明我们的 APT环境搭建完成
            options = processingEnvironment.getOptions().get(ProcessorConfig.OPTIONS);
            aptPackage = processingEnvironment.getOptions().get(ProcessorConfig.APT_PACKAGE);
            print("options = " + options);
            print("aptPackage = " + aptPackage);
            if (options != null && aptPackage != null) {
                print("APT环境搭建完成");
            } else {
                print("APT环境有问题,请检查options 和 aptPackage");
            }
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            // 获取到系统类Activity的类型
            TypeElement activityType = elementTool.getTypeElement(ProcessorConfig.ACTIVITY_PACKAGE);
            TypeMirror activityMirror = activityType.asType();
    
            Set<? extends Element> elementSet = roundEnvironment.getElementsAnnotatedWith(Router.class);
            if (elementSet != null) {
                for (Element element : elementSet) {
                    print("发现@Router注解的地方" + element.getSimpleName());
    
    //                Router router = element.getAnnotation(Router.class);
                    // 必须是Activity
                    TypeMirror elementMirror = element.asType(); // 当前 == Activity
                    if (typeTool.isSubtype(elementMirror, activityMirror)) {
                        print("检查当前被注解的类,确认是Activity类或及子类");
                    } else {
                        // 不匹配抛出异常,这里谨慎使用!考虑维护问题
                        throw new RuntimeException("@ARouter注解目前仅限用于Activity类之上");
                    }
                }
    
                //获取到使用注解的类
                //通过得到的信息,自动生成一个路由表
                TypeElement implementType = elementTool.getTypeElement(ProcessorConfig.IROUTER_PATH);
                try {
                    createPathFile(elementSet, implementType);
                } catch (IOException e) {
                    e.printStackTrace();
                    print("自动创建路由表失败");
                }
                return true;
            }
            return false;
        }
    
        /**
         * PATH 生成
         *
         * @param implementType
         * @throws IOException
         */
        private void createPathFile(Set<? extends Element> elementSet, TypeElement implementType) throws IOException {
            // 判断 map仓库中,是否有需要生成的文件
            if (elementSet.isEmpty()) {
                return;
            }
    
            //类属性 private Map<String, Class> mRouterMap = new HashMap<>();
            TypeName mapTypeName = ParameterizedTypeName.get(
                    ClassName.get(Map.class), // Map
                    ClassName.get(String.class), // Map<String,
                    ClassName.get(Class.class)); // Map<String, Class>
    
            MethodSpec.Builder onCreateMethodBuilder = MethodSpec.methodBuilder(ProcessorConfig.METHOD_ONCREATE)
                    .addAnnotation(Override.class)
                    .addModifiers(Modifier.PUBLIC)
                    .returns(mapTypeName);
    
            //方法体 mRouterMap.put(string, clazz.class);
            for (Element element : elementSet) {
                Router router = element.getAnnotation(Router.class);
                onCreateMethodBuilder.addStatement("$T<$T, $T> routerMap = new $T<>()",
                        ClassName.get(Map.class), // Map
                        ClassName.get(String.class), // String
                        ClassName.get(Class.class), // Class
                        ClassName.get(HashMap.class)); // HashMap
                onCreateMethodBuilder.addStatement("routerMap.put($S, $T.class)",
                        router.path(),
                        ClassName.get(((TypeElement) element)))
                        .addStatement("return routerMap")
                        .returns(mapTypeName);
            }
    
            //类定义 public class RouterManager implements IRouter {...}
            String clazzName = ProcessorConfig.ROUTER_MANAGER + "$" + options;
            TypeSpec className = TypeSpec.classBuilder(clazzName)
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(onCreateMethodBuilder.build())
                    .addSuperinterface(ClassName.get(implementType))
                    .build();
    
            // 生成 和 类 等等,结合一体
            JavaFile.builder(aptPackage, // 包名
                    className) // 类构建完成
                    .build() // JavaFile构建完成
                    .writeTo(filer); // 文件生成器开始生成类文件
    
            print("自动生成" + clazzName + "成功,请在build/generated/ap_generated_sources/debug/out" + "目录下查看");
        }
    
    
        private void print(String msg) {
            messager.printMessage(Diagnostic.Kind.NOTE, msg);
        }
    

    如上,我们在router_compiler模块里新建一个处理器类RouterProcessor 继承自AbstractProcessor,并实现其两个方法,
    init(): 做一些初始化操作,如获取工具类,Messager(打印日志),filer(文件生成器)等等
    process(): 对注解进行处理。

    这里需要说明一下RouterProcessor类使用到的注解:
    @AutoService(Processor.class) : 把此注解处理类,进行注册。不注册不能发挥作用。
    @SupportedAnnotationTypes(ProcessorConfig.ROUTER_PACKAGE) :指明需要处理的注解
    @SupportedSourceVersion(SourceVersion.RELEASE_7) :指明Java版本

    process()方法里,我们拿到Router注解处理的类集合,根据这个集合,调用createPathFile()生成我们的路由表。

    createPathFile()生成路由表的过程,可以通过硬编码的形式写文件,不过这种方法容易出错。这里我们使用javapoet来生成代码,如何使用请移步https://github.com/square/javapoet
    下面是自动生成的payment模块的路由表。

    public class RouterTable$payment implements IRouter {
      @Override
      public Map<String, Class> onCreate() {
        Map<String, Class> routerMap = new HashMap<>();
        routerMap.put("com/example/payment/PaymentActivity", PaymentActivity.class);
        return routerMap;
      }
    }
    
    payment模块路由表目录

    说明:IRouter是在模块router里定义的一个接口,用于给各模块的路由表制定一个标准。里面定义一个方法,返回一个Map<String,Class>对象。

    public interface IRouter {
        Map<String, Class> onCreate();
    }
    

    到此,我们已经完成一大部分了,第个模块都生成了自己的路由表。但是这些路由表都是在各自的模块中,依然无法被其他模块引用到。此时我们想到,是否可以在common模块里,定义一个RouterManager处理类,把各模块的路由表集中到这个类里进行管理呢?

    public enum RouterManager {
        instance;
    
        private Map<String, Class> mRouterMap;
    
        public void addRouterTable(Map<String, Class> routerTable) {
            if (mRouterMap == null) {
                mRouterMap = new HashMap<>();
            }
            mRouterMap.putAll(routerTable);
        }
    
        public void startActivityWith(Context context, String path, Bundle bundle) {
            if (path == null || path.isEmpty()) {
                throw new RuntimeException("path can not be null or empty!");
            }
    
            Class clazz = mRouterMap.get(path);
            if (clazz != null) {
                Intent intent = new Intent(context, clazz);
                if (bundle != null) {
                    intent.putExtras(bundle);
                }
                context.startActivity(intent);
            } else {
                ToastManager.show(context, "要启动的Activity未注册: " + path);
            }
        }
    
        @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
        public void startActivityForResultWith(Context context, String path, Bundle bundle, int requestCode) {
            if (context instanceof Activity) {
                Class clazz = mRouterMap.get(path);
                if (clazz != null) {
                    Intent intent = new Intent(context, clazz);
                    if (bundle != null) {
                        intent.putExtras(bundle);
                    }
                    ((Activity) context).startActivityForResult(intent, requestCode);
                } else {
                    ToastManager.show(context, "要启动的Activity未注册: " + path);
                }
            }
        }
    }
    

    可以看到,这里是使用枚举定义了一个单例,提供了三个方法:
    addRouterTable():往mRouterMap里添加路由表;
    startActivityWith()startActivityForResultWith()则是根据根据传入的path,对路由进行查找,匹配之后,进行跨组件跳转。

    上在只是提供了添加路由的方法,但是在什么时候执行呢?
    当然是在app启动越早越好,因为你并不知道用户从启动app到需要跨组件需要多久,也许很快。
    所以我们想到,自定义一个Application,在Application的onCreate方法里,开个异步线程,通过反射获取实例化对应模块的路由表,然后调用RouterManager.instance.addRouterTable(routerMap);把路由添加进行RouterManager

    public class BaseApplication extends Application {
    
        private static final String TAG = "BaseApplication >>>>> ";
    
        @Override
        public void onCreate() {
            super.onCreate();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    String[] bundles = new String[]{
                            "com.example.router.RouterTable$payment",
                            "com.example.router.RouterTable$order",
                            "com.example.router.RouterTable$usercenter"
                    };
                    for (String bundle : bundles) {
                        try {
                            Class clazz = Class.forName(bundle);
                            IRouter iRouter = (IRouter) clazz.newInstance();
                            Map<String, Class> routerMap = iRouter.onCreate();
                            RouterManager.instance.addRouterTable(routerMap);
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InstantiationException e) {
                            e.printStackTrace();
                        }
                    }
                    Log.i(TAG, "路由表已建好...");
                }
            }).start();
        }
    }
    

    执行跨组件通信

    order模块的OrderActivity里,通过RouterManager.instance.startActivityForResultWith(this, "com/example/payment/PaymentActivity", bundle, payRequestCode);调用到支付模块进行支付:

    @Router(path = "com.example.order.OrderActivity")
    public class OrderActivity extends BaseActivity {
        private static final int payRequestCode = 111;
        private Button btnPay;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_order);
    
            initView();
        }
    
        @Override
        public void initView() {
            btnPay = findViewById(R.id.btnPay);
            btnPay.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
            super.onClick(v);
            if (v.getId() == R.id.btnPay) {
                Bundle bundle = new Bundle();
                bundle.putString("orderSn", "123456789");
                bundle.putFloat("orderAmount", 99.99f);
                RouterManager.instance.startActivityForResultWith(this, "com.example.payment.PaymentActivity", bundle, payRequestCode);
            }
        }
    
        @Override
        protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            if (requestCode == payRequestCode) {
                if (resultCode != Activity.RESULT_OK) {
                    return;
                }
    
                boolean paySuccess = data != null && data.getBooleanExtra("payResult", false);
                toast("订单'123456789'支付" + (paySuccess ? "成功" : "失败"));
            }
        }
    }
    

    到此,我们的组件化后,组件(Activity与Activity)之间的通信就完成了。

    你可能会问了,如果与另一个组件的非Activity类进行通信,怎么做呢?其实原理都是一样的,这里就不贴代码了。

    最后,再附上payment模块的gradle配置

    apply plugin: 'com.android.library'
    
    android {
        compileSdkVersion 29
        buildToolsVersion "29.0.2"
    
    
        defaultConfig {
            minSdkVersion 15
            targetSdkVersion 29
            versionCode 1
            versionName "1.0"
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
            consumerProguardFiles 'consumer-rules.pro'
    
            // 都是为了 传递给 注解处理器
            // 在gradle文件中配置选项参数值(用于APT传参接收)
            // 切记:必须写在defaultConfig节点下
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = [moduleName: project.getName(), packageNameForAPT: packageNameForAPT] //packageNameForAPT只是一个字符串,一个包名,存入模块生成的路由类。
                }
            }
        }
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
    
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
    
        implementation 'androidx.appcompat:appcompat:1.1.0'
        implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'androidx.test.ext:junit:1.1.1'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    
        // 公共基础库
        implementation project(path: ":common")
    
        // router 专用 注解模块
        implementation project(path: ":router")
    
        // router 专用 注解处理器
        annotationProcessor project(path: ':router_compiler')
    }
    

    相关文章

      网友评论

          本文标题:从0开始,编写一个组件化开发框架<一>

          本文链接:https://www.haomeiwen.com/subject/owewihtx.html