安卓的组件化之路由

1,708 阅读6分钟

1.什么是组件化:

组件,顾名思义,组装的零件,术语上叫做软件单元,可用于组装在应用程序中

从这个角度上看,组件化,关注可复用性、分离、功能单一、高内聚;是业务上能划分的的小单元

2.组件化的优点:

  • 组件,既可以作为library,又可以单独作为application,便于单独编译单独测试,大大的提高了编译和开发效率;
  • 组件,可有自己独立的版本,业务线互不干扰,可单独编译、测试、打包、部署
  • 各业务线共有的公共模块封装成组件,作为依赖库供各业务线调用,减少重复代码编写,减少冗余,便于维护

3.组件化架构

其中的“业务组件”,既可以单独打包为apk,又可以作为library按需组合为综合一些的应用程序

比如根据具体业务进行分层,如下图所示,其中“moudle_main”是主要的、且逻辑和代码相同的业务组件,“moudle1”和“moudle2”是拆分出来的业务组件,管理各自私有的逻辑和代码,互相没有依赖,版本有差别

\

4.组件之间不存在依赖关系如何交互

\

\

\

这就需要一个桥梁,从而引出了一个路由,目前用得比较多得,阿里巴巴开源得Arouter使用文档),美团开源的:WMrouter_Source;美团
(今天是以阿里的进行分析,美团的大家感兴趣可以下去看看)

5.路由要解决的问题

  • 解耦
  • 通信
  • 按需添加需要的模块

6.ARouter的使用

  1. 如何使用:
apply plugin: 'kotlin-kapt'

kapt {
    arguments {
        arg("AROUTER_MODULE_NAME", project.getName())
    }
}

dependencies {
    compile 'com.alibaba:arouter-api:x.x.x'
    kapt 'com.alibaba:arouter-compiler:x.x.x'
    ...
}
if (true) {           // 这两行必须写在init之前,否则这些配置在init过程中将无效
            ARouter.openLog();     // 打印日志
            ARouter.openDebug();   // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
        }
        ARouter.init(this); // 尽可能早,推荐在Application中初始化

\

@Route(path = AROUTER_MODULE_1)
class Module1Activity : BaseActivity() {}
@Route(path = ArouterConfig.AROUTER_MODULE_2)
class Module2Activity:BaseActivity() {}
ARouter.getInstance().build(ArouterConfig.AROUTER_MODULE_2).navigation()

2.如何模块之间进行数据交互

interface BaseService :IProvider {
}
interface Module1Service :BaseService {
    override fun init(p0: Context?) {

    }
}
interface Module2Service :BaseService {
    override fun init(p0: Context?) {

    }


    fun setInfo(info:String)


    fun  getInfo():String


    fun  start2Activity(activity: Activity)

}

@Route(path = ArouterConfig.SERVICE_MODULE_1)
class ImlModule1Service:Module1Service {

    fun setInfo(info:String){
        Log.d("ImlModule1Service",info)
    }

    fun  getInfo():String{
        return "ImlModule1Service"
    }
}
@Route(path = ArouterConfig.SERVICE_MODULE_2)
class ImlModule2Service:Module2Service {

    override fun setInfo(info:String){
        Log.d("ImlModule2Service",info)
    }

    override fun  getInfo():String{
        return "ImlModule2Service"
    }

    override fun  start2Activity(activity: Activity){
        activity.startActivity(Intent(activity,Module2Activity::class.java))
    }
}



var service2 =
            ARouter.getInstance().build(ArouterConfig.SERVICE_MODULE_2).navigation() as Module2Service
        var btn = findViewById<Button>(R.id.btn_open)
        btn.setOnClickListener {
            //service2.start2Activity(mActivity)
            service2.setInfo("嘻嘻,你是个傻子呀")
            var btnStr = service2.getInfo()
            btn.setText(btnStr)
        }

3.如何携带数据

ARouter.getInstance().build(ArouterConfig.AROUTER_MODULE_1).withString("key","Nihaoya").navigation()

//数据自动解析
@Route(path = AROUTER_MODULE_1)
class Module1Activity : BaseActivity() {
    @Autowired
    @JvmField
    var key :String ?=null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.module_1)
        ARouter.getInstance().inject(this)
        var tv = findViewById<TextView>(R.id.tv_module1)
        key?.apply { tv.setText(key) }

        var service2 =
            ARouter.getInstance().build(ArouterConfig.SERVICE_MODULE_2).navigation() as Module2Service
        var btn = findViewById<Button>(R.id.btn_open)
        btn.setOnClickListener {
            //service2.start2Activity(mActivity)
            service2.setInfo("嘻嘻,你是个傻子呀")
            var btnStr = service2.getInfo()
            btn.setText(btnStr)
        }
    }
}

7.ARouter解析

  1. 关于路由的Route的注册和初始化:

路由方面包含了比较多的信息,ARouter把路由分成了几种种类,Activity,Fragment,甚至是模块间通信的IProvider也是用路由注解。

public enum RouteType {
    ACTIVITY(0, "android.app.Activity"),
    SERVICE(1, "android.app.Service"),
    PROVIDER(2, "com.alibaba.android.arouter.facade.template.IProvider"),
    CONTENT_PROVIDER(-1, "android.app.ContentProvider"),
    BOARDCAST(-1, ""),
    METHOD(-1, ""),
    FRAGMENT(-1, "android.app.Fragment"),
    UNKNOWN(-1, "Unknown route type");
}

