日期: 2021 年 9 月 29 日

ARouter原理剖析及手动实现

ARouter原理剖析及手动实现

 

前言

路由跳转在项目中用了一段时间了,*近对Android中的ARouter路由原理也是研究了一番,于是就给大家分享一下自己的心得体会,并教大家如何实现一款简易的路由框架。

本篇文章分为两个部分,*部分着重剖析ARouter路由的原理,第二部分会带着大家仿照ARouter撸一个自己的路由框架,我们自己撸的路由框架可能没有Arouter众多的功能如过滤器、provider等,但是却实现了ARouter*核心的功能:路由跳转,同时你也能学会如何去设计一个框架等等。

*部分:ARouter原理剖析

说到路由便不得不提一下Android中的组件化开发思想,组件化是*近比较流行的架构设计方案,它能对代码进行高度的解耦、模块分离等,能*大地提高开发效率(如有同学对组件化有不理解,可以参考网上众多的博客等介绍,然后再阅读demo源码中的组件化配置进行熟悉)。路由和组件化本身没有什么联系,因为路由的责任是负责页面跳转,但是组件化中两个单向依赖的module之间需要互相启动对方的Activity,因为没有相互引用,startActivity()是实现不了的,必须需要一个协定的通信方式,此时类似ARouter和ActivityRouter等的路由框架就派上用场了。

  • *节:ARouter路由跳转的原理

<img src=”http://pcayc3ynm.bkt.clouddn.com/module_1.png” />

如上图,在组件化中,为了业务逻辑的彻底解耦,同时也为了每个module都可以方便的单独运行和调试,上层的各个module不会进行相互依赖(只有在正式联调的时候才会让app壳module去依赖上层的其他组件module),而是共同依赖于base module,base module中会依赖一些公共的第三方库和其他配置。那么在上层的各个module中,如何进行通信呢?

我们知道,传统的Activity之间通信,通过startActivity(intent),而在组件化的项目中,上层的module没有依赖关系(即便两个module有依赖关系,也只能是单向的依赖),那么假如login module中的一个Activity需要启动pay_module中的一个Activity便不能通过startActivity来进行跳转。那么大家想一下还有什么其他办法呢? 可能有同学会想到隐式跳转,这当然也是一种解决方法,但是一个项目中不可能所有的跳转都是隐式的,这样Manifest文件会有很多过滤配置,而且非常不利于后期维护。当然你用反射拿到Activity的class文件也可以实现跳转,但是*:大量的使用反射跳转对性能会有影响,第二:你需要拿到Activity的类文件,在组件开发的时候,想拿到其他module的类文件是很麻烦的,因为组件开发的时候组件module之间是没有相互引用的,你只能通过找到类的路径去反射拿到这个class,那么有没有一种更好的解决办法呢?办法当然是有的。下面看图:

<img src=”http://pcayc3ynm.bkt.clouddn.com/module_2.png” />

在组件化中,我们通常都会在base_module上层再依赖一个router_module,而这个router_module就是负责各个模块之间页面跳转的。

用过ARouter路由框架的同学应该都知道,在每个需要对其他module提供调用的Activity中,都会声明类似下面@Route注解,我们称之为路由地址

@Route(path = "/main/main")
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}


@Route(path = "/module1/module1main")
public class Module1MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_module1_main);
    }
}

那么这个注解有什么用呢,路由框架会在项目的编译期通过注解处理器扫描所有添加@Route注解的Activity类,然后将Route注解中的path地址和Activity.class文件映射关系保存到它自己生成的java文件中。为了让大家理解,我这里来使用近乎伪代码给大家简单演示一下。

public class MyRouters{

    //项目编译后通过apt生成如下方法
    public static HashMap<String, ClassBean> getRouteInfo(HashMap<String, ClassBean> routes) {
        route.put("/main/main", MainActivity.class);
        route.put("/module1/module1main", Module1MainActivity.class);
        route.put("/login/login", LoginActivity.class);
    }
}

这样我们想在app模块的MainActivity跳转到login模块的LoginActivity,那么便只需调用如下:

//不同模块之间启动Activity
public void login(String name, String password) {
    HashMap<String, ClassBean> route = MyRouters.getRouteInfo(new HashMap<String, ClassBean>);
    LoginActivity.class classBean = route.get("/login/login");
    Intent intent = new Intent(this, classBean);
    intent.putExtra("name", name);
    intent.putExtra("password", password);
    startActivity(intent);
}

这样是不是很简单就实现了路由的跳转,既没有隐式意图的繁琐,也没有反射对性能的损耗。用过ARouter的同学应该知道,用ARouter启动Activity应该是下面这个写法

// 2. Jump with parameters
ARouter.getInstance().build("/test/login")
            .withString("password", 666666)
            .withString("name", "小三")
            .navigation();

那么ARouter背后是怎么样实现跳转的呢?实际上它的核心思想跟上面讲解是一样的,我们在代码里加入的@Route注解,会在编译时期通过apt生成一些存储path和activity.class映射关系的类文件,然后app进程启动的时候会加载这些类文件,把保存这些映射关系的数据读到内存里(保存在map里),然后在进行路由跳转的时候,通过build()方法传入要到达页面的路由地址,ARouter会通过它自己存储的路由表找到路由地址对应的Activity.class(activity.class = map.get(path)),然后new Intent(context, activity.Class),当调用ARouter的withString()方法它的内部会调用intent.putExtra(String name, String value),调用navigation()方法,它的内部会调用startActivity(intent)进行跳转,这样便可以实现两个相互没有依赖的module顺利的启动对方的Activity了。

  • 第二节:ARouter映射关系如何生成

通过上节我们知道在Activity类上加上@Route注解之后,便可通过apt生成对应的路由表。那么现在我们来搞清楚,既然路由和Activity的映射关系我们可以很容易地得到(因为代码都是我们写的,当然很容易得到),那么为什么我们要繁琐的通过apt来生成类文件而不是自己直接写一个契约类来保存映射关系呢。如果站在一个框架开发者的角度去理解,就不难明白了,因为框架是给上层业务开发者调用的,如果业务开发者在开发页面的过程中还要时不时的更新或更改契约类文件,不免过于麻烦?如果有自动根据路由地址生成映射表文件的技术该多好啊!

技术当然是有的,那就是被众多框架使用的apt及javapoet技术,那么什么是apt,什么是javapoet呢?我们先来看下图:

<img src=”http://pcayc3ynm.bkt.clouddn.com/apt_javapoet.png” />

APT是Annotation Processing Tool的简称,即注解处理工具。由图可知,apt是在编译期对代码中指定的注解进行解析,然后做一些其他处理(如通过javapoet生成新的Java文件)。我们常用的ButterKnife,其原理就是通过注解处理器在编译期扫描代码中加入的@BindView、@OnClick等注解进行扫描处理,然后生成XXX_ViewBinding类,实现了view的绑定。javapoet是鼎鼎大名的squareup出品的一个开源库,是用来生成java文件的一个library,它提供了简便的api供你去生成一个java文件。可以如下引入javapoet

implementation 'com.squareup:javapoet:1.7.0'

下面我通过demo中的例子带你了解如何通过apt和javapoet技术生成路由映射关系的类文件:

首先*步,定义注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Route {
    /**
     * 路由的路径
     * @return
     */
    String path();

    /**
     * 将路由节点进行分组,可以实现动态加载
     * @return
     */
    String group() default "";

}

这里看到Route注解里有path和group,这便是仿照ARouter对路由进行分组。因为当项目变得越来越庞大的时候,为了便于管理和减小首次加载路由表过于耗时的问题,我们对所有的路由进行分组。在ARouter中会要求路由地址至少需要两级,如”/xx/xx”,一个模块下可以有多个分组,这里我们就将路由地址定为必须大于等于两级,其中*级是group。

第二步,在Activity上使用注解

@Route(path = "/main/main")
public class MainActivity extends AppCompatActivity {

}

@Route(path = "/main/main2")
public class Main2Activity extends AppCompatActivity {

}

@Route(path = "/show/info")
public class ShowActivity extends AppCompatActivity {

}

第三步,编写注解处理器,在编译器找到加入注解的类文件,进行处理,这里我只展示关键代码,具体的细节还需要你去demo中仔细研读:

@AutoService(Processor.class)
/**
  处理器接收的参数 替代 {@link AbstractProcessor#getSupportedOptions()} 函数
 */
@SupportedOptions(Constant.ARGUMENTS_NAME)
/**
 * 注册给哪些注解的  替代 {@link AbstractProcessor#getSupportedAnnotationTypes()} 函数
 */
@SupportedAnnotationTypes(Constant.ANNOTATION_TYPE_ROUTE)

public class RouterProcessor extends AbstractProcessor {
    /**
     * key:组名 value:类名
     */
    private Map<String, String> rootMap = new TreeMap<>();
    /**
     * 分组 key:组名 value:对应组的路由信息
     */
    private Map<String, List<RouteMeta>> groupMap = new HashMap<>();

