资讯专栏INFORMATION COLUMN

ARouter路由解析

stormgens / 3625人阅读

摘要:跳转完了找到了被拦截了找不到了拦截器在模块的时候讲述的使用,如果本次路由跳转不是走的绿色通道那么则会触发拦截器进行过滤。部分代码省略拦截器的初始化在刚开始初始化的时候,就已经做了这个操作。

目录介绍

01.原生跳转实现

02.实现组件跳转方式

2.1 传统跳转方式

2.2 为何需要路由

03.ARouter配置与优势

04.跨进程组件通信

4.1 URLScheme

4.2 AIDL

4.3 BroadcastReceiver

4.4 路由通信注意要点

05.ARouter的结构

06.ARouter的工作流程

6.1 初始化流程

6.2 跳转页面流程

07.ARouter简单调用api

7.1 最简单调用

7.2 build源码分析

7.3 navigation分析

08.Postcard信息携带

09.LogisticsCenter

10.DegradeService降级容错服务

11.Interceptor拦截器

12.数据传输和自动注入

13.多dex的支持

14.InstantRun支持

15.生成的编译代码

好消息

博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢!

链接地址:https://github.com/yangchong2...

如果觉得好,可以star一下,谢谢!当然也欢迎提出建议或者问题,万事起于忽微,量变引起质变!

注解学习小案例

注解学习小案例,比较系统性学习注解并且应用实践。简单应用了运行期注解,通过注解实现了setContentView功能;简单应用了编译器注解,通过注解实现了防暴力点击的功能,同时支持设置时间间隔;使用注解替代枚举;使用注解一步步搭建简单路由案例。结合相应的博客,在来一些小案例,从此应该对注解有更加深入的理解……

开源项目地址:https://github.com/yangchong2...

01.原生跳转实现

Google提供的原声路由主要是通过intent,可以分成显示和隐式两种。显示的方案会导致类之间的直接依赖问题,耦合严重;隐式intent需要的配置清单中统一声明,首先有个暴露的问题,另外在多模块开发中协作也比较困难。只要调用startActivity后面的环节我们就无法控制了,在出现错误时无能为力。

02.实现组件跳转方式 2.1 传统跳转方式

第一种,通过intent跳转

第二种,通过aidl跳转

第三种,通过scheme协议跳转

2.2 为何需要路由

显示Intent:项目庞大以后,类依赖耦合太大,不适合组件化拆分

隐式Intent:协作困难,调用时候不知道调什么参数

每个注册了Scheme的Activity都可以直接打开,有安全风险

AndroidMainfest集中式管理比较臃肿

无法动态修改路由,如果页面出错,无法动态降级

无法动态拦截跳转,譬如未登录的情况下,打开登录页面,登录成功后接着打开刚才想打开的页面

H5、Android、iOS地址不一样,不利于统一跳转

03.ARouter配置与优势 3.1 ARouter的优势

如下所示

直接解析URL路由,解析参数并赋值

支持多模块项目

支持InstantRun

允许自定义拦截器

ARouter可以提供IoC容器

映射关系自动注册

灵活的降级策略

3.2 至于配置和使用

直接看https://www.jianshu.com/p/fed...

04.跨进程组件通信 4.1 URLScheme【例如:ActivityRouter、ARouter等】

优势有:

基因中自带支持从webview中调用

不用互相注册(不用知道需要调用的app的进程名称等信息)

劣势有:

只能单向地给组件发送信息,适用于启动Activity和发送指令,不适用于获取数据(例如:获取用户组件的当前用户登录信息)

需要有个额外的中转Activity来统一处理URLScheme

如果设备上安装了多个使用相同URLScheme的app,会弹出选择框(多个组件作为app同时安装到设备上时会出现这个问题)

无法进行权限设置,无法进行开关设置,存在安全性风险

4.2 AIDL

优势有:

可以传递Parcelable类型的对象

效率高

可以设置跨app调用的开关

劣势有:

调用组件之前需要提前知道该组件在那个进程,否则无法建立ServiceConnection

组件在作为独立app和作为lib打包到主app时,进程名称不同,维护成本高

4.3 BroadcastReceiver

BroadcastReceiver + Service + LocalSocket。该方案是参考cc路由框架!

