本章知识点

  • JDK内置系统注解
  • 元注释以及自定义注释
  • 注解解析器实现
  • Android中的专用注解

JDK内置系统注解

Java提供了三种内建注解。

@Override
这个估计开发中是最常见的一个注解了,它用于表示所注解的方法是一个用于覆盖父类的一个方法,一旦存在任何的拼写错误导致方法签名对不上被覆盖的方法,编译器就会发出错误提示。
@Deprecated
这个一般用于标记某个方法不在使用了,通过这个注释,开发者可以了解到哪些接口是处于遗弃状态的。
@SuppressWarnings
这个注释用于忽视某个警告信息。

元注释以及自定义注释

Java中提供了四种元注释用于定义自定义注释,如下所示:

@Target 表示该注解可以用在什么地方,由ElementType枚举定义 ,当注解未指定Target值时,该注解可以使用任何元素之上

PACKAGE:包声明
TYPE:类、接口 注解类型,enum声明
ANNOTATION_TYPE:注解声明(应用于另一个注解上)
FIELD:域声明(包括enum实例)
CONSTRUCTOR:构造器的声明
METHOD:方法声明
LOCAL_VARIABLE:局部变量声明
PARAMETER:参数声明
TYPE_PARAMETER:类型参数声明(1.8新加入)
TYPE_USE:类型使用声明(1.8新加入)

@Retention 表示需要在什么级别保存该注解信息,由RetentionPolicy枚举定义,当注解未定义Retention值时,默认值是CLASS

SOURCE:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里)
CLASS:注解在class文件中可用,但会被VM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机(JVM)中)
RUNTIMEVM将在运行期也保留注解信息,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息)

@Documented 表示注解会被包含在javaApi文档中
@Inherited 允许子类继承父类的注解,表示注解类型能被自动继承。 如果一个类使用了 @Inherited 类型的注解,则此类的子类也将含有该注解。

自定义注解语法:

import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface AnatationTest {
}

需要注意的是注解也将会编译成class文件,我们通过javac将上述的注解编译为字节码,然后使用javap对其进行反编译,将会得到如下的代码:

public interface AnatationTest extends java.lang.annotation.Annotation {
}

也就是说使用@interface 关键字声明一个注解,它会自动继承 java.lang.annotation.Annotaion 接口。

自定义注解的时候需要注意如下几点:

  • 参数成员访问修饰符只能使用 public 或者 default 这个和枚举是一样的
  • 注解方法不能有参数。
  • @interface 里面的每一个方法表示声明了一个可配置的参数,方法名即位参数名。返回值类型就是参数的类型,下面是能够允许的类型:
    所有基本类型(int,float,boolean,byte,double,char,long,short
    String
    Class
    enum
    Annotation
    以及上述类型所组成的 数组
  • 如果需要声明默认值可以使用default 关键字。
  • 注解元素必须有确定的值,要么在定义注解元素时默认值指定,要么使用此注解时指定。非基本类型注解元素的值不可为 null

注解解析器实现

注释的解析有两种方式:

  • 运行时解析
    运行时注解指的是@Retention的值为RUNTIME的注解。java.lang.reflect包中有一个AnnotatedElement接口,这个接口定义了用于获取注解信息的几个方法:
    getAnnotation(Class annotationClass) 当存在该元素的指定类型注解,则返回相应注释,否则返回null
    getAnnotations() 返回此元素上存在的所有注解
    getDeclaredAnnotations() 返回直接存在于此元素上的所有注解。
    isAnnotationPresent(Class<? extends Annotation> annotationClass) 当存在该元素的指定类型注解,则返回true,否则返回false
    使用这些接口就可以在运行时解析对应的注释。

下面是运行时解析的例子:

@Target(ElementType.PACKAGE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PackageAnnotation {
String desc() default "";
}


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeAnnotation {
String desc() default "";
}


@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.RUNTIME)
public @interface ConstructAnnotaion {
String desc() default "";

}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldAnnotation {
String desc() default "";
}


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodAnnotation {
String desc() default "";
}

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface ParamAnnotation {
String desc() default "";
}

@TypeAnnotation(desc = "This is TypeAnnotation")
public class TestClass {
@FieldAnnotation(desc = "This is FieldAnnotation")
public String mTestField = "mTestField";

@ConstructAnnotaion(desc = "This is ConstructAnnotaion")
public TestClass() {
}

@MethodAnnotation(desc = "This is MethodAnnotation")
public void method(@ParamAnnotation(desc = "This is ParamAnnotation")String param) {
String localvalue = "";
System.out.println("method!!!!!"+param);
}
}

package-info.java

@PackageAnnotation(desc = "This is a package annotation!")
package com.javabase.annotationbase.testclass;
import com.javabase.annotationbase.annotations.PackageAnnotation;
public class RuntimeAnnotationProcessor {

public void testPackageAnnotation(Class<?> clzz) {
Package packageName = clzz.getPackage();
if(packageName.isAnnotationPresent(PackageAnnotation.class)) {
PackageAnnotation packageAnnotation = packageName.getAnnotation(PackageAnnotation.class);
System.out.println(packageAnnotation.desc());
}
}

public void testClassAnnotation(Class<?> clz) {
if (clz.isAnnotationPresent(TypeAnnotation.class)) {
TypeAnnotation typeAnnotation = clz.getAnnotation(TypeAnnotation.class);
System.out.println(typeAnnotation.desc());
}
}

public void testConstructureAnnotation(Class<?> clz) {
Constructor[] constructors = clz.getConstructors();
for(Constructor constructor : constructors) {
if(constructor.isAnnotationPresent(ConstructAnnotaion.class)) {
ConstructAnnotaion annotation = (ConstructAnnotaion) constructor.getAnnotation(ConstructAnnotaion.class);
System.out.println(annotation.desc());
}
}
}

public void testFieldAnnotation(Class<?> clz) {
Field[] fields = clz.getFields();
if(fields != null && fields.length > 0) {
for (Field field: fields) {
if(field.isAnnotationPresent(FieldAnnotation.class)) {
FieldAnnotation fieldAnnotation = field.getAnnotation(FieldAnnotation.class);
System.out.println(fieldAnnotation.desc());
}
}
}
}

public void testMethodAnnotation(Class<?> clz) {
Method[] methods = clz.getMethods();
if (methods != null && methods.length > 0) {
for (Method method : methods) {
if (method.isAnnotationPresent(MethodAnnotation.class)) {
MethodAnnotation methodAnnotation = method.getAnnotation(MethodAnnotation.class);
try {
method.invoke(clz.newInstance(), "");
} catch (IllegalAccessException | InvocationTargetException
| InstantiationException e) {
e.printStackTrace();
}
System.out.println(methodAnnotation.desc());
}
}
}
}

public void testParamAnnotation(Class<?> clz) {
Method[] methods = clz.getMethods();
if (methods != null && methods.length > 0) {
for (Method method : methods) {
if (method.isAnnotationPresent(MethodAnnotation.class)) {
Parameter[] parameters = method.getParameters();
if (parameters != null && parameters.length > 0) {
for (Parameter parameter : parameters) {
if(parameter.isAnnotationPresent(ParamAnnotation.class)) {
ParamAnnotation paramAnnotation = parameter.getAnnotation(ParamAnnotation.class);
System.out.println(paramAnnotation.desc());
}
}
}
}
}
}
}

}

  • 编译时解析
    编译时注解指的是@Retention的值为CLASS的注解。对于这类注解的解析,我们需要自定义一个派生自 AbstractProcessor的“注解处理类”并重写process 函数。

  • APT(Annotation Processing Tool)是一种处理注释的工具
    Annotation处理器在处理注释时可以根据源文件中的Annotation生成额外的源文件和其它的文件,APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。

  • 整个注解处理器的可以由如下几个部分构成:
    注解处理器(AbstractProcess)+代码处理(JavaPoet)+处理器注册(AutoService)+apt

  • 整个过程分成如下几个部分:
    1.定义注解(如@automain)
    2.定义注解处理器
    3.在处理器里面完成处理方式,通常是生成java代码,这个就需要用到代码处理。
    4.注册处理器,这里就需要用到处理器注册。
    5.利用APT进一步处理。

建立一个APT 项目,并添加apt依赖:

buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

添加apt 插件

//添加APT plugin
apply plugin: 'com.neenbedankt.android-apt'

新建一个model 命名为compiler 用于存放注解处理器:
并添加autoservice 和javapoet依赖:

apply plugin: 'java'

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.squareup:javapoet:1.8.0'
compile 'com.google.auto.service:auto-service:1.0-rc2'
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

创建AnnotationProcessor

package com.example;

import com.google.auto.service.AutoService;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;

import java.io.IOException;
import java.util.Collections;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;

@AutoService(Processor.class)
public class AnnotationProcessor extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(TestAnnotation.class.getCanonicalName());
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
}

这时候编译后会在/app/build/generated/source/apt/debug/目录下生成HelloWorld.java文件

参考材料
http://www.race604.com/annotation-processing/
http://www.jianshu.com/p/1942ad208927
http://blog.csdn.net/lmj623565791/article/details/43452969
https://github.com/taoweiji/DemoAPT?utm_source=tuicool&utm_medium=referral
http://www.cnblogs.com/lbangel/p/3523741.html
http://www.jianshu.com/p/94979c056b20
http://alighters.com/blog/2016/05/10/apt-code-generate/

本章知识点

  • Enume 枚举类型的重要方法的使用
  • 自定义枚举变量并为自定义枚举类添加相关方法
  • 分类枚举的组织方式
  • EnumeSet 和 EnumeMap的使用

在开始介绍今天的专题之前我们首先需要明确下什么时候我们会使用到枚举,在未接触枚举之前,大家或许写过这样的代码:

public static final int LIGHT_STATUS_OFF = 1; //表示灯关闭的状态
public static final int LIGHT_STATUS_ON = 2; //表示灯开启的状态

也就是用一些常量表示某个时刻的状态,这种表示方式有如下几个缺点:

  • 类型不安全
    由于状态的对应值是整型,所以程序执行过程中很有可能传入一个任意的整数值,从而导致出现错误。
  • 一致性差
    因为整型枚举属于编译期常量,所以编译过程完成后,所有客户端和服务器端引用的地方,会直接将整数值写入。这样,当你修改旧的枚举整数值后或者增加新的枚举值后,所有引用地方代码都需要重新编译,否则运行时刻就会出现错误。
  • 可读性差
    由于上述用于枚举的仅仅是一些无任何含义的整数值,如果在运行期调试时候,你就会发现日志中有很多难以理解的数字,但除了程序员本身,其他人很难明白其所表示的真实含义,比较不直观。

为了解决这些问题在5.0 版本 SDK 发布时候引入了枚举特性,通过枚举,可以把相关的常量分组到一个枚举类型里,而且枚举提供了比常量更多的方法。

上面的常量可以通过下面的枚举变量进行实现:

public enume LIGHT_STATUS {
LIGHT_STATUS_OFF,
LIGHT_STATUS_ON
}

Enume 枚举类型的重要方法的使用

Enume 对象的可用方法不多,列举如下:

values() 用于返回整个枚举所包含的状态值
name() 用于返回某个enume item的名字
ordinal() 用于获取当前enume item 在枚举类中声明时候的次序
getDeclaringClass() 用于获取当前enume item在哪个枚举类中进行声明
private enum LIGHT_STATUS {
LIGHT_STATUS_ON,
LIGHT_STATUS_OFF;
}

public void enumBaseUsage() {
System.out.println("There are "+LIGHT_STATUS.values().length+" members in LIGHT_STATUS");
for(LIGHT_STATUS status:LIGHT_STATUS.values()) {
System.out.println(status.ordinal()+" : "+status.name()+ " : Declaring in "+status.getDeclaringClass().getSimpleName());
}
}

由于编译器会自动为我们提供equals()以及hashCode(),所以可以使用==来比较enume实例,同时由于它也实现了Comparable接口所以也可以使用compareTo方法进行比较,除了实现了Comparable 接口外,它还实现了Serializable接口。

public void enumeValueCompare() {

LIGHT_STATUS statusA = LIGHT_STATUS.LIGHT_STATUS_ON;
LIGHT_STATUS statusB = LIGHT_STATUS.LIGHT_STATUS_ON;
LIGHT_STATUS statusC = LIGHT_STATUS.LIGHT_STATUS_OFF;
if(statusA == statusB) {
System.out.println("equals");
} else {
System.out.println("not equals");
}

if(statusA == statusC) {
System.out.println("equals");
} else {
System.out.println("not equals");
}

if(statusA.compareTo(statusC)>0) {
System.out.println(" >0 ");
} else if(statusA.compareTo(statusC) == 0){
System.out.println(" =0");
} else {
System.out.println(" <0");
}
}

自定义枚举变量并为自定义枚举类添加相关方法

枚举的定义和类的定义十分相似,但是也存在着一些区别,下面是二者的区别点:

  • 首先枚举类型不能使用extends进行继承
  • 枚举的定义分成两个部分,第一部分用于定义枚举实例,第二部分用于定义属性以及方法,但是需要注意点是在定义enume实例之前不能定义先定义任何属性活着方法,
  • 对于枚举类的构造方法,一旦enume定义结束,编译器就不允许我们再使用其构造方法来创建任何实例。

下面是自定义的一个枚举类型:

