1.什么是组件化:
组件,顾名思义,组装的零件,术语上叫做软件单元,可用于组装在应用程序中
从这个角度上看,组件化,关注可复用性、分离、功能单一、高内聚;是业务上能划分的的小单元
2.组件化的优点:
- 组件,既可以作为library,又可以单独作为application,便于单独编译单独测试,大大的提高了编译和开发效率;
- 组件,可有自己独立的版本,业务线互不干扰,可单独编译、测试、打包、部署
- 各业务线共有的公共模块封装成组件,作为依赖库供各业务线调用,减少重复代码编写,减少冗余,便于维护
3.组件化架构
其中的“业务组件”,既可以单独打包为apk,又可以作为library按需组合为综合一些的应用程序
比如根据具体业务进行分层,如下图所示,其中“moudle_main”是主要的、且逻辑和代码相同的业务组件,“moudle1”和“moudle2”是拆分出来的业务组件,管理各自私有的逻辑和代码,互相没有依赖,版本有差别
\
4.组件之间不存在依赖关系如何交互
\
\
\
这就需要一个桥梁,从而引出了一个路由,目前用得比较多得,阿里巴巴开源得Arouter(使用文档),美团开源的:WMrouter_Source;美团
(今天是以阿里的进行分析,美团的大家感兴趣可以下去看看)
5.路由要解决的问题
- 解耦
- 通信
- 按需添加需要的模块
6.ARouter的使用
- 如何使用:
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解析
- 关于路由的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注解的参数里面定义的。
- 初始化
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());
}
\
- 初始化流程:
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生成的类,在应用启动的时候注入指定的方法,用于优化)
\
用用你的金手指,评论一下:(链接)