跨组件间通信实现的同时,应该满足以下条件:

每个app都能给其它app调用

app可以设置是否对外提供跨进程组件调用的支持

组件调用的请求发出去之后,能自动探测当前设备上是否有支持此次调用的app

支持超时、取消

4.4 路由通信注意要点 05.ARouter的结构

ARouter主要由三部分组成,包括对外提供的api调用模块、注解模块以及编译时通过注解生产相关的类模块。

arouter-annotation注解的声明和信息存储类的模块

arouter-compiler编译期解析注解信息并生成相应类以便进行注入的模块

arouter-api核心调用Api功能的模块

annotation模块

Route、Interceptor、Autowired都是在开发是需要的注解。

compiler模块

AutoWiredProcessor、InterceptorProcessor、RouteProcessor分别为annotation模块对应的Autowired、Interceptor、Route在项目编译时产生相关的类文件。

api模块

主要是ARouter具体实现和对外暴露使用的api。

06.ARouter的工作流程 6.1 初始化流程

初始化代码如下所示

/**

 */
public static void init(Application application) {
    //如果没有初始化,则
    if (!hasInit) {
        logger = _ARouter.logger;
        _ARouter.logger.info(Consts.TAG, "ARouter init start.");
        
        //做初始化工作
        hasInit = _ARouter.init(application);

        if (hasInit) {
            _ARouter.afterInit();
        }

        _ARouter.logger.info(Consts.TAG, "ARouter init over.");
    }
}
```

之后接着看_ARouter.init(application)这行代码,点击去查看

protected static synchronized boolean init(Application application) {
    //赋值上下文
    mContext = application;
    //初始化LogisticsCenter
    LogisticsCenter.init(mContext, executor);
    logger.info(Consts.TAG, "ARouter init success!");
    hasInit = true;
    mHandler = new Handler(Looper.getMainLooper());
    return true;
}

接下来看看LogisticsCenter里面做了什么

public class LogisticsCenter {
    /**

     */
    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        mContext = context;
        executor = tpe;
        try {
            long startInit = System.currentTimeMillis();
            Set routerMap;
            //debug或者版本更新的时候每次都重新加载router信息
            // 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 generate by arouter-compiler.
                //加载alibaba.android.arouter.routes包下载的类
                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 finish.
            } 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()));
            }

            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. 
                    //导入ARouter$$Root$$app.java,初始化Warehouse.groupsIndex集合
                    ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                    // Load interceptorMeta
                    //导入ARouter$$Interceptors$$app.java,初始化Warehouse.interceptorsIndex集合
                    ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                    // Load providerIndex
                    //导入ARouter$$Providers$$app.java,初始化Warehouse.providersIndex集合
                    ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                }
            }

            /*******部分代码省略********/
        } catch (Exception e) {
            throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
        }
    }
}
```

综上所述,整个初始化的流程大概就是:

初始化运行时的上下文环境

初始化日志logger

寻找router相关的类

解析并且缓存路由相关信息

初始化拦截服务

6.2 跳转页面流程

07.ARouter调用api 7.1 最简单调用

最简单的调用方式

ARouter.getInstance()
                .build("/user/UserFragment")
                .navigation();

7.2 build源码分析

这个主要是添加跳转的路径

public Postcard build(String path) {
    return _ARouter.getInstance().build(path);
}

然后把这个路径添加到默认的组中

/**

 */