private enum ITEM_TYPE {
ITEM_TYPE_NORMAL("The item which is used to show normal item"){
@Override
public void getInfo() {
System.out.println("ITEM_TYPE_NORMAL");
}
},
ITEM_TYPE_NEWEST("The item which is used to show newest item"){
@Override
public void getInfo() {
System.out.println("ITEM_TYPE_NEWEST");
}
},
ITEM_TYPE_HOTEST("The item which is used to show hotest item"){
@Override
public void getInfo() {
System.out.println("ITEM_TYPE_HOTEST");
}
};
private String description;
public abstract void getInfo();
ITEM_TYPE(String description) {
this.description = description;
}

public String getDescription() {
return description;
}
}

它包括每个枚举类型实例,以及枚举类型的描述,下面是测试例子:
上面的例子中我们通过声明抽象方法,在每个enume 实例中进行覆写,来指定每个enume实例相关的方法,而在实例声明之后的方法实现的是全部实例共有的方法。

public void customEnume() {
for (ITEM_TYPE itemType : ITEM_TYPE.values()) {
System.out.println(itemType.name() +" : " + itemType.getDescription() );
}
}

我们看sdk源码的时候可以发现enume实际上是继承自Enume类的,但是Enume 类中并没有values方法,实际上values是编译器添加的静态方法,所以如果我们将enume实例向上转型为Enum  那么 values方法就不可使用了。
解决这个问题我们可以借助Class类中的getEnumeConstants 方法来获取所有enume实例。

public void getEnumeConstants() {
Enum<ITEM_TYPE> itemTypeEnum = ITEM_TYPE.ITEM_TYPE_NEWEST;;
for (Enum item : itemTypeEnum.getClass().getEnumConstants()) {
System.out.println(item.name());
}
}

分类枚举的组织方式

有时候我们每个枚举实例中还包含其他分类,这时候就要讲究枚举类的分类了:
下面是一种比较标准的分类写法,它使用接口Food将不同的子类组织起来,再通过getEnumConstants
获取每种子类型的值:

private enum FOOD {
COFFEE(Food.Coffee.class),DESSERT(Food.Dessert.class);
private interface Food {
enum Coffee implements Food {
BLACK_COFFEE,WHITE_COFFEE,TEA;
}
enum Dessert implements Food {
FRUIT,GELATO;
}
}

Food[] ItemValues;
FOOD(Class<? extends Food> cls) {
ItemValues = cls.getEnumConstants();
}

public Food[] getItemValues() {
return ItemValues;
}
}

EnumeSet 和 EnumeMap的使用

EnumeSet用法:
在刚接触枚举的时候,当时没有觉得它有什么用处,第一感觉它和枚举没啥区别。但是后面才发现二者的区别,传统的enume不能添加或者删除元素,但是EnumeSet 可以添加删除。
假设我们有一种情况下,某个版本有不同的特性,这些特性分别用枚举实例来表示。那么这时候我们就可以构造一个具有全部特性的EnumeSet  再根据版本号来将代表某写特性的枚举值添加或者移除:

public void testEnumeSet() {
EnumSet<ITEM_TYPE> itemTypes = EnumSet.allOf(ITEM_TYPE.class);
System.out.println(itemTypes);
itemTypes = EnumSet.noneOf(ITEM_TYPE.class);
System.out.println(itemTypes);
itemTypes = EnumSet.of(ITEM_TYPE.ITEM_TYPE_HOTEST);
System.out.println(itemTypes);
itemTypes = EnumSet.range(ITEM_TYPE.ITEM_TYPE_NEWEST, ITEM_TYPE.ITEM_TYPE_HOTEST);
System.out.println(itemTypes);
}

EnumeMap用法:

public void testEnumeMap() {
EnumMap<LIGHT_STATUS, command> itemMap = new EnumMap<LIGHT_STATUS, command>(LIGHT_STATUS.class);
itemMap.put(LIGHT_STATUS.LIGHT_STATUS_ON, new command() {
@Override
public void action() {
System.out.println("The light is on, i will turn off the light");
}
});

itemMap.put(LIGHT_STATUS.LIGHT_STATUS_OFF, new command() {
@Override
public void action() {
System.out.println("The light is off, i will turn on the light");
}
});
itemMap.get(LIGHT_STATUS.LIGHT_STATUS_ON).action();
itemMap.get(LIGHT_STATUS.LIGHT_STATUS_OFF).action();
}

如何避免错误使用 Enum

在使用 Enum 时候有几个地方需要注意:
enum 类型不支持 public 和 protected 修饰符的构造方法,因此构造函数一定要是 private。也正因为如此,所以枚举对象是无法在程序中通过直接调用其构造方法来初始化的。
定义 enum 类型时候,如果是简单类型,那么最后一个枚举值后不用跟任何一个符号;但如果有定制方法,那么最后一个枚举值与后面代码要用分号’;’隔开,不能用逗号或空格。
由于 enum 类型的值实际上是通过运行期构造出对象来表示的,所以在 cluster 环境下,每个虚拟机都会构造出一个同义的枚举对象。因而在做比较操作时候就需要注意,如果直接通过使用等号 ( ‘ == ’ ) 操作符,这些看似一样的枚举值一定不相等,因为这不是同一个对象实例。
一个 Java 源文件中最多只能有一个 public 类型的枚举类,且该 Java 源文件的名字也必须和该枚举类的类名相同
使用 enum 定义的枚举类默认继承了 java.lang.Enum 类,并实现了 java.lang.Seriablizable 和 java.lang.Comparable 两个接口;
枚举类的所有实例(枚举值)必须在枚举类的第一行显式地列出,否则这个枚举类将永远不能产生实例。列出这些实例(枚举值)时,系统会自动添加 public static final 修饰,无需显式添加。

在Android 开发中为什么需要节制使用枚举

(该部分引用自 http://www.codeceo.com/article/why-android-not-use-enums.html)
关于Android性能优化中一个常见的建议是不要在你的代码中使用Enums,就连 Android官网上都强烈建议不要使用。为什么?
因为使用枚举之后增加了dex包的大小,理论上dex包越大,加载速度越慢
同时使用枚举,运行时的内存占用也会相对变大 (Enums often require more than twice as much memory as static constants)

Android中当你的App启动后系统会给App单独分配一块内存。App的DEX code、Heap以及运行时的内存分配都会在这块内存中。接下来看两种写法:

  • 使用Int表示状态
    public static final int VALUE1 =1;
    public static final int VALUE1 =2;
    public static final int VALUE1 =3;
  • 使用Enums表示状态
    public static enum Value{
    VALUE1,
    VALUE2,
    VALUE3
    }
    情形2中的DEX size增加是情形1中的13倍之多。这还只是DEX code的增加,同样,运行时的内存分配,一个enum值的声明会消耗至少20 bytes,这还不算其中的对象数组需要保持对enum值的引用。Why?使用javap反编译情形二中生成的class文件,去掉汇编代码后如下:
    public final class VALUE extends java.lang.Enum{  
    public static final VALUE VALUE1;
    public static final VALUE VALUE2;
    public static final VALUE VALUE3;
    private static final VALUE[] values[];
    static{}
    }
    可以看到实际上enum类型继承java.lang.Enum,每个枚举项都会被声明成一个静态变量,并被赋值。VALUE value1 = VALUE.VALUE1则会引起对静态变量的引用。
    因此,当你的代码或包含的Lib中大量使用enums时,对于本身内存小的手机将是灾难性的。不可否认enums会使得代码更易读更安全,那么我们需要如何在二者之间做权衡呢:
    我们可以看这个帖子的回答:

http://stackoverflow.com/questions/29183904/should-i-strictly-avoid-using-enums-on-android
大体的意思是,在如下情况下可以考虑用枚举

  • 需要类型检查的时候,我们只能接受某些非连续的值的情况,使用枚举变量可以有效避免传入非安全的值。
  • 更多的数据,你的数据包含不止一个信息并且这些信息不能放在一个变量中的时候。
  • 复杂的数据,你的数据需要一些操作方法作用在它上面的时候。

除了这些情况大家尽量不使用枚举,简单来说就是能不用enume 的尽量不用,那么我们是否能够克服使用静态常量的问题呢?肯定是有办法的,下面就介绍下Android中的替代方案:

Android中的替代方案

public class Sexs {
@IntDef({MALE, FEMALE})
@Retention(RetentionPolicy.SOURCE)
public @interface PersonSex{}

public static final int MALE = 0;
public static final int FEMALE = 1;
}
public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
Person person = new Person();
person.setSex(MALE);
((Button) findViewById(R.id.test)).setText(person.getSexDes());
}
class Person {

@Sexs.PersonSex
private int sex;

public void setSex(@Sexs.PersonSex int sex) {
this.sex = sex;
}

@Sexs.PersonSex
public int getSex() {
return sex;
}

public String getSexDes() {
if (sex == MALE) {
return "男";
} else {
return "女";
}
}
}
}

@SEX注解可以放到属性定义,参数,返回值等地方对数据类型进行限制。

为什么Enume 不能继承其他类,也不能被其他类继承

为了说明这个问题我们先编写一个枚举类如下所示:

public enum EnumeTest {
LIGHT_STATUS_ON,LIGHT_STATUS_OFF
}

然后紧接着将该类编译成class文件,然后使用javap来反编译查看到底长啥样,下面就是反编译后的样子:

 public final class EnumeTest extends java.lang.Enum<EnumeTest> {
public static final EnumeTest LIGHT_STATUS_ON;
public static final EnumeTest LIGHT_STATUS_OFF;
public static EnumeTest[] values();
public static EnumeTest valueOf(java.lang.String);
static {};
}

大家应该知道为什么enume后面不能使用extends来继承其他类了吧,因为已经继承了Enum,Java中不支持多继承,那为啥不能被其他继承?因为是final 类。

枚举与混淆

混淆大家估计都懂吧,即使不懂也听说过这个概念,就是用于增强反编译的难度,基本上大多数的app在发布之前都会通过混淆处理,在默认的混淆配置文件中,已经加入了对枚举混淆的处理

# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}

使用proguard优化

使用Proguard进行优化,可以将枚举尽可能的转换成int。配置如下

-optimizations class/unboxing/enum

确保上述代码生效,需要确proguard配置文件不包含-dontoptimize指令。

参考资料

http://www.ibm.com/developerworks/cn/java/j-lo-enum/
https://www.liaohuqiu.net/cn/posts/android-enum-memory-usage/
http://www.cnblogs.com/zgz345/p/5871351.html
http://stackoverflow.com/questions/29183904/should-i-strictly-avoid-using-enums-on-android
http://droidyue.com/blog/2016/11/29/dive-into-enum/index.html
http://www.jianshu.com/p/f8ac84a3e3c1

  1. 变量名可以使用$或者 _开头

  2. javascript 有七种数据类型:Number , String ,Boolean, Object,null,undefined,symbol.
    由于JavaScript的变量作用域实际上是函数内部,我们在for循环等语句块中是无法定义具有局部作用域的变量的:

    'use strict';
    function foo() {
    for (var i=0; i<100; i++) {
    //
    }
    i += 100; // 仍然可以引用变量i
    }

    为了解决块级作用域,ES6引入了新的关键字let,用let替代var可以申明一个块级作用域的变量:

    'use strict';
    function foo() {
    var sum = 0;
    for (let i=0; i<100; i++) {
    sum += i;
    }
    i += 1; // SyntaxError
    }

    由于var和let申明的是变量,如果要申明一个常量,在ES6之前是不行的,我们通常用全部大写的变量来表示“这是一个常量,不要修改它的值”:

    var PI = 3.14;

    ES6标准引入了新的关键字const来定义常量,const与let都具有块级作用域:

    'use strict';
    const PI = 3.14;
    PI = 3; // 某些浏览器不报错,但是无效果!
    PI; // 3.14
  3. 使用var申明的变量则不是全局变量,它的范围被限制在该变量被申明的函数体内,同名变量在不同的函数体内互不冲突。
    为了修补JavaScript这一严重设计缺陷,ECMA在后续规范中推出了strict模式,在strict模式下运行的JavaScript代码,强制通过var申明变量,未使用var申明变量就使用的,将导致运行错误。启用strict模式的方法是在JavaScript代码的第一行写上:

    'use strict';

    这是一个字符串,不支持strict模式的浏览器会把它当做一个字符串语句执行,支持strict模式的浏览器将开启strict模式运行JavaScript。
    JavaScript在设计之初,为了方便初学者学习,并不强制要求用var申明变量。这个设计错误带来了严重的后果:如果一个变量没有通过var申明就被使用,那么该变量就自动被申明为全局变量:
    i = 10; // i现在是全局变量
    定义值的标志有三种 var let const:

  4. null 和undefined的区别:
    JavaScript的设计者希望用null表示一个空的值,而undefined表示值未定义。事实证明,这并没有什么卵用,区分两者的意义不大。大多数情况下,我们都应该用null。undefined仅仅在判断函数参数是否传递的情况下有用。实际上也可以记住,对象类型用null。基本数据类型用undefined。

  5. 关于字符串
    ‘’单引号可以包含双引号,双引号内部可以包含单引号,· · 内部可以包含很多包括换行在内的符号由于多行字符串用\n写起来比较费事,所以最新的ES6标准新增了一种多行字符串的表示方法,用...表示:
    这是一个 多行 字符串;
    要把多个字符串连接起来,可以用+号连接:

    var name = '小明';
    var age = 20;
    var message = '你好, ' + name + ', 你今年' + age + '岁了!';
    alert(message);

    如果有很多变量需要连接,用+号就比较麻烦。ES6新增了一种模板字符串,表示方法和上面的多行字符串一样,但是它会自动替换字符串中的变量:

    var name = '小明';
    var age = 20;
    var message = `你好, ${name}, 你今年${age}岁了!`;
    alert(message);
  6. 要特别注意相等运算符==。JavaScript在设计时,有两种比较运算符:
    第一种是==比较,它会自动转换数据类型再比较,很多时候,会得到非常诡异的结果;
    第二种是===比较,它不会自动转换数据类型,如果数据类型不一致,返回false,如果一致,再比较。
    由于JavaScript这个设计缺陷,不要使用==比较,始终坚持使用===比较。
    还有一个特殊的对比符号为全不等,表示数值和类型都不相等。

  7. && 和|| 返回的是第一个可以判断结果的那个值

  8. for in
    for循环的一个变体是for … in循环,它可以把一个对象的所有属性依次循环出来:

    var o = {
    name: 'Jack',
    age: 20,
    city: 'Beijing'
    };
    for (var key in o) {
    alert(key); // 'name', 'age', 'city'
    }

    由于Array也是对象,而它的每个元素的索引被视为对象的属性,因此,for … in循环可以直接循环出Array的索引:

    var a = ['A', 'B', 'C'];
    for (var i in a) {
    alert(i); // '0', '1', '2'
    alert(a[i]); // 'A', 'B', 'C'
    }

    for of 与for in的区别