然后收集,所有被Route注解的Activity,Provider,Fragment都会按照不同分组收集到不同的类里面。类似下面这样:

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$module1 implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/module1/Module1Activity", RouteMeta.build(RouteType.ACTIVITY, Module1Activity.class, "/module1/module1activity", "module1", new java.util.HashMap<String, Integer>(){{put("key", 8); }}, -1, -2147483648));
  }
}

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$name implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/name/service1", RouteMeta.build(RouteType.PROVIDER, ImlModule1Service.class, "/name/service1", "name", null, -1, -2147483648));
  }
}

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Root$$comluoli implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("module1", ARouter$$Group$$module1.class);
    routes.put("name", ARouter$$Group$$name.class);
  }
}

这里特别需要注意的是路由是按组分开收集的,也就是说一个模块里面的Route可以定义多个分组,会生成多个路由表,分组名是/module1/Module1Activity里面的module1,当然换成别的就是别的分组了。这也是为什么不同的模块不能使用同一个分组的原因。

说明:固定包名com.alibaba.android.arouter.routes

ARouter$$Group$$+分组名,分组名称是在Route注解的参数里面定义的。

  1. 初始化
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        mContext = context;
        executor = tpe;

        try {
            long startInit = System.currentTimeMillis();
            //load by plugin first
            loadRouterMap();
            if (registerByPlugin) {
                logger.info(TAG, "Load router map by arouter-auto-register plugin.");
            } else {
                Set<String> routerMap;

                // It will rebuild router map every times when debuggable.
                if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                    logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
                    // These class was generated by arouter-compiler.
                    routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                    if (!routerMap.isEmpty()) {
                        context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                    }

                    PackageUtils.updateVersion(context);    // Save new version name when router map update finishes.
                } else {
                    logger.info(TAG, "Load router map from cache.");
                    routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
                }

                logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
                startInit = System.currentTimeMillis();

                for (String className : routerMap) {
                    if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                        // This one of root elements, load root.
                        ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                        // Load interceptorMeta
                        ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                        // Load providerIndex
                        ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                    }
                }
            }

            logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");

            if (Warehouse.groupsIndex.size() == 0) {
                logger.error(TAG, "No mapping files were found, check your configuration please!");
            }

            if (ARouter.debuggable()) {
                logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size()));
            }
        } catch (Exception e) {
            throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
        }
    }

上面的就是进行注解数据的加载与解析,存储在缓存中,我们发现如果一个应用几百个页面,这样加载的时候是不是会比较慢;所以上面做了一些列的优化

    • 分组加载优化
//然后收集,所有当前模块的对应的ARouter$$Group$$+分组名类,以分组名为key。
/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Root$$comluoli implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("module1", ARouter$$Group$$module1.class);
    routes.put("name", ARouter$$Group$$name.class);
  }
}
    • 使用gradle plugin生成代码调用
apply plugin: 'com.alibaba.arouter'

repositories {
        google()
        mavenCentral()
    }
    dependencies {
          classpath "com.alibaba:arouter-register:1.0.2"

        // NOTE: Do not place your application dependencies here; they belong
        // in th
private static void loadRouterMap() {
    registerByPlugin = false;
    //auto generate register code by gradle plugin: arouter-auto-register
    // looks like below:
    // registerProvider(new ARouter$$Providers$$news());
}

\

  1. 初始化流程:

4.跳转的解析:

ARouter.getInstance().build(ArouterConfig.AROUTER_MODULE_2).navigation()

\

 private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        final Context currentContext = postcard.getContext();

        switch (postcard.getType()) {
            case ACTIVITY:
                // Build intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());

                // Set flags.
                int flags = postcard.getFlags();
                if (0 != flags) {
                    intent.setFlags(flags);
                }

                // Non activity, need FLAG_ACTIVITY_NEW_TASK
                if (!(currentContext instanceof Activity)) {
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }

                // Set Actions
                String action = postcard.getAction();
                if (!TextUtils.isEmpty(action)) {
                    intent.setAction(action);
                }

                // Navigation in main looper.
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        startActivity(requestCode, currentContext, intent, postcard, callback);
                    }
                });

                break;
            case PROVIDER:
                return postcard.getProvider();
            case BOARDCAST:
            case CONTENT_PROVIDER:
            case FRAGMENT:
                Class<?> fragmentMeta = postcard.getDestination();
                try {
                    Object instance = fragmentMeta.getConstructor().newInstance();
                    if (instance instanceof Fragment) {
                        ((Fragment) instance).setArguments(postcard.getExtras());
                    } else if (instance instanceof android.support.v4.app.Fragment) {
                        ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                    }

                    return instance;
                } catch (Exception ex) {
                    logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
                }
            case METHOD:
            case SERVICE:
            default:
                return null;
        }

        return null;
    }

5.数据的自动解析

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class Module1Activity$$ARouter$$Autowired implements ISyringe {
  private SerializationService serializationService;

  @Override
  public void inject(Object target) {
    serializationService = ARouter.getInstance().navigation(SerializationService.class);
    Module1Activity substitute = (Module1Activity)target;
    substitute.key = substitute.getIntent().getExtras() == null ? substitute.key : substitute.getIntent().getExtras().getString("key", substitute.key);
  }
}

6.拦截的拓展(...)

总结:

组件化比较适合大型,模块化明显的项目,还是得看公司项目的实际需要

路由实现三板斧

  • 注解(收集)
  • apt(根据注解收集生成对应的类)
  • gradle plugin(根据apt生成的类,在应用启动的时候注入指定的方法,用于优化)

\

用用你的金手指,评论一下:(链接)