项目越做越复杂?组件化开发替你解决所有问题——一篇文章搞懂组件化

前言

什么是组件化?

最初的目的是代码重用,功能相对单一或者独立。在整个系统的代码层次上位于最底层,被其他代码所依赖,所以说组件化是纵向分层。

为什么要使用组件化?

当我们的项目越做越大的时候,有时间就很难去维护,有时候让我们去改点东西,根本实现不了,与其让我们修修改改,还不如自己重新去写代码。


其实无论是大项目也好,或者是中小型项目也好,如果去使用组件化进行开发,那会使你很难割舍,因为它能在你开发中带来很多的便利,包括一些功能解耦和和一些功能的复用

组件化和插件化区别?

其实表面上,它们是没有多大的区别。但是从根本目的上,看出它们的区别。


组件化最终的目的是要实现分开编译,就是每一个功能模块,都可以去编译,去测试


模块化的根本目的并不是说分开编译,而是功能复用这一块


所以,他俩的区别就是,一个为了每个模块都可以去分开编译去运行,例一个是为了我们的功能更加好的被复用

1.如何将项目组件化

学习目标:把项目搭建成组件化开发项目

使用Android Studio 新建一个Android工程。用这个工程里面创建一个个组件。

把目录切换为Project界面,右击项目名 点击 new -->Module --> 选择 Phone&Tablet Module --> 接下来给组件名字,不能取MainActivity,如果取,合并的时候会报错,因为不能有两个Activity 名字相同。此处演示,故使用 t1–>
下一个界面选择Activity初始类型,这个看个人情况,因此处为演示,选默认Empty Activity -->接下来给Activity起名字,组件名+Activity即可 --> 点击Finish加载完成即可
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这样,一个组件就创建完成了。
注意点:名字不能与app其他组件一致
在这里插入图片描述

2.如何统一管理模块

学习目标模块化开发中,gradle中的版本进行统一管理

方便统一管理所有组件版本号

创建完成后,在Project界面 gradle.properties --> 把下面(1)写进去 -->
打开新建组件和app组件的build.gradle文件进行修改,修改后如(2)

(1).
		#具体多少详见自己SDK配置
		#最小SDK版本
		MIN_SDK_VERSION = 16
		#当前SDK版本
		TAR_SDK_VERSION = 29
		#Android使用的SDK版本
		COMPILER_SDK_VERSION = 29
(2).
	apply plugin: 'com.android.application'

