组件化攻略

一.什么是组件化

1.解决打包速度慢;

2.组件化模块功能,加速测试流程;

3.多人协同开发,任务分离。

二.组件化的实现

1.公共配置

在项目的目录下添加config.gradle配置文件;

1.版本控制

ini 复制代码
rootProject.ext {

    androidId = [
            compileSdkVersion: 33,
            minSdkVersion    : 22,
            targetSdkVersion : 33,
            versionCode      : 1,
            versionName      : "1.0"
    ]
    appId = [
            app    : "com.seven.componenttest",
            library: "com.seven.componentlibrary",
            library2: "com.seven.componentlibrary2"
    ]
 

    def ktx = "1.9.0"
    def appcompat = "1.6.1"
    def material = "1.8.0"
    def constraintlayout = "2.1.4"
    dependencies = [
            "ktx"             : "androidx.core:core-ktx:$ktx",
            "appcompat"       : "androidx.appcompat:appcompat:$appcompat",
            "material"        : "com.google.android.material:material:$material",
            "constraintlayout": "androidx.constraintlayout:constraintlayout:$constraintlayout",
   
    ]
}

2.依赖控制、打包配置

ruby 复制代码
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}
apply {
    from("../config.gradle")
}
def androidId = rootProject.ext.androidId 
def appId = rootProject.ext.androidId  
def support =rootProject.ext.dependencies 
def url=rootProject.ext.url