    /**
     *
     * @param set 使用了支持处理注解的节点集合
     * @param roundEnvironment 表示当前或是之前的运行环境,可以通过该对象查找找到的注解。
     * @return true 表示后续处理器不会再处理(已经处理)
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (!Utils.isEmpty(set)) {
            //被Route注解的节点集合
            Set<? extends Element> rootElements = roundEnvironment.getElementsAnnotatedWith(Route.class);
            if (!Utils.isEmpty(rootElements)) {
                processorRoute(rootElements);
            }
            return true;
        }
        return false;
    }


    //...

}

如代码中所示,要想在编译期对注解做处理,就需要RouterProcessor继承自AbstractProcessor并通过@AutoService注解进行注册,然后实现process()方法。还没有完,你还需要通过@SupportedAnnotationTypes(Constant.ANNOTATION_TYPE_ROUTE)指定要处理哪个注解,Constant.ANNOTATION_TYPE_ROUTE便是我们的Route注解的路径。看process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)方法,set集合就是编译期扫描代码得到的加入了Route注解的文件集合,然后我们就可以在process方法生成java文件了。

这里的@AutoService是为了注册注解处理器,需要我们引入一个google开源的自动注册工具AutoService,如下依赖(当然也可以手动进行注册,不过略微麻烦,这里不太推荐):

implementation 'com.google.auto.service:auto-service:1.0-rc2'

第四步:通过javapoet生成java类:
在第三步中process()方法里有一句代码:processorRoute(rootElements),这个就是生成java文件的方法了,下面我贴出代码:

private void processorRoute(Set<? extends Element> rootElements) {

    //...

    //生成Group记录分组表
    generatedGroup(iRouteGroup);

    //生成Root类 作用:记录<分组,对应的Group类>
    generatedRoot(iRouteRoot, iRouteGroup);
}

processorRoute()方法内容很多,这里我只贴出生成java文件相关,其他代码我会在第二部分手动实现路由框架中详细介绍。如上,generatedGroup(iRouteGroup)和generatedRoot(iRouteRoot, iRouteGroup)就是生成java文件的核心了。这里我只贴出generatedRoot()方法,因为生成类文件的原理都是一样的,至于生成什么功能的类,只要你会一个,举一反三,这便没有什么难度。

/**
 * 生成Root类  作用:记录<分组,对应的Group类>
 * @param iRouteRoot
 * @param iRouteGroup
 */
private void generatedRoot(TypeElement iRouteRoot, TypeElement iRouteGroup) {
    //创建参数类型 Map<String,Class<? extends IRouteGroup>> routes>
    //Wildcard 通配符
    ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(
            ClassName.get(Map.class),
            ClassName.get(String.class),
            ParameterizedTypeName.get(
                    ClassName.get(Class.class),
                    WildcardTypeName.subtypeOf(ClassName.get(iRouteGroup))
            ));
    //生成参数 Map<String,Class<? extends IRouteGroup>> routes> routes
    ParameterSpec parameter = ParameterSpec.builder(parameterizedTypeName, "routes").build();

    //生成函数 public void loadInfo(Map<String,Class<? extends IRouteGroup>> routes> routes)
    MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constant.METHOD_LOAD_INTO)
            .addModifiers(Modifier.PUBLIC)
            .addAnnotation(Override.class)
            .addParameter(parameter);
    //生成函数体
    for (Map.Entry<String, String> entry : rootMap.entrySet()) {
        methodBuilder.addStatement("routes.put($S, $T.class)", entry.getKey(), ClassName.get(Constant.PACKAGE_OF_GENERATE_FILE, entry.getValue()));
    }
    //生成$Root$类
    String className = Constant.NAME_OF_ROOT + moduleName;
    TypeSpec typeSpec = TypeSpec.classBuilder(className)
            .addSuperinterface(ClassName.get(iRouteRoot))
            .addModifiers(Modifier.PUBLIC)
            .addMethod(methodBuilder.build())
            .build();
    try {
      //生成java文件,PACKAGE_OF_GENERATE_FILE就是生成文件需要的路径
        JavaFile.builder(Constant.PACKAGE_OF_GENERATE_FILE, typeSpec).build().writeTo(filerUtils);
        log.i("Generated RouteRoot:" + Constant.PACKAGE_OF_GENERATE_FILE + "." + className);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

如上,我把每一块代码的作用注释了出来,相信大家很容易就能理解每一个代码段的作用。可见,其实生成文件只是调用一些api而已,只要我们熟知api的调用,生成java文件便没有什么难度。

第二部分:动手实现一个路由框架

通过*部分的讲述,我相信大家对于ARouter的原理已经有了整体轮廓的理解,这一部分,我便会通过代码带你去实现一个自己的路由框架。要实现这个路由框架,我们先来实现生成路由映射文件这一块,因为这一块是路由框架能够运行起来的核心。

  • *节:生成路由映射文件

通过*部分的讲述我们知道在Activity类上加上@Route注解之后,便可通过apt来生成对应的路由表,那么现在我们就来生成这些路由映射文件。首先,我们要理解一个问题,就是我们的路由映射文件是在编译期间生成的,那么在程序的运行期间我们要统一调用这些路由信息,便需要一个统一的调用方式。我们先来定义这个调用方式:

public interface IRouteGroup {
    void loadInto(Map<String, RouteMeta> atlas);
}

public interface IRouteRoot {
    void loadInto(Map<String, Class<? extends IRouteGroup>> routes);
}

我们定义两个接口来对生成的java文件进行约束,IRouteGroup是生成的分组关系契约,IRouteRoot是单个分组路由信息契约,只要我们生成的java文件继承自这个接口并实现loadInto()方法,在运行期间我们就可以统一的调用生成的java文件,获取路由映射信息。

现在我们来把RouterProcessor生成路由映射文件相关的代码补全:

@AutoService(Processor.class)
/**
  处理器接收的参数 替代 {@link AbstractProcessor#getSupportedOptions()} 函数
 */
@SupportedOptions(Constant.ARGUMENTS_NAME)
/**
 * 指定使用的Java版本 替代 {@link AbstractProcessor#getSupportedSourceVersion()} 函数
 */
@SupportedSourceVersion(SourceVersion.RELEASE_7)
/**
 * 注册给哪些注解的  替代 {@link AbstractProcessor#getSupportedAnnotationTypes()} 函数
 */
@SupportedAnnotationTypes(Constant.ANNOTATION_TYPE_ROUTE)

public class RouterProcessor extends AbstractProcessor {
    /**
     * key:组名 value:类名
     */
    private Map<String, String> rootMap = new TreeMap<>();
    /**
     * 分组 key:组名 value:对应组的路由信息
     */
    private Map<String, List<RouteMeta>> groupMap = new HashMap<>();

    /**
     * 节点工具类 (类、函数、属性都是节点)
     */
    private Elements elementUtils;

    /**
     * type(类信息)工具类
     */
    private Types typeUtils;

    /**
     * 文件生成器 类/资源
     */
    private Filer filerUtils;

    private String moduleName;

    private Log log;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        //获得apt的日志输出
        log = Log.newLog(processingEnvironment.getMessager());
        elementUtils = processingEnvironment.getElementUtils();
        typeUtils = processingEnvironment.getTypeUtils();
        filerUtils = processingEnvironment.getFiler();

        //参数是模块名 为了防止多模块/组件化开发的时候 生成相同的 xx$$ROOT$$文件
        Map<String, String> options = processingEnvironment.getOptions();
        if (!Utils.isEmpty(options)) {
            moduleName = options.get(Constant.ARGUMENTS_NAME);
        }
        if (Utils.isEmpty(moduleName)) {
            throw new RuntimeException("Not set processor moudleName option !");
        }
        log.i("init RouterProcessor " + moduleName + " success !");
    }

    /**
     *
     * @param set 使用了支持处理注解的节点集合
     * @param roundEnvironment 表示当前或是之前的运行环境,可以通过该对象查找找到的注解。
     * @return true 表示后续处理器不会再处理(已经处理)
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (!Utils.isEmpty(set)) {
            //被Route注解的节点集合
            Set<? extends Element> rootElements = roundEnvironment.getElementsAnnotatedWith(Route.class);
            if (!Utils.isEmpty(rootElements)) {
                processorRoute(rootElements);
            }
            return true;
        }
        return false;
    }


    //...

}

我们通过@SupportedOptions(Constant.ARGUMENTS_NAME)拿到每个module的名字,用来生成对应module下存放路由信息的类文件名。这里变量Constant.ARGUMENTS_NAME的值就是moduleName,在这之前,我们需要在每个组件module的gradle下配置如下

javaCompileOptions {
            annotationProcessorOptions {
                arguments = [moduleName: project.getName()]
            }
        }

@SupportedAnnotationTypes(Constant.ANNOTATION_TYPE_ROUTE)指定了需要处理的注解的路径地址,在此就是Route.class的路径地址。

RouterProcessor中我们实现了init方法,拿到log apt日志输出工具用以输出apt日志信息,并通过以下代码得到上面提到的每个module配置的moduleName

//参数是模块名 为了防止多模块/组件化开发的时候 生成相同的 xx$$ROOT$$文件
Map<String, String> options = processingEnvironment.getOptions();
if (!Utils.isEmpty(options)) {
    moduleName = options.get(Constant.ARGUMENTS_NAME);
}
if (Utils.isEmpty(moduleName)) {
    throw new RuntimeException("Not set processor moudleName option !");
}

然后在process()方法里开始生成文件名以EaseRouter_Route_moduleName和EaseRouter_Group_moduleName命名的文件。(这里的moduleName指具体的module名,demo中apt相关的代码实现都在easy-compiler module中),生成EaseRouter_Route_moduleName相关文件存储的就是分组关系,生成EaseRouter_Group_moduleName相关文件里存储的就是分组下的路由映射关系。

好了,我们终于可以生成文件了,在process()方法里有如下代码,

if (!Utils.isEmpty(set)) {
    //被Route注解的节点集合
    Set<? extends Element> rootElements = roundEnvironment.getElementsAnnotatedWith(Route.class);
    if (!Utils.isEmpty(rootElements)) {
        processorRoute(rootElements);
    }
    return true;
}
return false;

set就是扫描得到的支持处理注解的节点集合,然后得到rootElements,即被@Route注解的节点集合,此时就可以调用
processorRoute(rootElements)方法去生成文件了。processorRoute(rootElements)方法实现如下:

private void processorRoute(Set<? extends Element> rootElements) {
    //获得Activity这个类的节点信息
    TypeElement activity = elementUtils.getTypeElement(Constant.ACTIVITY);
    TypeElement service = elementUtils.getTypeElement(Constant.ISERVICE);
    for (Element element : rootElements) {
        RouteMeta routeMeta;
        //类信息
        TypeMirror typeMirror = element.asType();
        log.i("Route class:" + typeMirror.toString());
        Route route = element.getAnnotation(Route.class);
        if (typeUtils.isSubtype(typeMirror, activity.asType())) {
            routeMeta = new RouteMeta(RouteMeta.Type.ACTIVITY, route, element);
        } else if (typeUtils.isSubtype(typeMirror, service.asType())) {
            routeMeta = new RouteMeta(RouteMeta.Type.ISERVICE, route, element);
        } else {
            throw new RuntimeException("Just support Activity or IService Route: " + element);
        }
        categories(routeMeta);
    }
    TypeElement iRouteGroup = elementUtils.getTypeElement(Constant.IROUTE_GROUP);
    TypeElement iRouteRoot = elementUtils.getTypeElement(Constant.IROUTE_ROOT);

    //生成Group记录分组表
    generatedGroup(iRouteGroup);

    //生成Root类 作用:记录<分组,对应的Group类>
    generatedRoot(iRouteRoot, iRouteGroup);
}

上面提到的生成的EaseRouter_Route_moduleName文件和EaseRouter_Group_moduleName文件分别实现了IRouteRoot和IRouteGroup接口,就是通过下面这两行代码拿到IRootGroup和IRootRoot的字节码信息,然后传入generatedGroup(iRouteGroup)和generatedRoot(iRouteRoot, iRouteGroup)方法,这两个方法内部会通过javapoet api生成java文件,并实现这两个接口。

TypeElement iRouteGroup = elementUtils.getTypeElement(Constant.IROUTE_GROUP);
TypeElement iRouteRoot = elementUtils.getTypeElement(Constant.IROUTE_ROOT);

generatedGroup(iRouteGroup)和generatedRoot(iRouteRoot, iRouteGroup)就是生成上面提到的EaseRouter_Root_app和EaseRouter_Group_main等文件的具体实现,生成的方法我在*部分已经贴出来过了,这里不再阐述。

好了,现在我们编译下项目就会在每个组件module的build/generated/source/apt目录下生成相关映射文件。这里我把app module编译后生成的文件贴出来,app module编译后会生成EaseRouter_Root_app文件和EaseRouter_Group_main、EEaseRouter_Group_show等文件,EaseRouter_Root_app文件对应于app module的分组,里面记录着本module下所有的分组信息,EaseRouter_Group_main、EaseRouter_Group_show文件分别记载着当前分组下的所有路由地址和ActivityClass映射信息。如下所示:

public class EaseRouter_Root_app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("main", EaseRouter_Group_main.class);
    routes.put("show", EaseRouter_Group_show.class);
  }
}


public class EaseRouter_Group_main implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/main/main",RouteMeta.build(RouteMeta.Type.ACTIVITY,Main2\Activity.class,"/main/main","main"));
    atlas.put("/main/main2",RouteMeta.build(RouteMeta.Type.ACTIVITY,Main2\Activity.class,"/main/main2","main"));
  }
}

public class EaseRouter_Group_show implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/show/info",RouteMeta.build(RouteMeta.Type.ACTIVITY,ShowActivity.class,"/show/info","show"));
  }
}

大家会看到生成的类分别实现了IRouteRoot和IRouteGroup接口,并且实现了loadInto()方法,而loadInto方法通过传入一个特定类型的map就能把分组信息放入map里,只要分组信息存入到特定的map里后,我们就可以随意的从map里取路由地址对应的Activity.class做跳转使用。那么如果我们在login_module中想启动app_module中的MainActivity类,首先,我们已知MainActivity类的路由地址是”/main/main”,*个”/main”代表分组名,那么我们岂不是可以像下面这样调用去得到MainActivity类文件,然后startActivity()跳转到MainActivity。(这里的RouteMeta只是存有Activity class文件的封装类,先不用理会)。

public void test() {
    EaseRouter_Root_app rootApp = new EaseRouter_Root_app();
    HashMap<String, Class<? extends IRouteGroup>> rootMap = new HashMap<>();
    rootApp.loadInto(rootMap);

    //得到/main分组
    Class<? extends IRouteGroup> aClass = rootMap.get("main");
    try {
        HashMap<String, RouteMeta> groupMap = new HashMap<>();
        aClass.newInstance().loadInto(groupMap);
        //得到MainActivity
        RouteMeta main = groupMap.get("/main/main");
        Class<?> mainActivityClass = main.getDestination();

        Intent intent = new Intent(this, mainActivityClass);
        startActivity(intent);
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }

}

可以看到,只要有了这些附带路由映射信息的类文件,并将其保存的映射关系存入map里,我们便能轻易的启动其他module的Activity了。

  • 第二节 路由框架的初始化

上节我们已经通过apt生成了映射文件,并且知道了如何通过映射文件去调用Activity,然而我们要实现一个路由框架,就要考虑在合适的时机拿到这些映射文件中的信息,以供上层业务做跳转使用。那么在什么时机去拿到这些映射文件中的信息呢?首先我们需要在上层业务做路由跳转之前把这些路由映射关系拿到手,但我们不能事先预知上层业务会在什么时候做跳转,那么拿到这些路由关系*好的时机就是应用程序初始化的时候。

知道了在什么时机去拿到映射关系,接下来就要考虑如何拿了。我们在上面已经介绍过实现IRouteRoot接口的所有类文件里保存着各个module的分组文件(分组文件就是实现了IRouteGroup接口的类文件),那么只要拿到所有实现IRouteGroup接口的类的集合,便能得到左右的路由信息了。下面看初始化的代码:

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        EasyRouter.init(this);
    }
}

//我们手动实现的路由框架,我们就叫它EasyRouter
public class EasyRouter {

  private static final String TAG = "EasyRouter";
   private static final String ROUTE_ROOT_PAKCAGE = "com.xsm.easyrouter.routes";
   private static final String SDK_NAME = "EaseRouter";
   private static final String SEPARATOR = "_";
   private static final String SUFFIX_ROOT = "Root";

   private static EasyRouter sInstance;
   private static Application mContext;
   private Handler mHandler;

   private EasyRouter() {
       mHandler = new Handler(Looper.getMainLooper());
   }

   public static EasyRouter getsInstance() {
       synchronized (EasyRouter.class) {
           if (sInstance == null) {
               sInstance = new EasyRouter();
           }
       }
       return sInstance;
   }

   public static void init(Application application) {
       mContext = application;
       try {
           loadInfo();
       } catch (Exception e) {
           e.printStackTrace();
           Log.e(TAG, "初始化失败!", e);
       }
   }

   //...
}

可以看到,进程启动的时候我们调用EasyRouter.init()方法,init()方法中调用了loadInfo()方法,而这个loadInfo()便是我们初始化的核心。我把loadInfo的代码贴出来:

private static void loadInfo() throws PackageManager.NameNotFoundException, InterruptedException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    //获得所有 apt生成的路由类的全类名 (路由表)
    Set<String> routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
    for (String className : routerMap) {
        if (className.startsWith(ROUTE_ROOT_PAKCAGE + "." + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
            //root中注册的是分组信息 将分组信息加入仓库中
            ((IRouteRoot) Class.forName(className).getConstructor().newInstance()).loadInto(Warehouse.groupsIndex);
        }
    }
    for (Map.Entry<String, Class<? extends IRouteGroup>> stringClassEntry : Warehouse.groupsIndex.entrySet()) {
        Log.d(TAG, "Root映射表[ " + stringClassEntry.getKey() + " : " + stringClassEntry.getValue() + "]");
    }

}

我们首先通过ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE)得到apt生成的所有实现IRouteRoot接口的类文件集合,通过上面的讲解我们知道,拿到这些类文件便可以得到所有的路由地址和Activity映射关系。

这个ClassUtils.getFileNameByPackageName()方法就是具体的实现了,下面我们看具体的代码:

   /**
     * 得到路由表的类名
     * @param context
     * @param packageName
     * @return
     * @throws PackageManager.NameNotFoundException
     * @throws InterruptedException
     */
    public static Set<String> getFileNameByPackageName(Application context, final String packageName)
            throws PackageManager.NameNotFoundException, InterruptedException {
        final Set<String> classNames = new HashSet<>();
        List<String> paths = getSourcePaths(context);
        //使用同步计数器判断均处理完成
        final CountDownLatch countDownLatch = new CountDownLatch(paths.size());
        ThreadPoolExecutor threadPoolExecutor = DefaultPoolExecutor.newDefaultPoolExecutor(paths.size());
        for (final String path : paths) {
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    DexFile dexFile = null;
                    try {
                        //加载 apk中的dex 并遍历 获得所有包名为 {packageName} 的类
                        dexFile = new DexFile(path);
                        Enumeration<String> dexEntries = dexFile.entries();
                        while (dexEntries.hasMoreElements()) {
                            String className = dexEntries.nextElement();
                            if (!TextUtils.isEmpty(className) && className.startsWith(packageName)) {
                                classNames.add(className);
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        if (null != dexFile) {
                            try {
                                dexFile.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                        //释放一个
                        countDownLatch.countDown();
                    }
                }
            });
        }
        //等待执行完成
        countDownLatch.await();
        return classNames;
    }

这个方法会通过开启子线程,去扫描apk中所有的dex,遍历找到所有包名为packageName的类名,然后将类名再保存到classNames集合里。

List<String> paths = getSourcePaths(context)这句代码会获得所有的apk文件(instant run会产生很多split apk),这个方法的具体实现大家看demo即可,不再阐述。这里用到了CountDownLatch类,会分path一个文件一个文件的检索,等到所有的类文件都找到后便会返回这个Set<String>集合。所以我们可以知道,初始化时找到这些类文件会有一定的耗时,如果你已经看过ARouter的源码便会知道ARouter这里会有一些优化,只会遍历找一次类文件,找到之后就会保存起来,下次app进程启动会检索是否有保存这些文件,如果有就会直接调用保存后的数据去初始化。

  • 第三节 路由跳转实现

经过上节的介绍,我们已经能够在进程初始化的时候拿到所有的路由信息,那么实现跳转便好做了。直接看代码:

@Route(path = "/main/main")
public class MainActivity extends AppCompatActivity {

  public void startModule1MainActivity(View view) {
    EasyRouter.getsInstance().build("/module1/module1main").navigation();
  }

}

在build的时候,传入要跳转的路由地址,build()方法会返回一个Postcard对象,我们称之为跳卡。然后调用Postcard的navigation()方法完成跳转。用过ARouter的对这个跳卡都应该很熟悉吧!Postcard里面保存着跳转的信息。下面我把Postcard类的代码实现粘下来:

public class Postcard extends RouteMeta {
    private Bundle mBundle;
    private int flags = -1;
    //新版风格
    private Bundle optionsCompat;
    //老版
    private int enterAnim;
    private int exitAnim;

    public Postcard(String path, String group) {
        this(path, group, null);
    }

    public Postcard(String path, String group, Bundle bundle) {
        setPath(path);
        setGroup(group);
        this.mBundle = (null == bundle ? new Bundle() : bundle);
    }

    public Bundle getExtras() {return mBundle;}

    public int getEnterAnim() {return enterAnim;}

    public int getExitAnim() {return exitAnim;}

    /**
     * 跳转动画
     * @param enterAnim
     * @param exitAnim
     * @return
     */
    public Postcard withTransition(int enterAnim, int exitAnim) {
        this.enterAnim = enterAnim;
        this.exitAnim = exitAnim;
        return this;
    }

    /**
     * 转场动画
     * @param compat
     * @return
     */
    public Postcard withOptionsCompat(ActivityOptionsCompat compat) {
        if (null != compat) {
            this.optionsCompat = compat.toBundle();
        }
        return this;
    }

    public Postcard withString(@Nullable String key, @Nullable String value) {
        mBundle.putString(key, value);
        return this;
    }


    public Postcard withBoolean(@Nullable String key, boolean value) {
        mBundle.putBoolean(key, value);
        return this;
    }

    public Postcard withInt(@Nullable String key, int value) {
        mBundle.putInt(key, value);
        return this;
    }

    //还有许多给intent中bundle设置值得方法我就不一一列出来了,可以看demo里所有的细节

    public Bundle getOptionsBundle() {
        return optionsCompat;
    }

    public Object navigation() {
        return EasyRouter.getsInstance().navigation(null, this, -1, null);
    }

    public Object navigation(Context context) {
        return EasyRouter.getsInstance().navigation(context, this, -1, null);
    }


    public Object navigation(Context context, NavigationCallback callback) {
        return EasyRouter.getsInstance().navigation(context, this, -1, callback);
    }

    public Object navigation(Context context, int requestCode) {
        return EasyRouter.getsInstance().navigation(context, this, requestCode, null);
    }

    public Object navigation(Context context, int requestCode, NavigationCallback callback) {
        return EasyRouter.getsInstance().navigation(context, this, requestCode, callback);
    }


}

如果你是一个Android开发,Postcard类里面的东西就不用我再给你介绍了吧!(哈哈)我相信你一看就明白了。我们只介绍一个方法navigation(),他有好几个重载方法,方法里面会调用EasyRouter类的navigation()方法。EaseRouter的navigation()方法,就是跳转的核心了。下面请看:

protected Object navigation(Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    try {
        prepareCard(postcard);
    }catch (NoRouteFoundException e) {
        e.printStackTrace();
        //没找到
        if (null != callback) {
            callback.onLost(postcard);
        }
        return null;
    }
    if (null != callback) {
        callback.onFound(postcard);
    }

    switch (postcard.getType()) {
        case ACTIVITY:
            final Context currentContext = null == context ? mContext : context;
            final Intent intent = new Intent(currentContext, postcard.getDestination());
            intent.putExtras(postcard.getExtras());
            int flags = postcard.getFlags();
            if (-1 != flags) {
                intent.setFlags(flags);
            } else if (!(currentContext instanceof Activity)) {
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            }
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    //可能需要返回码
                    if (requestCode > 0) {
                        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) {
                        //老版本
                        ((Activity) currentContext).overridePendingTransition(postcard
                                        .getEnterAnim()
                                , postcard.getExitAnim());
                    }
                    //跳转完成
                    if (null != callback) {
                        callback.onArrival(postcard);
                    }
                }
            });
            break;
        default:
            break;
    }
    return null;
}

这个方法里先去调用了prepareCard(postcard)方法,prepareCard(postcard)代码我贴出来,

