Androidのコンポーネント化されたルーティング

1.コンポーネント化とは:

コンポーネントは、その名前が示すように、アプリケーションで組み立てるために使用できる、ソフトウェアユニットと呼ばれる組み立てられたパーツです。

この観点から、コンポーネント化は、再利用性、分離、単一機能、および高い凝集度に重点を置いています。これは、ビジネスで分割できる小さなユニットです。

2.コンポーネント化の利点:

  • ライブラリと個別のアプリケーションの両方として使用できるコンポーネントは、個別にコンパイルおよびテストするのが簡単であり、コンパイルと開発の効率が大幅に向上します。
  • コンポーネントは独自の独立したバージョンを持つことができ、ビジネスラインは互いに干渉せず、独立してコンパイル、テスト、パッケージ化、および展開できます。
  • 各ビジネスラインで共有される共通モジュールはコンポーネントにカプセル化され、呼び出す各ビジネスラインの依存関係ライブラリとして使用され、繰り返しのコード書き込みを減らし、冗長性を減らし、メンテナンスを容易にします。

3.コンポーネント化されたアーキテクチャ

その中で、「ビジネスコンポーネント」は、apkとして個別にパッケージ化でき、必要に応じてライブラリとして組み合わせて包括的なアプリケーションにすることができます。

たとえば、特定のビジネスに応じて、次の図に示すように、「moudle_main」は同じロジックとコードを持つメインのビジネスコンポーネントであり、「moudle1」と「moudle2」は独自のプライベートロジックを管理するための分割されたビジネスコンポーネントです。そして、コード、お互いに依存関係はありません、バージョンは異なります

\

4.コンポーネント間の依存関係なしで対話する方法

\

\

\

これには、現在多く使用されているルートにつながるブリッジが必要です。Alibabaはドキュメントを使用して)オープンソースのArouterを使用し、Meituanはオープンソースを使用しています: ;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. ルートのルートの登録と初期化について:

ルーティングには多くの情報が含まれています。ARouterはルーティングをいくつかのタイプに分類します。アクティビティ、フラグメント、さらにはモジュール間通信用の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