android {
    
    
    compileSdkVersion COMPILER_SDK_VERSION.toInteger()
    buildToolsVersion "29.0.3"

    defaultConfig {
    
    
        applicationId "com.example.t1"
        minSdkVersion MIN_SDK_VERSION.toInteger()
        targetSdkVersion TAR_SDK_VERSION.toInteger()
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

让组件在library和application之间进行转换

Project界面 --> gradle.properties --> 在里面设置变量 IS_APPLICATION = true -->
在新建组建的main文件夹下建立manifest文件夹,并把清单文件复制到里面 (注意:是复制,不是剪切) --> 修改被复制的清单文件,修改后如(1)所示 -->
打开新建组件的build.gradle文件进行修改,主要修改三处,修改后如(2)

(1)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.t1">

    <application>
        <activity android:name=".t1Activity">
            
        </activity>
    </application>

</manifest>
	(2).
		<1>.
			if(IS_APPLICATION.toBoolean()){
    
    
			    apply plugin: 'com.android.application'
			}else {
    
    
			    apply plugin: 'com.android.library'
			}
		<2>.
			  if(IS_APPLICATION.toBoolean()) {
    
    
				applicationId "com.zrc.myhome"
				}
		<3>.
			buildTypes {
    
    
				release {
    
    
				    minifyEnabled false
				    proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
				}
			    }

			    sourceSets {
    
    
        main{
    
    
            if(IS_APPLICATION.toBoolean()){
    
    
                manifest.srcFile 'src/main/AndroidManifest.xml'
            }else{
    
    
                manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
            }
        }
    }

操作完成后,只要修改IS_APPLICATION,就可以控制该组件是library还是application,true为application,false为library

重复上面操作,新创建一个组件,名为t2

让t1,t2与app组件依赖,在打开app组件的build.gradle ,在里面加入以下代码

if(IS_APPLICATION.toBoolean()){
    
    
        implementation project(path: ':t2')
        implementation project(path: ':t1')
    }

3.编译时技术的使用&手撸组件化路由框架

学习目标:
1.掌握注解加注解处理器组成的编译时技术
2.使用APT+ANNOTATION以及代理的模式来构架超易用的路由框架

组件创建完成,如果t1组件中的Activity,调用t2组件中的Acticity,要怎么办?
假设我们用平时的跳转操作
在这里插入图片描述
它就会报错,提示找不到这个Activity,原因很简单,因为跨包,不可能拿到类对象,因为没有去依赖

想要两个独立的组件有联系,就需要有中间人,这个中间人就是路由框架

创建路由框架
new Module --> Android Library --> arouter --> 让所有组件都依赖arouter
在这里插入图片描述
这个中间人(路由框架),必须满足的条件,是两个组件,都必须和它有联系,故让所有组件都依赖arouter 。

使用路由框架,让没有关联的组件通信

现在,所以组件都依赖了arouter,那这个arouter,到底要做什么事情,那它到底怎么做,才能满足中间人的要求?

解决一个问题,不知道怎么办,那我们就回到问题的本质
在t1Activity里面,我们拿不到t2Activity的内容,这才是问题的本质。
既然我们现在拿不到类对象,那我们能不能让arouter把所有的类对象,都存在一个容器里面,假设这个容器,存取了所有Activity类对象,那我们拿t2Activity,那是不是从arouter里面就拿得到啊。

在arouter中新建class作为中介。存储所有Activity,写法同(1) --> 调用如(2)

(1)
package com.example.arouter;

import android.app.Activity;

import java.util.HashMap;
import java.util.Map;

/**
 * 中间人 代理
 * */
public class ARouter {
    
    
    //所有的地方,用到的都可以是同一个对象的,都可以定义为单例
    private static ARouter aRouter = new ARouter();
    //容器 装载所有模块中Activity类对象
    private Map<String,Class<?extends Activity>> map;
    private ARouter(){
    
    
        map = new HashMap<>();
    }
    public static ARouter getInstance(){
    
    
        return aRouter;
    }

    /**
     * 将类对象加入到容器的方法
     * */
    public void putActivity(String key,Class<? extends Activity> clazz){
    
    
        if(key!=null&&clazz!=null){
    
    
            map.put(key,clazz);
        }
    }
    public Class<? extends Activity>  getActivity(String key){
    
    
        Class<? extends Activity> aClass = map.get(key);
        if(aClass!=null){
    
    
            return aClass;
        }
        return null;
    }

}
(2)
Intent intent = new Intent(t1Activity.this, ARouter.getInstance().getActivity("t2Activity"));

这样,我们就可以通过key获取到t2Activity的类对象。
这样写,没有问题,但是我们回归到框架的本质。框架不仅仅是在这一个项目中用的,我们在其他项目中也能用到。框架存在的意义,是替程序员去写代码。让程序员减少开发时间。那我们为什么把Intent方法一块封装到ARouter里面呢?没必要让它getActivity
优化后的(1),优化后,调用(2)

(1)
package com.example.arouter;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

import java.util.HashMap;
import java.util.Map;

/**
 * 中间人 代理
 * */
public class ARouter {
    
    
    //所有的地方,用到的都可以是同一个对象的,都可以定义为单例
    private static ARouter aRouter = new ARouter();
    //容器 装载所有模块中Activity类对象
    private 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;
    }
    /**
     * 将类对象加入到容器的方法
     * */
    public void putActivity(String key,Class<? extends Activity> clazz){
    
    
        if(key!=null&&clazz!=null){
    
    
            map.put(key,clazz);
        }
    }

    /*
     * 跳转窗体的方法
     * */
    public void jumpActivity(String key, Bundle bundle){
    
    
        Class<? extends Activity> aclass = map.get(key);
        if(aclass!=null){
    
    
            Intent intent = new Intent(context,aclass);
            if(bundle!=null){
    
    
                intent.putExtras(bundle);
            }
            context.startActivity(intent);
        }
    }

}

ARouter.getInstance().jumpActivity("t1Activity",null);

框架存在的意义在于,让程序员随意的去扩展,如果一个框架,程序员改不动,那这个框架是有问题的。

问:现在可以进行窗口跳转了吗?

答案是否定的,为什么不行呢?上面那些,都是基于map里面有数据,已经把所有Activity都加入到了map里面,但是现在,我们的map里面,并没有数据。那我们怎么样,才可以让所有Activity都加进去哪?

常规方法:

把所有Activity存入Map --> 在arouter中新建一个接口如(1) -->
在每一个组件中建一个类,在这个类里面继承接口,传入Activity,如(2)

(1)
package com.example.arouter;

public interface IRouter {
    
    
    void putActivity();
}
2)
package com.example.t1;