protected Postcard build(String path) {
    if (TextUtils.isEmpty(path)) {
        throw new HandlerException(Consts.TAG + "Parameter is invalid!");
    } else {
        PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
        if (null != pService) {
            path = pService.forString(path);
        }
        return build(path, extractGroup(path));
    }
}
```

7.3 navigation分析

如下所示

final class _ARouter {
    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        try {
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
            /**************部分代码省略***************/
            if (null != callback) {
                callback.onLost(postcard);
            } else {    // No callback for this invoke, then we use the global degrade service.
                DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
                if (null != degradeService) {
                    degradeService.onLost(context, postcard);
                }
            }
            return null;
        }

        if (null != callback) {
            callback.onFound(postcard);
        }
        //是否为绿色通道,是否进过拦截器处理
        if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
            interceptorService.doInterceptions(postcard, new InterceptorCallback() {
                @Override
                public void onContinue(Postcard postcard) {
                    _navigation(context, postcard, requestCode, callback);
                }
                @Override
                public void onInterrupt(Throwable exception) {
                    //中断处理
                    if (null != callback) {
                        callback.onInterrupt(postcard);
                    }
                }
            });
        } else {
            return _navigation(context, postcard, requestCode, callback);
        }

        return null;
    }

    private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        //没有上下文环境,就用Application的上下文环境
        final Context currentContext = null == context ? mContext : context;
        switch (postcard.getType()) {
            case ACTIVITY:
                // Build intent 构建跳转的intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());
                // Set flags. 设置flag
                int flags = postcard.getFlags();
                if (-1 != flags) {
                    intent.setFlags(flags);
                } else if (!(currentContext instanceof Activity)) {    // Non activity, need less one flag.
                    //如果上下文不是Activity,则添加FLAG_ACTIVITY_NEW_TASK的flag
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }
                // Navigation in main looper.  切换到主线程中
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        if (requestCode > 0) {  // Need start for result
                            ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
                        } else {
                            ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
                        }

                        if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) {    // Old version.
                            ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
                        }

                        if (null != callback) { // Navigation over.
                            callback.onArrival(postcard);
                        }
                    }
                });

                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;
    }
}

08.Postcard信息携带

Postcard主要为信息的携带者,内容是在构造一次路由信息的时候生产的,其继承于RouteMeta。RouteMeta是在代码编译时生成的内容,主要在初始化WareHouse时对跳转信息做了缓存。

看看代码如下所示

//Postcard继承于RouteMeta
public final class Postcard extends RouteMeta


//然后看看编译生成的文件
/**

public class ARouter$$Group$$me implements IRouteGroup {
  @Override
  public void loadInto(Map atlas) {
    atlas.put("/me/ExperienceCouponActivity", RouteMeta.build(RouteType.ACTIVITY, ExperienceCouponActivity.class, "/me/experiencecouponactivity", "me", null, -1, -2147483648));
    atlas.put("/me/ServiceActivity", RouteMeta.build(RouteType.ACTIVITY, ServiceActivity.class, "/me/serviceactivity", "me", null, -1, -2147483648));
    atlas.put("/me/SettingActivity", RouteMeta.build(RouteType.ACTIVITY, SettingActivity.class, "/me/settingactivity", "me", null, -1, -2147483648));
    atlas.put("/me/UdeskServiceActivity", RouteMeta.build(RouteType.ACTIVITY, UdeskServiceActivity.class, "/me/udeskserviceactivity", "me", null, -1, -2147483648));
  }
}
```
10.DegradeService降级容错服务

首先,自定义一个类,需要继承DegradeService类,如下所示

/**
 * 
 *     @author 杨充
 *     blog  : https://github.com/yangchong211
 *     time  : 2018/08/24
 *     desc  : ARouter路由降级处理
 *     revise:

 */
@Route(path = DegradeServiceImpl.PATH)
public class DegradeServiceImpl implements DegradeService {

    static final String PATH = "/service/DegradeServiceImpl";

    @Override
    public void onLost(Context context, Postcard postcard) {
        if (context != null && postcard.getGroup().equals("activity")) {
            Intent intent = new Intent(context, WebViewActivity.class);
            intent.putExtra(Constant.URL, Constant.GITHUB);
            intent.putExtra(Constant.TITLE, "github地址");
            ActivityCompat.startActivity(context, intent, null);
        }
    }