private void prepareCard(Postcard card) {
    RouteMeta routeMeta = Warehouse.routes.get(card.getPath());
    if (null == routeMeta) {
        Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(card.getGroup());
        if (null == groupMeta) {
            throw new NoRouteFoundException("没找到对应路由:分组=" + card.getGroup() + "   路径=" + card.getPath());
        }
        IRouteGroup iGroupInstance;
        try {
            iGroupInstance = groupMeta.getConstructor().newInstance();
        } catch (Exception e) {
            throw new RuntimeException("路由分组映射表记录失败.", e);
        }
        iGroupInstance.loadInto(Warehouse.routes);
        //已经准备过了就可以移除了 (不会一直存在内存中)
        Warehouse.groupsIndex.remove(card.getGroup());
        //再次进入 else
        prepareCard(card);
    } else {
        //类 要跳转的activity 或IService实现类
        card.setDestination(routeMeta.getDestination());
        card.setType(routeMeta.getType());
        switch (routeMeta.getType()) {
            case ISERVICE:
                Class<?> destination = routeMeta.getDestination();
                IService service = Warehouse.services.get(destination);
                if (null == service) {
                    try {
                        service = (IService) destination.getConstructor().newInstance();
                        Warehouse.services.put(destination, service);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                card.setService(service);
                break;
            default:
                break;
        }
    }
}

注意,Warehouse就是专门用来存放路由映射关系的类,里面保存着存路由信息的map,这在ARouter里面也是一样的。这段代码Warehouse.routes.get(card.getPath())通过path拿到对应的RouteMeta,这个RouteMeta里面保存了activityClass等信息。继续往下看,如果判断拿到的RouteMeta是空,说明这个路由地址还没有加载到map里面(初始化时为了节省性能,只会加载所有的分组信息,而每个分组下的路由映射关系,会使用懒加载,在首次用到的时候去加载),只有在*次用到当前路由地址的时候,会去Warehouse.routes里面拿routeMeta,如果拿到的是空,会根据当前路由地址的group拿到对应的分组,通过反射创建实例,然后调用实例的loadInfo方法,把它里面保存的映射信息添加到Warehouse.routes里面,并且再次调用prepareCard(card),这时再通过Warehouse.routes.get(card.getPath())就可以顺利拿到RouteMeta了。进入else{}里面,调用了card.setDestination(routeMeta.getDestination()),这个setDestination就是将RouteMeta里面保存的activityClass放入Postcard里面,下面switch代码块可以先不用看,这是实现ARouter中通过依赖注入实现Provider 服务的逻辑,有心研究的同学可以去读一下demo。

好了,prepareCard()方法调用完成后,我们的postcard里面就保存了activityClass,然后switch (postcard.getType()){}会判断postcard的type为ACTIVITY,然后通过ActivityCompat.startActivity启动Activity。到这里,路由跳转的实现已经讲解完毕了。

小结

EaseRouter本身只是参照ARouter手动实现的路由框架,并且剔除掉了很多东西,如过滤器等,如果想要用在项目里,建议还是用ARouter更好,毕竟这只是个练手项目,功能也不够全面,当然有同学想对demo扩展后使用那当然更好,遇到什么问题可以及时联系我。我的目的是通过自己手动实现路由框架来加深对知识的理解,如这里面涉及到的知识点apt、javapoet和组件化思路、编写框架的思路等。看到这里,如果感觉干货很多,欢迎关注我的github,里面会有更多干货!

阿里ARouter使用及源码解析(一)

在app的开发中,页面之间的相互跳转是*基本常用的功能。在Android中的跳转一般通过显式intent和隐式intent两种方式实现的,而Android的原生跳转方式会存在一些缺点:

  • 显式intent的实现方式,因为会存在直接的类依赖的问题,导致耦合严重;
  • 隐式intent的实现方式,则会出现规则集中式管理,导致协作变得困难;
  • 可配置性较差,一般而言配置规则都是在Manifest中的,这就导致了扩展性较差;
  • 跳转过程无法控制,一旦使用了StartActivity()就无法插手其中任何环节了,只能交给系统管理;
  • 当多组件化开发,使用原生的路由方式很难实现完全解耦;

而阿里的ARouter路由框架具有解耦、简单易用、支持多模块项目、定制性较强、支持拦截逻辑等诸多优点,很好的解决了上述的问题。关于ARouter具体实现功能,典型应用以及相应技术方案实现的介绍不在这详细介绍,具体可参见开源*佳实践:Android平台页面路由框架ARouter。

阿里ARouter的分析计划

  • 阿里ARouter使用及源码解析(一)
  • 阿里ARouter拦截器使用及源码解析(二)
  • 阿里ARouter参数自动装载使用及源码解析(三)
基本功能使用

1.添加依赖和配置

android {
    defaultConfig {
    ...
    javaCompileOptions {
        annotationProcessorOptions {
        arguments = [ moduleName : project.getName() ]
        }
    }
    }
}

dependencies {
    compile 'com.alibaba:arouter-api:1.2.1.1'
    annotationProcessor 'com.alibaba:arouter-compiler:1.1.2.1'
    ...
}

2.添加注解

// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx
@Route(path = "/test/test1")
public class Test1Activity extends AppCompatActivity{

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test1);
    }
}

3.初始化SDK

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button btn1,btn2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn1 = (Button) findViewById(R.id.btn1);
        btn2 = (Button) findViewById(R.id.btn2);

        btn1.setOnClickListener(this);
        btn2.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btn1) {
            // 如果使用了InstantRun,必须在初始化之前开启调试模式,但是上线前需要关闭,InstantRun仅用于开发阶段,
            // 线上开启调试模式有安全风险,可以使用BuildConfig.DEBUG来区分环境
            ARouter.openDebug();
            ARouter.init(getApplication()); // 尽可能早,推荐在Application中初始化
        }
    }
}

4.发起跳转操作

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button btn1,btn2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn1 = (Button) findViewById(R.id.btn1);
        btn2 = (Button) findViewById(R.id.btn2);

        btn1.setOnClickListener(this);
        btn2.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btn1) {
            ....
        } else if (v.getId() == R.id.btn2){
            ARouter.getInstance().build("/test/test1").navigation();
        }
    }
}

以上相关代码就是ARouter的*基本功能使用的步骤,下面来分析跳转功能是如何实现的。

原理分析
1.ARouter编译的过程

ARouter在编译期的时候,利用自定义注解完成了页面的自动注册。相关注解源码参见arouter-annotation,编译处理器源码参见arouter-compiler

下面是注解@Route的源码介绍:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {

    /**
     *路由的路径,标识一个路由节点
     */
    String path();

    /**
     * 将路由节点进行分组,可以实现按组动态加载
     */
    String group() default "";

    /**
     * 路由节点名称,可用于生成javadoc文档
     */
    String name() default "undefined";

    /**
     * 用32位int类型标示,可用于页面的一些配置
     */
    int extras() default Integer.MIN_VALUE;

    /**
     * 路由的优先级
     */
    int priority() default -1;
}

Route中的extra值是个int值,由32位表示,即转换成二进制后,一个int中可以配置31个1或者0,而每一个0或者1都可以表示一项配置(排除符号位),如果从这31个位置中随便挑选出一个表示是否需要登录就可以了,只要将标志位置为1,就可以在声明的拦截器中获取到这个标志位,通过位运算的方式判断目标页面是否需要登录。所以可以通过extra给页面配置30多个属性,然后在拦截器中去进行处理。
ARouter在拦截器中会把目标页面的信息封装一个类Postcard,这个类就包含了目标页面注解上@Route标识的各种信息。关于拦截器的使用以及源码分析,后续会有介绍。

将代码编译一遍,可以看到ARouter生成下面几个源文件:

%title插图%num

上面三个文件均是通过注解处理器RouteProcessor生成的,关于如何自定义注解处理器,可以阅读Android编译时注解APT实战(AbstractProcessor),同时也需要学习JavaPoet的基本使用。下面我们看RouteProcessor是如何生成相关文件的。

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        //判断被注解了的元素集合是否为空
        if (CollectionUtils.isNotEmpty(annotations)) {
            //获取所有被@Route注解的元素集合,Element可以是类、方法、变量等
            Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
            try {
                logger.info(">>> Found routes, start... <<<");
                //具体处理注解,生成java文件的方法
                this.parseRoutes(routeElements);

            } catch (Exception e) {
                logger.error(e);
            }
            return true;
        }

        return false;
    }

process()方法相当于处理器的主函数main(),可以在这个方法中扫描、评估和处理注解的代码,以及生成Java文件。RouteProcessor中调用了parseRoutes(),用来处理所有被@Route注解的元素。在分析上述三个java文件如何生成之前,先看看生成文件的具体代码。

  • ARouter$$Root$$app类
public class ARouter$$Root$$app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("test", ARouter$$Group$$test.class);
  }
}
  • ARouter$$Group$$test类
public class ARouter$$Group$$test implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/test/test1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/test1", "test", null, -1, -2147483648));
  }
}
  • ARouter$$Providers$$app类
public class ARouter$$Providers$$app implements IProviderGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> providers) {
  }
}

我们接着分析上述三个文件是如何生成的

1.首先获取生成方法的参数的类型和参数名称

 private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
        if (CollectionUtils.isNotEmpty(routeElements)) {
          
            logger.info(">>> Found routes, size is " + routeElements.size() + " <<<");

            rootMap.clear();
             // TypeElement 表示一个类或接口元素
            // public static final String ACTIVITY = "android.app.Activity";
            //得到类activity元素
            TypeElement type_Activity = elementUtil.getTypeElement(ACTIVITY);
            // public static final String SERVICE = "android.app.Service";
            //得到类service的元素
            TypeElement type_Service = elementUtil.getTypeElement(SERVICE);
            // public static final String SERVICE = "android.app.Fragment";
            TypeMirror fragmentTm = elements.getTypeElement(FRAGMENT).asType();
             // public static final String SERVICE = "android.support.v4.app.Fragment";
            TypeMirror fragmentTmV4 = elements.getTypeElement(Consts.FRAGMENT_V4).asType();

            // public static final String IROUTE_GROUP = "com.alibaba.android.arouter.facade.template.IRouteGroup";
            //得到接口IRouteGroup元素
            TypeElement type_IRouteGroup = elementUtil.getTypeElement(IROUTE_GROUP);
          // public static final String IROUTE_GROUP = "com.alibaba.android.arouter.facade.template.IProviderGroup";
            //得到接口IProviderGroup元素
            TypeElement type_IProviderGroup = elementUtil.getTypeElement(IPROVIDER_GROUP);
            //获取RouteMeta,RouteType类名
            ClassName routeMetaCn = ClassName.get(RouteMeta.class);
            ClassName routeTypeCn = ClassName.get(RouteType.class);

            //下面代码是获取生成java文件中方法的参数类型名称和参数名称。
            /*
              获取获取ARouter$$Root$$app 类中方法参数Map<String, Class<? extends IRouteGroup>>类型的名称
             */
            ParameterizedTypeName inputMapTypeOfRoot = ParameterizedTypeName.get(
                    ClassName.get(Map.class),
                    ClassName.get(String.class),
                    ParameterizedTypeName.get(
                            ClassName.get(Class.class),
                            WildcardTypeName.subtypeOf(ClassName.get(type_IRouteGroup))
                    )
            );

            /*
              获取ARouter$$Group$$test,ARouter$$Providers$$app类中方法参数 Map<String, RouteMeta>类型的名称
             */
            ParameterizedTypeName inputMapTypeOfGroup = ParameterizedTypeName.get(
                    ClassName.get(Map.class),
                    ClassName.get(String.class),
                    ClassName.get(RouteMeta.class)
            );

            /*
             获取相关的参数
             */
            //获取ARouter$$Root$$app 类中方法的参数Map<String, Class<? extends IRouteGroup>> routes
            ParameterSpec rootParamSpec = ParameterSpec.builder(inputMapTypeOfRoot, "routes").build();
           //获取ARouter$$Group$$test类中方法的参数Map<String, RouteMeta> atlas
            ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build();
             //获取ARouter$$Providers$$app类中方法的参数Map<String, RouteMeta> providers
            ParameterSpec providerParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "providers").build();  

          
          .....
        }
    }

2.获取了方法的参数的类型和参数名称后,下面便是生成相应的方法

 private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
        if (CollectionUtils.isNotEmpty(routeElements)) {
            ........

            /*
              首先创建ARouter$$Root$$xxx 类中的loadInto()方法
              @Override
              public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {}
             */
            MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                    .addAnnotation(Override.class)
                    .addModifiers(PUBLIC)
                    .addParameter(rootParamSpec);

            //  遍历所有被@Route注解的元素
            for (Element element : routeElements) {
                TypeMirror tm = element.asType();
                Route route = element.getAnnotation(Route.class);
                RouteMeta routeMete = null;
                
                //判断该元素否为 Activity 、IProvider 、 Service 的子类,然后创建相应的RouteMeta 对象
                if (typeUtil.isSubtype(tm, type_Activity.asType())) {                 // Activity
                    logger.info(">>> Found activity route: " + tm.toString() + " <<<");

                    // 如果是acitiviy类型,获取所有被@Autowired的属性
                    //关于@Autowired的注解,我们之后再进行分析
                    Map<String, Integer> paramsType = new HashMap<>();
                    for (Element field : element.getEnclosedElements()) {
                        if (field.getKind().isField() && field.getAnnotation(Autowired.class) != null && !typeUtil.isSubtype(field.asType(), iProvider)) {
                            // It must be field, then it has annotation, but it not be provider.
                            Autowired paramConfig = field.getAnnotation(Autowired.class);
                            paramsType.put(StringUtils.isEmpty(paramConfig.name()) ? field.getSimpleName().toString() : paramConfig.name(), TypeUtils.typeExchange(field.asType()));
                        }
                    }
                    // ACTIVITY类型节点
                    routeMete = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
                } else if (typeUtil.isSubtype(tm, iProvider)) {         // IProvider
                    logger.info(">>> Found provider route: " + tm.toString() + " <<<");
                    //从该判断可看出,如果要想成功注册一个 PROVIDER 类型的路由节点,
                    //一定要实现 com.alibaba.android.arouter.facade.template.IProvider 这个接口
                    routeMete = new RouteMeta(route, element, RouteType.PROVIDER, null);
                } else if (typeUtil.isSubtype(tm, type_Service.asType())) {           // Service
                    logger.info(">>> Found service route: " + tm.toString() + " <<<");
                     //SERVICE类型节点
                    routeMete = new RouteMeta(route, element, RouteType.parse(SERVICE), null);
                } else if (types.isSubtype(tm, fragmentTm) || types.isSubtype(tm, fragmentTmV4)) {
                    logger.info(">>> Found fragment route: " + tm.toString() + " <<<");
                   //FRAGMENT类型节点
                    routeMete = new RouteMeta(route, element, RouteType.parse(FRAGMENT), null);
                }
                
                //routeMete包含了每个路由节点的各种信息,下面的方法的主要功能就是根据@Route注解信息对节点进行分组,保存在groupMap集合中。
               //关于方法的具体实现,后面会有解析
                categories(routeMete);
          
            }

            .........
        }
    }

以上代码主要功能就是遍历所有被@Route注解的元素,然后将每个路由节点的信息按照类型(ACTIVITY类型,实现了IProvider 接口类型以及SERVICE类型)封装到RouteMeta中,*后调用categories(routeMete)方法将节点分组,保存在groupMap集合。

继续往下分析

 private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
        if (CollectionUtils.isNotEmpty(routeElements)) {
            ........

             /*
              然后创建ARouter$$Providers$$xxx 类中的loadInto()方法
             @Override
             public void loadInto(Map<String, RouteMeta> providers) {}
             */
            MethodSpec.Builder loadIntoMethodOfProviderBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                    .addAnnotation(Override.class)
                    .addModifiers(PUBLIC)
                    .addParameter(providerParamSpec);

            //遍历分组的集合,生成相应的java文件
           //因为本文使用的例子没有对页面进行分组,所以只生成了一个组文件ARouter$$Group$$xxx
            for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
                String groupName = entry.getKey();
               /*
                  创建ARouter$$Group$$xxx 类中的loadInto()方法
                 @Override
                 public void loadInto(Map<String, RouteMeta> atlas) {}
             */
                MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                        .addAnnotation(Override.class)
                        .addModifiers(PUBLIC)
                        .addParameter(groupParamSpec);

                // 生成loadInto()方法体
                Set<RouteMeta> groupData = entry.getValue();
                //遍历每个组里面的路由节点
                for (RouteMeta routeMeta : groupData) {
                    switch (routeMeta.getType()) {
                        //如果节点类型是PROVIDER,
                        case PROVIDER:  
                          //获取路由节点元素的接口集合
                            List<? extends TypeMirror> interfaces = ((TypeElement) routeMeta.getRawType()).getInterfaces();
                            for (TypeMirror tm : interfaces) {
                             if (types.isSameType(tm, iProvider)) {   // Its implements iProvider interface himself.
                                   //路由节点元素其中一个接口是 com.alibaba.android.arouter.facade.template.IProvider 
                                  //给ARouter$$Providers$$xxx 类中的loadInto()添加方法体
                                    loadIntoMethodOfProviderBuilder.addStatement(
                                            "providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                                            (routeMeta.getRawType()).toString(),//路由节点元素的全名
                                            routeMetaCn,
                                            routeTypeCn,
                                            ClassName.get((TypeElement) routeMeta.getRawType()),
                                            routeMeta.getPath(),
                                            routeMeta.getGroup());
                                } else if (types.isSubtype(tm, iProvider)) {
                                   //路由节点元素其中一个接口是com.alibaba.android.arouter.facade.template.IProvider 接口的子类型
                                    loadIntoMethodOfProviderBuilder.addStatement(
                                            "providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                                            tm.toString(),   //IProvider子类型的全名
                                            routeMetaCn,
                                            routeTypeCn,
                                            ClassName.get((TypeElement) routeMeta.getRawType()),
                                            routeMeta.getPath(),
                                            routeMeta.getGroup());
                                }
                            //上面方法体的代码为:
                          //providers.put("实现接口的名称", RouteMeta.build(RouteType.PROVIDER, 类名.class,   "@Route.path", "@Route.group", null, @Route.priority, @Route.extras));
                            }
                            break;
                        default:
                            break;
                    }

                    // 将路由节点中被@Autowired注解的属性集合转换成字符串
                    StringBuilder mapBodyBuilder = new StringBuilder();
                    //获取路由节点中被@Autowired注解的属性集合
                    Map<String, Integer> paramsType = routeMeta.getParamsType();
                    if (MapUtils.isNotEmpty(paramsType)) {
                        for (Map.Entry<String, Integer> types : paramsType.entrySet()) {
                            mapBodyBuilder.append("put(\"").append(types.getKey()).append("\", ").append(types.getValue()).append("); ");
                        }
                    }
                    String mapBody = mapBodyBuilder.toString();
                    
                    //给ARouter$$Group$$xxx 类中的loadInto()添加方法体
                    //注意:有多个分组就会创建多个组文件
                    loadIntoMethodOfGroupBuilder.addStatement(
                            "atlas.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, " + (StringUtils.isEmpty(mapBody) ? null : ("new java.util.HashMap<String, Integer>(){{" + mapBodyBuilder.toString() + "}}")) + ", " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                            routeMeta.getPath(),
                            routeMetaCn,
                            routeTypeCn,
                            ClassName.get((TypeElement) routeMeta.getRawType()),
                            routeMeta.getPath().toLowerCase(),
                            routeMeta.getGroup().toLowerCase());
                }

                  // 真正生成ARouter$$Group$$test JAVA文件
                 //NAME_OF_GROUP = ARouter$$Group$$
                //  groupName = test; 关于groupname的值在方法categories(routeMete)中会有讲解
                String groupFileName = NAME_OF_GROUP + groupName;
                JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                        TypeSpec.classBuilder(groupFileName)
                                .addJavadoc(WARNING_TIPS)
                                .addSuperinterface(ClassName.get(type_IRouteGroup))
                                .addModifiers(PUBLIC)
                                .addMethod(loadIntoMethodOfGroupBuilder.build())
                                .build()
                ).build().writeTo(mFiler);

                logger.info(">>> Generated group: " + groupName + "<<<");
                //将生成的组文件放在rootmap集合中去,为下面生成ARouter$$Root$$xxx文件做准备
                rootMap.put(groupName, groupFileName);
            }

         .......
        }
    }