android {
    namespace 'com.seven.componenttest'
    compileSdk androidId.compileSdkVersion

    defaultConfig {
        applicationId appId.app
        minSdk androidId.minSdkVersion
        targetSdk  androidId.targetSdkVersion
        versionCode androidId.versionCode
        versionName androidId.versionName
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            buildConfigField("String","url",""${url.debug}"") 
        }
        debug{
            minifyEnabled false
            buildConfigField("String", "url", ""${url.release}"")
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
 
}

dependencies {
    support.each { k, v -> implementation v }

}

库配置

java 复制代码
plugins {
    id 'org.jetbrains.kotlin.android'
}
def isLibrary=componentLibray.toBoolean()
if (isLibrary) {
    apply plugin: 'com.android.library'
} else {
    apply plugin: 'com.android.application'
}
def androidId = rootProject.ext.androidId 
def appId = rootProject.ext.appId.library 
def support =rootProject.ext.dependencies 


android {
    namespace 'com.seven.componentlibrary'
    compileSdk  androidId.compileSdkVersion

    defaultConfig {
        if(!isLibrary)
            applicationId appId
        versionCode androidId.versionCode
        versionName  androidId.versionName
        minSdk  androidId.minSdkVersion
        targetSdk  androidId.targetSdkVersion

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }

    }
    sourceSets {
     
        main {
            if (isLibrary) {
                manifest.srcFile('src/main/AndroidManifest.xml') 
            } else {
                manifest.srcFile('src/main/DEBUG/AndroidManifest.xml')
            }
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {
    support.each { k, v -> implementation v }
}

2.实现模块间跳转

跳转方案与参数传递:

方案1:

1.EventBus 一对多 可能回有点混乱

2.BroadCastReceiver 动态注册

3.反射 维护成本高

4.隐式意图 比较麻烦

5.类加载 需要全类名,维护成本高 class.forName("xxxActivity")

方案2:定义一个基础模块使用Map将需要跳转的Activity的对应的path和class注册进去。

typescript 复制代码
public class RecordPathManager {

 
    private static Map<String, List<PathBean>> groupMap = new HashMap<>();

    public static void joinGroup(String groupName, String pathName, Class<?> clazz) {

        List<PathBean> pathBeans = groupMap.get(groupName);
        if (pathBeans == null) {
            pathBeans = new ArrayList<>();
            pathBeans.add(new PathBean(pathName, clazz));
            groupMap.put(groupName, pathBeans);
        } else {
            for (PathBean pathBean : pathBeans) {
                if (!pathBean.getPath().equals(pathName)) {
                    pathBeans.add(new PathBean(pathName, clazz));
                    groupMap.put(groupName, pathBeans);
                }
            }
        }
    }


    public static Class<?> getTargetClazz(String groupName, String pathName) {

        List<PathBean> pathBeans = groupMap.get(groupName);
        if (null == pathBeans)
            return null;
        for (PathBean pathBean : pathBeans) {
            if (pathBean.getPath().equalsIgnoreCase(pathName)) {
                return pathBean.getClazz();
            }
        }
        return null;
    }

加入到路由表中去

kotlin 复制代码
class App :Application() {

    override fun onCreate() {
        super.onCreate()
        RecordPathManager.joinGroup("app","main",MainActivity::class.java)
        RecordPathManager.joinGroup("component1","main",com.seven.componentlibrary.MainActivity::class.java)
        RecordPathManager.joinGroup("component2","main",com.seven.componentlibrary2.MainActivity::class.java)
    }

}

跳转测试

kotlin 复制代码
    fun main2C1(view: View) {
        startActivity(Intent(this, RecordPathManager.getTargetClazz("component1", "main")))

    }

    fun main2C2(view: View) {
        startActivity(Intent(this, RecordPathManager.getTargetClazz("component2", "main")))
    }

3.APT技术实现模块跳转

上面的代码帮助我们做了简单的路由跳转的实现,但是我们需要手动的添加或者删除路由信息,比较麻烦,所以我们使用APT技术帮助我们实现:

kotlin 复制代码
    fun main2C1(view: View) {

        startActivity(Intent(this,  com.seven.componentlibrary.`MainActivity$$ARouter`.findTargetClass("/component1/main/MainActivity")))
        //startActivity(Intent(this, RecordPathManager.getTargetClazz("component1", "main")))

    }

    fun main2C2(view: View) {
        startActivity(Intent(this,
            com.seven.componentlibrary2.`MainActivity$$ARouter`.findTargetClass("/component2/main/MainActivity")))
        //startActivity(Intent(this, RecordPathManager.getTargetClazz("component2", "main")))
    }
ini 复制代码
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        elementUtils=processingEnv.getElementUtils();
        typeUtils=processingEnv.getTypeUtils();
        messager=processingEnv.getMessager();
        filer=processingEnv.getFiler();
        String content = processingEnv.getOptions().get("content");
        messager.printMessage(Diagnostic.Kind.NOTE,content+"----.>>>>>>");

    }




    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        messager.printMessage(Diagnostic.Kind.NOTE,"process");
        if(set.isEmpty()){

            messager.printMessage(Diagnostic.Kind.NOTE,"set empty");

            return false;
        }
   
        Set<? extends Element> elements=roundEnvironment.getElementsAnnotatedWith(ARouter.class);



        for (Element element:elements) {

            String packageName=elementUtils.getPackageOf(element).getQualifiedName().toString();
            String clazzName=element.getSimpleName().toString();

            messager.printMessage(Diagnostic.Kind.NOTE,"注解的类:"+clazzName);
MainActivity$$ARouter.findTargetClass("/app/main/MainActivity")来查找clazz
            String finalClzName=clazzName+"$$ARouter";

            try{
                JavaFileObject sourceFile = filer.createSourceFile( packageName +"."+ finalClzName);
                Writer writer = sourceFile.openWriter();
               
                writer.write( "package "+ packageName +";\n");
                writer.write( "public class "+ finalClzName + "{\n");
                writer. write( "public static Class<?> findTargetClass(String path){\n");
               
                ARouter aRouter = element.getAnnotation(ARouter.class);
                writer.write("if(path.equals(""+ aRouter.path()+"")){\n");
                writer.write( "return "+ clazzName +".class;\n}\n");
                writer.write( "return null;\n");
                writer.write( "}\n}");
                writer.close();
            }catch (Exception e){
                e.printStackTrace();
            }

        }

        return true;
    }

但是这种无法在组件间路由,因为每个路由虽能被app拿到,但是各个组件间是无法拿到彼此的路由的。

4.javapoet增加基类接口实现模块跳转

1.顶层接口定义

vbnet 复制代码
public interface ARouterLoadGroup {

    /**
     * 获取分组的对应的clz
     * String 为group
     * @return
     */
    Map<String,Class<? extends ARouterLoadPath>> loadGroup();
}

public interface ARouterLoadPath {

    /**
     * 分组下的 String 为path
     * @return
     */
    Map<String, RouterBean> loadPath();
}

2.定义注解和注解处理器

less 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface ARouter {
    String path();

    String group() default "";
}
arduino 复制代码
public class RouterBean {
    public enum TYPE {
        ACTIVITY
    }

    public TYPE getType() {
        return type;
    }

    public Element getElement() {
        return element;
    }

    public Class<?> getClazz() {
        return clazz;
    }

    public String getGroup() {
        return group;
    }

    public String getPath() {
        return path;
    }

    public void setType(TYPE type) {
        this.type = type;
    }

    public void setGroup(String group) {
        this.group = group;
    }

    public void setPath(String path) {
        this.path = path;
    }


    private TYPE type;
    private Element element;


    private Class<?> clazz;

    private String group;
    private String path; 

    @Override
    public String toString() {
        return "RouterBean{" +
                "group='" + group + ''' +
                ", path='" + path + ''' +
                '}';
    }



    private RouterBean(Builder builder) {
        this.element = builder.element;
        this.group = builder.group;
        this.path = builder.path;
    }

    private  RouterBean(TYPE type, Class<?> clazz, String group, String path) {
        this.type = type;
        this.clazz = clazz;
        this.group = group;
        this.path = path;
    }

    public static RouterBean create(TYPE type, Class<?> clazz, String group, String path) {
        return new RouterBean(type, clazz, group, path);
    }

    public final static class Builder {
        private Element element;//结点
        private String group;
        private String path;


        public Builder setElement(Element element) {
            this.element = element;
            return this;
        }


        public Builder setGroup(String group) {
            this.group = group;
            return this;
        }

        public Builder setPath(String path) {
            this.path = path;
            return this;
        }


        public RouterBean builder() {

            if (null == path || path.length() == 0) {
                throw new IllegalArgumentException("RouterBean 路由地址 不可以为空");
            }

            return new RouterBean(this);
        }


    }
}

解析注解并且生成对应的实现

typescript 复制代码
public class ARouterProcessor extends AbstractProcessor {


    private Elements elementUtils;

    private Types typeUtils;

    private Messager messager;


    private Filer filer;

    String moduleName;
    String packageNameForAPT;


    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        elementUtils = processingEnv.getElementUtils();
        typeUtils = processingEnv.getTypeUtils();
        messager = processingEnv.getMessager();
        filer = processingEnv.getFiler();


        moduleName = processingEnv.getOptions().get(Constants.MODULE_NAME);
        packageNameForAPT = processingEnv.getOptions().get(Constants.APT_PACKAGE_NAME);
        messager.printMessage(Diagnostic.Kind.NOTE, "module" + moduleName + "----.>>>>>>packageNameForAPT:" + packageNameForAPT);
        if (EmptyUtils.isEmpty(moduleName) || EmptyUtils.isEmpty(packageNameForAPT)) {
            throw new RuntimeException("注解处理器的参数ModuleName或者packageName为空");
        }


    }

    //处理注解
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

        if (!EmptyUtils.isEmpty(set)) {
   
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
            if (!EmptyUtils.isEmpty(elements)) {
   
                try {
                    parseElements(elements);
                } catch (IOException e) {
                    messager.printMessage(Diagnostic.Kind.NOTE,"error:"+e.getMessage());
                    e.printStackTrace();
                }
            }

            return true;
        }
        return true;
    }

最终我们生成的模板代码样例如下:

typescript 复制代码
public class ARouter$$Group$$User implements ARouterLoadGroup {
    @Override
    public Map<String, Class<? extends ARouterLoadPath>> loadGroup() {
        Map<String ,Class<? extends ARouterLoadPath>> groupMap=new HashMap<>();
        groupMap.put("user",ARouter$$Path$$User.class);
        return groupMap;
    }
}
public class ARouter$$Path$$User implements ARouterLoadPath {

    @Override
    public Map<String, RouterBean> loadPath() {

        Map<String,RouterBean> pathMap=new HashMap<>();

   
        pathMap.put("/user/UserActivity",RouterBean.create(RouterBean.TYPE.ACTIVITY,
                UserActivity.class,"user","/user/UserActivity"));

        return pathMap;
    }
}

3.提供跳转api

apt帮助我们生成的代码在各自的组件中,所以我们需要在公共router_api中添加查找和跳转的接口:

csharp 复制代码
public class RouterManager {

    private static volatile RouterManager instance;

    private String group;
    private String path;


    private LruCache<String, ARouterLoadGroup> groupLruCache;

  
    private LruCache<String, ARouterLoadPath> pathLruCache;


    private static final String group_file_prefix = ".ARouter$$Group$$";

    private RouterManager() {
        groupLruCache = new LruCache<>(100);
        pathLruCache = new LruCache<>(100);
    }

    public static RouterManager getInstance() {
        if (null == instance) {
            synchronized (RouterManager.class) {
                if (null == instance) {
                    instance = new RouterManager();
                }
            }
        }
        return instance;
    }


    public BundleManager build(String path) {

        if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
            throw new IllegalArgumentException("未按照规范配置");
        }
        this.group = subFromPath2Group(path);
        System.out.println("BundleManager build:"+group);
        this.path = path;
        return new BundleManager();
    }


    private String subFromPath2Group(String path) {
        if (path.lastIndexOf("/") == 0) {
            throw new IllegalArgumentException("未按照规范配置");
        }
      
        String finalGroup = path.substring(1, path.indexOf("/", 1));
  
        if (finalGroup.contains("/")) {
            throw new IllegalArgumentException("未按照规范配置");
        }
        if (TextUtils.isEmpty(finalGroup)) {
            throw new IllegalArgumentException("未按照规范配置");
        }
        return finalGroup;
    }


    public Object navigation(Context context, BundleManager bundleManager, int code) {
        String groupClassName = /*context.getPackageName() + */"com.seven.arouter.apt" + group_file_prefix+group; 
        System.out.println("RouterManager navigation groupClazzName:" + groupClassName);
      
        try {

     
            ARouterLoadGroup groupLoad = groupLruCache.get(group);
            if (groupLoad == null) {
     
                Class<?> clazz = Class.forName(groupClassName);
                groupLoad = (ARouterLoadGroup) clazz.newInstance();

                groupLruCache.put(group, groupLoad);
            }
            
            if (groupLoad.loadGroup().isEmpty()) {
                throw new RuntimeException("路由表group加载失败!");
            }

            ARouterLoadPath pathLoad = pathLruCache.get(path);
            if (pathLoad == null) {
           
                Class<? extends ARouterLoadPath> clazz = groupLoad.loadGroup().get(group);
       
                if (null != clazz)
                    pathLoad = clazz.newInstance();
                if (pathLoad != null)
                    pathLruCache.put(group, pathLoad);
            }

            if (pathLoad != null) {
                if (pathLoad.loadPath().isEmpty()) {
                    throw new RuntimeException("路由表path加载失败");
                }
                RouterBean routerBean = pathLoad.loadPath().get(path);

                if (null != routerBean) {

                    switch (routerBean.getType()) {
                        case ACTIVITY:

                            Intent intent = new Intent(context, routerBean.getClazz());
                            intent.putExtras(bundleManager.getBundle());
                            if (bundleManager.isResult()) {
                                ((Activity) (context)).setResult(code,intent);
                                ((Activity) (context)).finish();
                                break;
                            }
                            if(code>0){
                                ((Activity) context). startActivityForResult(intent, code, bundleManager.getBundle());
                            } else {
                                context.startActivity(intent, bundleManager.getBundle());
                            }
                            break;
                        default:
                            break;

                    }


                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
}

三.Arouter的使用与基本原理

1.基本配置

javascript 复制代码
kapt {
      arguments {
          arg("AROUTER_MODULE_NAME", project.getName())
        }
    }

api 'com.alibaba:arouter-api:1.5.2'		 
kapt 'com.alibaba:arouter-compiler:1.5.2'

2.使用教程

1.跳转带参数请求码

kotlin 复制代码
ARouter.getInstance().build(RouterConstant.ORDER.mainPath)
    .withString("main", "main 2 user")
    .navigation(this, requestCode) 

参数需要在接收方使用@AutoWired注解标志,并且使用ARoute.getInstance.inject(this)注入,如下所示:

scss 复制代码
    @Autowired(name = "router")
    String router;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user);
        ARouter.getInstance().inject(this);
        System.out.println("UserActivity rec router----->>>>>:" + router);
    }

2.跳转回调和拦截器

回调会接收到跳转的状态,拦截器是基于切面AOP编程思想,可以帮助我们统一处理。

kotlin 复制代码
 fun gotoUser(view: View) {
        ARouter.getInstance().build(RouterConstant.User.mainPath)
            .withString("router", "main 2 user")
            .navigation(this, object : NavigationCallback {
                override fun onFound(postcard: Postcard?) {
            
                }

                override fun onLost(postcard: Postcard?) {

                }

                override fun onArrival(postcard: Postcard?) {
    
                }

                override fun onInterrupt(postcard: Postcard?) {
          
                }
            })

    }

@Interceptor(priority = 1, name = "测试拦截器1")
class NavigationLogInterceptor : IInterceptor {
    private var context: Context? = null
    override fun init(context: Context?) {
        println("NavigationLogInterceptor---->init$context")
        this.context = context
    }

    override fun process(postcard: Postcard?, callback: InterceptorCallback?) {
        println("NavigationLogInterceptor---->process$postcard")
        if (postcard?.path.equals(RouterConstant.User.mainPath)) {
            callback?.onContinue(postcard)
        } else {
            Handler(Looper.getMainLooper()).post {
                println("NavigationLogInterceptor---->process$--->>show")
                Toast.makeText(
                    context, "拦截跳转:${postcard?.path}",
                    Toast.LENGTH_SHORT
                ).show
            }
        }

    }
}

3.Fragment跳转

kotlin 复制代码
    //加载fragment
    fun loadBlankFragment(view: View) {
        val fragment: Fragment =
            ARouter.getInstance().build(RouterConstant.Main.blankFragmentPath).navigation() as Fragment
        supportFragmentManager.beginTransaction().add(R.id.container,fragment).commit()
    }

4.URI跳转

kotlin 复制代码
    fun gotoSchemeFilter(view: View) {
        // 1. 默认路径
        val uri = Uri.parse("feather://www.seven.com:12345${RouterConstant.Main.schemeFilterPath}")
        ARouter.getInstance().build(uri).withString("str","schemeFilterStr---<").navigation()

    }

5.带动画的跳转

scss 复制代码
  ARouter.getInstance()
            .build(uri)
            .withTransition(
                android.R.animator.fade_in, android.R.animator.fade_out
            )
            .withString("str", "schemeFilterStr---<").navigation()

3.基本原理

APT技术

相关推荐
從南走到北2 小时前
JAVA国际版同城外卖跑腿团购到店跑腿多合一APP系统源码支持Android+IOS+H5
android·java·ios·微信小程序·小程序
岸芷漫步2 小时前
android框架层弹出对话框的分析
android
Android疑难杂症2 小时前
鸿蒙Media Kit媒体服务开发快速指南
android·harmonyos·音视频开发
马 孔 多 在下雨3 小时前
Android动画集大成之宗-MotionLayout基础指南
android
用户413079810613 小时前
Android动画集大成之宗-MotionLayout
android
金鸿客3 小时前
在Compose中使用camerax进行拍照和录视频
android
伟大的大威5 小时前
Android 端离线语音控制设备管理系统:完整技术方案与实践
android·macos·xcode
骑驴看星星a8 小时前
【Three.js--manual script】4.光照
android·开发语言·javascript
TDengine (老段)14 小时前
TDengine 字符串函数 CONCAT_WS 用户手册
android·大数据·数据库·时序数据库·tdengine·涛思数据