目录介绍

  • 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. HBase-hbase shell操作

    hbase shell操作 一.DDL操作 1.开启hbase shell hbase shell 2.查看hbase状态 Status 3.查看hbase版本 Version 4.创建命名空间 cr ...

  2. 那些年我一直在用的高效开发者工具-Typora

    今天跟大家介绍一款我平时一直在用的本地Markdown工具,对比了国内外几款相似工具,Typora简洁.干练.清爽.功能完备特性深深吸引了我.我平时一般用它记录一些学习文章撰写,工作会议内容记录.项目 ...

  3. P5943 [POI2002] 最大的园地 题解

    题目传送门 前置知识 单调栈 简化题意 在一个 \(n \times n\) 的正方形内找到最大的由 \(0\) 组成的子矩形的面积. 解法 令 \(f_{i,j}(1 \le i,j \le n)\ ...

  4. 【Unity3D】缩放、平移、旋转场景

    1 前言 ​ 场景缩放.平移.旋转有两种实现方案,一种是对场景中所有物体进行同步变换,另一种方案是对相机的位置和姿态进行变换. ​ 对于方案一,如果所有物体都在同一个根对象下(其子对象或孙子对象),那 ...

  5. 关于谷歌浏览器出现“错误代码:net::ERR_UNSAFE_PORT”的解决办法

    搭建项目时需要自己配置端口信息,但是有人搭建之后会出现如下情况 但是换用edge等浏览器没有问题,这是因为chorme浏览器有自己的默认非安全端口, 若访问这些端口就会出现这个错误,并且所有采用cho ...

  6. matrox的RAP4G4C12 CXP采集卡软件安装

    Hello-FPGA info@hello-fpga.cOM matrox的RAP4G4C12 CXP采集卡软件安装 目录 matrox的RAP4G4C12 CXP采集卡软件安装 4 1 前言 4 2 ...

  7. javascript浮点数相减、相乘出现一长串小数

    149.7 * 100 = 14969.999999999998 3.57 - 2.33 = 1.2399999999999998 这是JavaScript浮点运算采用IEEE 754标准导致的Bug ...

  8. 【Azure 媒体服务】记录一个简单的媒体视频上传到Media Service无法播放问题

    问题描述 从本地上传到Azure Media Service Portal的视频,并且新增定位符后,无法播放.但是上传的其他视频是可以的.疑惑中!! 问题自查 自查发现,是视频的文件名中有个特殊符号. ...

  9. 【Azure 应用服务】记一次 App Service 部分请求一直返回 401 "No Authority" 的情况

    问题描述 发现部署在App Service上的 WCF 应用对于所请求的接口出现部分返回 401 - No Authority 消息,10次中有一次这样的概率.比较疑惑的问题是,应用没有更新,所以怀疑 ...

  10. C++ //类模板成员函数类外实现

    1 #include <iostream> 2 #include <string> 3 #include<fstream> 4 using namespace st ...