以上代码主要功能由几点:

  • 遍历groupmap集合给ARouter$$Group$$xxx类中的loadInto()添加方法体,并且生成ARouter$$Group$$xxx JAVA文件,而文件命名为ARouter$$Group$$+groupname,其中有多个分组就会创建多个组文件。比如AROUTER源码中的样例就生成了多个分组文件
%title插图%num
两个分组文件

关于生成的loadInto()中的方法体的例子,来自 AROUTER源码中的样例:

public class ARouter$$Group$$test implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    //存在被@Autowired注解参数生成的代码
    atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("name", 18); put("boy", 0); put("age", 3); put("url", 18); }}, -1, -2147483648));
    .....
   //没有被@Autowired注解参数生成的代码
    atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
    ....
  }
}
  • 遍历每个组里面的路由节点,查找节点类型是否为PROVIDER类型,如果是就向给ARouter$$Providers$$xxx类中的loadInto()添加方法,其文件命名ARouter$$Providers$$+modulename。关于生成的loadInto()中的方法体的例子,来自 AROUTER源码中的样例:
public class ARouter$$Providers$$app implements IProviderGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> providers) {
    providers.put("com.alibaba.android.arouter.demo.testservice.HelloService", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello", "service", null, -1, -2147483648));
    //路由节点元素其中一个接口是IProvider的子类型
    providers.put("com.alibaba.android.arouter.facade.service.SerializationService", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/service/json", "service", null, -1, -2147483648));
     //路由节点元素其中一个接口是IProvider接口
    providers.put("com.alibaba.android.arouter.demo.testservice.SingleService", RouteMeta.build(RouteType.PROVIDER, SingleService.class, "/service/single", "service", null, -1, -2147483648));
  }
}
  • 将生成的组文件放在rootmap集合中去,为下面生成ARouter$$Root$$xxx文件做准备,其文件命名ARouter$$Root$$+modulename。

我们接着分析parseRoutes()方法*后一段代码,这段代码其实很简单,主要目的就是给ARouter$$Root$$xxx的loadInto()添加方法体,*后生成Router$$Providers$$xxx,ARouter$$Root$$xxx文件

 private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
        if (CollectionUtils.isNotEmpty(routeElements)) {
            ........
            //遍历rootMap集合,给ARouter$$Root$$xxx的`loadInto()`添加方法体
            if (MapUtils.isNotEmpty(rootMap)) {
                // Generate root meta by group name, it must be generated before root, then I can findout the class of group.
                for (Map.Entry<String, String> entry : rootMap.entrySet()) {
                    loadIntoMethodOfRootBuilder.addStatement("routes.put($S, $T.class)", entry.getKey(), ClassName.get(PACKAGE_OF_GENERATE_FILE, entry.getValue()));
                }
            }

            // 生成Router$$Providers$$xxx文件
            String providerMapFileName = NAME_OF_PROVIDER + SEPARATOR + moduleName;
            JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                    TypeSpec.classBuilder(providerMapFileName)
                            .addJavadoc(WARNING_TIPS)
                            .addSuperinterface(ClassName.get(type_IProviderGroup))
                            .addModifiers(PUBLIC)
                            .addMethod(loadIntoMethodOfProviderBuilder.build())
                            .build()
            ).build().writeTo(mFiler);

            logger.info(">>> Generated provider map, name is " + providerMapFileName + " <<<");

            // 生成ARouter$$Root$$xxx文件
            String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName;
            JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                    TypeSpec.classBuilder(rootFileName)
                            .addJavadoc(WARNING_TIPS)
                            .addSuperinterface(ClassName.get(elementUtil.getTypeElement(ITROUTE_ROOT)))
                            .addModifiers(PUBLIC)
                            .addMethod(loadIntoMethodOfRootBuilder.build())
                            .build()
            ).build().writeTo(mFiler);

            logger.info(">>> Generated root, name is " + rootFileName + " <<<");
        }
    }

关于生成的loadInto()中的方法体的例子,来自 AROUTER源码中的样例:

public class ARouter$$Root$$app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("service", ARouter$$Group$$service.class);
    routes.put("test", ARouter$$Group$$test.class);
  }
}

上面分析的便是parseRoutes()方法所有代码的解析

3.*后我们看下categories()方法是如何分组的

   private void categories(RouteMeta routeMete) {
        //如果路由路径合法,且有groupname进行执行
        if (routeVerify(routeMete)) {
            logger.info(">>> Start categories, group = " + routeMete.getGroup() + ", path = " + routeMete.getPath() + " <<<");
             //根据groupname获取该组的路由节点集合,如果集合为空,则创建一个新的组,将该节点添加进去,并将组集合保存在groupmap中;
          //不为空,则添加到所属的组集合中去
            Set<RouteMeta> routeMetas = groupMap.get(routeMete.getGroup());
            if (CollectionUtils.isEmpty(routeMetas)) {
                Set<RouteMeta> routeMetaSet = new TreeSet<>(new Comparator<RouteMeta>() {
                    @Override
                    public int compare(RouteMeta r1, RouteMeta r2) {
                        try {
                            return r1.getPath().compareTo(r2.getPath());
                        } catch (NullPointerException npe) {
                            logger.error(npe.getMessage());
                            return 0;
                        }
                    }
                });
                routeMetaSet.add(routeMete);
                groupMap.put(routeMete.getGroup(), routeMetaSet);
            } else {
                routeMetas.add(routeMete);
            }
        } else {
            logger.warning(">>> Route meta verify error, group is " + routeMete.getGroup() + " <<<");
        }
    }

//判断路由路径是否合法,并且设置groupname
 private boolean routeVerify(RouteMeta meta) {
        String path = meta.getPath();
        //如果路径为空,或者不是由'/'开头,返回false
        if (StringUtils.isEmpty(path) || !path.startsWith("/")) {   // The path must be start with '/' and not empty!
            return false;
        }

         //如果在@Route注解中没有设置group标识,那么就默认取path路径*段路径名作为groupname
        if (StringUtils.isEmpty(meta.getGroup())) { // Use default group(the first word in path)
            try {
                String defaultGroup = path.substring(1, path.indexOf("/", 1));
                if (StringUtils.isEmpty(defaultGroup)) {
                    return false;
                }

                meta.setGroup(defaultGroup);
                return true;
            } catch (Exception e) {
                logger.error("Failed to extract default group! " + e.getMessage());
                return false;
            }
        }

        return true;
    }

通过分析,如果@Route注解中有设置group标识,作为groupname,如果没有就取/xxx1/xxx2,xxx1作为groupname,并将同一组的路由节点放到同一个集合中去。

至此关于@Route注解在编译期时生成ARouter$$Root$$xxx,Router$$Providers$$xxx,ARouter$$Group$$xxx三种映射文件的源码分析完毕。

2.ARouter初始化过程

ARouter经过代码编译后,生成了相应的映射文件,我们可以断定,ARouter 的初始化会将这些文件加载到内存中去,形成一个路由表,以供后面路由查找跳转之用。其相关源码可参见 arouter-api

  • ARouterinit()方法
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 ()方法,而且其他的跳转方法*终调用的也是_ARouter 种的方法。

  • _ARouterinit()方法
  protected static synchronized boolean init(Application application) {
        mContext = application;
        LogisticsCenter.init(mContext, executor);
        logger.info(Consts.TAG, "ARouter init success!");
        hasInit = true;

        return true;
    }

_ARouter中又调用了LogisticsCenter.init(),继续追踪下去,其中传入了一个线程池executor,这个线程池在拦截器的时候会使用到。

    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        mContext = context;
        executor = tpe;

        try {
             //ROUTE_ROOT_PAKCAGE = "com.alibaba.android.arouter.routes"
            // 获取ROUTE_ROOT_PAKCAGE 包里面的所有文件
            List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);

            //遍历所有ROUTE_ROOT_PAKCAGE 包里的文件
            for (String className : classFileNames) {
                //文件名以“com.alibaba.android.arouter.routes.ARouter$$Root”开头执行下面代码
                if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                    // 通过反射实例化,并且调用loadInto(),目的即是将编译生成的ARouter$$Group$$xxx文件加载到内存中,保存在Warehouse.groupsIndex;
                    ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                    //文件名以“com.alibaba.android.arouter.routes.ARouter$$Interceptors”开头执行下面代码
                    //  执行编译生成的ARouter$$Interceptors$$xxx的loadInto(),将自定义拦截器类存放在Warehouse.interceptorsIndex中
                    ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                     //文件名以“com.alibaba.android.arouter.routes.ARouter$$Providers”开头执行下面代码
                   //  执行编译生成的ARouter$$Interceptors$$xxx的loadInto()
                    ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                }
            }

            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() + "]");
        }
    }
  • _ARouterafterInit()方法
static void afterInit() {
        // 通过路由机制,初始化路由拦截机制。关于路由拦截机制的使用和原理,后续文章会有分析
        interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
    }

以上就是ARouter初始化的所有代码,关于如何查找到com.alibaba.android.arouter.routes包内所有文件这里便不做过多分析,大家可以去阅读 arouter-api中ClassUtils这个类的源码。
总结下来,其实ARouter 的初始化只做了一件事,找到自己编译期产生的清单文件,把 Group 、Interceptor 、Provider 三种清单加载到 Warehouse 内存仓库中。即下面这些文件,来源自AROUTER源码中的样例

%title插图%num

值得注意的是,在初始化阶段,ARouter 仅载入了 Group 清单,并没有具体载入每个 Group 中包含的具体的路由节点清单,只有当使用到具体的 Group 时,才会加载对应的 Group 列表。这种分组管理,按需加载,大大的降低了初始化时的内存压力。并且Warehouse类中保存了路由清单,并且将使用过的路由对象缓存起来,之后查找都是直接使用缓存的对象 。

3.ARouter调用过程分析

页面跳转*基本方法

ARouter.getInstance().build(“/test/activity2”).navigation();

获取Provider服务(实现了IProvider接口以及IProvider子类接口的服务类)的方法有两种:

1.byName方式
ARouter.getInstance().build(“/service/hello”).navigation()

2.byType方式
ARouter.getInstance().navigation(HelloService.class)

ARouter路由跳转采用链式调用,ARouter.getInstance()其中采用的单例模式,获取ARouter的实例,这个就不作过多分析,主要分析build()navigation()

build()方法
ARouter的build(String path)init()方法一样,调用的是_ARouterbuild(String 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));
        }
    }

其中extractGroup(String path)就是根据path获取分组名,即path*段“/”符号之间的值

  private String extractGroup(String path) {
        if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
            throw new HandlerException(Consts.TAG + "Extract the default group failed, the path must be start with '/' and contain more than 2 '/'!");
        }

        try {
            //    /xxx1/xxx2   ===>  defaulGroup = xxx1
            String defaultGroup = path.substring(1, path.indexOf("/", 1));
            if (TextUtils.isEmpty(defaultGroup)) {
                throw new HandlerException(Consts.TAG + "Extract the default group failed! There's nothing between 2 '/'!");
            } else {
                return defaultGroup;
            }
        } catch (Exception e) {
            logger.warning(Consts.TAG, "Failed to extract default group! " + e.getMessage());
            return null;
        }
    }

build(String path)方法*终调用的是build(String path, String group)

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

值得注意的是其中ARouter.getInstance().navigation(PathReplaceService.class)就是得到实现PathReplaceService接口的一个服务对象,对原始path进行处理后,生成新的path路径。而这个类需要我们自己自定义去实现,如果没有实现,pService=null,原始path不做任何处理。
下面是PathReplaceService接口,我们可以通过实现forString()forUri()方法,对某些url进行替换处理,跳转到其他的目标页面。

public interface PathReplaceService extends IProvider {

    /**
     * For normal path.
     *
     * @param path raw path
     */
    String forString(String path);

    /**
     * For uri type.
     *
     * @param uri raw uri
     */
    Uri forUri(Uri uri);
}

*后返回一个Postcard实例对象,里面封装了路由节点的路径,分组等节点信息。其实build()方法的目的只有一个就是根据路由,封装成Postcard对象,其对象贯穿之后整个路由过程。Postcard 包含了众多的属性值,提供了路由过程中所有的控制变量。

public final class Postcard extends RouteMeta {
    private Uri uri;
    private Object tag;             // A tag prepare for some thing wrong.
    private Bundle mBundle;         // 传递的参数
    private int flags = -1;         // intent 的flag标志
    private int timeout = 300;      // Navigation timeout, TimeUnit.Second !
    private IProvider provider;     // IProvider服务对象
    private boolean greenChannal;
    private SerializationService serializationService;//序列化服务对象

     // 跳转动画
    private Bundle optionsCompat;    // The transition animation of activity
    private int enterAnim;
    private int exitAnim;

    // copy from RouteMeta 
    private RouteType type;         // 路由节点类型
    private Element rawType;        
    private Class<?> destination;  //需要跳转到的页面
    private String path;            // 路径
    private String group;           // 分组
    private int priority = -1;      // 优先级
    private int extra;              // 配置标识
    private Map<String, Integer> paramsType;  // 路由页面被@Autowired注解属性
    // ......
}

navigation()方法
关于页面跳转的navigation()方法有多个重载的方法,但*终都会调用_ARouter下面这个方法

    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        try {
            //首先对postcard进行一些处理,设置postcard的destination,type,priority 等一些属性值,completion()后面会有分析
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
            logger.warning(Consts.TAG, ex.getMessage());

            if (debuggable()) { // Show friendly tips for user.
                Toast.makeText(mContext, "There's no route matched!\n" +
                        " Path = [" + postcard.getPath() + "]\n" +
                        " Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
            }
            // 如果处理postcard失败,通过 callback 回调失败结果
           // callback为空的情况下,如果有定义全局的降级处理(DegradeService),则使用全局处理
           //降级处理也需要我们自己实现DegradeService接口
            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;
        }
         //路由处理成功,回调callback.onFound()
        if (null != callback) {
            callback.onFound(postcard);
        }
        
        //目前来说,PROVIDER服务类型,以及FRAGMENT类型不需要通过拦截器外,其他类型均需要通过拦截器
        //关于拦截器相关用法及原理分析在后续的文章中会讲解到,大家去可以关注下
        if (!postcard.isGreenChannel()) {   
            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;
    }

值得注意的是,当跳转路由处理失败的时候,会获取一个降级服务,我们可以实现DegradeService接口,实现onLost()方法,对路由处理失败的情况进行处理,比如跳转到一个信息提示页面,让用户去更新版本等操作等。下面是DegradeService接口:

public interface DegradeService extends IProvider {

    /**
     * Router has lost.
     *
     * @param postcard meta
     */
    void onLost(Context context, Postcard postcard);
}

通过上面代码的分析,不管是否通过拦截器进行处理,*后都会调用_navigation()达到路由的目的:

private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        final Context currentContext = null == context ? mContext : context;

        switch (postcard.getType()) {
            case ACTIVITY:
                //下面就是*基本的使用intent进行activity进行跳转
                // 创建intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                //设置传参
                intent.putExtras(postcard.getExtras());

                //activity启动标志
                int flags = postcard.getFlags();
                if (-1 != flags) {
                    intent.setFlags(flags);
                } else if (!(currentContext instanceof Activity)) {    // Non activity, need less one flag.
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }

                // 在主线程中进行跳转
                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());
                        }
                        //跳转成功,回调callback.onArrival()
                        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 {
                     //实例化fragment,并传递参数
                    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;
    }