for … in循环由于历史遗留问题,它遍历的实际上是对象的属性名称。一个Array数组实际上也是一个对象,它的每个元素的索引被视为一个属性。当我们手动给Array对象添加了额外的属性后,for … in循环将带来意想不到的意外效果:

var a = ['A', 'B', 'C'];
a.name = 'Hello';
for (var x in a) {
alert(x); // '0', '1', '2', 'name'
}

for … in循环将把name包括在内,但Array的length属性却不包括在内。
for … of循环则完全修复了这些问题,它只循环集合本身的元素:

var a = ['A', 'B', 'C'];
a.name = 'Hello';
for (var x of a) {
alert(x); // 'A', 'B', 'C'
}

这就是为什么要引入新的for … of循环。

  1. JavaScript把null、undefined、0、NaN和空字符串’’视为false,其他值一概视为true

NaN这个特殊的Number与所有其他值都不相等,包括它自己:

NaN === NaN; // false

唯一能判断NaN的方法是通过isNaN()函数:

isNaN(NaN); // true  
  1. 数组:

    [1, 2, 3.14, 'Hello', null, true];
    new Array(1, 2, 3); // 创建了数组[1, 2, 3]

    可以是任意元素
    可以读和修改某个元素
    要取得Array的长度,直接访问length属性:

    var arr = [1, 2, 3.14, 'Hello', null, true];
    arr.length; // 6

    请注意,直接给Array的length赋一个新的值会导致Array大小的变化:

    var arr = [1, 2, 3];
    arr.length; // 3
    arr.length = 6;
    arr; // arr变为[1, 2, 3, undefined, undefined, undefined]
    arr.length = 2;
    arr; // arr变为[1, 2]

    Array可以通过索引把对应的元素修改为新的值,因此,对Array的索引进行赋值会直接修改这个Array:

    var arr = ['A', 'B', 'C'];
    arr[1] = 99;
    arr; // arr现在变为['A', 99, 'C']

    请注意,如果通过索引赋值时,索引超过了范围,同样会引起Array大小的变化:

    var arr = [1, 2, 3];
    arr[5] = 'x';
    arr; // arr变为[1, 2, 3, undefined, undefined, 'x']

    大多数其他编程语言不允许直接改变数组的大小,越界访问索引会报错。然而,JavaScript的Array却不会有任何错误。在编写代码时,不建议直接修改Array的大小,访问索引时要确保索引不会越界。

  2. 函数

参数类
JavaScript的函数在查找变量时从自身函数定义开始,从“内”向“外”查找。如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量。
参数个数判断
如果没有return语句,函数执行完毕后也会返回结果,只是结果为undefined。
由于JavaScript允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,虽然函数内部并不需要这些参数:
传入的参数比定义的少也没有问题。

arguments

JavaScript还有一个免费赠送的关键字arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments类似Array但它不是一个Array:

function foo(x) {
alert(x); // 10for (var i=0; i<arguments.length; i++) {
alert(arguments[i]); // 10, 20, 30
}
}
foo(10, 20, 30);

利用arguments,你可以获得调用者传入的所有参数。也就是说,即使函数不定义任何参数,还是可以拿到参数的值:

function abs() {if (arguments.length === 0) {
return 0;
}
var x = arguments[0];
return x >= 0 ? x : -x;
}
abs(); // 0
abs(10); // 10
abs(-9); // 9

实际上arguments最常用于判断传入参数的个数。你可能会看到这样的写法:

// foo(a[, b], c)// 接收2~3个参数,b是可选参数,如果只传2个参数,b默认为null:function foo(a, b, c) {if (arguments.length === 2) {
// 实际拿到的参数是a和b,c为undefined
c = b; // 把b赋给c
b = null; // b变为默认值
}
// ...
}

要把中间的参数b变为“可选”参数,就只能通过arguments判断,然后重新调整参数并赋值。

rest参数

由于JavaScript函数允许接收任意个参数,于是我们就不得不用arguments来获取所有参数:

function foo(a, b) {var i, rest = [];
if (arguments.length > 2) {
for (i = 2; i<arguments.length; i++) {
rest.push(arguments[i]);
}
}
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}

为了获取除了已定义参数a、b之外的参数,我们不得不用arguments,并且循环要从索引2开始以便排除前两个参数,这种写法很别扭,只是为了获得额外的rest参数,有没有更好的方法?

ES6标准引入了rest参数,上面的函数可以改写为:

function foo(a, b, ...rest) {
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}

foo(1, 2, 3, 4, 5);
// 结果:// a = 1// b = 2// Array [ 3, 4, 5 ]

foo(1);
// 结果:// a = 1// b = undefined// Array []

rest参数只能写在最后,前面用…标识,从运行结果可知,传入的参数先绑定a、b,多余的参数以数组形式交给变量rest,所以,不再需要arguments我们就获取了全部参数。

如果传入的参数连正常定义的参数都没填满,也不要紧,rest参数会接收一个空数组(注意不是undefined

由于JavaScript的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量,反过来则不行:

'use strict';

function foo() {var x = 1;
function bar() {var y = x + 1; // bar可以访问foo的变量x!
}
var z = y + 1; // ReferenceError! foo不可以访问bar的变量y!
}

名字空间

全局变量会绑定到window上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。

减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。例如:

// 唯一的全局变量MYAPP:var MYAPP = {};

// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;

// 其他函数:
MYAPP.foo = function () {return 'foo';
};

把自己的代码全部放入唯一的名字空间MYAPP中,会大大减少全局变量冲突的可能。

全局作用域

不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性:

'use strict';

var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 'Learn JavaScript'

因此,直接访问全局变量course和访问window.course是完全一样的。

你可能猜到了,由于函数定义有两种方式,以变量方式var foo = function () {}定义的函数实际上也是一个全局变量,因此,顶层函数的定义也被视为一个全局变量,并绑定到window对象:

'use strict';

function foo() {
alert('foo');
}

foo(); // 直接调用foo()
window.foo(); // 通过window.foo()调用

名称空间
全局作用域
局部作用域
参数作用域

对象
访问属性是通过.操作符完成的,但这要求属性名必须是一个有效的变量名。如果属性名包含特殊字符,就必须用’’括起来:
访问这个属性也无法使用.操作符,必须用[‘xxx’]来访问:

如果我们要检测xiaoming是否拥有某一属性,可以用in操作符:
要判断一个属性是否是xiaoming自身拥有的,而不是继承得到的,可以用hasOwnProperty()方法:

'name' in xiaoming; // true
'grade' in xiaoming; // false
xiaoming.hasOwnProperty('name'); // true
xiaoming.hasOwnProperty('toString'); // false

JavaScript对每个创建的对象都会设置一个原型,指向它的原型对象。

当我们用obj.xxx访问一个对象的属性时,JavaScript引擎先在当前对象上查找该属性,如果没有找到,就到其原型对象上找,如果还没有找到,就一直上溯到Object.prototype对象,最后,如果还没有找到,就只能返回undefined。

var myObject = {
objectName : "jimmy",/*注意这里是逗号*/
"object-Age" : 29, /*名字可以包含特殊字符,如果有特殊字符需要使用引号阔起来*/
getAge : function() {
return this["object-Age"];
}/*注意这里没有分号*/
}
console.log(myObject.getAge());
console.log(myObject["object-Age"]);

function User(firstName,lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.sayHello = function() {
console.log(firstName + " : " +lastName);
};
}
var user = new User("jimmy","lin");
user.sayHello();
//删除某个熟悉
delete user.firstName;
for(var key in user) {
console.log("key ===="+key);
}

使用apply和call来绑定this的指向
虽然在一个独立的函数调用中,根据是否是strict模式,this指向undefined或window,不过,我们还是可以控制this的指向的!

要指定函数的this指向哪个对象,可以用函数本身的apply方法,它接收两个参数,第一个参数就是需要绑定的this变量,第二个参数是Array,表示函数本身的参数。

用apply修复getAge()调用:

function getAge() {var y = new Date().getFullYear();
return y - this.birth;
}

var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};

xiaoming.age(); // 25
getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空
另一个与apply()类似的方法是call(),唯一区别是:

apply()把参数打包成Array再传入;

call()把参数按顺序传入。

比如调用Math.max(3, 5, 4),分别用apply()和call()实现如下:

Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5

对普通函数调用,我们通常把this绑定为null。

装饰器:

利用apply(),我们还可以动态改变函数的行为。

JavaScript的所有对象都是动态的,即使内置的函数,我们也可以重新指向新的函数。

现在假定我们想统计一下代码一共调用了多少次parseInt(),可以把所有的调用都找出来,然后手动加上count += 1,不过这样做太傻了。最佳方案是用我们自己的函数替换掉默认的parseInt():

var count = 0;
var oldParseInt = parseInt; // 保存原函数

window.parseInt = function () {
count += 1;
return oldParseInt.apply(null, arguments); // 调用原函数
};

// 测试:
parseInt('10');
parseInt('20');
parseInt('30');
count; // 3

另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。我们来看一个例子:

返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。
下面就是我的学习笔记,对于Javascript初学者应该是很有用的。
一、变量的作用域
要理解闭包,首先必须理解Javascript特殊的变量作用域。
变量的作用域无非就是两种:全局变量和局部变量。
Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。
  var n=999;
  function f1(){
    alert(n);
  }
  f1(); // 999
另一方面,在函数外部自然无法读取函数内的局部变量。
  function f1(){
    var n=999;
  }
  alert(n); // error
这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!
  function f1(){
    n=999;
  }
  f1();
  alert(n); // 999
二、如何从外部读取局部变量?
出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。
那就是在函数的内部,再定义一个函数。
  function f1(){
    var n=999;
    function f2(){
      alert(n); // 999
    }
  }
在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,
对f1就是不可见的。这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会 "一级一级" 地向上寻找所有父对象的变量。
所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!
  function f1(){
    var n=999;
    function f2(){
      alert(n);
    }
    return f2;
  }
  var result=f1();
  result(); // 999
三、闭包的概念
上一节代码中的f2函数,就是闭包。
各种专业文献上的"闭包"(closure)定义非常抽象,很难看懂。我的理解是,闭包就是能够读取其他函数内部变量的函数。
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
四、闭包的用途
闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
怎么来理解这句话呢?请看下面的代码。
  function f1(){
    var n=999;
    nAdd=function(){n+=1}
    function f2(){
      alert(n);
    }
    return f2;
  }
  var result=f1();
  result(); // 999
  nAdd();
  result(); // 1000
在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000
这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,
因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。
其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,
所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。
五、使用闭包的注意点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。
解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),
把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
六、思考题
如果你能理解下面两段代码的运行结果,应该就算理解闭包的运行机制了。
代码片段一。
  var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){//alert(this.name);//My Object
      return function(){
        return this.name;// 空
      };
    }
  };
alert(this.name);// The Window
  alert(object.getNameFunc()()); // 空

代码片段二。
  var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name;
      };
    }
  };
  alert(object.getNameFunc()()); // My Object


一层访问一层,不能跨层访问

箭头函数: 用箭头定义函数……..

var fun = x=>x*x
alert(fun(2)) //单参数
var fun1 = ()=>2
alert(fun1()) //无参数
var fun2 = (x,y)=>x+y
alert(fun2(1,2)) //双参数
var fun3 = ()=>({a:12})
alert(fun3().a) //返回值是对象(要加括号)

箭头函数有两种格式,一种像上面的,只包含一个表达式,连{ … }和return都省略掉了。还有一种可以包含多条语句,这时候就不能省略{ … }和return:
箭头函数和匿名函数一个明显的区别是this指针。箭头函数中的this指针由上下文决定。

在函数中定义函数
将函数作为参数
将函数作为返回值

  1. 引入Retrofit 相关的库
    compile 'io.reactivex:rxjava:1.1.0'      //RxJava 相关
    compile 'io.reactivex:rxandroid:1.1.0' //RxJava 相关
    compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0-beta4' //Retrofit Rxjava Adapter
    compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4' //Retrofit
    compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4' //Gson Adapter
    compile 'com.google.code.gson:gson:2.6.2' //Gson
  2. 定义 Api 接口
    一个Service是一个接口如下所示,它一般指定了http方式是一个get还是post方式,以及一个相对的路径。
    public interface OpenWeatherApi {
    @GET("weather")
    Call<WeatherResult> getCityWeather(@Query("id") String id,@Query("appid") String ApiId);
    }
  3. 使用注释来创建url
    这个方面请大家看下这篇博客,个人认为总结得十分全面,所以就直接转过来了:
    http://blog.csdn.net/stven_king/article/details/52372172
    最常使用的请求方式:
    @GET @POST
    请求url相关的注释:
    @Query,@QueryMap,@Field,@FieldMap,@FormUrlEncoded,@Path,@Url