import com.example.arouter.ARouter;
import com.example.arouter.IRouter;

public class ActivityUtil implements IRouter {
    
    
    @Override
    public void putActivity() {
    
    
        ARouter.getInstance().putActivity("t1Activity",t1Activity.class);
    }
}

虽然说,这种方式,能够让我们拿到所有的Activity放到map里面去,但是这样些,管理起来非常麻烦,一点也不自动化,如果有100个组件,一个个去写,那工程量是非常大的。那有没有方式,程序员不用写,程序自动处理,答案是肯定的,这就是我们的,编译时技术。


编译时技术,技术在编译时,自动帮助我们生成一些类。它有两个非常重要的概念——注解和注解处理器
注解有一个非常重要的意义,就是标识,标识你这个类到底要干嘛,就想看到@Override,就知道这个方法是重写的方法,所以,给Activity加上注解,就可以说明它是需要加入到map里面去的。注解类似于牌匾。

首先,我们键两个模块,这两个模块,都必须是javaLibrary,最新版本是java or kotlin Library,注解名为annotations,注解处理器为annotations_compller,创建完成后,让所有模块都依赖这两个模块
注意点:注解处理器的依赖方式,不是implementation,而是
annotationProcessor,我们依赖后还需要去build.gradle文件中去修改。
在这里插入图片描述
注解 --> 新建javaLibrary --> annotations --> 新建Bing类,代码如(1) -->注解写法如(2),在类上面写

(1)
package com.example.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE}) //声明注解要放在什么地方  作用域 注解在类上面
@Retention(RetentionPolicy.CLASS) //声明注解存在的时间  生命周期    源码期 < 编译器 < 运行期
public @interface BindPath {
    String value();
}

(2)
	@BindPath("t1/t1")

现在注解有了,我们来看我们的注解处理器annotations_compller。首先我们要去注册,注册使用到一个api,我们需要引入一个谷歌的第三方库。
在Android Studio以上,写

annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
compileOnly 'com.google.auto.service:auto-service:1.0-rc3'

以下的话,只需要写

implementation 'com.google.auto.service:auto-service:1.0-rc3'

就可以了!

创建AnnotationCompiler类,写法如(1)

package com.example.annotation_compller;

import com.example.annotations.BindPath;
import com.google.auto.service.AutoService;

import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileObject;


/**
 * 这个类存在的意义:生成类,写代码
 * */