    @Override
    public void init(Context context) {

    }
}
```

如何使用该降级方案,十分简单。

NavigationCallback callback = new NavCallback() {
    @Override
    public void onArrival(Postcard postcard) {
        LogUtils.i("ARouterUtils"+"---跳转完了");
    }

    @Override
    public void onFound(Postcard postcard) {
        super.onFound(postcard);
        LogUtils.i("ARouterUtils"+"---找到了");
    }

    @Override
    public void onInterrupt(Postcard postcard) {
        super.onInterrupt(postcard);
        LogUtils.i("ARouterUtils"+"---被拦截了");
    }

    @Override
    public void onLost(Postcard postcard) {
        super.onLost(postcard);
        LogUtils.i("ARouterUtils"+"---找不到了");
        DegradeServiceImpl degradeService = new DegradeServiceImpl();
        degradeService.onLost(Utils.getApp(),postcard);
    }
};

11.Interceptor拦截器

在ARouter模块的时候讲述Interceptor的使用,如果本次路由跳转不是走的绿色通道那么则会触发拦截器进行过滤。

final class _ARouter {
    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        /************部分代码省略************/

        if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
            interceptorService.doInterceptions(postcard, new InterceptorCallback() {
                /**

                 *
                 * @param postcard route meta
                 */
                @Override
                public void onContinue(Postcard postcard) {
                    _navigation(context, postcard, requestCode, callback);
                }

                /**
                 * Interrupt process, pipeline will be destory when this method called.
                 *
                 * @param exception Reson of interrupt.
                 */
                @Override
                public void onInterrupt(Throwable exception) {
                    if (null != callback) {
                        callback.onInterrupt(postcard);
                    }

                    logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
                }
            });
        } else {
            return _navigation(context, postcard, requestCode, callback);
        }
        return null;
    }
}
```

拦截器的初始化

在刚开始初始化的时候,就已经做了这个操作。

final class _ARouter {
    static void afterInit() {
        // Trigger interceptor init, use byName.
        interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
    }
}

InterceptorServiceImpl的init方法:

@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService {
    @Override
    public void init(final Context context) {
        LogisticsCenter.executor.execute(new Runnable() {
            @Override
            public void run() {
                if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
                    //循环遍历仓库中的拦截器
                    for (Map.Entry> entry : Warehouse.interceptorsIndex.entrySet()) {
                        Class interceptorClass = entry.getValue();
                        try {
                            //反射机制构造自定义的每一个拦截器实例
                            IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();
                            iInterceptor.init(context);
                            //并将其添加在缓存中
                            Warehouse.interceptors.add(iInterceptor);
                        } catch (Exception ex) {
                            throw new HandlerException(TAG + "ARouter init interceptor error! name = [" + interceptorClass.getName() + "], reason = [" + ex.getMessage() + "]");
                        }
                    }
                    interceptorHasInit = true;
                    logger.info(TAG, "ARouter interceptors init over.");
                    synchronized (interceptorInitLock) {
                        interceptorInitLock.notifyAll();
                    }
                }
            }
        });
    }
}

拦截器的工作过程

@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService {
    @Override
    public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
        if (null != Warehouse.interceptors && Warehouse.interceptors.size() > 0) {
            //检测是否初始化完所有的烂机器
            checkInterceptorsInitStatus();
            //没有完成正常的初始化,抛异常
            if (!interceptorHasInit) {
                callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));
                return;
            }
            //顺序遍历每一个拦截器,
            LogisticsCenter.executor.execute(new Runnable() {
                @Override
                public void run() {
                    CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
                    try {
                        _excute(0, interceptorCounter, postcard);
                        interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
                        //拦截器的遍历终止之后,如果有还有没有遍历的拦截器,则表示路由事件被拦截
                        if (interceptorCounter.getCount() > 0) {    // Cancel the navigation this time, if it hasn"t return anythings.
                            callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
                        } else if (null != postcard.getTag()) {    // Maybe some exception in the tag.
                            callback.onInterrupt(new HandlerException(postcard.getTag().toString()));
                        } else {
                            callback.onContinue(postcard);
                        }
                    } catch (Exception e) {
                        callback.onInterrupt(e);
                    }
                }
            });
        } else {
            callback.onContinue(postcard);
        }
    }

    //执行拦截器的过滤事件
    private static void _excute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
        if (index < Warehouse.interceptors.size()) {
            IInterceptor iInterceptor = Warehouse.interceptors.get(index);
            iInterceptor.process(postcard, new InterceptorCallback() {
                @Override
                public void onContinue(Postcard postcard) {
                    // Last interceptor excute over with no exception.
                    counter.countDown();
                    //如果当前没有拦截过滤,那么使用下一个拦截器
                    _excute(index + 1, counter, postcard);  // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know.
                }

                @Override
                public void onInterrupt(Throwable exception) {
                    // Last interceptor excute over with fatal exception.
                    postcard.setTag(null == exception ? new HandlerException("No message.") : exception.getMessage());    // save the exception message for backup.
                    counter.cancel();
                }
            });
        }
    }
}

