Android의 컴포넌트화된 라우팅

1. 구성 요소화란 무엇입니까?

구성 요소는 이름에서 알 수 있듯이 응용 프로그램에서 조립하는 데 사용할 수 있는 조립된 부품, 즉 소프트웨어 단위입니다.

이러한 관점에서 컴포넌트화 는 재사용성, 분리성, 단일 기능 및 높은 응집력에 중점을 두고 있습니다.

2. 컴포넌트화의 장점:

  • 라이브러리와 별도의 응용 프로그램으로 모두 사용할 수 있는 구성 요소는 개별적으로 컴파일 및 테스트하기 쉽기 때문에 컴파일 및 개발 효율성이 크게 향상됩니다.
  • 구성 요소는 자체 독립 버전을 가질 수 있으며 비즈니스 라인은 서로 간섭하지 않으며 독립적으로 컴파일, 테스트, 패키지 및 배포할 수 있습니다.
  • 각 비즈니스 라인에서 공유하는 공통 모듈은 구성 요소로 캡슐화되고 각 비즈니스 라인이 호출하는 종속성 라이브러리로 사용되어 반복적인 코드 작성 감소, 중복 감소 및 유지 관리 용이

3. 컴포넌트화된 아키텍처

그 중 "비즈니스 구성 요소"는 apk로 별도로 패키징할 수 있으며 필요에 따라 라이브러리로 통합하여 포괄적인 응용 프로그램에 사용할 수 있습니다.

예를 들어, 특정 비즈니스에 따라 아래 그림과 같이 "moudle_main"은 동일한 논리와 코드를 가진 주요 비즈니스 구성 요소이며 "moudle1"과 "moudle2"는 자체 private logic을 관리하기 위해 분할된 비즈니스 구성 요소입니다. 그리고 코드는 서로 의존성이 없으며 버전이 다릅니다.

\

4. 컴포넌트 간의 의존성 없이 상호작용하는 방법

\

\

\

이것은 현재 많이 사용되는 경로로 연결되는 브리지가 필요합니다.Alibaba는 Arouter ( 문서 사용 )를 오픈 소스했고 Meituan.WMrouter_Source

5. 라우팅을 통해 해결해야 할 문제

  • 디커플링
  • 의사소통
  • 필요에 따라 필수 모듈 추가

6. AR라우터의 사용

  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. AR라우터 분석

  1. 경로의 경로 등록 및 초기화 정보:

라우팅에는 많은 정보가 포함되어 있는데, 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生成的类,在应用启动的时候注入指定的方法,用于优化)

\

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

추천

출처juejin.im/post/7116451169282555940