( 1 ) GET 请求方式:

public interface GitHubService {//无参数@GET("users/stven0king/repos")
Call<List<Repo>> listRepos();
//少数参数@GET("users/stven0king/repos")
Call<List<Repo>> listRepos(@Query("time") long time);
//参数较多@GET("users/stven0king/repos")
Call<List<Repo>> listRepos(@QueryMap Map<String, String> params);
}

( 2 ) POST 请求方式:

public interface GitHubService {//无参数@POST("users/stven0king/repos")
Call<List<Repo>> listRepos();
//少数参数@FormUrlEncoded@POST("users/stven0king/repos")
Call<List<Repo>> listRepos(@Field("time") long time);
//参数较多@FormUrlEncoded@POST("users/stven0king/repos")
Call<List<Repo>> listRepos(@FieldMap Map<String, String> params);
}

@POST @GET多了一个@FromUrlEncoded的注解。如果去掉@FromUrlEncoded在post请求中使用@Field和@FieldMap,那么程序会抛出Java.lang.IllegalArgumentException: @Field parameters can only be used with form encoding. (parameter #1)的错误异常。

( 3 ) 半静态的url 地址请求:

public interface GitHubService {@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}

( 4 ) 动态的url地址请求:

public interface GitHubService {@GET
Call<List<Repo>> listRepos(@Url String user);
}
  1. 创建retrofit对象
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("http://api.openweathermap.org/data/2.5/")
    .addConverterFactory(GsonConverterFactory.create())
    .build();
    在Retrofit 2.0中,需要我们自己引入Converter 不然的话Retrofit 只能接收字符串结果,下面是retrofit 支持的json converter
    Gson: com.squareup.retrofit:converter-gson
    Jackson: com.squareup.retrofit:converter-jackson
    Moshi: com.squareup.retrofit:converter-moshi
    Protobuf: com.squareup.retrofit:converter-protobuf
    Wire: com.squareup.retrofit:converter-wire
    Simple XML: com.squareup.retrofit:converter-simplexml
  2. 使用retrofit对象来创建对应的API接口:
    OpenWeatherApi mWeatherService = retrofit.create(OpenWeatherApi.class);
    5.使用上面创建的API接口来访问对应的服务:
    (这里为了安全所以我这边用了一个伪appid,大家可以自己申请然后替换)
    Call<WeatherResult> resultCall = mWeatherService.getCityWeather("1790437","f8ddddd63c40c0bcb89");
    resultCall.enqueue(new Callback<WeatherResult>() {
    @Override
    public void onResponse(Call<WeatherResult> call, Response<WeatherResult> response) {
    if(response.body() == null) {
    Log.i("xiaohai.lin","Request Failed Response Code = "+ response.code());
    return;
    }
    Log.i("xiaohai.lin","Templete = "+response.body().getMain().getTemp());
    Log.i("xiaohai.lin","getDescription = "+response.body().getWeather().get(0).getDescription());
    }
    @Override
    public void onFailure(Call<WeatherResult> call, Throwable t) {}
    });
  3. 异步请求
    resultCall.enqueue(new Callback<WeatherResult>() {
    @Override
    public void onResponse(Call<WeatherResult> call, Response<WeatherResult> response) {
    if(response.body() == null) {
    Log.i("xiaohai.lin","Request Failed Response Code = "+ response.code());
    return;
    }
    Log.i("xiaohai.lin","Templete = "+response.body().getMain().getTemp());
    Log.i("xiaohai.lin","getDescription = "+response.body().getWeather().get(0).getDescription());
    }
    @Override
    public void onFailure(Call<WeatherResult> call, Throwable t) {}
    });
    以上代码发起了一个在后台线程的请求并从response 的response.body()方法中获取一个结果对象。这里onResponse和onFailure方法是在主线程中调用的。
  4. 同步请求
    同步请求需要调用execute方法,但是由于同步方式会阻塞线程所以不能在主线程中调用。否则会遇到NetworkOnMainThreadException异常。
    new AsyncTask<String, Integer, Response<WeatherResult>>(){
    @Override
    protected Response<WeatherResult> doInBackground(String... params) {
    if(params == null|| params[0] == null||params[1]== null) {
    return null;
    }
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("http://api.openweathermap.org/data/2.5/")
    .addConverterFactory(GsonConverterFactory.create())
    .build();
    OpenWeatherApi mWeatherService = retrofit.create(OpenWeatherApi.class);
    final Call<WeatherResult> resultCall = mWeatherService.getCityWeather(params[0],params[1]);
    Response<WeatherResult> result = null;
    try {
    result = resultCall.execute();
    } catch (IOException e) {
    e.printStackTrace();
    }
    return result;
    }
    @Override
    protected void onPostExecute(Response<WeatherResult> response) {
    if(response == null || response.body() == null) {
    return;
    }
    Log.i("xiaohai.lin","Templete = "+response.body().getMain().getTemp());
    Log.i("xiaohai.lin","getDescription = "+response.body().getWeather().get(0).getDescription());
    }
    }.execute("1790437","f8ddddcb89");
    8 取消正在进行中访问
    call.cancel();
    9 获取结果的处理

在Retrofit 1.9中,如果获取的 response 不能被解析成定义好的对象,则会调用failure。但是在Retrofit 2.0中,不管 response 是否能被解析。onResponse总是会被调用。但是在结果不能被解析的情况下,response.body()会返回null。

如果response存在什么问题,onResponse也会被调用。我们可以从response.errorBody().string()中获取错误信息的主体。

10 Retrofix 结合 Rxjava

public interface OpenWeatherApis {
@GET("weather")
Observable<WeatherResult> getCityWeather(@Query("id") String id, @Query("appid") String ApiId);
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://api.openweathermap.org/data/2.5/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
OpenWeatherApis openWeatherApis = retrofit.create(OpenWeatherApis.class);
Observable<WeatherResult> result = openWeatherApis.getCityWeather("1790437","f8d8edded5c40c0bcbdd89");
result.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new Action1<WeatherResult>() {
@Override
public void call(WeatherResult weatherResult) {
Log.i("xiaohai.lin","Templete = "+weatherResult.getMain().getTemp());
Log.i("xiaohai.lin","getDescription = "+weatherResult.getWeather().get(0).getDescription());
}
});

11 添加log支持

HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
.build();

Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://api.openweathermap.org/data/2.5/")
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
OpenWeatherApi mWeatherService = retrofit.create(OpenWeatherApi.class);
final Call<WeatherResult> resultCall = mWeatherService.getCityWeather("1790437","f8d8ead8299f963abcded5c40c0bcb89");
resultCall.enqueue(new Callback<WeatherResult>() {
@Override
public void onResponse(Call<WeatherResult> call, Response<WeatherResult> response) {

if(response.body() == null) {
Log.i("xiaohai.lin","Request Failed Response Code = "+ response.code());
return;
}
Log.i("xiaohai.lin","Templete = "+response.body().getMain().getTemp());
Log.i("xiaohai.lin","getDescription = "+response.body().getWeather().get(0).getDescription());
}

@Override
public void onFailure(Call<WeatherResult> call, Throwable t) {

}
});

11 Retrofix 结合 Realm:

Realm 是我个人的喜好,是用得最爽的一个nosql类型的数据库,没有之一。
Realm 可以与 Retrofit 1.x 和 2.x 无缝配合工作。但注意 Retrofit 不会自动将对象存入 Realm。
需要通过调用 Realm.copyToRealm() 或 Realm.copyToRealmOrUpdate() 来将它们存入 Realm。

realm.beginTransaction();
WeatherResult realmWhether = realm.copyToRealmOrUpdate(result);
realm.commitTransaction();

今天开始我将对目前较为流行的开源库以及开源框架的源码进行分析,希望能够通过学习这些源码背后的设计思想,从而让自己的编程和设计能力有所提高。
废话不多说,切入正题。今天给大家介绍的picasso是一个图片缓存库,想必很多人对他都很熟悉了,它支持三级缓存,它的使用极为简单,同时也支持十分灵活的配置方式。大家可以通过如下地址下载到源码 http://square.github.io/picasso/

  1. 分析情景介绍

我们以一个最简单的使用情景来对源码进行分析:

Picasso.with(context).load("http://image.baidu.com/search/detail?z=0&ipn=").into(imageView);

上面的代码是执行从网络上加载地址为http://image.baidu.com/search/detail?z=0&ipn= 的图片到指定的imageView控件中。

  1. 代码组织结构介绍

在分析源码之前我们先看下整个开源库的代码组织,但是大家如果将代码下载到本地后会感到失望,因为个人认为picasso的代码组织是极为不规范的,整个源码只有一个package。下面是我根据自己的理解将其源码进行重新的组织。总共分成5个包,

  • action包包含有:Action抽象类,FetchAction,GetAction,ImageViewAction,RemoteViewsAction,Target,TargetAction。
  • requst包中包含的是:Request,RequestCreator,DeferredRequestCreator
  • requesthandler包中包含的是:AssetRequestHandler,ContactsPhotoRequestHandler,ContentStreamRequestHandler,FileRequestHandler,MediaStoreRequestHandler,NetworkRequestHandler,ResourceRequestHandler,RequestHandler
  • core包中包含有如下几个文件:

为什么这样分包大家可以在源码分析完后对整个流程以及每个类对职责有较为深入了解后进行思考。

3 .详细流程介绍

3.1 with 流程解析:
with阶段的主要任务是创建全局默认的Picasso实例,所创建的实例一般来说能够适合于绝大多数应用场景,如果不满足可以自己通过Builder来创建,也可以通过setSingletonInstance来注入我们自己创建的Picasso,但是这个方法需要在with方法前调用,因为这里创建picasso使用对是单例模式。

picasso默认对配置如下:
(1) 缓存使用LRU 缓存方式,缓存大小占用应用可用存储的15% RAM
(2) 硬盘缓存空间占用手机存储的2%,并且大小不小于5M,最大50MB。
(3) 具有3个线程用于磁盘和网络访问
(4) 缓存位于应用目录下对picasso-cache目录。

我们看下它是如何完成这部分的工作的,首先它使用建造者模式结合单例来创建picasso对象,之所以采用建造者模式是因为这种模式能够给配置参数多的对象的构建带来很大的方便。

public static Picasso with(@NonNull Context context) {
if (context == null) {
throw new IllegalArgumentException("context == null");
}
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
singleton = new Builder(context).build();
}
}
}
return singleton;
}

我们能从Builder对象中获取到什么信息呢?一般一个对象的Builder是用于向外面暴露设置内部组件的接口(此接口非彼接口),通过暴露的接口来注入我们自定义的对象。Picasso 的 Builder也是一样的。通过它我们可以注入自定义的下载器,线程池,缓存,请求转换器,请求处理器,内部事件监听。它还有一个builder方法,它的用处就是在全部设置后,检查重要的组件是否有设置了,如果没有设置,那么就创建初始化组件来使用。

public static class Builder {
private final Context context; //上下文
private Downloader downloader; //自定义的下载器
private ExecutorService service; //自定义的线程池
private Cache cache; //自定义的缓存
private Listener listener; //监听器
private RequestTransformer transformer; //请求转换器
private List<RequestHandler> requestHandlers; //请求处理器
private Bitmap.Config defaultBitmapConfig; //图像配置
private boolean indicatorsEnabled; //是否显示指示标志
private boolean loggingEnabled; //是否开启Log

/**Builder构造方法*/
public Builder(@NonNull Context context) {
if (context == null) {
throw new IllegalArgumentException("Context must not be null.");
}
this.context = context.getApplicationContext();
}
/**指定默认用于解码图片的的BitmapConfig*/
public Builder defaultBitmapConfig(@NonNull Bitmap.Config bitmapConfig) {
if (bitmapConfig == null) {
throw new IllegalArgumentException("Bitmap config must not be null.");
}
this.defaultBitmapConfig = bitmapConfig;
return this;
}
/**指定用于下载图片的下载器*/
public Builder downloader(@NonNull Downloader downloader) {
if (downloader == null) {
throw new IllegalArgumentException("Downloader must not be null.");
}
if (this.downloader != null) {
throw new IllegalStateException("Downloader already set.");
}
this.downloader = downloader;
return this;
}
/** 指定用于在后台下载图片的后台线程池 */
public Builder executor(@NonNull ExecutorService executorService) {
if (executorService == null) {
throw new IllegalArgumentException("Executor service must not be null.");
}
if (this.service != null) {
throw new IllegalStateException("Executor service already set.");
}
this.service = executorService;
return this;
}
/** 指定用于存放最近使用的图片的缓存 */
public Builder memoryCache(@NonNull Cache memoryCache) {
if (memoryCache == null) {
throw new IllegalArgumentException("Memory cache must not be null.");
}
if (this.cache != null) {
throw new IllegalStateException("Memory cache already set.");
}
this.cache = memoryCache;
return this;
}
/** 指定一个用于监听事件的监听器 */
public Builder listener(@NonNull Listener listener) {
if (listener == null) {
throw new IllegalArgumentException("Listener must not be null.");
}
if (this.listener != null) {
throw new IllegalStateException("Listener already set.");
}
this.listener = listener;
return this;
}
/**指定一个对所有到来请求进行转换的请求转换器*/
public Builder requestTransformer(@NonNull RequestTransformer transformer) {
if (transformer == null) {
throw new IllegalArgumentException("Transformer must not be null.");
}
if (this.transformer != null) {
throw new IllegalStateException("Transformer already set.");
}
this.transformer = transformer;
return this;
}
/** 添加请求处理器 */
public Builder addRequestHandler(@NonNull RequestHandler requestHandler) {
if (requestHandler == null) {
throw new IllegalArgumentException("RequestHandler must not be null.");
}
if (requestHandlers == null) {
requestHandlers = new ArrayList<RequestHandler>();
}
if (requestHandlers.contains(requestHandler)) {
throw new IllegalStateException("RequestHandler already registered.");
}
requestHandlers.add(requestHandler);
return this;
}
/** 是否显示调试的标志在图片上 */
public Builder indicatorsEnabled(boolean enabled) {
this.indicatorsEnabled = enabled;
return this;
}
/** 是否显示Log*/
public Builder loggingEnabled(boolean enabled) {
this.loggingEnabled = enabled;
return this;
}
/** 配置了诸如默认的下载器,请求转换器,默认的memory cache等: */
public Picasso build() {
//如果某些没有进行自定义那么就在这里使用默认的配置
Context context = this.context;
if (downloader == null) {
downloader = Utils.createDefaultDownloader(context);
}
if (cache == null) {
cache = new LruCache(context);
}
if (service == null) {
service = new PicassoExecutorService();
}
if (transformer == null) {
transformer = RequestTransformer.IDENTITY;
}
Stats stats = new Stats(cache);
Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, downloader, cache, stats);
return new Picasso(context, dispatcher, cache, listener, transformer, requestHandlers, stats,
defaultBitmapConfig, indicatorsEnabled, loggingEnabled);
}
}