目前仅ARouter实现了 ACTIVITY , PROVIDER ,FRAGMENT三种种类型。上面关于postcard的provider,destination的值都是在completion()中设置的。我们接着看LogisticsCentercompletion(Postcard postcard)

    public synchronized static void completion(Postcard postcard) {
        if (null == postcard) {
            throw new NoRouteFoundException(TAG + "No postcard!");
        }
      
        // 查找Warehouse仓库的路由节点缓存,看是否已在缓存中
        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
        if (null == routeMeta) {   
          // 如果没有,查找仓库的组别清单中是否存在该组别,组别清单已经在初始化的时候加载到仓库中去了
            Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  
            //如果没有抛出异常
            if (null == groupMeta) {
                throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
            } else {
                // Load route and cache it into memory, then delete from metas.
                try {
                    if (ARouter.debuggable()) {
                        logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                    }
                    // 实例化个组别的类,调用loadInto(),将组别中所有的路由节点加载进仓库Warehouse.routes,缓存
                    IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                    iGroupInstance.loadInto(Warehouse.routes);
                     // 从组别清单中删除已加载的组别,防止重复加载
                    Warehouse.groupsIndex.remove(postcard.getGroup());

                    if (ARouter.debuggable()) {
                        logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                    }
                } catch (Exception e) {
                    throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
                }
                //当路由节点加载到缓存中去后,重新查找执行else代码,对postcard进行处理
                completion(postcard);   // Reload
            }
        } else {
            //给postcard设置destination,type,priority等值,供上面讲解到的_navigation()进行使用
            // 其中routeMeta是在ARouter$$Group$$xxx的loadInto中创建的
            postcard.setDestination(routeMeta.getDestination());
            postcard.setType(routeMeta.getType());
            postcard.setPriority(routeMeta.getPriority());
            postcard.setExtra(routeMeta.getExtra());

            //如果通过build(Uri url) 进行跳转的话 通过解析url ,将传参保存进bundle中
            Uri rawUri = postcard.getUri();
            if (null != rawUri) {  
                //splitQueryParameters()就是在uri中携带的参数进行解析
                Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
                Map<String, Integer> paramsType = routeMeta.getParamsType();

                if (MapUtils.isNotEmpty(paramsType)) {
                    // Set value by its type, just for params which annotation by @Param
                    for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
                        setValue(postcard,
                                params.getValue(),
                                params.getKey(),
                                resultMap.get(params.getKey()));
                    }

                    // Save params name which need autoinject.
                    postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
                }

                // Save raw uri
                postcard.withString(ARouter.RAW_URI, rawUri.toString());
            }
            
            //从这里也可以看出PROVIDER,FRAGMENT不需要通过拦截器
            switch (routeMeta.getType()) {
                case PROVIDER:  
                    // 如果是PROVIDER节点类型,从服务节点列表中获取,如果没有,则实例化,并保存在服务节点列表Warehouse.providers中
                  //并将实例化的对象设置给postcard的provider属性
                    Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                    IProvider instance = Warehouse.providers.get(providerMeta);
                    if (null == instance) { // There's no instance of this provider
                        IProvider provider;
                        try {
                            provider = providerMeta.getConstructor().newInstance();
                            provider.init(mContext);
                            Warehouse.providers.put(providerMeta, provider);
                            instance = provider;
                        } catch (Exception e) {
                            throw new HandlerException("Init provider failed! " + e.getMessage());
                        }
                    }
                    postcard.setProvider(instance);
                    postcard.greenChannel();    // Provider should skip all of interceptors
                    break;
                case FRAGMENT:
                    postcard.greenChannel();    // Fragment needn't interceptors
                default:
                    break;
            }
        }
    }

分析到这里,关于页面基本跳转的原理分析就已经结束了。*后就是关于获取Provider服务两种方法的源码分析。其中byName方式,和页面跳转是一模一样的。我们只需要看看byType方式即可。byType方式*后调用的是_ARouternavigation(Class<? extends T> service)

  protected <T> T navigation(Class<? extends T> service) {
        try {
            // 通过 className 获取 Postcard 对象
            Postcard postcard = LogisticsCenter.buildProvider(service.getName());

            // 兼容1.0.5 compiler sdk版本.
            if (null == postcard) { // No service, or this service in old version.
                postcard = LogisticsCenter.buildProvider(service.getSimpleName());
            }
           // 对 Postcard 对象进行处理
            LogisticsCenter.completion(postcard);
             //返回 Postcard 中的 provider 属性值
            return (T) postcard.getProvider();
        } catch (NoRouteFoundException ex) {
            logger.warning(Consts.TAG, ex.getMessage());
            return null;
        }
    }

上面代码中的completion()方法之前已经分析过了,只需要看下LogisticsCenter.buildProvider(service.getName())即可。

  public static Postcard buildProvider(String serviceName) {
        RouteMeta meta = Warehouse.providersIndex.get(serviceName);

        if (null == meta) {
            return null;
        } else {
            return new Postcard(meta.getPath(), meta.getGroup());
        }
    }

这个方法非常的简单,就是根据服务类名去仓库Warehouse.providersIndex中获去路由节点元素,然后封装在Postcard对象中。服务类清单列表Warehouse.providersIndex中的值是在初始化时缓存的。值得注意的是,PROVIDER 类型的路由节点既存在于对应的分组中,也存在于服务类清单列表中。所以,ARouter 可通过byType,byName两种方式来获取

补充

关于ARouter的基本用法上面只有*基本跳转的介绍,下面对其他一些基本使用进行下补充

  • 带参数跳转
//1.传递参数
 ARouter.getInstance().build("/test/activity1")
                        .withString("name", "老王")
                        .withInt("age", 18)
                        .withBoolean("boy", true)
                        .withLong("high", 180)
                        .withString("url", "https://a.b.c")
                        .withParcelable("pac", testParcelable)
                        .withObject("obj", testObj)
                        .navigation();

//2.直接传递Bundle
  Bundle params = new Bundle();
  ARouter.getInstance()
          .build("/test/activity1")
          .with(params)
          .navigation();

这些传参都是保存在生成的postcard对象中的mBundle属性里,然后在跳转的时候通过intent.putExtras(postcard.getExtras())达到传送参数的目的。
值得注意的是,关于对象的传递有两种,一种是withParcelable()方法,不过此方法需要传递的对象实现Parcelable接口,达到序列化的目的;另外一种是withObject()方法,此方法的原理是将实体类转换成json字符串,通过String的方式进行传递,而且使用这种方式需要实现 SerializationService,并使用@Route注解标注,下面是ARouter样例:

@Route(path = "/service/json")
public class JsonServiceImpl implements SerializationService {
    @Override
    public void init(Context context) {

    }

    @Override
    public <T> T json2Object(String text, Class<T> clazz) {
        return JSON.parseObject(text, clazz);
    }

    @Override
    public String object2Json(Object instance) {
        return JSON.toJSONString(instance);
    }
}

而且,需要在跳转到的页面获取JsonServiceImpl服务,将json字符串转换成对象。

SerializationService serializationService = ARouter.getInstance().navigation(SerializationService.class);
TestObj obj = serializationService.json2Object(getIntent().getString("obj"), TestObj.class);
  • 带返回结果跳转
ARouter.getInstance().build("/test/activity2").navigation(this, 666);

值得注意的是,这时候的 navigation需要传递activit和requestCode。

  • 获取Fragment的实例

定义一个fragment

@Route(path = "/test/fragment")
public class BlankFragment extends Fragment {
    public BlankFragment() {
        //必须要一个空的构造器
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        TextView textView = new TextView(getActivity());
        return textView;
    }

}

获取frament

Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();
  • 带转场动画跳转
// 转场动画(常规方式)
 ARouter.getInstance() .build("/test/activity2")
                      .withTransition(R.anim.slide_in_bottom, R.anim.slide_out_bottom)
                      .navigation(this);

// 转场动画(API16+)
 ActivityOptionsCompat compat = ActivityOptionsCompat.makeScaleUpAnimation(v, v.getWidth() / 2, v.getHeight() / 2, 0, 0);
ARouter.getInstance().build("/test/activity2").withOptionsCompat(compat) .navigation();
  • 获取服务

服务是全局单例的,只有在*次使用到的时候才会被初始化。
暴露服务,必须实现IProvider 接口 或者其子类型

// 声明接口,其他组件通过接口来调用服务
public interface HelloService extends IProvider {
    String sayHello(String name);
}

// 实现接口
@Route(path = "/service/hello", name = "测试服务")
public class HelloServiceImpl implements HelloService {

    @Override
    public String sayHello(String name) {
    return "hello, " + name;
    }

    @Override
    public void init(Context context) {

    }
}

获取服务

//bytype
HelloService helloService1 = ARouter.getInstance().navigation(HelloService.class);
//byname
HelloService helloService2 = (HelloService) ARouter.getInstance().build("/service/hello").navigation();
  • 多模块结构
%title插图%num

app中可能存在多个模块,每个模块下面都有一个root结点,每个root结点都会管理整个模块中的group节点,每个group结点则包含了该分组下的所有页面,而每个模块允许存在多个分组,每个模块中都会有一个拦截器节点就是Interceptor结点,除此之外每个模块还会有控制拦截反转的provider结点

*后

到此,关于ARouter的基本用法以及原理分析的就全部结束了,如果有不清楚或者错误的地方,希望各位同学指出。关于ARouter拦截器,各种服务,依赖注入等更多进阶用法及源码分析会更新在后续的文章。

如果各位同学认为本文对你有一些帮助,希望能点个喜欢,谢谢!

Android 多线程之HandlerThread 完全详解

之前对线程也写过几篇文章,不过倒是没有针对android,因为java与android在线程方面大部分还是相同,不过本篇我们要介绍的是android的专属类HandlerThread,因为HandlerThread在设置思想上还是挺值得我们学习的,那么我们下面来就了解它吧,我们先来看看HandlerThread有那些特点:

HandlerThread本质上是一个线程类,它继承了Thread;
HandlerThread有自己的内部Looper对象,可以进行looper循环;
通过获取HandlerThread的looper对象传递给Handler对象,可以在handleMessage方法中执行异步任务。
创建HandlerThread后必须先调用HandlerThread.start()方法,Thread会先调用run方法,创建Looper对象。
一、HandlerThread常规使用步骤
了解完上面HandlerThread的一些特点后,我们先来看看HandlerThread使用步骤。
1.创建实例对象

1. HandlerThread handlerThread = new HandlerThread(“downloadImage”);
1
传入参数的作用主要是标记当前线程的名字,可以任意字符串。
2.启动HandlerThread线程

1. //必须先开启线程
2. handlerThread.start();
1
2
到此,我们创建完HandlerThread并启动了线程。那么我们怎么将一个耗时的异步任务投放到HandlerThread线程中去执行呢?接下来看下面步骤:
3.构建循环消息处理机制

/**
* 该callback运行于子线程
*/
class ChildCallback implements Handler.Callback {
@Override
public boolean handleMessage(Message msg) {
//在子线程中进行相应的网络请求

//通知主线程去更新UI
mUIHandler.sendMessage(msg1);
return false;
}
}

4.构建异步handler

//子线程Handler
Handler childHandler = new Handler(handlerThread.getLooper(),new ChildCallback());
1
2
第3步和第4步是构建一个可以用于异步操作的handler,并将前面创建的HandlerThread的Looper对象以及Callback接口类作为参数传递给当前的handler,这样当前的异步handler就拥有了HandlerThread的Looper对象,由于HandlerThread本身是异步线程,因此Looper也与异步线程绑定,从而handlerMessage方法也就可以异步处理耗时任务了,这样我们的Looper+Handler+MessageQueue+Thread异步循环机制构建完成,来看看一个完整的使用案例。

二、HandlerThread的使用案例
主要代码如下:
activity_handler_thread.xml

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical” android:layout_width=”match_parent”
android:layout_height=”match_parent”>

<ImageView
android:id=”@+id/image”
android:layout_width=”match_parent”
android:layout_height=”match_parent” />
</LinearLayout>

HandlerThreadActivity.java

package com.zejian.handlerlooper;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.widget.ImageView;
import com.zejian.handlerlooper.model.ImageModel;
import com.zejian.handlerlooper.util.LogUtils;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* Created by zejian on 16/9/2.
*/
public class HandlerThreadActivity extends Activity {
/**
* 图片地址集合
*/
private String url[]={
“https://img-blog.csdn.net/20160903083245762”,
“https://img-blog.csdn.net/20160903083252184”,
“https://img-blog.csdn.net/20160903083257871”,
“https://img-blog.csdn.net/20160903083257871”,
“https://img-blog.csdn.net/20160903083311972”,
“https://img-blog.csdn.net/20160903083319668”,
“https://img-blog.csdn.net/20160903083326871”
};
private ImageView imageView;
private Handler mUIHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
LogUtils.e(“次数:”+msg.what);
ImageModel model = (ImageModel) msg.obj;
imageView.setImageBitmap(model.bitmap);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_thread);
imageView= (ImageView) findViewById(R.id.image);
//创建异步HandlerThread
HandlerThread handlerThread = new HandlerThread(“downloadImage”);
//必须先开启线程
handlerThread.start();
//子线程Handler
Handler childHandler = new Handler(handlerThread.getLooper(),new ChildCallback());
for(int i=0;i<7;i++){
//每个1秒去更新图片
childHandler.sendEmptyMessageDelayed(i,1000*i);
}
}
/**
* 该callback运行于子线程
*/
class ChildCallback implements Handler.Callback {
@Override
public boolean handleMessage(Message msg) {
//在子线程中进行网络请求
Bitmap bitmap=downloadUrlBitmap(url[msg.what]);
ImageModel imageModel=new ImageModel();
imageModel.bitmap=bitmap;
imageModel.url=url[msg.what];
Message msg1 = new Message();
msg1.what = msg.what;
msg1.obj =imageModel;
//通知主线程去更新UI
mUIHandler.sendMessage(msg1);
return false;
}
}
private Bitmap downloadUrlBitmap(String urlString) {
HttpURLConnection urlConnection = null;
BufferedInputStream in = null;
Bitmap bitmap=null;
try {
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
bitmap=BitmapFactory.decodeStream(in);
} catch (final IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
if (in != null) {
in.close();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
return bitmap;
}
}

在这个案例中,我们创建了两个Handler,一个用于更新UI线程的mUIHandler和一个用于异步下载图片的childHandler。*终的结果是childHandler会每个隔1秒钟通过sendEmptyMessageDelayed方法去通知ChildCallback的回调函数handleMessage方法去下载图片并告诉mUIHandler去更新UI界面,以上便是HandlerThread常规使用,实际上在android比较典型的应用是IntentService,这个我们将放在下篇分析,这里就先不深入了,案例运行截图如下:

三.HandlerThread源码解析
HandlerThread的源码不多只有140多行,那就一步一步来分析吧,先来看看其构造函数

/**
* Handy class for starting a new thread that has a looper. The looper can then be
* used to create handler classes. Note that start() must still be called.
*/
public class HandlerThread extends Thread {
int mPriority;//线程优先级
int mTid = -1;
Looper mLooper;//当前线程持有的Looper对象
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}

/**
* Constructs a HandlerThread.
* @param name
* @param priority The priority to run the thread at. The value supplied must be from
* {@link android.os.Process} and not from java.lang.Thread.
*/
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}

/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
protected void onLooperPrepared() {
}

从源码可以看出HandlerThread继续自Thread,构造函数的传递参数有两个,一个是name指的是线程的名称,一个是priority指的是线程优先级,我们根据需要调用即可。其中成员变量mLooper就是HandlerThread自己持有的Looper对象。onLooperPrepared()该方法是一个空实现,是留给我们必要时可以去重写的,但是注意重写时机是在Looper循环启动前,再看看run方法:

@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll(); //唤醒等待线程
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}

前面我们在HandlerThread的常规使用中分析过,在创建HandlerThread对象后必须调用其start()方法才能进行其他操作,而调用start()方法后相当于启动了线程,也就是run方法将会被调用,而我们从run源码中可以看出其执行了Looper.prepare()代码,这时Looper对象将被创建,当Looper对象被创建后将绑定在当前线程(也就是当前异步线程),这样我们才可以把Looper对象赋值给Handler对象,进而确保Handler对象中的handleMessage方法是在异步线程执行的。接着将执行代码:

synchronized (this) {
mLooper = Looper.myLooper();
notifyAll(); //唤醒等待线程
}

这里在Looper对象创建后将其赋值给HandlerThread的内部变量mLooper,并通过notifyAll()方法去唤醒等待线程,*后执行Looper.loop();代码,开启looper循环语句。那这里为什么要唤醒等待线程呢?我们来看看,getLooper方法

public Looper getLooper() {
//先判断当前线程是否启动了
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();//等待唤醒
} catch (InterruptedException e) {
}
}
}
return mLooper;
}

事实上可以看出外部在通过getLooper方法获取looper对象时会先先判断当前线程是否启动了,如果线程已经启动,那么将会进入同步语句并判断Looper是否为null,为null则代表Looper对象还没有被赋值,也就是还没被创建,此时当前调用线程进入等待阶段,直到Looper对象被创建并通过 notifyAll()方法唤醒等待线程,*后才返回Looper对象,之所以需要等待唤醒机制,是因为Looper的创建是在子线程中执行的,而调用getLooper方法则是在主线程进行的,这样我们就无法保障我们在调用getLooper方法时Looper已经被创建,到这里我们也就明白了在获取mLooper对象时会存在一个同步的问题,只有当线程创建成功并且Looper对象也创建成功之后才能获得mLooper的值,HandlerThread内部则通过等待唤醒机制解决了同步问题。

