组件化攻略

一.什么是组件化

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技术

相关推荐
踢球的打工仔1 天前
PHP面向对象(7)
android·开发语言·php
安卓理事人1 天前
安卓socket
android
安卓理事人1 天前
安卓LinkedBlockingQueue消息队列
android
万能的小裴同学1 天前
Android M3U8视频播放器
android·音视频
q***57741 天前
MySql的慢查询(慢日志)
android·mysql·adb
JavaNoober1 天前
Android 前台服务 "Bad Notification" 崩溃机制分析文档
android
城东米粉儿1 天前
关于ObjectAnimator
android
zhangphil1 天前
Android渲染线程Render Thread的RenderNode与DisplayList,引用Bitmap及Open GL纹理上传GPU
android
火柴就是我1 天前
从头写一个自己的app
android·前端·flutter
lichong9511 天前
XLog debug 开启打印日志,release 关闭打印日志
android·java·前端