12.数据传输和自动注入 13.多dex的支持

可查看multidex源码:

public class ClassUtils {  
  /**
     * Identifies if the current VM has a native support for multidex, meaning there is no need for

     *
     * @return true if the VM handles multidex
     */
    private static boolean isVMMultidexCapable() {
        boolean isMultidexCapable = false;
        String vmName = null;

        try {
            if (isYunOS()) {    // YunOS需要特殊判断
                vmName = ""YunOS"";
                isMultidexCapable = Integer.valueOf(System.getProperty("ro.build.version.sdk")) >= 21;
            } else {    // 非YunOS原生Android
                vmName = ""Android"";
                String versionString = System.getProperty("java.vm.version");
                if (versionString != null) {
                    Matcher matcher = Pattern.compile("(d+).(d+)(.d+)?").matcher(versionString);
                    if (matcher.matches()) {
                        try {
                            int major = Integer.parseInt(matcher.group(1));
                            int minor = Integer.parseInt(matcher.group(2));
                            isMultidexCapable = (major > VM_WITH_MULTIDEX_VERSION_MAJOR)
                                    || ((major == VM_WITH_MULTIDEX_VERSION_MAJOR)
                                    && (minor >= VM_WITH_MULTIDEX_VERSION_MINOR));
                        } catch (NumberFormatException ignore) {
                            // let isMultidexCapable be false
                        }
                    }
                }
            }
        } catch (Exception ignore) {

        }

        Log.i(Consts.TAG, "VM with name " + vmName + (isMultidexCapable ? " has multidex support" : " does not have multidex support"));
        return isMultidexCapable;
    }
}
```

14.InstantRun支持

什么是InstantRun支持?

Android Studio 2.0 中引入的 Instant Run 是 Run 和 Debug 命令的行为,可以大幅缩短应用更新的时间。尽管首次构建可能需要花费较长的时间,Instant Run 在向应用推送后续更新时则无需构建新的 APK,因此,这样可以更快地看到更改。

15.生成的编译代码

如下所示

关于其他内容介绍 01.关于博客汇总链接

1.技术博客汇总

2.开源项目汇总

3.生活博客汇总

4.喜马拉雅音频汇总

5.其他汇总

02.关于我的博客

我的个人站点:www.yczbj.org,www.ycbjie.cn

github:https://github.com/yangchong211

知乎:https://www.zhihu.com/people/...

简书:http://www.jianshu.com/u/b7b2...

csdn:http://my.csdn.net/m0_37700275

喜马拉雅听书:http://www.ximalaya.com/zhubo...

开源中国:https://my.oschina.net/zbj161...

泡在网上的日子:http://www.jcodecraeer.com/me...

邮箱:yangchong211@163.com

阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV

segmentfault头条:https://segmentfault.com/u/xi...

掘金:https://juejin.im/user/593943...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/73348.html

相关文章

  • 关于Apt注解实践与总结【包含20篇博客】

    摘要:使用实现功能运行期注解案例使用简单的注解,便可以设置布局,等效于使用实现路由综合型案例比较全面的介绍从零起步,一步一步封装简易的路由开源库。申明注解用的就是。返回值表示这个注解里可以存放什么类型值。 YCApt关于apt方案实践与总结 目录介绍 00.注解系列博客汇总 01.什么是apt 02.annotationProcessor和apt区别 03.项目目录结构 04.该案例作用 ...

    gnehc 评论0 收藏0
  • MVPArms官方快速组件化方案开源,来自5K star的信赖

    摘要:原文地址前言起源组件化方案分析业务组件的划分和代码隔离路由框架基础库的优势简介什么是组件化为什么要组件化分析现有的组件化方案如何选择组件化方案组件化方案描述架构图一览架构图详解宿主层业务层业务模块的拆分基础层核心基础业务公共服务基础组件其他 原文地址: https://www.jianshu.com/p/f67... 0 前言 0.1 起源 0.2 组件化方案分析 0.2....

    aikin 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<