public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}

从源码可以看出当我们调用quit方法时,其内部实际上是调用Looper的quit方法而*终执行的则是MessageQueue中的removeAllMessagesLocked方法(Handler消息机制知识点),该方法主要是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送)还是非延迟消息。
当调用quitSafely方法时,其内部调用的是Looper的quitSafely方法而*终执行的是MessageQueue中的removeAllFutureMessagesLocked方法,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理完成后才停止Looper循环,quitSafely相比于quit方法安全的原因在于清空消息之前会派发所有的非延迟消息。*后需要注意的是Looper的quit方法是基于API 1,而Looper的quitSafely方法则是基于API 18的。
好~,到此对于HandlerThread的所有分析就到此完结。

android多线程-AsyncTask之工作原理深入解析(下)

android多线程-AsyncTask之工作原理深入解析(下)

关联文章:
Android 多线程之HandlerThread 完全详解
Android 多线程之IntentService 完全详解
android多线程-AsyncTask之工作原理深入解析(上)
android多线程-AsyncTask之工作原理深入解析(下)

上篇分析AsyncTask的一些基本用法以及不同android版本下的区别,接着本篇我们就来全面剖析一下AsyncTask的工作原理。在开始之前我们先来了解一个多线程的知识点——Callable<V> 、Future<V>和FutureTask类

一、理解Callable<V> 、Future<V>以及FutureTask类

Callable<V>

Callable的接口定义如下:

public interface Callable<V> {   
      V   call()   throws Exception;   
}   

 

Callable接口声明了一个名称为call()的方法,该方法可以有返回值V,也可以抛出异常。Callable也是一个线程接口,它与Runnable的主要区别就是Callable在线程执行完成后可以有返回值而Runnable没有返回值,Runnable接口声明如下:

public interface Runnable {
    public abstract void run();
}

 

那么Callable接口如何使用呢,Callable需要和ExcutorService结合使用,其中ExecutorService也是一个线程池对象继承自Executor接口,对于线程池的知识点不了解可以看看我的另一篇文章,这里就不深入了,接着看看ExecutorService提供了那些方法供我们使用:

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

 

  • submit(Callable task),传递一个实现Callable接口的任务,并且返回封装了异步计算结果的Future。
  • submit(Runnable task, T result),传递一个实现Runnable接口的任务,并且指定了在调用Future的get方法时返回的result对象。
  • submit(Runnable task),传递一个实现Runnable接口的任务,并且返回封装了异步计算结果的Future。

因此我们只要创建好我们的线程对象(实现Callable接口或者Runnable接口),然后通过上面3个方法提交给线程池去执行即可。Callable接口介绍就先到这,再来看看Future时什么鬼。

Future<V>

Future接口是用来获取异步计算结果的,说白了就是对具体的Runnable或者Callable对象任务执行的结果进行获取(get()),取消(cancel()),判断是否完成等操作。其方法如下:

public interface Future<V> {
    //取消任务
    boolean cancel(boolean mayInterruptIfRunning);

    //如果任务完成前被取消,则返回true。
    boolean isCancelled();

    //如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回true。
    boolean isDone();

    //获取异步执行的结果,如果没有结果可用,此方法会阻塞直到异步计算完成。
    V get() throws InterruptedException, ExecutionException;

    // 获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,
    //如果阻塞时间超过设定的timeout时间,该方法将返回null。
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;

}

 

总得来说Future有以下3点作用:

  • 能够中断执行中的任务
  • 判断任务是否执行完成
  • 获取任务执行完成后额结果。

但是Future只是接口,我们根本无法将其创建为对象,于官方又给我们提供了其实现类FutureTask,这里我们要知道前面两个接口的介绍都只为此类做铺垫,毕竟AsncyTask中使用到的对象是FutureTask。

FutureTask

先来看看FutureTask的实现:

public class FutureTask<V> implements RunnableFuture<V> {  
  • 1

显然FutureTask类实现了RunnableFuture接口,我们再看一下RunnableFuture接口的实现:

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}
  • 1
  • 2
  • 3

从接口实现可以看出,FutureTask除了实现了Future接口外还实现了Runnable接口,因此FutureTask既可以当做Future对象也可是Runnable对象,当然FutureTask也就可以直接提交给线程池来执行。接着我们*关心的是如何创建FutureTask对象,实际上可以通过如下两个构造方法来构建FutureTask

public FutureTask(Callable<V> callable) {  
}  
public FutureTask(Runnable runnable, V result) {  
}  

 

从构造方法看出,我们可以把一个实现了Callable或者Runnable的接口的对象封装成一个FutureTask对象,然后通过线程池去执行,那么具体如何使用呢?简单案例,CallableDemo.java代码如下:


package com.zejian.Executor;
import java.util.concurrent.Callable;
/**
 * Callable接口实例 计算累加值大小并返回
 */
public class CallableDemo implements Callable<Integer> {

    private int sum;
    @Override
    public Integer call() throws Exception {
        System.out.println("Callable子线程开始计算啦!");
        Thread.sleep(2000);

        for(int i=0 ;i<5000;i++){
            sum=sum+i;
        }
        System.out.println("Callable子线程计算结束!");
        return sum;
    }
}

 

CallableTest.java测试代码如下:

package com.zejian.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
public class CallableTest {

public static void main(String[] args) {
//*种使用方式
//      //创建线程池
//      ExecutorService es = Executors.newSingleThreadExecutor();
//      //创建Callable对象任务
//      CallableDemo calTask=new CallableDemo();
//      //提交任务并获取执行结果
//      Future<Integer> future =es.submit(calTask);
//      //关闭线程池
//      es.shutdown();

    //第二中使用方式

    //创建线程池
    ExecutorService es = Executors.newSingleThreadExecutor();
    //创建Callable对象任务
    CallableDemo calTask=new CallableDemo();
    //创建FutureTask
    FutureTask<Integer> futureTask=new FutureTask<>(calTask);
    //执行任务
    es.submit(futureTask);
    //关闭线程池
    es.shutdown();
    try {
        Thread.sleep(2000);
    System.out.println("主线程在执行其他任务");

    if(futureTask.get()!=null){
        //输出获取到的结果
        System.out.println("futureTask.get()-->"+futureTask.get());
    }else{
        //输出获取到的结果
        System.out.println("futureTask.get()未获取到结果");
    }

    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println("主线程在执行完成");
}
}

 

代码非常简单,注释也很明朗,这里我们分析一下第2种执行方式,先前声明一个CallableDemo类,该类实现了Callable接口,接着通过call方法去计算sum总值并返回。然后在测试类CallableTest中,把CallableDemo实例类封装成FutureTask对象并交给线程池去执行,*终执行结果将封装在FutureTask中,通过FutureTask#get()可以获取执行结果。*种方式则是直接把Callable实现类丢给线程池执行,其结果封装在Future实例中,第2种方式执行结果如下:

Callable子线程开始计算啦!
主线程在执行其他任务
Callable子线程计算结束!
futureTask.get()-->12497500
主线程在执行完成

 

ok~,到此我们对Callable、Future和FutureTask就介绍到这,有了这个知识铺垫,我们就可以愉快的撩开AsyncTask的内部工作原理了。

二、AsyncTask的工作原理完全解析

在上篇中,使用了如下代码来执行AsyncTask的异步任务:

new AysnTaskDiff("AysnTaskDiff-1").execute("");
  • 1

从代码可知,入口是execute方法,那我们就先看看execute的源码:

    @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

 

很明显execute方法只是一个壳子,直接调用了executeOnExecutor(sDefaultExecutor, params),其中sDefaultExecutor是一个串行的线程池,接着看看sDefaultExecutor内部实现:

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
/**
 * An {@link Executor} that executes tasks one at a time in serial
 * order.  This serialization is global to a particular process.
 */
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

//串行线程池类,实现Executor接口
private static class SerialExecutor implements Executor {
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
    Runnable mActive;

    public synchronized void execute(final Runnable r) {
        mTasks.offer(new Runnable() { //插入一个Runnble任务
            public void run() {
                try {
                    r.run();
                } finally {
                    scheduleNext();
                }
            }
        });
        //判断是否有Runnable在执行,没有就调用scheduleNext方法
        if (mActive == null) {
            scheduleNext();
        }
    }

    protected synchronized void scheduleNext() {
      //从任务队列mTasks中取出任务并放到THREAD_POOL_EXECUTOR线程池中执行.
      //由此也可见任务是串行进行的。
        if ((mActive = mTasks.poll()) != null) {
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}

 

从源码可以看出,ArrayDeque是一个存放任务队列的容器(mTasks),任务Runnable传递进来后交给SerialExecutor的execute方法处理,SerialExecutor会把任务Runnable插入到任务队列mTasks尾部,接着会判断是否有Runnable在执行,没有就调用scheduleNext方法去执行下一个任务,接着交给THREAD_POOL_EXECUTOR线程池中执行,由此可见SerialExecutor并不是真正的线程执行者,它只是是保证传递进来的任务Runnable(实例是一个FutureTask)串行执行,而真正执行任务的是THREAD_POOL_EXECUTOR线程池,当然该逻辑也体现AsyncTask内部的任务是默认串行进行的。顺便看一下THREAD_POOL_EXECUTOR线程池的声明:

//CUP核数
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//核心线程数量
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
//*大线程数量
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
//非核心线程的存活时间1s
private static final int KEEP_ALIVE = 1;
//线程工厂类
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
    private final AtomicInteger mCount = new AtomicInteger(1);

    public Thread newThread(Runnable r) {
        return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
    }
};
//线程队列,核心线程不够用时,任务会添加到该队列中,队列满后,会去调用非核心线程执行任务
private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(128);

/**
 * An {@link Executor} that can be used to execute tasks in parallel.
 * 创建线程池
 */
public static final Executor THREAD_POOL_EXECUTOR
        = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

 

ok~,关于sDefaultExecutor,我们先了解到这,回到之前execute方法内部调用的executeOnExecutor方法的步骤,先来看看executeOnExecutor都做了些什么事?其源码如下:

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
   //判断在那种状态
   if (mStatus != Status.PENDING) {
       switch (mStatus) {
           case RUNNING:
               throw new IllegalStateException("Cannot execute task:"
                       + " the task is already running.");
           case FINISHED://只能执行一次!
               throw new IllegalStateException("Cannot execute task:"
                       + " the task has already been executed "
                       + "(a task can be executed only once)");
       }
   }

   mStatus = Status.RUNNING;
   //onPreExecute()在此执行了!!!
   onPreExecute();
   //参数传递给了mWorker.mParams
   mWorker.mParams = params;
   //执行mFuture任务,其中exec就是传递进来的sDefaultExecutor
   //把mFuture交给线程池去执行任务
   exec.execute(mFuture);

   return this;
    }

 

从executeOnExecutor方法的源码分析得知,执行任务前先会去判断当前AsyncTask的状态,如果处于RUNNING和FINISHED状态就不可再执行,直接抛出异常,只有处于Status.PENDING时,AsyncTask才会去执行。然后onPreExecute()被执行的,该方法可以用于线程开始前做一些准备工作。接着会把我们传递进来的参数赋值给 mWorker.mParams ,并执行开始执行mFuture任务,那么mWorker和mFuture到底是什么?先看看mWorker即WorkerRunnable的声明源码:

//抽象类
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {

    Params[] mParams;

}

 

WorkerRunnable抽象类实现了Callable接口,因此WorkerRunnable本质上也算一个Callable对象,其内部还封装了一个mParams的数组参数,因此我们在外部执行execute方法时传递的可变参数*终会赋值给WorkerRunnable的内部数组mParams,这些参数*后会传递给doInBackground方法处理,这时我们发现doInBackground方法也是在WorkerRunnable的call方法中被调用的,看看其源码如下:

public AsyncTask() {
   //创建WorkerRunnable mWorker,本质上就是一个实现了Callable接口对象
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            //设置标志
            mTaskInvoked.set(true);

         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            //执行doInBackground,并传递mParams参数
            Result result = doInBackground(mParams);
            Binder.flushPendingCommands();
            //执行完成调用postResult方法更新结果
            return postResult(result);
        }
    };
//把mWorker(即Callable实现类)封装成FutureTask实例
//*终执行结果也就封装在FutureTask中
    mFuture = new FutureTask<Result>(mWorker) {
        //任务执行完成后被调用
        @Override
        protected void done() {
            try {
             //如果还没更新结果通知就执行postResultIfNotInvoked
                postResultIfNotInvoked(get());
            } catch (InterruptedException e) {
                android.util.Log.w(LOG_TAG, e);
            } catch (ExecutionException e) {
                throw new RuntimeException("An error occurred while executing doInBackground()",
                        e.getCause());
            } catch (CancellationException e) {
                //抛异常
                postResultIfNotInvoked(null);
            }
        }
    };
}

 

可以看到在初始化AsyncTask时,不仅创建了mWorker(本质实现了Callable接口的实例类)而且也创建了FutureTask对象,并把mWorker对象封装在FutureTask对象中,*后FutureTask对象将在executeOnExecutor方法中通过线程池去执行。给出下图协助理解:
这里写图片描述

AsynTask在初始化时会创建mWorker实例对象和FutureTask实例对象,mWorker是一个实现了Callable线程接口并封装了传递参数的实例对象,然后mWorker实例会被封装成FutureTask实例中。在AsynTask创建后,我们调用execute方法去执行异步线程,其内部又直接调用了executeOnExecutor方法,并传递了线程池exec对象和执行参数,该方法内部通过线程池exec对象去执行mFuture实例,这时mWorker内部的call方法将被执行并调用doInBackground方法,*终通过postResult去通知更新结果。关于postResult方法,其源码如下:

private Result postResult(Result result) {
    @SuppressWarnings("unchecked")
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
    message.sendToTarget();
    return result;
}

 

显然是通过Handler去执行结果更新的,在执行结果成返回后,会把result封装到一个AsyncTaskResult对象中,*后把MESSAGE_POST_RESULT标示和AsyncTaskResult存放到Message中并发送给Handler去处理,这里我们先看看AsyncTaskResult的源码:

private static class AsyncTaskResult<Data> {
        final AsyncTask mTask;
        final Data[] mData;

        AsyncTaskResult(AsyncTask task, Data... data) {
            mTask = task;
            mData = data;
        }
    }

 

显然AsyncTaskResult封装了执行结果的数组以及AsyncTask本身,这个没什么好说的,接着看看AsyncTaskResult被发送到handler后如何处理的。

private static class InternalHandler extends Handler {
    public InternalHandler() {
        //获取主线程的Looper传递给当前Handler,这也是为什么AsyncTask只能在主线程创建并执行的原因
        super(Looper.getMainLooper());
    }

    @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
    @Override
    public void handleMessage(Message msg) {
    //获取AsyncTaskResult
        AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
        switch (msg.what) {
            //执行完成
            case MESSAGE_POST_RESULT:
                // There is only one result
                result.mTask.finish(result.mData[0]);
                break;
                //更新进度条的标志
            case MESSAGE_POST_PROGRESS:
            //执行onProgressUpdate方法,自己实现。
                result.mTask.onProgressUpdate(result.mData);
                break;
        }
    }
}

 

从Handler的源码分析可知,该handler绑定的线程为主线线程,这也就是为什么AsyncTask必须在主线程创建并执行的原因了。接着通过handler发送过来的不同标志去决定执行那种结果,如果标示为MESSAGE_POST_RESULT则执行AsyncTask的finish方法并传递执行结果给该方法,finish方法源码如下:

private void finish(Result result) {
        if (isCancelled()) {//判断任务是否被取消
            onCancelled(result);
        } else {//执行onPostExecute(result)并传递result结果
            onPostExecute(result);
        }
        //更改AsyncTask的状态为已完成
        mStatus = Status.FINISHED;
    }

 

该方法先判断任务是否被取消,如果没有被取消则去执行onPostExecute(result)方法,外部通过onPostExecute方法去更新相关信息,如UI,消息通知等。*后更改AsyncTask的状态为已完成。到此AsyncTask的全部流程执行完。
这里还有另一种标志MESSAGE_POST_PROGRESS,该标志是我们在doInBackground方法中调用publishProgress方法时发出的,该方法原型如下:

protected final void publishProgress(Progress... values) {
    if (!isCancelled()) {
    //发送MESSAGE_POST_PROGRESS,通知更新进度条
        getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                new AsyncTaskResult<Progress>(this, values)).sendToTarget();
    }
}

 

ok~,AsyncTask的整体流程基本分析完,*后来个总结吧:当我们调用execute(Params… params)方法后,其内部直接调用executeOnExecutor方法,接着onPreExecute()被调用方法,执行异步任务的WorkerRunnable对象(实质为Callable对象)*终被封装成FutureTask实例,FutureTask实例将由线程池sExecutor执行去执行,这个过程中doInBackground(Params… params)将被调用(在WorkerRunnable对象的call方法中被调用),如果我们覆写的doInBackground(Params… params)方法中调用了publishProgress(Progress… values)方法,则通过InternalHandler实例sHandler发送一条MESSAGE_POST_PROGRESS消息,更新进度,sHandler处理消息时onProgressUpdate(Progress… values)方法将被调用;*后如果FutureTask任务执行成功并返回结果,则通过postResult方法发送一条MESSAGE_POST_RESULT的消息去执行AsyncTask的finish方法,在finish方法内部onPostExecute(Result result)方法被调用,在onPostExecute方法中我们可以更新UI或者释放资源等。这既是AsyncTask内部的工作流程,可以说是Callable+FutureTask+Executor+Handler内部封装。结尾我们献上一张执行流程,协助大家理解整个流程:
这里写图片描述
好~,本篇到此结束。。。