接下来我们重点看下builder方法,如果我们没有注入自定义的下载器,那么就会调用createDefaultDownloader创建出一个下载器:
下面这种写法也是比较值得借鉴的。下面的create方法只是在应用缓存目录下创建一个picasso-cache的缓存目录。后续下载的图片都缓存在这个目录下。
/**

  • 优先选用OKHttp3.如果没有那么就使用OkHttp,再没有那么使用Android 默认的下载方式
    */
    public static Downloader createDefaultDownloader(Context context) {
    if (SDK_INT >= GINGERBREAD) {
    try {
    Class.forName("okhttp3.OkHttpClient");
    //这里会先创建一个缓存目录
    return OkHttp3DownloaderCreator.create(context);
    } catch (ClassNotFoundException ignored) {
    }
    try {
    Class.forName("com.squareup.okhttp.OkHttpClient");
    //这里会先创建一个缓存目录
    return OkHttpDownloaderCreator.create(context);
    } catch (ClassNotFoundException ignored) {
    }
    }
    return new UrlConnectionDownloader(context);
    }
    接着我们看下默认缓存的设置:
public LruCache(@NonNull Context context) {
this(Utils.calculateMemoryCacheSize(context));
}

根据是否是大堆栈类型,如果是则获取大堆栈存储,否则获取标准堆栈存储。然后使用大概15%的存储空间作为缓存。

static int calculateMemoryCacheSize(Context context) {
ActivityManager am = getService(context, ACTIVITY_SERVICE);
boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0;
int memoryClass = am.getMemoryClass();
if (largeHeap && SDK_INT >= HONEYCOMB) {
memoryClass = ActivityManagerHoneycomb.getLargeMemoryClass(am);
}
// Target ~15% of the available heap.
return (int) (1024L * 1024L * memoryClass / 7);
}

接着我们看下默认的线程池的设置:
它是继承自ThreadPoolExcutor

class PicassoExecutorService extends ThreadPoolExecutor
PicassoExecutorService() {
/**
* corePoolSize 线程池中容纳的核心线程数目,即使这些线程是空闲的,它会被保留在线程池中,除非allowCoreThreadTimeOut设置为true
* maximumPoolSize 最大线程池大小
* keepAliveTime 存活时间 当线程池中的线程数大于核心线程数的时候,在达到这个时间的时候等待新任务的空闲线程将会被终止
* unit keepAliveTime 参数的单位
* workQueue 用于在任务执行前保存任务的队列,这个队列将只会保留通过execute方法提交的Runnable任务
* threadFactory 在excutor创建一个线程时候使用的工厂类
*/
super(DEFAULT_THREAD_COUNT, DEFAULT_THREAD_COUNT, 0, TimeUnit.MILLISECONDS,
new PriorityBlockingQueue<Runnable>(), new Utils.PicassoThreadFactory());
}
static class PicassoThreadFactory implements ThreadFactory {
@SuppressWarnings("NullableProblems")
public Thread newThread(Runnable r) {
return new PicassoThread(r);
}
}
private static class PicassoThread extends Thread {
public PicassoThread(Runnable r) {
super(r);
}
@Override public void run() {
Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
super.run();
}
}

紧接着我们来看下请求转换器:
请求转换器会在每次请求被提交的时候被调用,它主要用于修改某个请求的信息。我们看到默认的请求,并没有对请求做任何事情,只是简单的将原先的请求返回。

public interface RequestTransformer {
Request transformRequest(Request request);
RequestTransformer IDENTITY = new RequestTransformer() {
@Override public Request transformRequest(Request request) {
return request;
}
};
}

Picasso 还有一个统计对象States,用于统计Picasso中的各种事件。它的主要对象只有三个带有消息处理能力的HandlerThread线程,一个Cache引用,一个Handler。

final HandlerThread statsThread;
final Cache cache;
final Handler handler;
它的统计对象数据有:
long cacheHits; //通过缓存获取到图片
long cacheMisses; //通过其他方式获取图片
long totalDownloadSize; //下载的总数据量大小
long totalOriginalBitmapSize; //解码后统计总图片大小
long totalTransformedBitmapSize; //图像转换大小
long averageDownloadSize; //平均下载大小
long averageOriginalBitmapSize; //平均解码后图片大小
long averageTransformedBitmapSize; //平均转换后图片大小
int downloadCount; //下载次数
int originalBitmapCount; //原始图片数量
int transformedBitmapCount; //转换图片数量

整个流程如下所示:
外界通过State对象调用dispatchXXXX方法,在dispatch方法中往Handler中发送事件,然后再调用performXXXX来处理这个事件。

接下来我们继续看下Dispatcher,它负责分发Action的:
我们先看下她的构造方法:

Dispatcher(Context context, ExecutorService service, Handler mainThreadHandler,
Downloader downloader, Cache cache, Stats stats) {
//分发线程
this.dispatcherThread = new DispatcherThread();
this.dispatcherThread.start();
//分发Handler
this.handler = new DispatcherHandler(dispatcherThread.getLooper(), this);
//主线程Handler
this.mainThreadHandler = mainThreadHandler;
//线程池
this.service = service;
this.hunterMap = new LinkedHashMap<String, BitmapHunter>();
this.failedActions = new WeakHashMap<Object, Action>();
this.pausedActions = new WeakHashMap<Object, Action>();
this.pausedTags = new HashSet<Object>();
//下载器
this.downloader = downloader;
this.cache = cache;
this.stats = stats;
this.batch = new ArrayList<BitmapHunter>(4);
this.airplaneMode = Utils.isAirplaneModeOn(this.context);
this.scansNetworkChanges = hasPermission(context, Manifest.permission.ACCESS_NETWORK_STATE);
//网络监听器
this.receiver = new NetworkBroadcastReceiver(this);
receiver.register();
}

整个流程还是和State对象一致,也是由HandlerThread + Handler构成,外部调用dispatchXXX方法传递事件,在Handler中周转下最终调用performXXXX进行处理。这个具体涉及到的时候再重点介绍。
在build最后新建一个Picass对象返回,我们看下Picass对象的构造方法:
/**

  • Picasso 有如下几个重要的对象:
  • RequestTransformer:请求转换器
  • Dispatcher:请求分发器
  • allRequestHandlers:请求处理器
  • Cache 缓存器
  • Stats 事件统计
  • CleanupThread 清除线程
    */
    Picasso(Context context, Dispatcher dispatcher, Cache cache, Listener listener,
    RequestTransformer requestTransformer, List<RequestHandler> extraRequestHandlers, Stats stats,
    Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) {

    this.context = context;
    this.dispatcher = dispatcher;
    this.cache = cache;
    this.listener = listener;
    this.requestTransformer = requestTransformer;
    this.defaultBitmapConfig = defaultBitmapConfig;

    //内部有7个请求处理器,额外的有一个
    int builtInHandlers = 7; // Adjust this as internal handlers are added or removed.
    int extraCount = (extraRequestHandlers != null ? extraRequestHandlers.size() : 0);
    List<RequestHandler> allRequestHandlers = new ArrayList<RequestHandler>(builtInHandlers + extraCount);

    //将各个RequestHandler添加到allRequestHandlers
    allRequestHandlers.add(new ResourceRequestHandler(context));
    if (extraRequestHandlers != null) {
    allRequestHandlers.addAll(extraRequestHandlers);
    }
    allRequestHandlers.add(new ContactsPhotoRequestHandler(context));
    allRequestHandlers.add(new MediaStoreRequestHandler(context));
    allRequestHandlers.add(new ContentStreamRequestHandler(context));
    allRequestHandlers.add(new AssetRequestHandler(context));
    allRequestHandlers.add(new FileRequestHandler(context));
    allRequestHandlers.add(new NetworkRequestHandler(dispatcher.downloader, stats));
    requestHandlers = Collections.unmodifiableList(allRequestHandlers);

    this.stats = stats;
    //用于存储Traget和Action的Map
    this.targetToAction = new WeakHashMap<Object, Action>();
    this.targetToDeferredRequestCreator = new WeakHashMap<ImageView, DeferredRequestCreator>();
    //软引用队列
    this.referenceQueue = new ReferenceQueue<Object>();
    //清除线程
    this.cleanupThread = new CleanupThread(referenceQueue, HANDLER);
    this.cleanupThread.start();
    //调试相关
    this.indicatorsEnabled = indicatorsEnabled;
    this.loggingEnabled = loggingEnabled;
    }
    我们可以看到在构造方法中添加了很多的requestHandler,用于对请求进行处理,这个在后面介绍流程的时候在进行介绍。
    到目前为止我们知道了整个Picasso的大体结构入下图所示:

    主要包括请求转换器,请求处理类,请求分发器,而请求分发器则负责将请求的分发和执行。

with流程总结:
在with阶段主要是使用建造模式和单例模式,创建Picasso对象。在通过Picasso的Builder接口我们可以看出Picasso可以允许我们注入自定义的下载器,线程池,缓存,请求转换器,请求处理器,内部事件监听。然后通过build方法中检查哪些对象还没创建,如果还没创建就使用默认的方案,在创建Picasso对象的阶段会注册系列的请求处理器为后续的请求处理做准备。
我们可以从这个部分学到什么?

  1. Builder + 单例模式创建对象
  2. 通过Builder注入自定义对象,这里的自定义对象一般是抽象类或者接口,这样可以提供一个模板,用户可以通过这种方式来根据模板创建需要的对象。

3.2 load流程:

public RequestCreator load(@Nullable Uri uri) {
return new RequestCreator(this, uri, 0);
}

在load阶段传递的是用于标示图片的Uri. path resourceId等。返回的是RequestCreator

RequestCreator(Picasso picasso, Uri uri, int resourceId) {
if (picasso.shutdown) {
throw new IllegalStateException(
"Picasso instance already shut down. Cannot submit new requests.");
}
this.picasso = picasso;
this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig);
}

这里实际上只是创建了一个Request.Builder为创建请求对象做准备。load阶段很简单吧。 这个阶段重点记住load提供的接口有如下几种:

public RequestCreator load(@Nullable Uri uri) 
public RequestCreator load(@Nullable String path) //android.resource: file: content:
public RequestCreator load(@NonNull File file)
public RequestCreator load(@DrawableRes int resourceId)

load阶段总结:
load阶段就完成两个任务,一个是创建一个RequestCreator,另一个是创建Requst.Builder.为into阶段做准备。

3.3 into流程分析:
下面代码是整个流程的代码,每个步骤我们将会在后面一一展开。

public void into(ImageView target, Callback callback) {
//获取创建请求的时间
long started = System.nanoTime();
//注意into阶段必须在主线程,所以在这个阶段需要检查下当前是否是在主线程
checkMain();
if (target == null) {
throw new IllegalArgumentException("Target must not be null.");
}
//对输入的Uri进行检查,如果没有Uri或者resourceId那么就取消请求,根据要求看下是否显示占位图
if (!data.hasImage()) {
picasso.cancelRequest(target);
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
return;
}

if (deferred) {
if (data.hasSize()) {
throw new IllegalStateException("Fit cannot be used with resize.");
}
int width = target.getWidth();
int height = target.getHeight();
if (width == 0 || height == 0 || target.isLayoutRequested()) {
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
picasso.defer(target, new DeferredRequestCreator(this, target, callback));
return;
}
data.resize(width, height);
}
//创建请求,请求有两个重要的元素id以及创建时间,创建完后需要通过请求转换器先进行初步的转换
Request request = createRequest(started);
//使用请求信息,生成一个请求的key
String requestKey = createKey(request);
//查看请求策略,是否需要从缓存中获取数据
if (shouldReadFromMemoryCache(memoryPolicy)) {
//使用请求的key在cache中尝试获取图像,并统计获取结果
Bitmap bitmap = picasso.quickMemoryCacheCheck(requestKey);
//如果获取到了就取消请求,并设置图片,回调返回
if (bitmap != null) {
picasso.cancelRequest(target);
setBitmap(target, picasso.context, bitmap, MEMORY, noFade, picasso.indicatorsEnabled);
if (picasso.loggingEnabled) {
log(OWNER_MAIN, VERB_COMPLETED, request.plainId(), "from " + MEMORY);
}
if (callback != null) {
callback.onSuccess();
}
return;
}
}

//如果需要从其他地方获取,那么就先显示占位图
if (setPlaceholder) {
setPlaceholder(target, getPlaceholderDrawable());
}
//初始化一个Action并提交
Action action = new ImageViewAction(picasso, target, request, memoryPolicy,networkPolicy, errorResId, errorDrawable, requestKey, tag, callback, noFade);
picasso.enqueueAndSubmit(action);
}

