目录介绍

  • 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/yangchong211/YCBlogs
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议或者问题,万事起于忽微,量变引起质变!

注解学习小案例

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

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/fed5d5b95bae

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 初始化流程

  • 初始化代码如下所示

    /**
    * Init, it must be call before used router.
    */
    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 {
    /**
    * LogisticsCenter init, load all metas in memory. Demand initialization
    */
    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    mContext = context;
    executor = tpe;
    try {
    long startInit = System.currentTimeMillis();
    Set<String> 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<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.
    //导入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);
    }
  • 然后把这个路径添加到默认的组中
    /**
    * Build postcard by path and default group
    */
    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 //然后看看编译生成的文件
    /**
    * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
    public class ARouter$$Group$$me implements IRouteGroup {
    @Override
    public void loadInto(Map<String, RouteMeta> 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类,如下所示

    /**
    * <pre>
    * @author 杨充
    * blog : https://github.com/yangchong211
    * time : 2018/08/24
    * desc : ARouter路由降级处理
    * revise:
    * </pre>
    */
    @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() {
    /**
    * Continue process
    *
    * @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<Integer, Class<? extends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) {
    Class<? extends IInterceptor> 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
    * additional installation by this library.
    *
    * @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.关于博客汇总链接

02.关于我的博客

ARouter路由解析的更多相关文章

  1. ARouter 路由 组件 跳转 MD

    目录 简介 支持的功能 典型应用 简单使用 进阶使用 更多功能 其他 Q&A Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs bai ...

  2. ASP.NET 路由解析

    这段时间在读园子里Artech大神的<ASP.NET MVC5框架揭秘>,慢慢地从底层了解了MVC模式的设计思路.下面是一些阅读的总结. 传统的Web Forms应用,URL指向的是具体的 ...

  3. AspNet Mvc 路由解析中添加.html 等后缀 出现404错误的解决办法

    使用Mvc 有时候我们希望,浏览地址以.html .htm 等后缀名进行结尾. 于是我们就在RouteConfig 中修改路由配置信息,修改后的代码如下 routes.IgnoreRoute(&quo ...

  4. Gin 路由解析树详解

    说明: 无意间看到gin 中有trees的属性,好奇想一探究竟,到底gin是怎样生成路由解析树的? 这是一个测试截图,图中大概可以了解到gin是怎样做路由解析的.配合源码的阅读,解析树大致如下: 通过 ...

  5. WebApi路由解析增加版本控制

    1.自定义路由解析类 public class VersionHttpControllerSelector : IHttpControllerSelector { private const stri ...

  6. WCF技术解剖2-TcpTracer路由解析代码

    TcpTrace路由解析,参考页面-http://www.cnblogs.com/artech/archive/2008/09/19/1294227.html. TcpTrace工具下载地址:http ...

  7. 2016/5/6 thinkphp ①框架 ② 框架项目部署 ③MVC模式 ④控制器访问及路由解析 ⑤开发和生产模式 ⑥控制器和对应方法创建 ⑦视图模板文件创建 ⑧url地址大小写设置 ⑨空操作空控制器 ⑩项目分组

    真实项目开发步骤: 多人同时开发项目,协作开发项目.分工合理.效率有提高(代码风格不一样.分工不好) 测试阶段 上线运行 对项目进行维护.修改.升级(单个人维护项目,十分困难,代码风格不一样) 项目稳 ...

  8. 使用阿里ARouter路由实现组件化(模块化)开发流程

    Android平台中对页面.服务提供路由功能的中间件,我的目标是 —— 简单且够用. 这是阿里对Arouter的定位,那么我们一起来梳理一下Arouter使用流程,和使用中我所遇到的一些问题! 先来看 ...

  9. MVC路由解析---IgnoreRoute

    MVC路由解析---IgnoreRoute   文章引导 MVC路由解析---IgnoreRoute MVC路由解析---MapRoute MVC路由解析---UrlRoutingModule Are ...

  10. MVC路由解析---MapRoute

    文章引导 MVC路由解析---IgnoreRoute MVC路由解析---MapRoute MVC路由解析---UrlRoutingModule Area的使用 引言 前面我们讲了IgnoreRout ...

随机推荐

  1. 让python程序一直在window后台进程运行

    一.让python程序后台运行 1.创建一个app.py文件,如 while 1: print(123)2.创建一个set_py.bat文件,里面写 python app.py3.创建一个start_ ...

  2. Pandas 人口密度案例分析

    from turtle import left import pandas as pd """ 需求: 1.导入文件,查看原始数据 2.将人口数据和各州简称数据进行合并 ...

  3. 吴X凡绯闻女友小怡同学被骂到清空社交平台?各大平台连敏感词库都没有的吗?

    敏感词都没有的平台 最近某加拿大籍贯的 rapper 被曝私生活不检点,且极有可能涉及诱X未成年少女,成为一个 raper. 当然至于是否属实,其实一个人是否是海王,微信.QQ 聊天记录里面记得清清楚 ...

  4. ORA-14550错误解决方法

    工作中修改临时表,报错: ---------------------------------- 以SYSDBA身份登录,执行以下语句: select a.sid, a.serial#,        ...

  5. Spring Boot图书管理系统项目实战-10.借还统计

    导航: pre:  9.归还图书 next:11.检索图书 只挑重点的讲,具体的请看项目源码. 1.项目源码 需要源码的朋友,请捐赠任意金额后留下邮箱发送:) 2.页面设计 2.1 bookStat. ...

  6. Puppeteer介绍

    Puppeteer是什么 Puppeteer是一个Node库,它提供了一个高级API来通过DevTools协议控制Chromium或Chrome. 可以使用Puppeteer来自动化完成浏览器的操作, ...

  7. OsgEarth开发笔记(二):Osg3.6.3+OsgEarth3.1+vs2019x64开发环境搭建(中)

    上一篇:<OsgEarth开发笔记(一):Osg3.6.3+OsgEarth3.1+vs2019x64开发环境搭建(上)>下一篇:敬请期待-   前言  上一篇编译了osg和osgCurl ...

  8. django时区相关说明

    # naive time 从字面意思上理解,这是个"幼稚的时间",所以可以理解为它是个本地时间,不带时区信息,不能直接用于存储,如下 import datetime datetim ...

  9. 用BootstrapBlazor组件制作新增Customer Order的页面

    1.在Shared目录下新建OrderCreateView.razor文件: 2.在OrderCreateView.razor里用最简单的表格准备好布局 3.准备好BootstrapBlazor的组件 ...

  10. 【Azure Function App】解决Function App For Container 遇见ServiceUnavailable的异常

    问题描述 在使用Terraform创建Function App 后,部署函数时候遇见 ServiceUnavailable (Bad Request -- Encountered an error ( ...