android多线程-AsyncTask之工作原理深入解析(上)

android多线程-AsyncTask之工作原理深入解析(上)

关联文章:
Android 多线程之HandlerThread 完全详解
Android 多线程之IntentService 完全详解
android多线程-AsyncTask之工作原理深入解析(上)
android多线程-AsyncTask之工作原理深入解析(下)

前两篇我们分析android的异步线程类HandlerThread与IntentService,它们都是android系统独有的线程类,而android中还有另一个比较重要的异步线程类,它就是AsyncTask。本篇我们将从以下3点深入分析AsyncTask。

  • AsyncTask的常规使用分析以及案例实现
  • AsyncTask在不同android版本的下的差异
  • AsyncTask的工作原理流程

一、AsyncTask的常规使用分析以及案例实现

AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后会把执行的进度和*终结果传递给主线程并更新UI。AsyncTask本身是一个抽象类它提供了Params、Progress、Result 三个泛型参数,其类声明如下:

public abstract class AsyncTask<Params, Progress, Result> {

由类声明可以看出AsyncTask抽象类确实定义了三种泛型类型 Params,Progress和Result,它们分别含义如下:

  • Params :启动任务执行的输入参数,如HTTP请求的URL
  • Progress : 后台任务执行的百分比
  • Result :后台执行任务*终返回的结果类型

如果AsyncTask不需要传递具体参数,那么这三个泛型参数可以使用Void代替。好~,我们现在创建一个类继承自AsyncTask如下:

package com.zejian.handlerlooper;

import android.graphics.Bitmap;
import android.os.AsyncTask;

/**
 * Created by zejian
 * Time 16/9/4.
 * Description:
 */
public class DownLoadAsyncTask extends AsyncTask<String,Integer,Bitmap> {

    /**
     * onPreExecute是可以选择性覆写的方法
     * 在主线程中执行,在异步任务执行之前,该方法将会被调用
     * 一般用来在执行后台任务前对UI做一些标记和准备工作,
     * 如在界面上显示一个进度条。
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    /**
     * 抽象方法必须覆写,执行异步任务的方法
     * @param params
     * @return
     */
    @Override
    protected Bitmap doInBackground(String... params) {
        return null;
    }

    /**
     * onProgressUpdate是可以选择性覆写的方法
     * 在主线程中执行,当后台任务的执行进度发生改变时,
     * 当然我们必须在doInBackground方法中调用publishProgress()
     * 来设置进度变化的值
     * @param values
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
    }

    /**
     * onPostExecute是可以选择性覆写的方法
     * 在主线程中执行,在异步任务执行完成后,此方法会被调用
     * 一般用于更新UI或其他必须在主线程执行的操作,传递参数bitmap为
     * doInBackground方法中的返回值
     * @param bitmap
     */
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        super.onPostExecute(bitmap);
    }

    /**
     * onCancelled是可以选择性覆写的方法
     * 在主线程中,当异步任务被取消时,该方法将被调用,
     * 要注意的是这个时onPostExecute将不会被执行
     */
    @Override
    protected void onCancelled() {
        super.onCancelled();
    }
}

 

如代码所示,我们创建一个继承自AsyncTask的异步线程类,在泛型参数方面,传递String类型(Url) , Integer类型(显示进度),Bitmap类型作为返回值。接着重写了抽象方法doInBackground(),以及覆写了onPreExecute()、onProgressUpdate()、onPostExecute()、onCancelled()等方法,它们的主要含义如下:

  • (1)onPreExecute(), 该方法在主线程中执行,将在execute(Params… params)被调用后执行,一般用来做一些UI的准备工作,如在界面上显示一个进度条。
  • (2)doInBackground(Params…params), 抽象方法,必须实现,该方法在线程池中执行,用于执行异步任务,将在onPreExecute方法执行后执行。其参数是一个可变类型,表示异步任务的输入参数,在该方法中还可通过publishProgress(Progress… values)来更新实时的任务进度,而publishProgress方法则会调用onProgressUpdate方法。此外doInBackground方法会将计算的返回结果传递给onPostExecute方法。
  • (3)onProgressUpdate(Progress…),在主线程中执行,该方法在publishProgress(Progress… values)方法被调用后执行,一般用于更新UI进度,如更新进度条的当前进度。
  • (4)onPostExecute(Result), 在主线程中执行,在doInBackground 执行完成后,onPostExecute 方法将被UI线程调用,doInBackground 方法的返回值将作为此方法的参数传递到UI线程中,并执行一些UI相关的操作,如更新UI视图。
  • (5)onCancelled(),在主线程中执行,当异步任务被取消时,该方法将被调用,要注意的是这个时onPostExecute将不会被执行。

我们这里再强调一下它们的执行顺序,onPreExecute方法先执行,接着是doInBackground方法,在doInBackground中如果调用了publishProgress方法,那么onProgressUpdate方法将会被执行,*后doInBackground方法执行后完后,onPostExecute方法将被执行。说了这么多,我们还没说如何启动AsyncTask呢,其实可以通过execute方法启动异步线程,其方法声明如下:

public final AsyncTask<Params, Progress, Result> execute(Params... params)
  • 1

该方法是一个final方法,参数类型是可变类型,实际上这里传递的参数和doInBackground(Params…params)方法中的参数是一样的,该方法*终返回一个AsyncTask的实例对象,可以使用该对象进行其他操作,比如结束线程之类的。启动范例如下:

new DownLoadAsyncTask().execute(url1,url2,url3);
  • 1

当然除了以上介绍的内容外,我们在使用AsyncTask时还必须遵守一些规则,以避免不必要的麻烦。

  • (1) AsyncTask的实例必须在主线程(UI线程)中创建 ,execute方法也必须在主线程中调用
  • (2) 不要在程序中直接的调用onPreExecute(), onPostExecute(Result),doInBackground(Params…), onProgressUpdate(Progress…)这几个方法
  • (3) 不能在doInBackground(Params… params)中更新UI
  • (5) 一个AsyncTask对象只能被执行一次,也就是execute方法只能调用一次,否则多次调用时将会抛出异常

到此,AsyncTask的常规方法说明和使用以及注意事项全部介绍完了,下面我们来看一个下载案例,该案例是去下载一张大图,并实现下载实时进度。先来看看AsynTaskActivity.java的实现:

package com.zejian.handlerlooper;

import android.content.Context;
import android.os.AsyncTask;
import android.os.Environment;
import android.os.PowerManager;
import android.widget.Toast;

import com.zejian.handlerlooper.util.LogUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * Created by zejian
 * Time 16/9/4.
 * Description:
 */
public class DownLoadAsyncTask extends AsyncTask<String, Integer, String> {
    private PowerManager.WakeLock mWakeLock;
    private int ValueProgress=100;
    private Context context;


    public DownLoadAsyncTask(Context context){
        this.context=context;
    }

    /**
     * sync method which download file
     * @param params
     * @return
     */
    @Override
    protected String doInBackground(String... params) {
        InputStream input = null;
        OutputStream output = null;
        HttpURLConnection connection = null;
        try {
            URL url = new URL(params[0]);
            connection = (HttpURLConnection) url.openConnection();
            connection.connect();
            // expect HTTP 200 OK, so we don't mistakenly save error report
            // instead of the file
            if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                return "Server returned HTTP " + connection.getResponseCode()
                        + " " + connection.getResponseMessage();
            }
            // this will be useful to display download percentage
            // might be -1: server did not report the length
            int fileLength = connection.getContentLength();
            // download the file
            input = connection.getInputStream();
            //create output
            output = new FileOutputStream(getSDCardDir());
            byte data[] = new byte[4096];
            long total = 0;
            int count;
            while ((count = input.read(data)) != -1) {
                // allow canceling with back button
                if (isCancelled()) {
                    input.close();
                    return null;
                }
                total += count;
                // publishing the progress....
                if (fileLength > 0) // only if total length is known
                    publishProgress((int) (total * 100 / fileLength));
                //
                Thread.sleep(100);
                output.write(data, 0, count);
            }
        } catch (Exception e) {
            return e.toString();
        } finally {
            try {
                if (output != null)
                    output.close();
                if (input != null)
                    input.close();
            } catch (IOException ignored) {
            }
            if (connection != null)
                connection.disconnect();
        }

        return null;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        // take CPU lock to prevent CPU from going off if the user
        // presses the power button during download
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                getClass().getName());
        mWakeLock.acquire();
        //Display progressBar
//        progressBar.setVisibility(View.VISIBLE);
    }

    @Override
    protected void onPostExecute(String values) {
        super.onPostExecute(values);
        mWakeLock.release();
        if (values != null)
            LogUtils.e("Download error: "+values);
        else {
            Toast.makeText(context, "File downloaded", Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * set progressBar
     * @param values
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
//        progressBar.setmProgress(values[0]);
        //update progressBar
        if(updateUI!=null){
            updateUI.UpdateProgressBar(values[0]);
        }
    }

    /**
     * get SD card path
     * @return
     */
    public File getSDCardDir(){
        if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){
            // 创建一个文件夹对象,赋值为外部存储器的目录
            String dirName = Environment.getExternalStorageDirectory()+"/MyDownload/";
            File f = new File(dirName);
            if(!f.exists()){
                f.mkdir();
            }
            File downloadFile=new File(f,"new.jpg");
            return downloadFile;
        }
        else{
            LogUtils.e("NO SD Card!");
            return null;

        }

    }

    public UpdateUI updateUI;


    public interface UpdateUI{
        void UpdateProgressBar(Integer values);
    }

    public void setUpdateUIInterface(UpdateUI updateUI){
        this.updateUI=updateUI;
    }
}

 

简单说明一下代码,在onPreExecute方法中,可以做了一些准备工作,如显示进度圈,这里为了演示方便,进度圈在常态下就是显示的,同时,我们还锁定了CPU,防止下载中断,而在doInBackground方法中,通过HttpURLConnection对象去下载图片,然后再通过int fileLength =connection.getContentLength();代码获取整个下载图片的大小并使用publishProgress((int) (total * 100 / fileLength));更新进度,进而调用onProgressUpdate方法更新进度条。*后在onPostExecute方法中释放CPU锁,并通知是否下载成功。接着看看Activity的实现:
activity_download.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:customView="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.zejian.handlerlooper.util.LoadProgressBarWithNum
        android:id="@+id/progressbar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        customView:progress_radius="100dp"
        android:layout_centerInParent="true"
        customView:progress_strokeWidth="40dp"
        customView:progress_text_size="35sp"
        customView:progress_text_visibility="visible"
        customView:progress_value="0"
        />

    <Button
        android:id="@+id/downloadBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="start download"
        android:layout_centerHorizontal="true"
        android:layout_below="@id/progressbar"
        android:layout_marginTop="40dp"
        />
</RelativeLayout>

 

AsynTaskActivity.java

package com.zejian.handlerlooper;

import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.view.View;
import android.widget.Button;

import com.zejian.handlerlooper.util.LoadProgressBarWithNum;
import com.zejian.handlerlooper.util.LogUtils;

/**
 * Created by zejian
 * Time 16/9/4.
 * Description:AsynTaskActivity
 */
public class AsynTaskActivity extends Activity implements DownLoadAsyncTask.UpdateUI {
    private static int WRITE_EXTERNAL_STORAGE_REQUEST_CODE=0x11;
    private static String DOWNLOAD_FILE_JPG_URL="http://img2.3lian.com/2014/f6/173/d/51.jpg";
    private LoadProgressBarWithNum progressBar;

    private Button downloadBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_download);
        progressBar= (LoadProgressBarWithNum) findViewById(R.id.progressbar);
        downloadBtn= (Button) findViewById(R.id.downloadBtn);
        //create DownLoadAsyncTask
        final DownLoadAsyncTask  downLoadAsyncTask= new DownLoadAsyncTask(AsynTaskActivity.this);
        //set Interface
        downLoadAsyncTask.setUpdateUIInterface(this);
        //start download
        downloadBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //execute
                downLoadAsyncTask.execute(DOWNLOAD_FILE_JPG_URL);
            }
        });

        //android 6.0 权限申请
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            //android 6.0 API 必须申请WRITE_EXTERNAL_STORAGE权限
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    WRITE_EXTERNAL_STORAGE_REQUEST_CODE);
        }
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        doNext(requestCode,grantResults);
    }

    private void doNext(int requestCode, int[] grantResults) {
        if (requestCode == WRITE_EXTERNAL_STORAGE_REQUEST_CODE) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // Permission Granted
                LogUtils.e("Permission Granted");
            } else {
                // Permission Denied
                LogUtils.e("Permission Denied");
            }
        }
    }

    /**
     * update progressBar
     * @param values
     */
    @Override
    public void UpdateProgressBar(Integer values) {
        progressBar.setmProgress(values);;
    }
}

 

在AsynTaskActivity中实现了更新UI的接口DownLoadAsyncTask.UpdateUI,用于更新主线程的progressBar的进度,由于使用的测试版本是android6.0,涉及到外部SD卡读取权限的申请,所以在代码中对SD卡权限进行了特殊处理(这点不深究,不明白可以google一下),LoadProgressBarWithNum是一个自定义的进度条控件。ok~,*后看看我们的运行结果:
%title插图%num
效果符合预期,通过这个案例,相信我们对AsyncTask的使用已相当清晰了。基本使用到此,然后再来聊聊AsyncTask在不同android版本中的差异。

二、AsyncTask在不同android版本的下的差异

这里我们主要区分一下android3.0前后版本的差异,在android 3.0之前,AsyncTask处理任务时默认采用的是线程池里并行处理任务的方式,而在android 3.0之后 ,为了避免AsyncTask处理任务时所带来的并发错误,AsyncTask则采用了单线程串行执行任务。但是这并不意味着android 3.0之后只能执行串行任务,我们仍然可以采用AsyncTask的executeOnExecutor方法来并行执行任务。接下来,编写一个案例,分别在android 2.3.3 和 android 6.0上执行,然后打印输出日志。代码如下:

package com.zejian.handlerlooper;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import com.zejian.handlerlooper.util.LogUtils;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Created by zejian
 * Time 16/9/5.
 * Description:
 */
public class ActivityAsyncTaskDiff extends Activity {
    private Button btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_async_diff);
        btn= (Button) findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new AysnTaskDiff("AysnTaskDiff-1").execute("");
                new AysnTaskDiff("AysnTaskDiff-2").execute("");
                new AysnTaskDiff("AysnTaskDiff-3").execute("");
                new AysnTaskDiff("AysnTaskDiff-4").execute("");
                new AysnTaskDiff("AysnTaskDiff-5").execute("");
            }
        });
    }

    private static class AysnTaskDiff extends AsyncTask<String ,Integer ,String>{
        private String name;
        public AysnTaskDiff(String name){
            super();
            this.name=name;
        }

        @Override
        protected String doInBackground(String... params) {
            try {
                Thread.sleep(2000);
            }catch (Exception ex){
                ex.printStackTrace();
            }

            return name;
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            LogUtils.e(s+" execute 执行完成时间:"+df.format(new Date()));
        }
    }

}

 

案例代码比较简单,不过多分析,我们直接看在android 2.3.3 和 android 6.0上执行的结果,其中android 2.3.3上执行Log打印如下:
%title插图%num
在 android 6.0上执行Log打印如下:
%title插图%num
从打印log可以看出AsyncTask在android 2.3.3上确实是并行执行任务的,而在 android 6.0上则是串行执行任务。那么了解这点有什么用呢?其实以前我也只是知道这回事而已,不过*近在SDK开发中遇到了AsyncTask的开发问题,产生问题的场景是这样的,我们团队在SDK中使用了AsyncTask作为网络请求类,因为现在大部分系统都是在Android 3.0以上的系统运行的,所以默认就是串行运行,一开始SDK在海外版往外提供也没有出现什么问题,直到后面我们提供国内一个publisher海外版本时,问题就出现了,该publisher接入我们的SDK后,他们的应用网络加载速度变得十分慢,后来他们一直没排查出啥问题,我们这边也在懵逼中……直到我们双方都找到一个点,那就是publisher的应用和我们的SDK使用的都是AsyncTask作为网络请求,那么问题就来,我们SDK是在在Application启动时触发网络的,而他们的应用也是启动Activity时去访问网络,所以SDK比应用先加载网络数据,但是!!!AsyncTask默认是串行执行的,所以!!只有等我们的SDK网络加载完成后,他们应用才开始加载网络数据,这就造成应用的网络加载延迟十分严重了。后面我们SDK在内部把AsyncTask改为并行任务后问题也就解决了(当然这也是SDK的一个BUG,考虑欠佳)。在Android 3.0之后我们可以通过下面代码让AsyncTask执行并行任务,其AsyncTask.THREAD_POOL_EXECUTOR为AsyncTask的内部线程池。

new AysnTaskDiff("AysnTaskDiff-5").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"");
  • 1

*个参数传递是线程池,一般使用AsyncTask内部提供的线程池即可(也可以自己创建),第二个参数,就是*终会传递给doInBackground方法的可变参数,这里不传,所以直接给了空白符。执行效果就不再演示了,大家可以自行测试一下。
ok~,到此AsyncTask在不同android版本中的差异也分析完,感觉文章有点长了,那么AsyncTask工作原理分析就放到下篇吧。