@AutoService(Process.class)  //注册注解管理器
public class AnnotationCompiler extends AbstractProcessor {
    
    
    //生成文件的对象
    Filer filer;

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

    }

    /**
     * 声明注解处理器支持的java版本
     * */
    @Override
    public SourceVersion getSupportedSourceVersion() {
    
    
        return processingEnv.getSourceVersion();
    }

    /**
     * 声明注解处理器要声明的注解有哪些
     * */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
    
    
        Set<String> types = new HashSet<>();
        types.add(BindPath.class.getCanonicalName());
        //如果有更多的注解,重复上面的话就可以了
        return types;
    }

    /**
     * 生成类,写代码
     * */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    
    
        Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindPath.class);
        // TypeElement 类节点
        // ExecutableElement 方法节点
        // VariableElement 成员变量节点
        Map<String,String> map = new HashMap<>();
        for (Element element : elementsAnnotatedWith){
    
    
            TypeElement typeElement = (TypeElement) element;
            //得到这个节点上面的注解里面的参数
            String key = typeElement.getAnnotation(BindPath.class).value();
            String activityName = typeElement.getQualifiedName().toString();
            map.put(key,activityName+".class");
        }

        if(map.size()>0){
    
    
            Writer writer = null;
            //创建一个新类名
            String activityName = "ActivityUtil" + System.currentTimeMillis();
            try {
    
    
                //生成文件
                JavaFileObject sourceFile = filer.createSourceFile("com.example.util" + activityName);
                writer = sourceFile.openWriter();
                writer.write("package com.example.util;\n" +
                        "\n" +
                        "import com.example.arouter.ARouter;\n" +
                        "import com.example.arouter.IRouter;\n" +
                        "\n" +
                        "\n" +
                        "public class "+activityName+" implements IRouter {\n" +
                        "    @Override\n" +
                        "    public void putActivity() {");
                Iterator<String> iterator = map.keySet().iterator();
                System.out.println("111111111111111");
                while (iterator.hasNext()){
    
    
                    String keys = iterator.next();
                    String activityNames = map.get(keys);
                    writer.write("ARouter.getInstance().putActivity(\""+keys+"\","+
                            activityNames+");\n");
                }
                writer.write("}\n}");
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }finally {
    
    
                if(writer!=null){
    
    
                    try {
    
    
                        writer.close();
                    } catch (IOException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }
        }
        return false;
    }
}

在arouter中的Arouter里面添加代码

package com.example.arouter;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import dalvik.system.DexFile;

/**
 * 中间人 代理
 * */
public class ARouter {
    
    
    //所有的地方,用到的都可以是同一个对象的,都可以定义为单例
    private static ARouter aRouter = new ARouter();
    //容器 装载所有模块中Activity类对象
    private 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;
        // 根据包名获取到这个包下面所有的类
        List<String> className = getClassName("com.example.util");
        for(String s: className){
    
    
            try{
    
    
                Class<?> aClass = Class.forName(s);
                //判断这个类是否是IRouter的接口
                if(IRouter.class.isAssignableFrom(aClass)){
    
    
                    IRouter iRouter = (IRouter) aClass.newInstance();
                    iRouter.putActivity();
                }
            }catch (Exception e){
    
    
                e.printStackTrace();
            }
        }
    }
    /**
     * 将类对象加入到容器的方法
     * */
    public void putActivity(String key,Class<? extends Activity> clazz){
    
    
        if(key!=null&&clazz!=null){
    
    
            map.put(key,clazz);
        }
    }

    /*
     * 跳转窗体的方法
     * */
    public void jumpActivity(String key, Bundle bundle){
    
    
        Class<? extends Activity> aclass = map.get(key);
        if(aclass!=null){
    
    
            Intent intent = new Intent(context,aclass);
            if(bundle!=null){
    
    
                intent.putExtras(bundle);
            }
            context.startActivity(intent);
        }
    }
    private List<String> getClassName(String packageName){
    
    
        //创建一个class对象的集合
        List<String> classList = new ArrayList<>();
        String path= null;
        try {
    
    
            //通过包的管理器 获取到应用信息类后获取到APK的完整路径
            path = context.getPackageManager().getApplicationInfo(context.getPackageName(),0).sourceDir;
            //根据APK的完整路径获取到编译后的dex文件
            DexFile dexFile = new DexFile(path);
            //获取编译后的dex文件中的所有class
            Enumeration entries = dexFile.entries();
            //然后遍历
            while(entries.hasMoreElements()){
    
    
                //通过遍历所有的class的类名
                String name = (String) entries.nextElement();
                //判断类的包名,是否符合com.example.util
                if(name.contains(packageName)){
    
    
                    //如果符合 就添加到集合中
                    classList.add(name);
                }
            }
        }catch (Exception e){
    
    
            e.printStackTrace();
        }
        return classList;
    }
}

然后在app模块里面,创建MyApplication,代码如下:

package com.example.t03_22_02;

import android.app.Application;

import com.example.arouter.ARouter;

public class MyApplication extends Application {
    
    
    @Override
    public void onCreate() {
    
    
        super.onCreate();
        ARouter.getInstance().init(this);
    }
}

然后在清单文件中写入
在这里插入图片描述

完工!!!

猜你喜欢

转载自blog.csdn.net/weixin_43912367/article/details/105009855