1. 구성 요소화란 무엇입니까?
구성 요소는 이름에서 알 수 있듯이 응용 프로그램에서 조립하는 데 사용할 수 있는 조립된 부품, 즉 소프트웨어 단위입니다.
이러한 관점에서 컴포넌트화 는 재사용성, 분리성, 단일 기능 및 높은 응집력에 중점을 두고 있습니다.
2. 컴포넌트화의 장점:
- 라이브러리와 별도의 응용 프로그램으로 모두 사용할 수 있는 구성 요소는 개별적으로 컴파일 및 테스트하기 쉽기 때문에 컴파일 및 개발 효율성이 크게 향상됩니다.
- 구성 요소는 자체 독립 버전을 가질 수 있으며 비즈니스 라인은 서로 간섭하지 않으며 독립적으로 컴파일, 테스트, 패키지 및 배포할 수 있습니다.
- 각 비즈니스 라인에서 공유하는 공통 모듈은 구성 요소로 캡슐화되고 각 비즈니스 라인이 호출하는 종속성 라이브러리로 사용되어 반복적인 코드 작성 감소, 중복 감소 및 유지 관리 용이
3. 컴포넌트화된 아키텍처
그 중 "비즈니스 구성 요소"는 apk로 별도로 패키징할 수 있으며 필요에 따라 라이브러리로 통합하여 포괄적인 응용 프로그램에 사용할 수 있습니다.
예를 들어, 특정 비즈니스에 따라 아래 그림과 같이 "moudle_main"은 동일한 논리와 코드를 가진 주요 비즈니스 구성 요소이며 "moudle1"과 "moudle2"는 자체 private logic을 관리하기 위해 분할된 비즈니스 구성 요소입니다. 그리고 코드는 서로 의존성이 없으며 버전이 다릅니다.
\
4. 컴포넌트 간의 의존성 없이 상호작용하는 방법
\
\
\
이것은 현재 많이 사용되는 경로로 연결되는 브리지가 필요합니다.Alibaba는 Arouter ( 문서 사용 )를 오픈 소스했고 Meituan 은 .WMrouter_Source
5. 라우팅을 통해 해결해야 할 문제
- 디커플링
- 의사소통
- 필요에 따라 필수 모듈 추가
6. AR라우터의 사용
- 사용하는 방법:
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. AR라우터 분석
- 경로의 경로 등록 및 초기화 정보:
라우팅에는 많은 정보가 포함되어 있는데, 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生成的类,在应用启动的时候注入指定的方法,用于优化)
\
用用你的金手指,评论一下:(链接)