3.1.1 Request的创建过程

into阶段会先对传入的Uri进行判断,如果为空就取消请求。并根据需要决定是否显示占位符
然后使用load阶段创建的Request.Builder来创建请求。每个请求都有两个关键的成员,一个id一个创建时间。创建完Request对象后会通过请求转换器对其进行转换。

private Request createRequest(long started) {
int id = nextId.getAndIncrement(); //获得id
Request request = data.build(); //调用RequestBuidler 创建请求对象Requst
request.id = id; //给请求设置id以及创建时间
request.started = started; //请求是通过这两个来标示一个请求对象的
Request transformed = picasso.transformRequest(request); //对请求进行转换,默认的请求转换器为空操作,直接返回原来的请求
return transformed;
}

我们先来看下Request.Builder 的 build方法:

public Request build() {
if (centerInside && centerCrop) {
throw new IllegalStateException("Center crop and center inside can not be used together.");
}
if (centerCrop && (targetWidth == 0 && targetHeight == 0)) {
throw new IllegalStateException(
"Center crop requires calling resize with positive width and height.");
}
if (centerInside && (targetWidth == 0 && targetHeight == 0)) {
throw new IllegalStateException(
"Center inside requires calling resize with positive width and height.");
}
if (priority == null) {
priority = Priority.NORMAL;
}
return new Request(uri, resourceId, stableKey, transformations, targetWidth, targetHeight,
centerCrop, centerInside, centerCropGravity, onlyScaleDown, rotationDegrees,
rotationPivotX, rotationPivotY, hasRotationPivot, purgeable, config, priority);
}
}

上述的build过程首先对参数进行检查,通过上述的Requst对象中注入了包括uri在内的众多参数。
紧接着就是根据request对象来生成用于标示Requst的RequestKey。

static String createKey(Request data, StringBuilder builder) {
if (data.stableKey != null) {
builder.ensureCapacity(data.stableKey.length() + KEY_PADDING);
builder.append(data.stableKey);
} else if (data.uri != null) {
String path = data.uri.toString();
builder.ensureCapacity(path.length() + KEY_PADDING);
builder.append(path);
} else {
builder.ensureCapacity(KEY_PADDING);
builder.append(data.resourceId);
}
builder.append(KEY_SEPARATOR);

if (data.rotationDegrees != 0) {
builder.append("rotation:").append(data.rotationDegrees);
if (data.hasRotationPivot) {
builder.append('@').append(data.rotationPivotX).append('x').append(data.rotationPivotY);
}
builder.append(KEY_SEPARATOR);
}
if (data.hasSize()) {
builder.append("resize:").append(data.targetWidth).append('x').append(data.targetHeight);
builder.append(KEY_SEPARATOR);
}
if (data.centerCrop) {
builder.append("centerCrop:").append(data.centerCropGravity).append(KEY_SEPARATOR);
} else if (data.centerInside) {
builder.append("centerInside").append(KEY_SEPARATOR);
}

if (data.transformations != null) {
//noinspection ForLoopReplaceableByForEach
for (int i = 0, count = data.transformations.size(); i < count; i++) {
builder.append(data.transformations.get(i).key());
builder.append(KEY_SEPARATOR);
}
}
return builder.toString();
}

3.1.2 发起请求

( 1 ) 从缓存预取
在发起请求的时候首先查看下缓存策略,看下是否优先从缓存中获取图片数据,如果是的话就调用picasso.quickMemoryCacheCheck来检查缓存中是否已经有需要的数据了。如果有就返回,并且将结果传递给统计对象。然后取消现有的请求,并将图像设置到ImageView上。

Bitmap quickMemoryCacheCheck(String key) {
Bitmap cached = cache.get(key);
if (cached != null) {
stats.dispatchCacheHit();
} else {
stats.dispatchCacheMiss();
}
return cached;
}

(2) 创建Action 通过其他途径获取
紧接着创建一个ImageViewAction然后调用picasso.enqueueAndSubmit将Action发出。接下来这部分是非常重要的,所以在进行这部分介绍之前进行一下总结:

在into阶段首先会使用load阶段创建的RequstCreator来创建一个Request,并设置Requst的id以及启动时间这两个关键,参数,在创建Request这个过程中会先检查一系列的参数,然后再new出一个Requst对象出来。然后再使用创建出的Requst对象的参数创建出作为Request标示的requestKey。
再根据缓存策略,查看是否优先使用缓存中的图像,如果是的话那么就使用上面创建出来的requstKey从缓存中获取,并将结果反馈给stats对象进行统计,如果不优先使用缓存中的图像的话那么就创建出一个Action,然后调用picasso.enqueueAndSubmit将Action发出。
Action 是Target 以及Request的封装,它有个重要的方法abstract void complete(Bitmap result, Picasso.LoadedFrom from)在请求结束的时候会调用这个进行处理,这个后续会进行介绍,Action的子类目前有GetAction FetchAction TargetAction ImageViewAction RmoteViewAction这些。

我们继续看接下来的流程:
在提交到分发器之前需要先检查当前Action的对象是否绑定了另外的Action,如果是的话那么取消原先的请求,将当前的Action和Target放到targetToAction。然后提交到分发器上。

void enqueueAndSubmit(Action action) {
//获取Action中的target
Object target = action.getTarget();
//如果当前targetToAction 中记录的 Target中指定的action不是当前的action 说明之前已经指定了,那么取消原先的
if (target != null && targetToAction.get(target) != action) {
cancelExistingRequest(target);
//提交到targetToAction
targetToAction.put(target, action);
}
//提交到分发器
submit(action);
}

分发器分发请求:

void submit(Action action) {
//分发器分发请求
dispatcher.dispatchSubmit(action);
}
void dispatchSubmit(Action action) {
handler.sendMessage(handler.obtainMessage(REQUEST_SUBMIT, action));
}
@Override 
public void handleMessage(final Message msg) {
switch (msg.what) {
case REQUEST_SUBMIT: {
Action action = (Action) msg.obj;
dispatcher.performSubmit(action);
break;
}
//...............
}
}
}
void performSubmit(Action action) {
performSubmit(action, true);
}

上面的流程在之前已经介绍了,由外部调用dispatchXXXX方法,然后传递给Hander,然后再由performXXXXX进行处理。

匹配requestHandler封装到BitmapHunter:
在performSubmit中根据Action来判断那个requstHandler可以处理这个请求,然后将这个requstHandler传递到新创建的BitmapHunter中。

void performSubmit(Action action, boolean dismissFailed) {

if (pausedTags.contains(action.getTag())) {
pausedActions.put(action.getTarget(), action);
//查看是否在停止的列表中
return;
}
//从hunterMap通过RequestKey查找是否有已经存在的BitmapHunter(搜索者)
BitmapHunter hunter = hunterMap.get(action.getKey());
if (hunter != null) {
hunter.attach(action);
return;
}
//查找能够处理请求的RequestHandler,并将其封装到BitmapHunter
hunter = forRequest(action.getPicasso(), this, cache, stats, action);
//BitmapHunter 实际上是一个线程,在这里将其丢到线程池进行执行,进入线程池后会调用其中的run方法
hunter.future = service.submit(hunter);
//添加到hunterMap
hunterMap.put(action.getKey(), hunter);
if (dismissFailed) {
failedActions.remove(action.getTarget());
}
}

forRequst 方法中取出请求,以及请求处理器列表。依次调用请求处理器的canHandleRequst来判断当前requstHandler是否能够处理当前的请求。

static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action) {
//获取Action中封装的请求
Request request = action.getRequest();
//获取Picasso注册的RequestHandlers
List<RequestHandler> requestHandlers = picasso.getRequestHandlers();
//遍历每个RequestHandler 看下那个Handler能够处理这个请求
for (int i = 0, count = requestHandlers.size(); i < count; i++) {
RequestHandler requestHandler = requestHandlers.get(i);
//使用请求的Uri来判断那个Handler进行处理
if (requestHandler.canHandleRequest(request)) { //如果可以处理当前请求那么就将Action传递给新建的BitmapHunter
return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
}
}
return new BitmapHunter(picasso, dispatcher, cache, stats, action, ERRORING_HANDLER);
}

每个requestHandler都有一个canHandleRequst的方法,将request进去,在这个方法中将根据请求的Uri来进行匹配。当前是否可以处理这个类型的请求。所以在new Picasso 对象的时候各个requestHandler的请求对象的添加顺序很关键。
下面是网络请求处理器的canHandleRequest的实现。

public boolean canHandleRequest(Request data) {
String scheme = data.uri.getScheme();
return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
}

在添加到线程池后将会调用BitmapHunter的run方法。在run方法中主要是获取最终的Bitmap,然后再通过分发器来返回结果。

public void run() {
//修改线程名字
updateThreadName(data);
//获取图片并完成转换,首先会先从缓存中获取,然后调用RequestHandler load方法进行获取,最后再进行内部转换以及自定义的图片转换
result = hunt();
//向分发器返回结果
if (result == null) {
dispatcher.dispatchFailed(this);
} else {
dispatcher.dispatchComplete(this);
}
}

通过requestHandler中的load方法加载图片数据:
获取图片主要是通过hunt方法,在这个方法中会先尝试从缓存中获取,如果缓存中没有,那么通过requstHandler中的load方法,这时候会有两种可能,一种是直接返回Bitmap。另一种是返回一个InputStream。如果是Bitmap那么就直接进入下一步,如果是输入流的话需要将从流中对其进行解码。

Bitmap hunt() throws IOException {
Bitmap bitmap = null;
//先尝试从缓存中获取图片
if (shouldReadFromMemoryCache(memoryPolicy)) {
bitmap = cache.get(key);
if (bitmap != null) {
stats.dispatchCacheHit();
loadedFrom = MEMORY;
return bitmap;
}
}
data.networkPolicy = retryCount == 0 ? NetworkPolicy.OFFLINE.index : networkPolicy;
//调用requestHandler的load方法进行加载图片
RequestHandler.Result result = requestHandler.load(data, networkPolicy);
if (result != null) {
loadedFrom = result.getLoadedFrom();
exifOrientation = result.getExifOrientation();
bitmap = result.getBitmap();
//首先会尝试直接获取Bitmap
if (bitmap == null) {
//如果不行的话尝试获取输入流
InputStream is = result.getStream();
try {
//使用输入流来创建Bitmap
bitmap = decodeStream(is, data);
} finally {
Utils.closeQuietly(is);
}
}
}
if (bitmap != null) {
//统计解码
stats.dispatchBitmapDecoded(bitmap);
//如果需要进行图片转换那么就进行转换
if (data.needsTransformation() || exifOrientation != 0) {
synchronized (DECODE_LOCK) {
//调用transformResult进行图片转换
if (data.needsMatrixTransform() || exifOrientation != 0) {
bitmap = transformResult(data, bitmap, exifOrientation);
}
//如果有自定义的转换效果那么就调用applyCustomTransformations 应用图片转换
if (data.hasCustomTransformations()) {
bitmap = applyCustomTransformations(data.transformations, bitmap);
}
}
//统计转换
if (bitmap != null) {
stats.dispatchBitmapTransformed(bitmap);
}
}
}
return bitmap;
}
public Result load(Request request, int networkPolicy) throws IOException {
//调用Downloader进行下载
Response response = downloader.load(request.uri, request.networkPolicy);
if (response == null) {
return null;
}
//查看是从哪里下载的?
Picasso.LoadedFrom loadedFrom = response.cached ? DISK : NETWORK;
//获取返回的图片
Bitmap bitmap = response.getBitmap();
//如果图片数据不为空那么返回图片
if (bitmap != null) {
return new Result(bitmap, loadedFrom);
}

InputStream is = response.getInputStream();
if (is == null) {
return null;
}
if (loadedFrom == DISK && response.getContentLength() == 0) {
Utils.closeQuietly(is);
throw new ContentLengthException("Received response with 0 content-length header.");
}
if (loadedFrom == NETWORK && response.getContentLength() > 0) {
stats.dispatchDownloadFinished(response.getContentLength());
}
return new Result(is, loadedFrom);
}

图像执行内部以及自定义变换处理:
获取到Bitmap数据之后接着就进行图像的转换,首先进行内部的转换,这个是通过调用transformRequst进行的。