关于Virtual Private Server

我现在放在美国的,访问大陆意外很快,但是访问大陆很慢。大家觉得放在香港会不会改善?
访问 大陆 virtual15 条回复 • 1970-01-01 08:00:00 +08:00
andyliu 1
andyliu 2012-06-08 14:57:54 +08:00
会 hk看ping值不错,但是打开速度一般。

带宽很一般
Zhang 2
Zhang 2012-06-08 14:58:31 +08:00
@andyliu 我要疯了
qiuai 3
qiuai 2012-06-08 15:03:26 +08:00
一般来说,香港的带宽是足够的.只要你不是用来做什么特殊的服务..联系我吧.Q:39831817
shiny 4
shiny 2012-06-08 15:04:42 +08:00
“访问大陆意外很快,但是访问大陆很慢”
看上去这句话自相矛盾么,看不懂。
Zhang 5
Zhang 2012-06-08 15:07:31 +08:00
@shiny 访问大陆以外的网站很快,访问大陆以内的网站很慢
finler 6
finler 2012-06-08 15:46:19 +08:00
我猜lz其实是用来做*的。
考虑用这个 code.google.com/p/chnroutes/
tension 7
tension 2012-06-08 15:54:55 +08:00
放国内吧
Zhang 8
Zhang 2012-06-08 15:57:53 +08:00
@finler 我说错了,是virtual private network
Zhang 9
Zhang 2012-06-08 15:58:17 +08:00
所以怎么可能放在国内呢?
welsmann 10
welsmann 2012-06-08 16:06:07 +08:00
是敏感词的原因么。。。为什么要写这么长的全名呢。。。。一眼愣是没看明白是啥。。
Zhang 11
Zhang 2012-06-08 16:07:53 +08:00
@welsmann 害怕是
Zhang 12
Zhang 2012-06-08 16:22:04 +08:00
@finler 貌似不支持L2TP呀
Zhang 13
Zhang 2012-06-08 16:22:40 +08:00
PPTP容易受到当局干扰,我已经受到过了
finler 14
finler 2012-06-09 09:42:57 +08:00
@Zhang 有人说可以l2tp,还得自己试试。pptp穿越性能一般,现在很多地方收紧了,即使拨上去也无法翻。
不知道pptp和l2能够共存。
Zhang 15
Zhang 2012-06-09 09:58:18 +08:00
@finler 目前pptp还能穿,不过打算义无反顾奔L2TP

ubuntu机器单网卡多IP问题

现有台ubuntu机器,单网卡上配置了多个IP,比如
192.168.10.51 – 59
怎么可以设定某个程序使用某个特定的IP地址发送/接受包?

当然不考虑改动程序代码的方法
网卡 Ubuntu IP22 条回复 • 1970-01-01 08:00:00 +08:00
liwei 1
liwei 2012-06-08 22:47:33 +08:00
如果程序通信的地址是固定,可以试试加一条路由:

ip route add DEST_ADDRESS via 192.168.10.51 dev eth0

PS: 不太清楚为什么会有这样的需求,能详细说说你需要这样配置的场景么?
deerlamp 2
deerlamp 2012-06-08 22:58:59 +08:00
@liwei 比如我从这台机器访问别的机器,想让对方看到我是在某个ip上的
liwei 3
liwei 2012-06-08 23:08:01 +08:00
@deerlamp 那你就只配置一个IP呗,干嘛配置那么多
deerlamp 4
deerlamp 2012-06-08 23:24:46 +08:00
@liwei …但是其他ip有其他的需要啊。。
kendisk 5
kendisk 2012-06-08 23:37:13 +08:00 ❤️ 1
开虚拟机,一个虚拟机可以给 128内存,装上linux,可以有很多了、
ljbha007 6
ljbha007 2012-06-08 23:42:29 +08:00
其实你可以把你的问题和要实现的功能说得更具体一点 我感觉你说的办法不一定是*好的办法 但是前提是得知道你具体的需求
deerlamp 7
deerlamp 2012-06-08 23:43:49 +08:00
@kendisk 这方法不错
deerlamp 8
deerlamp 2012-06-08 23:47:11 +08:00
@ljbha007 比如我在这台机器上开个客户端程序访问某服务器,但让对方服务器看到的是这个客户端程序是从某个特定的ip地址(这里比如是192.168.10.56,但该机器单网卡绑定了51-59个多个ip)访问的
服务器和该ubuntu机器是在同个lan里面
ljbha007 9
ljbha007 2012-06-08 23:51:48 +08:00
@deerlamp 既然是同一台机器发出来的请求 为什么要分别从不同的IP发送呢? 如果IP仅仅是一个区分功能用的参数的话那完全没必要这么做;比如如果是HTTP协议可以通过在请求头中加入额外的参数或者直接在请求方法的参数中加入特定的参数

所以还是不清楚为什么你要这么做
deerlamp 10
deerlamp 2012-06-09 00:24:08 +08:00
@ljbha007 可能从ip地址的安全性上来考虑 不想暴露主ip地址
ljbha007 11
ljbha007 2012-06-09 01:11:58 +08:00
@deerlamp 那直接用nginx做反向代理代理好了
deerlamp 12
deerlamp 2012-06-09 04:48:42 +08:00
@ljbha007 但如果都在一台机器上的话,反向代理也可以做到隐藏IP吗?
flyingnn 13
flyingnn 2012-06-09 09:59:48 +08:00
用iptable试试行不?
humiaozuzu 14
humiaozuzu 2012-06-09 10:16:25 +08:00
http://superuser.com/questions/241178/how-to-use-different-network-interfaces-for-different-processes
这里有linux下解决方案,不过我没有发现现成的程序,mac下倒是有很多。
ljbha007 15
ljbha007 2012-06-09 12:25:44 +08:00
@deerlamp 不好意思 我看错了 我把“主IP地址”看成了“主服务器IP地址”
ljbha007 16
ljbha007 2012-06-09 13:21:36 +08:00
@deerlamp 通过一个ifconfig 可以为一个网卡获取多个IP

http://linux.byexamples.com/archives/111/configure-multiple-ip-for-a-same-network-interface/

但是要指定程序使用哪个网络界面必须要程序有相关的参数设置才行 如果那个程序是写死的使用某一个固定界面或者系统默认路由的话 那就没有办法了 只能开虚拟机
deerlamp 17
deerlamp 2012-06-09 13:42:54 +08:00
@ljbha007 谢谢 权衡到现在 也是觉得虚拟机的方法更靠谱
搜了一下ubuntu下有KVM或XEN两种开源方案,网上都说KVM是未来方向,但据我了解KVM是全虚拟化,安全性更好,但就性能和稳定还不及XEN,v2ex上已有这方面的讨论吗?
另外对于KVM,ubuntu 10.04和12.04的内核对KVM的支持有多大的不同?
ljbha007 18
ljbha007 2012-06-09 15:43:11 +08:00
@deerlamp 不知道
kfc315 19
kfc315 2012-06-09 15:57:10 +08:00
传说中的 address minting 么 = =
我觉得 @liwei 说得靠谱。
haijd 20
haijd 2012-06-09 16:05:52 +08:00
这个问题的*佳办法是,看你那个客户端软件有没有绑定ip的功能。
deerlamp 21
deerlamp 2012-06-09 16:14:17 +08:00
@haijd 有的可以 有的不可以而且非开源。。。而且目的服务器有多个,IP也未知,不好设定路由表;所以目前我在考虑弄虚拟机
haijd 22
haijd 2012-06-09 17:48:30 +08:00
@deerlamp 这种情况基本上只能用虚拟机了

有人用过 SingleHop 的服务器么?

感觉怎样?
SingleHop 服务器 用过14 条回复 • 1970-01-01 08:00:00 +08:00
eric_q 1
eric_q 2012-05-24 12:50:37 +08:00
用过一个月的芝加哥的,ping值300左右,走nlayer线路
Livid 2
Livid V2EX Moderator 2012-05-25 06:33:48 +08:00
起步价格比 Linode 高,但是貌似*低一级的套餐都提供了 3T 的流量。
rhwood 3
rhwood 2012-05-25 09:11:12 +08:00
@Livid 大流量不靠谱的多了去了,SingleHop?Stay away
coagent 4
coagent 2012-05-25 09:16:17 +08:00
稳定优先,流量不是越大越好。
Livid 5
Livid V2EX Moderator 2012-05-25 09:20:12 +08:00
@coagent
@rhwood 你们觉得比较稳定的 US hosting 有?
coagent 6
coagent 2012-05-25 11:12:33 +08:00
@Livid
US: MT、Linode、Rackspace 的都比较稳定
tension 7
tension 2012-05-25 11:14:19 +08:00
@Livid 回国吧!!
HiVPS 8
HiVPS 2012-05-25 11:57:39 +08:00
@Livid 有一个不错的机房,怎么PM你?
muxi 9
muxi 2012-05-25 13:18:16 +08:00
@Livid *近你又是搭建云产品,又是看各个主机商,你不是想在美国搞个类Linode来服务国内的P民吧?
如果是的话,我愿意成为首批付费/测试用户
Showfom 10
Showfom 2012-05-25 13:32:27 +08:00 ❤️ 6
@Livid 作为资深的国外数据中心专家(自称的呵呵),和欧美亚洲各国大大小小十多个机房打过交道,我来说说我心目中美国机房的排名

在不考虑国内访问速度的情况下:

1、Softlayer

2、LiquidWeb

3、Gigenet

4、Peer1

5、Layered Tech

6、Midphase

7、SingleHop

欧洲的:

1、LeaseWeb

2、OVH

不要去使用的美国机房:

1、Ubiquityservers

2、HE.net

有大量国人的机房(看自己选择了,这些机房还是有重视国内客户的,毕竟是帮他们赚钱的大头)

1、Krypt

2、QuadraNet

3、Psychz

4、FDCServers

对电信线路好的机房:

1、Peer1

2、He.net

3、QuadraNet

4、Krypt

5、Psychz

对网通好的机房:

对电信网通都好的机房:


Showfom 11
Showfom 2012-05-25 13:39:58 +08:00 ❤️ 4
美国的机房内幕太多,我还在做这行也不能明了说

简单的说吧,大头的资源都是在大型的网络服务商(ISP)或者大型的做实业起家的公司(大多是房地产的,对于机房有天然的优势)

然后他们自己建立机房(是真正的机房,基于地皮的建立),建立好以后,从带宽到房间到机柜成批的批发给各个小型的服务器提供商

在上述所说的服务器提供商里,真正有自己实力建立一整个机房的有:

1、Softlayer

2、 He.net

3、Peer1

4、LeaseWeb (他们把整个荷兰电信的机房给租用了)

5、OVH

6、LiquidWeb

其他的小型服务器提供商大多是租用几个机柜,或者一整排机柜,或者一个单独的房间放满机柜,然后自己拉线路自己申请IP,直接是看不出来他们和上述大型服务商的区别的

很多美国的机房都提供这种服务,比较出名的有CoreSite、He、Cogent、Colo4、ColoAt,等等等等
TONYHEAD 12
TONYHEAD 2012-06-15 13:01:16 +08:00
@Showfom 请问下 ThePlanet.com Internet Services, Inc. 属于哪一个范围的呢?谢谢。
Showfom 13
Showfom 2012-06-17 01:25:22 +08:00 ❤️ 1
@TONYHEAD 被 Softlayer 买去了,都属于 Softlayer 的网络
csx163 14
csx163 2012-06-17 02:36:08 +08:00
兽兽?给力啊

关于网络地址转换

我*近终于搞清楚路由器的作用了,原来路由器是用来连接两层网络的。我发现给某个远程服务器发包时,途中经过n个路由器,也就是说途中要经过n-1层网络,每穿越一层网络,ip包的地址都要改写一次,这样怪不得网络延时会很高了。要是ipv6普及后,我们是不是可以不需要这么多层网络,所有的网络设备都在一层网络上,这样数据包就不需要穿越n层网络了,这样就节省了ip包地址改写的时间,那时候网络的延时就会下降到很可观,对不对?真是好憧憬呀!大家积*畅想呀!
网络 路由器 IP12 条回复 • 1970-01-01 08:00:00 +08:00
Zhang 1
Zhang 2012-06-18 14:59:27 +08:00
个人感觉多层网络简直就是灾难呀?这种网络根本不是平的!
lululau 2
lululau 2012-06-18 15:01:22 +08:00
路由器主要功能是还路由吧。。。。
dndx 3
dndx 2012-06-18 15:11:43 +08:00
LZ你的理解是不正确的,如果你没有公网IP,边界路由器需要做SNAT才能保证你的数据包能正确回传。一旦数据包的IP被改写为公网IP后,在互联网的传输过程中,中继路由器不会再改动你的源IP而只负责接力传输。如果真的像你说的那样,那么互联网就根本不可能实现,因为同时传输的数据量大到无法估计,如果每一跳都做SNAT,那么没有任何一个路由器能存储这么多的链接信息。
iyten 4
iyten 2012-06-18 15:24:22 +08:00
我就觉得奇怪。难道说ttl没减1那么ip就被改动一次?这个是不可能的。在公网上传输ip 要nat的估计就是isp的安全措施了。比如说电信骨干网上不可能大量出现铁通的ip,如果有那就说明有问题了,那么这时候就会nat,还有企业内部出去的话也是要做nat的。欢迎下面继续喷。
013231 5
013231 2012-06-18 15:25:56 +08:00
樓主的理解是完全錯誤的.
1. IP報文的源IP和目標IP在傳輸時通常不會變化(也有例外, 比如NAT).
2. 路由節點間不是”分層”關係.
3. IPv6對減少路由節點的數量沒有明顯幫助.
clowwindy 6
clowwindy 2012-06-18 15:39:27 +08:00
楼主的理解适用于长城宽带和宽带通 XD
blank_dlh 7
blank_dlh 2012-06-18 15:56:00 +08:00
不是每层都NAT的亲。。
局域网和公网(相对来说)连接处的路由做NAT,公网上的路由节点只是做转发用的。
ichigo 8
ichigo 2012-06-18 16:28:32 +08:00
其实你还是没明白路由功能..
Zhang 9
Zhang 2012-06-18 17:42:45 +08:00
@ichigo 你明白吗?
Zhang 10
Zhang 2012-06-18 17:49:42 +08:00
每经过一次NAT,应该说目的地ip不会变,而来源地ip会变的
billryan 11
billryan 2012-06-18 18:21:03 +08:00
IPV6的出现主要是为了解决IPV4地址不够的问题,NAT正是由于IPV4地址不够才出现的临时性解决方案,但是NAT违背了网络设计的初衷(分层结构设计)。IPV6普及后NAT后慢慢消失,确实节约了地址映射的那部分时间,但这与路由器没有直接的关系,路由器主要的功能是根据目的地址进行路由选路(是工作在第三层(网络层)的网络设备)。NAT只适用于那些无法分配到足够公网IP地址的场合,而且这是由一个叫做「NAT盒」来干的事,与路由器没半毛钱关系… 建议楼主看看潘爱民翻译的《计算机网络》补一补网络方面的知识
dndx 12
dndx 2012-06-18 18:32:24 +08:00
@Zhang 正如 @billryan 所说,SNAT是为了解决IP地址不足的问题,比如你有一个IP地址192.168.1.2,你要请求8.8.8.8一些信息,如果没有路由器帮你做SNAT,你的数据包【有可能】被8.8.8.8收到,【但是】8.8.8.8不知道怎么将包回复给192.168.1.2,这种目标地址是私有地址的数据包在Internet上是无意义的,会直接被BGP路由器丢弃。

同 @ichigo ,建议LZ补一补基础知识…

关于路由分析

我从重庆联通发到美国的ip数据包离开重庆后就到达广西南宁,再到达广东。这时候我以为我的数据包要出境了,不过蹊跷的是我的数据包被发到北京后才出境。光是从广东到北京就花了300ms。我的神呀!大家帮忙分析分析。
数据包 发到 出境6 条回复 • 1970-01-01 08:00:00 +08:00
hu437 1
hu437 2012-06-18 22:44:00 +08:00
这没啥好分析的,你的电信运营商的路由问题,我们决定不了,分析了也没用呀
Zhang 2
Zhang 2012-06-18 22:52:30 +08:00
为什么不能直接从广东出境呀?
derekwei 3
derekwei 2012-06-18 22:54:43 +08:00
@Zhang 这个是ISP路由设置的问题,你是对此是无能为力的。又一次我空间的服务商路由设置错误,线路去欧洲饶了一圈又回到了美国。。。。。。。。。。。。。。
Zhang 4
Zhang 2012-06-18 22:56:49 +08:00
不晓得随着时间的推移,能不能尽量抄近道!
hu437 5
hu437 2012-06-19 13:42:05 +08:00
能不能抄近道要看ISP的路由设置了,比如现在有ABCDEF几个节点,
a->b->e->f这是一条线
a->d->e->f这是一条线
a->b->c->e->f这也是一条线
a->f这也是一条线

明显3*长,4*短,这就是路由规则,但是这个规则不是我们可以定义的,这些是由ISP来定义的,如果某一天ISP做了优化,就可能将3调到4
johnnie502 6
johnnie502 2012-06-27 16:22:47 +08:00
在ISP那边怎么走多数情况是由经济成本决定的,而不是效率….

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速