static Bitmap transformResult(Request data, Bitmap result, int exifOrientation) {
int inWidth = result.getWidth();
int inHeight = result.getHeight();
boolean onlyScaleDown = data.onlyScaleDown;
int drawX = 0;
int drawY = 0;
int drawWidth = inWidth;
int drawHeight = inHeight;
Matrix matrix = new Matrix();
if (data.needsMatrixTransform() || exifOrientation != 0) {
int targetWidth = data.targetWidth;
int targetHeight = data.targetHeight;
float targetRotation = data.rotationDegrees;
if (targetRotation != 0) {
double cosR = Math.cos(Math.toRadians(targetRotation));
double sinR = Math.sin(Math.toRadians(targetRotation));
if (data.hasRotationPivot) {
matrix.setRotate(targetRotation, data.rotationPivotX, data.rotationPivotY);
// Recalculate dimensions after rotation around pivot point
double x1T = data.rotationPivotX * (1.0 - cosR) + (data.rotationPivotY * sinR);
double y1T = data.rotationPivotY * (1.0 - cosR) - (data.rotationPivotX * sinR);
double x2T = x1T + (data.targetWidth * cosR);
double y2T = y1T + (data.targetWidth * sinR);
double x3T = x1T + (data.targetWidth * cosR) - (data.targetHeight * sinR);
double y3T = y1T + (data.targetWidth * sinR) + (data.targetHeight * cosR);
double x4T = x1T - (data.targetHeight * sinR);
double y4T = y1T + (data.targetHeight * cosR);
double maxX = Math.max(x4T, Math.max(x3T, Math.max(x1T, x2T)));
double minX = Math.min(x4T, Math.min(x3T, Math.min(x1T, x2T)));
double maxY = Math.max(y4T, Math.max(y3T, Math.max(y1T, y2T)));
double minY = Math.min(y4T, Math.min(y3T, Math.min(y1T, y2T)));
targetWidth = (int) Math.floor(maxX - minX);
targetHeight = (int) Math.floor(maxY - minY);
} else {
matrix.setRotate(targetRotation);
// Recalculate dimensions after rotation (around origin)
double x1T = 0.0;
double y1T = 0.0;
double x2T = (data.targetWidth * cosR);
double y2T = (data.targetWidth * sinR);
double x3T = (data.targetWidth * cosR) - (data.targetHeight * sinR);
double y3T = (data.targetWidth * sinR) + (data.targetHeight * cosR);
double x4T = -(data.targetHeight * sinR);
double y4T = (data.targetHeight * cosR);
double maxX = Math.max(x4T, Math.max(x3T, Math.max(x1T, x2T)));
double minX = Math.min(x4T, Math.min(x3T, Math.min(x1T, x2T)));
double maxY = Math.max(y4T, Math.max(y3T, Math.max(y1T, y2T)));
double minY = Math.min(y4T, Math.min(y3T, Math.min(y1T, y2T)));
targetWidth = (int) Math.floor(maxX - minX);
targetHeight = (int) Math.floor(maxY - minY);
}
}
if (exifOrientation != 0) {
int exifRotation = getExifRotation(exifOrientation);
int exifTranslation = getExifTranslation(exifOrientation);
if (exifRotation != 0) {
matrix.preRotate(exifRotation);
if (exifRotation == 90 || exifRotation == 270) {
// Recalculate dimensions after exif rotation
int tmpHeight = targetHeight;
targetHeight = targetWidth;
targetWidth = tmpHeight;
}
}
if (exifTranslation != 1) {
matrix.postScale(exifTranslation, 1);
}
}
if (data.centerCrop) {
// Keep aspect ratio if one dimension is set to 0
float widthRatio =
targetWidth != 0 ? targetWidth / (float) inWidth : targetHeight / (float) inHeight;
float heightRatio =
targetHeight != 0 ? targetHeight / (float) inHeight : targetWidth / (float) inWidth;
float scaleX, scaleY;
if (widthRatio > heightRatio) {
int newSize = (int) Math.ceil(inHeight * (heightRatio / widthRatio));
if ((data.centerCropGravity & Gravity.TOP) == Gravity.TOP) {
drawY = 0;
} else if ((data.centerCropGravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
drawY = inHeight - newSize;
} else {
drawY = (inHeight - newSize) / 2;
}
drawHeight = newSize;
scaleX = widthRatio;
scaleY = targetHeight / (float) drawHeight;
} else if (widthRatio < heightRatio) {
int newSize = (int) Math.ceil(inWidth * (widthRatio / heightRatio));
if ((data.centerCropGravity & Gravity.LEFT) == Gravity.LEFT) {
drawX = 0;
} else if ((data.centerCropGravity & Gravity.RIGHT) == Gravity.RIGHT) {
drawX = inWidth - newSize;
} else {
drawX = (inWidth - newSize) / 2;
}
drawWidth = newSize;
scaleX = targetWidth / (float) drawWidth;
scaleY = heightRatio;
} else {
drawX = 0;
drawWidth = inWidth;
scaleX = scaleY = heightRatio;
}
if (shouldResize(onlyScaleDown, inWidth, inHeight, targetWidth, targetHeight)) {
matrix.preScale(scaleX, scaleY);
}
} else if (data.centerInside) {
// Keep aspect ratio if one dimension is set to 0
float widthRatio =
targetWidth != 0 ? targetWidth / (float) inWidth : targetHeight / (float) inHeight;
float heightRatio =
targetHeight != 0 ? targetHeight / (float) inHeight : targetWidth / (float) inWidth;
float scale = widthRatio < heightRatio ? widthRatio : heightRatio;
if (shouldResize(onlyScaleDown, inWidth, inHeight, targetWidth, targetHeight)) {
matrix.preScale(scale, scale);
}
} else if ((targetWidth != 0 || targetHeight != 0) //
&& (targetWidth != inWidth || targetHeight != inHeight)) {
// If an explicit target size has been specified and they do not match the results bounds,
// pre-scale the existing matrix appropriately.
// Keep aspect ratio if one dimension is set to 0.
float sx =
targetWidth != 0 ? targetWidth / (float) inWidth : targetHeight / (float) inHeight;
float sy =
targetHeight != 0 ? targetHeight / (float) inHeight : targetWidth / (float) inWidth;
if (shouldResize(onlyScaleDown, inWidth, inHeight, targetWidth, targetHeight)) {
matrix.preScale(sx, sy);
}
}
}
Bitmap newResult = Bitmap.createBitmap(result, drawX, drawY, drawWidth, drawHeight, matrix, true);
if (newResult != result) {
result.recycle();
result = newResult;
}
return result;
}

接下来就是进行自定义的处理,这个是我们可以进行自己设置的。

static Bitmap applyCustomTransformations(List<Transformation> transformations, Bitmap result) {
for (int i = 0, count = transformations.size(); i < count; i++) {
final Transformation transformation = transformations.get(i);
Bitmap newResult;
try {
newResult = transformation.transform(result);
} catch (final RuntimeException e) {
//.................
return null;
}
result = newResult;
}
return result;
}

往主线程中返回处理后的Bitmap:

void dispatchComplete(BitmapHunter hunter) {
handler.sendMessage(handler.obtainMessage(HUNTER_COMPLETE, hunter));
}
case HUNTER_COMPLETE: {
BitmapHunter hunter = (BitmapHunter) msg.obj;
dispatcher.performComplete(hunter);
break;
}
void performComplete(BitmapHunter hunter) {
//是否需要写入到缓存中
if (shouldWriteToMemoryCache(hunter.getMemoryPolicy())) {
cache.set(hunter.getKey(), hunter.getResult());
}
//移除hunterMap中注册的信息
hunterMap.remove(hunter.getKey());
//将图片展现出来
batch(hunter);
}
private void batch(BitmapHunter hunter) {
if (hunter.isCancelled()) {
return;
}
batch.add(hunter);
if (!handler.hasMessages(HUNTER_DELAY_NEXT_BATCH)) {
handler.sendEmptyMessageDelayed(HUNTER_DELAY_NEXT_BATCH, BATCH_DELAY);
}
}
case HUNTER_DELAY_NEXT_BATCH: {
dispatcher.performBatchComplete();
break;
}

这里将上面的BitmapHandler数组返回给主线程。

void performBatchComplete() {
List<BitmapHunter> copy = new ArrayList<BitmapHunter>(batch);
batch.clear();
//这里是将其转送到Picasso类中进行处理,mainThreadHandler是在初始化DisPatcher的时候传入的HANDLER
mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
}

显示图片处理:
在complete阶段每个BitmapHunter都会将自己传递给Picasso complete中进行处理。

case HUNTER_BATCH_COMPLETE: {
//获取BitmapHunter列表
List<BitmapHunter> batch = (List<BitmapHunter>) msg.obj;
//对BitmapHandler列表中的每个handler调用complete方法进行处理。
for (int i = 0, n = batch.size(); i < n; i++) {
//调用complete进行处理
BitmapHunter hunter = batch.get(i);
hunter.picasso.complete(hunter);
}
break;
}

在Picasso的complete方法中将会从BitmapHunter中获取到图片数据。通过deliverAction进行图片的显示。

void complete(BitmapHunter hunter) {
Action single = hunter.getAction();
List<Action> joined = hunter.getActions();

boolean hasMultiple = joined != null && !joined.isEmpty();
boolean shouldDeliver = single != null || hasMultiple;
if (!shouldDeliver) {
return;
}
//从hunter中获取Uri Exception result以及图片的来源
Uri uri = hunter.getData().uri;
Exception exception = hunter.getException();
Bitmap result = hunter.getResult();
LoadedFrom from = hunter.getLoadedFrom();

if (single != null) {
//显示图片
deliverAction(result, from, single);
}
if (hasMultiple) {
for (int i = 0, n = joined.size(); i < n; i++) {
Action join = joined.get(i);
deliverAction(result, from, join);
}
}
//通过回调返回结果
if (listener != null && exception != null) {
listener.onImageLoadFailed(this, uri, exception);
}
}

在deliverAction中将会调用Action中的complete方法进行将图像显示到控件上。

private void deliverAction(Bitmap result, LoadedFrom from, Action action) {

if (result != null) {
//调用Action的complete方法
action.complete(result, from);
} else {
action.error();
}
}

最终是调用PicassoDrawable的setBitmap方法显示到ImageView上的。

@Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
if (result == null) {
throw new AssertionError(
String.format("Attempted to complete action with no result!\n%s", this));
}

//取出要显示图片的ImageView
ImageView target = this.target.get();
if (target == null) {
return;
}

Context context = picasso.context;
//使用PicassoDrawable setBitmap 方法将图片显示到ImageView上
PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);

//回调放回结果
if (callback != null) {
callback.onSuccess();
}
}
static void setBitmap(ImageView target, Context context, Bitmap bitmap,
Picasso.LoadedFrom loadedFrom, boolean noFade, boolean debugging) {
Drawable placeholder = target.getDrawable();
if (placeholder instanceof AnimationDrawable) {
((AnimationDrawable) placeholder).stop();
}
PicassoDrawable drawable = new PicassoDrawable(context, bitmap, placeholder, loadedFrom, noFade, debugging);
//最终显示图片
target.setImageDrawable(drawable);
}

整个流程大致如下:

在看这篇博客之前请先看下Android 源码分析之TODO MVP 以及Android 进阶之设计模式 二 MVP模式。整个代码的流程在Android 源码分析之TODO MVP已经介绍过了,这篇博客将只介绍差异的部分。

还是老样子从TasksActivity开始分析:

public class TasksActivity extends AppCompatActivity {

private static final String CURRENT_FILTERING_KEY = "CURRENT_FILTERING_KEY";

private DrawerLayout mDrawerLayout;

private TasksPresenter mTasksPresenter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

//....................
TasksFragment tasksFragment =
(TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
if (tasksFragment == null) {
// Create the fragment
tasksFragment = TasksFragment.newInstance();
ActivityUtils.addFragmentToActivity(
getSupportFragmentManager(), tasksFragment, R.id.contentFrame);
}

// Create the presenter
TasksRepository repository = Injection.provideTasksRepository(getApplicationContext());
TasksLoader tasksLoader = new TasksLoader(getApplicationContext(), repository);

mTasksPresenter = new TasksPresenter(
tasksLoader,
getSupportLoaderManager(),
repository,
tasksFragment
);

//......................
}

//.........................

由于这个部分只是数据加载方式做了修改,所以View层TasksFragment没有多大的改变。 我们来看下数据层的代码:

public class TasksRepository implements TasksDataSource {

private static TasksRepository INSTANCE = null;

//远程数据源
private final TasksDataSource mTasksRemoteDataSource;
//本地数据源
private final TasksDataSource mTasksLocalDataSource;
//数据源监听器
private List<TasksRepositoryObserver> mObservers = new ArrayList<TasksRepositoryObserver>();

/**
* 内存缓存区
*/
Map<String, Task> mCachedTasks;

/**
* Marks the cache as invalid, to force an update the next time data is requested. This variable
* has package local visibility so it can be accessed from tests.
*/
//用于表示当前缓存数据是否可用
boolean mCacheIsDirty;

/**
* Returns the single instance of this class, creating it if necessary.
*
* @param tasksRemoteDataSource the backend data source
* @param tasksLocalDataSource the device storage data source
* @return the {@link TasksRepository} instance
*/
//单例方式创建TasksRepository
public static TasksRepository getInstance(TasksDataSource tasksRemoteDataSource,
TasksDataSource tasksLocalDataSource) {
if (INSTANCE == null) {
INSTANCE = new TasksRepository(tasksRemoteDataSource, tasksLocalDataSource);
}
return INSTANCE;
}

/**
* Used to force {@link #getInstance(TasksDataSource, TasksDataSource)} to create a new instance
* next time it's called.
*/
public static void destroyInstance() {
INSTANCE = null;
}

// Prevent direct instantiation.
private TasksRepository(@NonNull TasksDataSource tasksRemoteDataSource,
@NonNull TasksDataSource tasksLocalDataSource) {
mTasksRemoteDataSource = checkNotNull(tasksRemoteDataSource);
mTasksLocalDataSource = checkNotNull(tasksLocalDataSource);
}

//注册数据源变化监听器
public void addContentObserver(TasksRepositoryObserver observer) {
if (!mObservers.contains(observer)) {
mObservers.add(observer);
}
}

//移除数据源变化监听器
public void removeContentObserver(TasksRepositoryObserver observer) {
if (mObservers.contains(observer)) {
mObservers.remove(observer);
}
}

//通知监听者数据源发生变化
private void notifyContentObserver() {
for (TasksRepositoryObserver observer : mObservers) {
observer.onTasksChanged();
}
}

/**
*
* Gets tasks from cache, local data source (SQLite) or remote data source, whichever is
* available first. This is done synchronously because it's used by the {@link TasksLoader},
* which implements the async mechanism.
*/
@Nullable
@Override
public List<Task> getTasks() {
List<Task> tasks = null;
if (!mCacheIsDirty) {
//表示缓存中数据还可以用
// Respond immediately with cache if available and not dirty
if (mCachedTasks != null) {
//从缓存中获取数据
tasks = getCachedTasks();
return tasks;
} else {
//这个是第一次的情况下,从数据库中加载数据
// Query the local storage if available.
tasks = mTasksLocalDataSource.getTasks();
}
}
// To simplify, we'll consider the local data source fresh when it has data.
//如果从本地数据库中加载完就会跑到这里,对结果进行判断,如果是空则表示,数据库中没有想要的数据,那么就从远程数据源中获取。
if (tasks == null || tasks.isEmpty()) {
// Grab remote data if cache is dirty or local data not available.
tasks = mTasksRemoteDataSource.getTasks();
// We copy the data to the device so we don't need to query the network next time
//从远程数据源中获取后将其缓存到数据库中,供下一次使用。
saveTasksInLocalDataSource(tasks);
}
//不论是从数据库中加载还是从远程数据源中加载数据都需要将数据缓存到内存中
processLoadedTasks(tasks);
return getCachedTasks();

}

//判断缓存数据是否可用
public boolean cachedTasksAvailable() {
return mCachedTasks != null && !mCacheIsDirty;
}

//获取缓存中的数据
public List<Task> getCachedTasks() {
return mCachedTasks == null ? null : new ArrayList<>(mCachedTasks.values());
}

//获取缓存中的指定数据
public Task getCachedTask(String taskId) {
return mCachedTasks.get(taskId);
}

//将数据保存到数据库中
private void saveTasksInLocalDataSource(List<Task> tasks) {
if (tasks != null) {
for (Task task : tasks) {
mTasksLocalDataSource.saveTask(task);
}
}
}

//将数据缓存到内存上
private void processLoadedTasks(List<Task> tasks) {
if (tasks == null) {
mCachedTasks = null;
mCacheIsDirty = false;
return;
}
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.clear();
for (Task task : tasks) {
mCachedTasks.put(task.getId(), task);
}
mCacheIsDirty = false;
}

//保存任务
@Override
public void saveTask(@NonNull Task task) {
checkNotNull(task);
mTasksRemoteDataSource.saveTask(task);
mTasksLocalDataSource.saveTask(task);
// Do in memory cache update to keep the app UI up to date
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.put(task.getId(), task);
// Update the UI
notifyContentObserver();
}

//添加完成的任务
@Override
public void completeTask(@NonNull Task task) {
checkNotNull(task);
mTasksRemoteDataSource.completeTask(task);
mTasksLocalDataSource.completeTask(task);
Task completedTask = new Task(task.getTitle(), task.getDescription(), task.getId(), true);

// Do in memory cache update to keep the app UI up to date
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.put(task.getId(), completedTask);
// Update the UI
notifyContentObserver();
}

//添加完成的任务
@Override
public void completeTask(@NonNull String taskId) {
checkNotNull(taskId);
completeTask(getTaskWithId(taskId));
}

//添加激活的任务
@Override
public void activateTask(@NonNull Task task) {
checkNotNull(task);
mTasksRemoteDataSource.activateTask(task);
mTasksLocalDataSource.activateTask(task);

Task activeTask = new Task(task.getTitle(), task.getDescription(), task.getId());

// Do in memory cache update to keep the app UI up to date
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.put(task.getId(), activeTask);

// Update the UI
notifyContentObserver();
}

//添加激活的任务
@Override
public void activateTask(@NonNull String taskId) {
checkNotNull(taskId);
activateTask(getTaskWithId(taskId));
}

//清除完成的任务
@Override
public void clearCompletedTasks() {
mTasksRemoteDataSource.clearCompletedTasks();
mTasksLocalDataSource.clearCompletedTasks();

// Do in memory cache update to keep the app UI up to date
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
Iterator<Map.Entry<String, Task>> it = mCachedTasks.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Task> entry = it.next();
if (entry.getValue().isCompleted()) {
it.remove();
}
}

// Update the UI
notifyContentObserver();
}

/**
* Gets tasks from local data source (sqlite) unless the table is new or empty. In that case it
* uses the network data source. This is done to simplify the sample.
*/
@Override
public Task getTask(@NonNull final String taskId) {
checkNotNull(taskId);

Task cachedTask = getTaskWithId(taskId);

// Respond immediately with cache if we have one
if (cachedTask != null) {
return cachedTask;
}

// Is the task in the local data source? If not, query the network.
Task task = mTasksLocalDataSource.getTask(taskId);
if (task == null) {
task = mTasksRemoteDataSource.getTask(taskId);
}

return task;
}

@Nullable
private Task getTaskWithId(@NonNull String id) {
checkNotNull(id);
if (mCachedTasks == null || mCachedTasks.isEmpty()) {
return null;
} else {
return mCachedTasks.get(id);
}
}

@Override
public void refreshTasks() {
mCacheIsDirty = true;
notifyContentObserver();
}

@Override
public void deleteAllTasks() {
mTasksRemoteDataSource.deleteAllTasks();
mTasksLocalDataSource.deleteAllTasks();

if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.clear();

// Update the UI
notifyContentObserver();
}

@Override
public void deleteTask(@NonNull String taskId) {
mTasksRemoteDataSource.deleteTask(checkNotNull(taskId));
mTasksLocalDataSource.deleteTask(checkNotNull(taskId));

mCachedTasks.remove(taskId);

// Update the UI
notifyContentObserver();
}

public interface TasksRepositoryObserver {

void onTasksChanged();

}
}

从上面代码来看其实TasksRepository 主要的变化不大,主要是将原先的Callback回调变成了观察者模式。TasksLocalDataSource,以及TasksRemoteDataSource变化也不大。最大的变化应当属于TasksLoader
它是继承自AsyncTaskLoader以及实现了TasksRepositoryObserver。AsyncTaskLoader 这个之前都没介绍过,这个将会在后续的博客中补上。
一提到AsyncTaskLoader估计就会想到CursorLoader以及AysncTask,其实也差不多,它就是用于在后台中加载数据的。

public class TasksLoader extends AsyncTaskLoader<List<Task>>
implements TasksRepository.TasksRepositoryObserver{

private TasksRepository mRepository;

public TasksLoader(Context context, @NonNull TasksRepository repository) {
super(context);
checkNotNull(repository);
mRepository = repository;
}

//在后台加载数据
@Override
public List<Task> loadInBackground() {
return mRepository.getTasks();
}

@Override
public void deliverResult(List<Task> data) {

//如果后台加载线程没有开始,就直接返回
if (isReset()) {
return;
}

//将结果传递给UI线程
if (isStarted()) {
super.deliverResult(data);
}

}

@Override
protected void onStartLoading() {

//如果缓存中有任何可用的数据那么直接返回
// Deliver any previously loaded data immediately if available.
if (mRepository.cachedTasksAvailable()) {
deliverResult(mRepository.getCachedTasks());
}

//监听数据集的变化
// Begin monitoring the underlying data source.
mRepository.addContentObserver(this);

//如果内容有改变或者内存缓存内的任务不可用那么强制执行一次加载
if (takeContentChanged() || !mRepository.cachedTasksAvailable()) {
// When a change has been delivered or the repository cache isn't available, we force
// a load.
forceLoad();
}
}

@Override
protected void onStopLoading() {
cancelLoad();
}

@Override
protected void onReset() {
cancelLoad();
mRepository.removeContentObserver(this);
}

//如果数据集中发生变化启动一次加载
@Override
public void onTasksChanged() {
if (isStarted()) {
forceLoad();
}
}
}
public class TasksPresenter implements TasksContract.Presenter,
LoaderManager.LoaderCallbacks<List<Task>> {

private final static int TASKS_QUERY = 1;

private final TasksRepository mTasksRepository;

private final TasksContract.View mTasksView;

private final TasksLoader mLoader;

private final LoaderManager mLoaderManager;

private List<Task> mCurrentTasks;

private TasksFilterType mCurrentFiltering = TasksFilterType.ALL_TASKS;

private boolean mFirstLoad;

public TasksPresenter(@NonNull TasksLoader loader, @NonNull LoaderManager loaderManager,
@NonNull TasksRepository tasksRepository, @NonNull TasksContract.View tasksView) {
mLoader = checkNotNull(loader, "loader cannot be null!");
mLoaderManager = checkNotNull(loaderManager, "loader manager cannot be null");
mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null");
mTasksView = checkNotNull(tasksView, "tasksView cannot be null!");
mTasksView.setPresenter(this);
}

@Override
public void result(int requestCode, int resultCode) {
// If a task was successfully added, show snackbar
if (AddEditTaskActivity.REQUEST_ADD_TASK == requestCode && Activity.RESULT_OK == resultCode) {
mTasksView.showSuccessfullySavedMessage();
}
}

//这里启动AsyncTaskLoader
@Override
public void start() {
mLoaderManager.initLoader(TASKS_QUERY, null, this);
}

@Override
public Loader<List<Task>> onCreateLoader(int id, Bundle args) {
mTasksView.setLoadingIndicator(true);
//返回从TaskActivity中传来的TasksLoader
return mLoader;
}

@Override
public void onLoadFinished(Loader<List<Task>> loader, List<Task> data) {
mTasksView.setLoadingIndicator(false);

mCurrentTasks = data;
if (mCurrentTasks == null) {
mTasksView.showLoadingTasksError();
} else {
showFilteredTasks();
}
}

private void showFilteredTasks() {
List<Task> tasksToDisplay = new ArrayList<>();
if (mCurrentTasks != null) {
for (Task task : mCurrentTasks) {
switch (mCurrentFiltering) {
case ALL_TASKS:
tasksToDisplay.add(task);
break;
case ACTIVE_TASKS:
if (task.isActive()) {
tasksToDisplay.add(task);
}
break;
case COMPLETED_TASKS:
if (task.isCompleted()) {
tasksToDisplay.add(task);
}
break;
default:
tasksToDisplay.add(task);
break;
}
}
}
processTasks(tasksToDisplay);
}

@Override
public void onLoaderReset(Loader<List<Task>> loader) {
// no-op
}

/**
* @param forceUpdate Pass in true to refresh the data in the {@link TasksDataSource}
*/
public void loadTasks(boolean forceUpdate) {
if (forceUpdate || mFirstLoad) {
mFirstLoad = false;
mTasksRepository.refreshTasks();
} else {
showFilteredTasks();
}
}

private void processTasks(List<Task> tasks) {
if (tasks.isEmpty()) {
// Show a message indicating there are no tasks for that filter type.
processEmptyTasks();
} else {
// Show the list of tasks
mTasksView.showTasks(tasks);
// Set the filter label's text.
showFilterLabel();
}
}

private void showFilterLabel() {
switch (mCurrentFiltering) {
case ACTIVE_TASKS:
mTasksView.showActiveFilterLabel();
break;
case COMPLETED_TASKS:
mTasksView.showCompletedFilterLabel();
break;
default:
mTasksView.showAllFilterLabel();
break;
}
}

private void processEmptyTasks() {
switch (mCurrentFiltering) {
case ACTIVE_TASKS:
mTasksView.showNoActiveTasks();
break;
case COMPLETED_TASKS:
mTasksView.showNoCompletedTasks();
break;
default:
mTasksView.showNoTasks();
break;
}
}

@Override
public void addNewTask() {
mTasksView.showAddTask();
}

@Override
public void openTaskDetails(@NonNull Task requestedTask) {
checkNotNull(requestedTask, "requestedTask cannot be null!");
mTasksView.showTaskDetailsUi(requestedTask.getId());
}

@Override
public void completeTask(@NonNull Task completedTask) {
checkNotNull(completedTask, "completedTask cannot be null!");
mTasksRepository.completeTask(completedTask);
mTasksView.showTaskMarkedComplete();
loadTasks(false);
}

@Override
public void activateTask(@NonNull Task activeTask) {
checkNotNull(activeTask, "activeTask cannot be null!");
mTasksRepository.activateTask(activeTask);
mTasksView.showTaskMarkedActive();
loadTasks(false);
}

@Override
public void clearCompletedTasks() {
mTasksRepository.clearCompletedTasks();
mTasksView.showCompletedTasksCleared();
loadTasks(false);
}

/**
* Sets the current task filtering type.
*
* @param requestType Can be {@link TasksFilterType#ALL_TASKS},
* {@link TasksFilterType#COMPLETED_TASKS}, or {@link TasksFilterType#ACTIVE_TASKS}
*/
@Override
public void setFiltering(TasksFilterType requestType) {
mCurrentFiltering = requestType;
}

@Override
public TasksFilterType getFiltering() {
return mCurrentFiltering;
}
}

我们返过头看下,整个代码和之前的差别不是很大,差别主要是增加了TasksLoader,将原先在主要线程加载数据改成使用Loader在后台加载数据,并且使用Observer来监听数据集合的变化,来代替回调的方式。

下面是整个代码的结构: