澳门新葡萄京官网注册 1

Android 中的注解深入探究

本文系GDG Android Meetup分享内容总计小说

源自JakeWharton的经文制作ButterKnife, 相信当先56%人都用的很内行,
它必然程度上简化了作者们的findViewById以至OnClick等操作,给平常支出带给了非常大的有益。此处对是还是不是合宜运用它,笔者不作商议,亦不是本篇的指标。

正文首要介绍Android之神JakeWharton的一个批注框架,听大人说将来边试官未来面试都会问知道还是不知道道JakeWharton,不清楚的直接略过。废话超少说,大家接下去就简单介绍下ButterKnife的兑现,希望对大家具备利于,还应该有有个别,以下旧事纯属捏造,如有雷同,纯属巧合。

讲解是大家日常接触的技术,Java有表明,Android也许有表明,本文将希图介绍Android中的申明,以致ButterKnife和奥托这么些依照申明的库的一部分做事原理.

以此动用了多年的框架,
你真的驾驭是何等规律吗?它的实现是何许的,你有发出过好奇心吧?那么就让我们来解读一下。

目录

  • ButterKnife的来源于和做事流程
  • ButterKnife模块
  • ButterKnife微电脑模块
  • 总结

总结来讲,Android中的注明大致有以下好处


ButterKnife的来源于和劳作流程

  • 加强大家的支付作用
  • 更早的意识前后相继的难点大概不当
  • 更加好的充实代码的描述技能
  • 一发便利大家的有的正式限定
  • 提供搞定难点的更优解

解读ButterKnife之筹划篇

起源

1.我们平日日常都以在xml文件中写好构造,然后在Activity中经过上面代码查找和管理控件

public class MainActivity extends Activity implements View.OnClickListener {
    private TextView mTextview;
    private Button mButton;
    private ImageView mImageview;

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

    private void initView() {
        mTextview = (TextView) findViewById(R.id.textview);
        mButton = (Button) findViewById(R.id.button);
        mImageview = (ImageView) findViewById(R.id.imageview);
        mButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button:
                Toast.makeText(this, "罗马不是一天建成的", Toast.LENGTH_SHORT).show();
                break;
        }
    }
}

2.醒目那些findViewById和setOnClickListener异常碍眼,于是有人想是或不是足以把findViewById和setOnClickListener那么些操作放外边儿呢,这样在MainActivity里边只须求注解变量和回调方法,大家就足以只关怀大家须求关爱的,于是乎就有了之类代码

public class MainActivity extends Activity {
    TextView mTextview;
    Button mButton;
    ImageView mImageview;
    private TestMainActivity_ViewBinding mBind;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBind = TestButterKnife.bind(this);
    }

    public void onClick() {
        Toast.makeText(this, "罗马不是一天建成的", Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mBind.unbind();
    }
}

public class TestButterKnife {
    public static TestMainActivity_ViewBinding bind(Activity target) {
        return new TestMainActivity_ViewBinding((MainActivity) target);
    }
}

public class TestMainActivity_ViewBinding {
    private MainActivity target;

    public TestMainActivity_ViewBinding(MainActivity target) {
        this(target, target.getWindow().getDecorView());
    }

    public TestMainActivity_ViewBinding(final MainActivity target, View source) {
        this.target = target;
        target.mTextview = (TextView) source.findViewById(R.id.textview);
        target.mImageview = (ImageView) source.findViewById(R.id.imageview);
        target.mButton = (Button) source.findViewById(R.id.button);
        target.mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                target.onClick();
            }
        });
    }

    public void unbind() {
        MainActivity target = this.target;
        if (target == null) throw new IllegalStateException("Bindings already cleared.");
        this.target = null;
        target.mTextview = null;
        target.mImageview = null;
        target.mButton.setOnClickListener(null);
        target.mButton = null;
    }
}

3.探访上边的代码一定有成都百货上千人都想吐,笔者也想说me
too,那样岂不是要码相当多代码,还要建许多类,有一些舍本逐末的赶脚。那时Android之神JakeWharton站出来了,发表了一席谈话,概况内容是说可以写个小框架,让客商在MainActivity里面只要求写点注脚,别的的事体交给框架来做,也没有必要建那么类,码非常多代码,于是就有了如下设计

public class MainActivity extends Activity {
    @BindView(R.id.textview)
    TextView mTextview;
    @BindView(R.id.button)
    Button mButton;
    @BindView(R.id.imageview)
    ImageView mImageview;
    private Unbinder mBind;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBind = ButterKnife.bind(this);
    }

    @OnClick(R.id.button)
    public void onClick() {
        Toast.makeText(this, "罗马不是一天建成的", Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mBind.unbind();
    }
}

预备职业

暗中同意情形下,Android中的评释包并不曾富含在framework中,它独自成叁个单独的包,常常我们供给引进那么些包.

dependencies {
    compile 'com.android.support:support-annotations:22.2.0'
}

唯独要是大家已经引进了appcompat则尚未需要再度引述support-annotations,因为appcompat默许包括了对其引用.

反射

那是三个大部分框架都会选拔的本领,只要精通类或对象,大家得以剖判出有关此类的所有的事音讯,胡作非为。一提反射,很三个人会感到质量消耗不会相当的大呢?确定你也带着相符的主见啊?那么作者来声称一下,反射存在品质消耗是部分,不过还没想象中的浮夸;即使组合缓存使用以来,能够非常的大的平衡带给的性质的损耗,进而越来越好的享受它所带给的造福。作为多少个Java开采者,不会反射真的有一点过于了..

行事流程

看完下边包车型客车宏图,断定有人就能想,那样的话须要使用运维时注脚达成吗,在ButterKnife的bind(this卡塔尔方法实施的时候经过反射获取MainActivity中有所的带有注解的质量而且赢得申明中的奔驰G级.id.xxx值,最后还是经过反射调用Activity.findViewById(State of Qatar方法来赢得View,并赋值给Activity中的有些属性。但是那样做不会有标题吧,我们都精通在Activity运转时多量选取反射会耳闻则诵App的周转品质,变成卡顿等一文山会海主题材料。其实这种主题材料JakeWharton早已想到了,于是乎选取了编写翻译时表明来兑现,也正是Java
Annotation
Processing本事。下边初阶简介下ButterKnife的干活流程,该库主要采纳编写翻译时注脚和javapoet在编写翻译时生成协理类,在运作时经过反射调用扶植类的构造和措施来得以达成。

澳门新葡萄京官网注册 1

大致流程.png

1.编写翻译时ButterKnifeProcessor类的process(State of Qatar方法管理进程如下

  • 获取具备的暗号了@BindView、@OnClick等注明的成分
  • 概念叁个key为类级其余要素,value为声明音信的Map集结,依照得到的成分得以得到当下类等级的成分和注释标志的新闻,然后put进去,那样Map里边就封存了全部要生成类和类中的音讯
  • 依据Map会集通过
    javapoet
    生成雷同MainActivity_ViewBinding那样的大多类,包罗包名,导包,类名,字段,构造,方法等新闻都会生成,可以预知那个库的精锐,这种库最难的应该就是导包了呢

2.运行时

  • ButterKnife.bind(this卡塔尔国;那些方法会反射创立并赶回MainActivity_ViewBinding那样的实例,MainActivity_ViewBinding的构培育能实行,结构中会举行findViewById和setOnClickListener那几个操作,那与来自中第四个模块里边的TestMainActivity_ViewBinding代码很日常
  • 当然通过ButterKnife.bind(this卡塔尔(قطر‎;的重返值,你能够在onDestroy(卡塔尔中调用MainActivity_ViewBinding的unbind(卡塔尔国方法来消释绑定释放财富

代替枚举

在最先的时候,当我们想要做一些值得节制达成枚举的功能,经常是

  • 概念多少个常量用于节制
  • 从地方的常量选用值举办利用

二个相比较描述方面难题的为人师表代码如下

public static final int COLOR_RED = 0;
public static final int COLOR_GREEN = 1;
public static final int COLOR_YELLOW = 2;

public void setColor(int color) {
    //some code here
}
//调用
setColor(COLOR_RED)

而是下面的照旧有不尽完备之处

  • setColor(COLOR_RED)setColor(0)职能相像,而后人可读性相当糟糕,但却得以符合规律运作
  • setColor方法能够担任枚举之外的值,举个例子setColor(3),这种情况下程序可能出标题

二个周旋较优的消除办法就是行使Java中的Enum.使用枚举达成的魔法如下

// ColorEnum.java
public enum ColorEmun {
    RED,
    GREEN,
    YELLOW
}

public void setColorEnum(ColorEmun colorEnum) {
    //some code here
}

setColorEnum(ColorEmun.GREEN);

可是Enum也毫不最好,Enum因为其对待方案一的常量来讲,占用内部存款和储蓄器相对大过多而非常受曾经被谷歌列为不提出利用,为此谷歌刻意引进了有些有关的注脚来取代枚举.

Android中新引进的代表枚举的表明有IntDefStringDef,这里以IntDef做例子说美赞臣(Meadjohnson卡塔尔下.

public class Colors {
    @IntDef({RED, GREEN, YELLOW})
    @Retention(RetentionPolicy.SOURCE)
    public @interface LightColors{}

    public static final int RED = 0;
    public static final int GREEN = 1;
    public static final int YELLOW = 2;
}
  • 声称须求的int常量
  • 宣示七个讲解为LightColors
  • 澳门新葡萄京官网注册,利用@IntDef修饰LightColors,参数设置为待枚举的汇集
  • 行使@Retention(RetentionPolicy.SOURCE卡塔尔(قطر‎钦命注脚仅存在与源码中,不步向到class文件中
AnnotationProcessor

大家都通晓,ButterKnifed
便利来自于评释。那么既然存在申明,表明微机技能的采纳是迟早的。今后的框架差异于今后;从前的框架好像XUtils的疏解相当的大程度上是采用反射来深入分析的,上面说过,反射带给质量消耗仍然局部;不过未来,
大大多的笺注框架都以依附Apt或AnnotationProcessor的编写翻译时剖析达成的。试想一下,在先后编写翻译时就到位了疏解深入分析的专门的学问,又会给品质带给如何震慑呢?答案自然是没影响。

早先时期的注明微处理器多数使用开源的Apt,
但是一是Apt已不复被小编所保险;二则谷歌推出了AnnotationProcessor来代替它,更是集成到了API中,所以怎么看,使用AnnotationProcessor都是叁个很好的挑精拣肥。

ButterKnife模块

本条模块主要有四个东东,叁个是ButterKnife类,多个Utils类,叁个是Unbinder接口。ButterKnife重要是用来周转时反射调用扶助类和重回帮助类实例,Utils类会被变型扶助类调用,Unbinder是生成的协理类供给落实的接口,下边依次来介绍

Null相关的笺注

和Null相关的注释有七个

@Nullable 注解的元素可以是Null
@NonNull 注解的元素不能是Null

地方的多个可以修饰如下的成分

  • 分子属性
  • 措施参数
  • 艺术的再次来到值

@Nullable
private String obtainReferrerFromIntent(@NonNull Intent intent) {
    return intent.getStringExtra("apps_referrer");
}

NonNull检查测量检验生效的原则

  • 显式传入null
  • 在调用方法以前曾经推断了参数为null时

setReferrer(null);//提示警告

//不提示警告
String referrer = getIntent().getStringExtra("apps_referrer");
setReferrer(referrer);

//提示警告
String referrer = getIntent().getStringExtra("apps_referrer");
if (referrer == null) {
    setReferrer(referrer);
}

private void setReferrer(@NonNull String referrer) {
    //some code here
}
Javapoet

那是三个很奇妙的手艺,依赖它,大家得以省事的更改我们想要的代码。最少如今来看,格林Dao生成的Bean与Dao,
ButterKnife生成的Binding以至ARouter生成的Router&&Group&&xx等,都以采取了Poet本领。

如上3项本领被大多数框架所选用,要是您还不会,你已经Out了。那么读完那篇随笔,让您脱离Out的天地。


ButterKnife类

1.mBind = ButterKnife.bind(thisState of Qatar,会走如下代码

  //UiThread表明这个方法必须在UI线程调用
  @NonNull @UiThread
  public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    return createBinding(target, sourceView);
  }

2.任何时候调用createBinding(target, sourceView卡塔尔

private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    //主要调用如下方法,该方法会返回辅助类的构造器
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
    if (constructor == null) {
      return Unbinder.EMPTY;
    }
    //省去了try-catch,这里创建辅助类的实例并返回,注意这里会调用辅助类两个参数的构造方法
    return constructor.newInstance(target, source);
}

3.底下大家看findBindingConstructorForClass(targetClass卡塔尔国,这里运用了享元情势的准备,这种规划在很对框架中都会有,这里维护的是三个Map群集,集合中有那一个key对应的value直接回到,未有才会创设,塑造完后会放入Map中,那样大大收缩了内部存款和储蓄器中对象的多少

  //BINDINGS是一个key为Class,value为构造器的Map
  static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();

  private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
     //集合中通过key来获取构造器
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      //构造器不为空,说明之前创建过就会直接返回
      return bindingCtor;
    }
    String clsName = cls.getName();
    //类名不能以android.和java.开头
    if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return null;
    }
    try {
      //可以看到生成的辅助类的类名是原始类名加上_ViewBinding,比如MainActivity_ViewBinding
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      //创建构造器
      bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
      if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
    } catch (ClassNotFoundException e) {
      if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
      bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
    } catch (NoSuchMethodException e) {
      throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
    }
    //创建完后放入Map集合中
    BINDINGS.put(cls, bindingCtor);
    //返回构造器
    return bindingCtor;
  }

区间范围表明

Android中的IntRange和FloatRange是多少个用来界定区间范围的笺注,

float currentProgress;

public void setCurrentProgress(@FloatRange(from=0.0f, to=1.0f) float progress) {
    currentProgress = progress;
}

只要大家传入违法的值,如下所示

setCurrentProgress(11);

就能够获得这么的荒诞

Value must be >=0.0 and <= 1.0(was 11)

ButterKnife的使用

这里不主要说怎么用ButterKnife,
或许说作为三个那样轻易的框架,压根就毫无多说。这里引进它的行使,为ButterKnife的深入分析作为一个辅导。

// 初始化
ButterKnife.bind(Object target, View source);

// 加入注解
@BindView(id)
Button 

上述就是ButterKnife的为主调用。初叶化,然后增加种种表明,如BindView,
OnClick, BindBitmap。
如若您看过它的源码,你就能够掌握,ButterKnife帮衬的注释主要分为3类:
BindView之绑定View, BindBitmap/ BindColor等绑定能源,
OnClick等绑定事件。至于缘何不加上绑定setContentView,
大神说只可以替换一行代码而已,没意思。大神便是那般放肆…


Utils类

这里大概介绍多少个法子,前不熟习成的支持类会调用那些类中艺术
1.findRequiredViewAsType()

  public static <T> T findRequiredViewAsType(View source, @IdRes int id,String who,Class<T> cls) {
    //这里会调用下面2中的方法拿到一个没有强转的View
    View view = findRequiredView(source, id, who);
    //会调用3中的方法根据返回一个强制后的T,T表示@BindView所修饰控件类型,比如TextView、Button等
    return castView(view, id, who, cls);
  }

2.findRequiredView(View source, @IdRes int id, String who)

  public static View findRequiredView(View source, @IdRes int id, String who) {
    //我们期望看到的findViewById在这里
    View view = source.findViewById(id);
    if (view != null) {
      return view;
    }
    //省略异常处理
  }

3.castView(View view, @IdRes int id, String who, Class<T> cls)

  public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
      //省略try-catch,这里调用Class类的cast方法进行强转,我们稍微看下这个方法
      return cls.cast(view);
  }

    //Class类中的cast方法
    public T cast(Object obj) {
        if (obj != null && !isInstance(obj))
            throw new ClassCastException(cannotCastMsg(obj));
        //这里强转了下然后返回
        return (T) obj;
    }

长度以至数组大小限定

限制字符串的尺寸

private void setKey(@Size(6) String key) {
}

界定数组集结的分寸

private void setData(@Size(max = 1) String[] data) {
}
setData(new String[]{"b", "a"});//error occurs

范围特殊的数老板度,举个例子3的倍数

private void setItemData(@Size(multiple = 3) String[] data) {
}

ButterKnife的达成观念

从表明,
从本人列举的备选技能,笔者想了然的您曾经猜到了ButterKnife是如何贯彻了吧。是的,它所运用的大旨本事能够称之为依傍注入。常言说正是,大家不在Activity中早先化那一个View,
那堆财富,
那类别的点击事件;大家透过改变自定义类来暗自的开端化,偷偷的绑定监听。对,偷偷的…

Unbinder接口

以此接口中就八个格局和二个空实现的积极分子变量

public interface Unbinder {
  @UiThread void unbind();
  //这个成员变量会在构造器为null的异常情况下直接返回,可以在上面ButterKnife类中的createBinding方法中看到
  Unbinder EMPTY = new Unbinder() {
    @Override public void unbind() {}
  };
}

权限相关

在Android中,有不菲境况都急需使用权力,无论是Marshmallow早先依旧之后的动态权限管理.都需求在manifest中展开宣示,假诺忘记了,则会导致程序崩溃.
幸好有三个注解能扶助大家防止这么些难题.使用RequiresPermission申明就可以.

@RequiresPermission(Manifest.permission.SET_WALLPAPER)
    public void changeWallpaper(Bitmap bitmap) throws IOException {
}
1卡塔尔(قطر‎增多注解

这么些没什么多说的, ButterKnife的骨干就是通过注脚来简化操作。

ButterKnife微处理器模块

以此模块中大家只关切ButterKnifeProcessor那么些类,该类世袭了AbstractProcessor抽象类,下边我们看它重写的多少个点子

1.首先我们看@AutoService注明,那是四个此外注脚微机中引进的讲授。它是谷歌开采的,用来生成META-INF/services/javax.annotation.processing.Processor文件的,大家直接用就能够

@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
      //省略
}

2.随之大家看init(ProcessingEnvironment
env卡塔尔国,表明微电脑工具APT会调用这几个主意并传播ProcessingEnvironment
,笔者晓得的是该办法是提须要我们用来做客APT的条件

  private int sdk = 1;
  private static final String OPTION_SDK_INT = "butterknife.minSdk";
  private static final String OPTION_DEBUGGABLE = "butterknife.debuggable";
  private boolean debuggable = true;
  private Elements elementUtils;
  private Types typeUtils;
  private Filer filer;
  private Trees trees;

  @Override
  public synchronized void init(ProcessingEnvironment env) {
    super.init(env);
    //env.getOptions()拿到的是传递给注释处理工具的Map选项
    String sdk = env.getOptions().get(OPTION_SDK_INT);
    if (sdk != null) {
        //省去try-catch
        this.sdk = Integer.parseInt(sdk);
    }
    debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE));
    //获取元素实用工具
    elementUtils = env.getElementUtils();
    //获取类型实用工具
    typeUtils = env.getTypeUtils();
    //获取用来创建文件的Filter
    filer = env.getFiler();
    try {
      trees = Trees.instance(processingEnv);
    } catch (IllegalArgumentException ignored) {
    }
  }

3.我们看getSupportedSourceVersion(卡塔尔(قطر‎,那个法子用来钦点你选择的Java版本

  @Override 
  public SourceVersion getSupportedSourceVersion() {
    //通常这样返回就行了,它会根据你的环境返回对应的版本枚举
    return SourceVersion.latestSupported();
  }

4.接下来正是getSupportedAnnotationTypes(卡塔尔(قطر‎方法,钦定帮衬的疏解

  @Override 
  public Set<String> getSupportedAnnotationTypes() {
    Set<String> types = new LinkedHashSet<>();
    for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
      //annotation.getCanonicalName()为获取全类名,即包名+类名
      types.add(annotation.getCanonicalName());
    }
    return types;
  }

  private Set<Class<? extends Annotation>> getSupportedAnnotations() {
    Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
    annotations.add(BindAnim.class);
    annotations.add(BindArray.class);
    annotations.add(BindBitmap.class);
    annotations.add(BindBool.class);
    annotations.add(BindColor.class);
    annotations.add(BindDimen.class);
    annotations.add(BindDrawable.class);
    annotations.add(BindFloat.class);
    annotations.add(BindFont.class);
    annotations.add(BindInt.class);
    annotations.add(BindString.class);
    annotations.add(BindView.class);
    annotations.add(BindViews.class);
    annotations.addAll(LISTENERS);
    return annotations;
  }

5.结尾来详细介绍下process(卡塔尔国,那几个办法也便是种种微处理器的main(卡塔尔函数,在那间能够写你的扫视、管理表明以至生成Java文件的代码,由此那几个主意是全方位计算机模块的入眼

  @Override 
  public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //根据env获取一个Map集合,其中key为类级别的元素,value为BindingSet
    //BindingSet是一个自定义类,其中含有当前类中所有的注解信息
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();
      //注意下面的两行代码就是生成辅助类啦,由于需要用到JavaPoet库,后面会详细讲解
      JavaFile javaFile = binding.brewJava(sdk, debuggable);
      try {
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }
    return false;
  }

能源表明

在Android中差比少之又少全数的能源都足以有对应的能源id.比如获取定义的字符串,大家可以通过上面包车型的士法子

public String getStringById(int stringResId) {
    return getResources().getString(stringResId);
}

使用这几个方法,大家得以非常轻易的拿走到定义的字符串,但是那样的写法也存在着风险.

getStringById(R.mipmap.ic_launcher)

若是大家在不知情或然忽略情况下,传入那样的值,就能够并发问题.
不过只要大家使用能源相关的注释修饰了参数,就会相当的大程度上防止不当的情状.

public String getStringById(@StringRes  int stringResId) {
    return getResources().getString(stringResId);
}

在Android中财富注明如下所示

  • AnimRes
  • AnimatorRes
  • AnyRes
  • ArrayRes
  • AttrRes
  • BoolRes
  • ColorRes
  • DimenRes
  • DrawableRes
  • FractionRes
  • IdRes
  • IntegerRes
  • InterpolatorRes
  • LayoutRes
  • MenuRes
  • PluralsRes
  • RawRes
  • StringRes
  • StyleRes
  • StyleableRes
  • TransitionRes
  • XmlRes
2)分析注脚,生成注入类

加表明的目的是怎么样?正是偷偷生成叁个类,在此个类里面悄悄的消食你表明的原委,用大家最熟识的主意。那样大家看起来只要加个注脚就贯彻了,好狠心;即便你看了转移的Binding类,你就明白,切,那不是我们全日写的吧?

今后看一下变化的类

public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  private View view2131165245;

  @UiThread
  public MainActivity_ViewBinding(MainActivity target) {
    this(target, target.getWindow().getDecorView());
  }

  @UiThread
  public MainActivity_ViewBinding(final MainActivity target, View source) {
    this.target = target;

    View view;
    view = Utils.findRequiredView(source, R.id.id_button, "method 'doClick'");
    view2131165245 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.doClick(p0);
      }
    });
  }

  @Override
  @CallSuper
  public void unbind() {
    if (target == null) throw new IllegalStateException("Bindings already cleared.");
    target = null;


    view2131165245.setOnClickListener(null);
    view2131165245 = null;
  }
}

上面包车型大巴类正是ButterKnife自动生成的类。里面含有的@OnClick的管理,作者来深入分析一下,你就了解了。

率先看类名, 类名的款型你一看就驾驭了,便是@申明的类 +
叁个后缀ViewBinding。

附带,看一下变量,
MainActivity不用解释了啊,你是在MainActivity的主意上丰盛的@OnClick;
View就是你点击的控件。

同期看一下组织,构造传入了MainActivity,
然后findRequiredView,其实那中间正是findViewById。后边的完结看一眼就明白了,在生成类里面安装监听,然后调用你增多注脚的Method。难以理解吧?
很简短的落到实处啊。那正是回调的采用啊,相信我们都日常使用,在View点击的时候,调用钦赐的章程。

最终看unbind(卡塔尔(قطر‎方法,在页面实施完结后,是会活动释放财富的。

再来看下BindView生成的Binding类

public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  @UiThread
  public MainActivity_ViewBinding(MainActivity target) {
    this(target, target.getWindow().getDecorView());
  }

  @UiThread
  public MainActivity_ViewBinding(MainActivity target, View source) {
    this.target = target;

    target.btn = Utils.findRequiredViewAsType(source, R.id.id_button, "field 'btn'", Button.class);
    target.tv = Utils.findRequiredViewAsType(source, R.id.id_textview, "field 'tv'", TextView.class);
  }

  @Override
  @CallSuper
  public void unbind() {
    MainActivity target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");
    this.target = null;

    target.btn = null;
    target.tv = null;
  }
}

和OnClick生成的类很相通,只是改成了findRequiredViewAsType。
但是以此措施的兑现不过是findViewById加类型调换,相当于临近的

Button btn = (Button)findViewById(R.id.btn);

寓目此间,也许有人就意外了,你是怎么变化的? 答:技艺就是接收JavaPoet。
至于这么些属性怎么获得的, 如类名, id值, Activity与View, 控件是何许,
绑定的onClick方法是什么?
那您得去问AnnotationProcessor了,正是那样无所不能够。


咱俩来捋一下思路,

1 增加注脚
2State of Qatar在AnnotationProcessor中分析注明(至于怎么剖判,上面说卡塔尔(قطر‎
3卡塔尔(قطر‎依照深入分析的消息,自动生成Binding类
4卡塔尔在ButterKnife.bind时,日常是onCreate,
拿到相应的Binding类,通过反射,生成Binding类的靶子,施行Binding类的构造函数。在组织中私下的findViewById,
setOnClickListener等。当时在对应的Activity中增多申明的控件就有值了。增添OnClick的控件就安装了监听了。

看了上述的思绪,是还是不是清醒?所以本人才说那是三个超级轻易的框架。那么接下去,解读一下疏解管理的源码与生成类的源码

第一看安排

// 导入生成代码的库
 api deps.javapoet
// 导入自动注册的库
  compileOnly deps.auto.service

// 上面的库是什么
javapoet: 'com.squareup:javapoet:1.10.0',
'auto': [
         'service': 'com.google.auto.service:auto-service:1.0-rc4',
         'common': 'com.google.auto:auto-common:0.10',
      ],

下一场看下声明微型机的代码, ButterKnifeProcessor, 当中process为分析的艺术

@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);

    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();

      JavaFile javaFile = binding.brewJava(sdk, debuggable);
      try {
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }

    return false;
  }
######################################

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();

    scanForRClasses(env);

    // Process each @BindAnim element.
    for (Element element : env.getElementsAnnotatedWith(BindAnim.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceAnimation(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindAnim.class, e);
      }
    }

    // Process each @BindArray element.
    for (Element element : env.getElementsAnnotatedWith(BindArray.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceArray(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindArray.class, e);
      }
    }

    // Process each @BindBitmap element.
    for (Element element : env.getElementsAnnotatedWith(BindBitmap.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceBitmap(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindBitmap.class, e);
      }
    }

    // Process each @BindBool element.
    for (Element element : env.getElementsAnnotatedWith(BindBool.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceBool(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindBool.class, e);
      }
    }

    // Process each @BindColor element.
    for (Element element : env.getElementsAnnotatedWith(BindColor.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceColor(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindColor.class, e);
      }
    }

    // Process each @BindDimen element.
    for (Element element : env.getElementsAnnotatedWith(BindDimen.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceDimen(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindDimen.class, e);
      }
    }

    // Process each @BindDrawable element.
    for (Element element : env.getElementsAnnotatedWith(BindDrawable.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceDrawable(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindDrawable.class, e);
      }
    }

    // Process each @BindFloat element.
    for (Element element : env.getElementsAnnotatedWith(BindFloat.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceFloat(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindFloat.class, e);
      }
    }

    // Process each @BindFont element.
    for (Element element : env.getElementsAnnotatedWith(BindFont.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceFont(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindFont.class, e);
      }
    }

    // Process each @BindInt element.
    for (Element element : env.getElementsAnnotatedWith(BindInt.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceInt(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindInt.class, e);
      }
    }

    // Process each @BindString element.
    for (Element element : env.getElementsAnnotatedWith(BindString.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseResourceString(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindString.class, e);
      }
    }

    // Process each @BindView element.
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
      // 遍历带有{@interface: BindView}的元素
      // we don't SuperficialValidation.validateElement(element)
      // so that an unresolved View type can be generated by later processing rounds
      try {
        parseBindView(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }

    // Process each @BindViews element.
    for (Element element : env.getElementsAnnotatedWith(BindViews.class)) {
      // we don't SuperficialValidation.validateElement(element)
      // so that an unresolved View type can be generated by later processing rounds
      try {
        parseBindViews(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindViews.class, e);
      }
    }

    // Process each annotation that corresponds to a listener.
    for (Class<? extends Annotation> listener : LISTENERS) {
      findAndParseListener(env, listener, builderMap, erasedTargetNames);
    }

    // Associate superclass binders with their subclass binders. This is a queue-based tree walk
    // which starts at the roots (superclasses) and walks to the leafs (subclasses).
    Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries =
        new ArrayDeque<>(builderMap.entrySet());
    Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
    while (!entries.isEmpty()) {
      Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();

      TypeElement type = entry.getKey();
      BindingSet.Builder builder = entry.getValue();

      TypeElement parentType = findParentType(type, erasedTargetNames);
      if (parentType == null) {
        bindingMap.put(type, builder.build());
      } else {
        BindingSet parentBinding = bindingMap.get(parentType);
        if (parentBinding != null) {
          builder.setParent(parentBinding);
          bindingMap.put(type, builder.build());
        } else {
          // Has a superclass binding but we haven't built it yet. Re-enqueue for later.
          entries.addLast(entry);
        }
      }
    }

    return bindingMap;
  }

############################################

private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
      Set<TypeElement> erasedTargetNames) {
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Start by verifying common generated code restrictions.
    boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
        || isBindingInWrongPackage(BindView.class, element);

    // Verify that the target type extends from View.
    TypeMirror elementType = element.asType();
    if (elementType.getKind() == TypeKind.TYPEVAR) {
      TypeVariable typeVariable = (TypeVariable) elementType;
      elementType = typeVariable.getUpperBound();
    }
    Name qualifiedName = enclosingElement.getQualifiedName();
    Name simpleName = element.getSimpleName();
    if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
      if (elementType.getKind() == TypeKind.ERROR) {
        note(element, "@%s field with unresolved type (%s) "
                + "must elsewhere be generated as a View or interface. (%s.%s)",
            BindView.class.getSimpleName(), elementType, qualifiedName, simpleName);
      } else {
        error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
            BindView.class.getSimpleName(), qualifiedName, simpleName);
        hasError = true;
      }
    }

    if (hasError) {
      return;
    }

    // Assemble information on the field.
    int id = element.getAnnotation(BindView.class).value();

    BindingSet.Builder builder = builderMap.get(enclosingElement);
    QualifiedId qualifiedId = elementToQualifiedId(element, id);
    if (builder != null) {
      String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
      if (existingBindingName != null) {
        error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
            BindView.class.getSimpleName(), id, existingBindingName,
            enclosingElement.getQualifiedName(), element.getSimpleName());
        return;
      }
    } else {
      builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    }

    String name = simpleName.toString();
    TypeName type = TypeName.get(elementType);
    boolean required = isFieldRequired(element);

    builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));

    // Add the type-erased version to the valid binding targets set.
    erasedTargetNames.add(enclosingElement);
  }

以上代码片段分为3部分。

一部分1为process试行的部分,从代码能够摸清,首要有两部完成: 1)
找到全部的申明标签与消息 2)生成代码

有的2为寻觅全部的注脚新闻,如下以BindView为例

  // Process each @BindView element.
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
      // 遍历带有{@interface: BindView}的元素
      // we don't SuperficialValidation.validateElement(element)
      // so that an unresolved View type can be generated by later processing rounds
      try {
        parseBindView(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }

遍历全体带BindView的价签,然后深入分析评释音讯。

有的3为深入分析注解消息, 大家得以获取阐明的类的包名,类名,
注明的值是怎么样等等。


赢得以上消息, 大家有了生成Binding类的装有准绳。那么看一下怎么生成类的

JavaFile brewJava(int sdk, boolean debuggable) {
    return JavaFile.builder(bindingClassName.packageName(), createType(sdk, debuggable))
        .addFileComment("Generated code from Butter Knife. Do not modify!")
        .build();
  }

  private TypeSpec createType(int sdk, boolean debuggable) {
    TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
        .addModifiers(PUBLIC);
    if (isFinal) {
      result.addModifiers(FINAL);
    }

    if (parentBinding != null) {
      result.superclass(parentBinding.bindingClassName);
    } else {
      result.addSuperinterface(UNBINDER);
    }

    if (hasTargetField()) {
      result.addField(targetTypeName, "target", PRIVATE);
    }

    if (isView) {
      result.addMethod(createBindingConstructorForView());
    } else if (isActivity) {
      result.addMethod(createBindingConstructorForActivity());
    } else if (isDialog) {
      result.addMethod(createBindingConstructorForDialog());
    }
    if (!constructorNeedsView()) {
      // Add a delegating constructor with a target type + view signature for reflective use.
      result.addMethod(createBindingViewDelegateConstructor());
    }
    result.addMethod(createBindingConstructor(sdk, debuggable));

    if (hasViewBindings() || parentBinding == null) {
      result.addMethod(createBindingUnbindMethod(result));
    }

    return result.build();
  }

 private MethodSpec createBindingConstructorForView() {
    MethodSpec.Builder builder = MethodSpec.constructorBuilder()
        .addAnnotation(UI_THREAD)
        .addModifiers(PUBLIC)
        .addParameter(targetTypeName, "target");
    if (constructorNeedsView()) {
      builder.addStatement("this(target, target)");
    } else {
      builder.addStatement("this(target, target.getContext())");
    }
    return builder.build();
  }

看了吧?
依照MethodSpec可以扭转大肆的主意,依据TypeSpec能够扭转放肆的类;只要大家获取生成供给的相干消息。


全总流程都顺遂了,那么怎么调用的Binding类达成注入的呢?
答案是在ButterKnife.bind时。
在意:在编写翻译的时候曾经更动对应的Binding类,
在bind时只要求寻觅对应的类

看一下源码

 @NonNull @UiThread
  public static Unbinder bind(@NonNull Object target, @NonNull Dialog source) {
    View sourceView = source.getWindow().getDecorView();
    return createBinding(target, sourceView);
  }

大旨措施是在createBinding中,继续往下看

private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

    if (constructor == null) {
      return Unbinder.EMPTY;
    }

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
      return constructor.newInstance(target, source);
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InstantiationException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InvocationTargetException e) {
      Throwable cause = e.getCause();
      if (cause instanceof RuntimeException) {
        throw (RuntimeException) cause;
      }
      if (cause instanceof Error) {
        throw (Error) cause;
      }
      throw new RuntimeException("Unable to create binding instance.", cause);
    }
  }

你见到了什么样?
findBindingConstructorForClass很无不侧目是依靠targetClass(MainActivity)获得布局函数。然后
return constructor.newInstance(target, source卡塔尔;
生成实例,此时就调用了布局函数。而在结构函数中,它施行了findViewById与setOnClickListener,
并实现了赋值。此时, 加多注解的Button
、View等就完结findViewById的赋值了。Button.setOnClickListener也产生了。

若是您不甘心,继续看一下如何获取构造函数的吧,那就用到了反光了。

private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      return bindingCtor;
    }
    String clsName = cls.getName();
    if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return null;
    }
    try {
      Class<?> bindingClass = cls.getClassLoader().(clsName + "_ViewBinding");
      //noinspection unchecked
      bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
      if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
    } catch (ClassNotFoundException e) {
      if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
      bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
    } catch (NoSuchMethodException e) {
      throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
    }
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
  }

没骗你吧, 加载Binding类,然后getConstructure,获得布局函数了。


透过上述的深入深入分析,你还渺茫吗?若是你把本身列举的3个技能构思,那么您就足以成功自个儿的ButterKnife了。是否很简短吗?
终归是最轻便易行的框架之一了。好了不早了,拜拜了~!

要么那句话,假如你以为那篇随笔对你有赞助,请帮笔者打Call,
如有疑问,请留言,多谢!!!

率先我们来看findAndParseTargets(env卡塔尔是如何营造Map集结的
  private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    //key为类级别的元素,value为BindingSet的内部类Builder,看到Builder我们应该都很敏感,这是典型的
    //建造者模式,这种模式特别常见,不管是在第三方库OkHttp中,还是在Android提供的对话框API中都可以看见
    //它的身影,对于设计模式我们这里不再详细展开
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    //Set集合中保存了所有类级别的元素
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    scanForRClasses(env);
    //由于定义的注解很多,这里省略了其他注解,只关注@BindView
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
      try {
        //解析@BindView注解到buildMap中
        parseBindView(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }
    //builderMap是key为类级别的元素,value为BindingSet的内部类Builder,这里会转为bindingMap,即
    //把value通过builder()方法构建成BindingSet,这就是典型的建造者模式
    Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries =
        new ArrayDeque<>(builderMap.entrySet());
    Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
    while (!entries.isEmpty()) {
      Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();
      TypeElement type = entry.getKey();
      BindingSet.Builder builder = entry.getValue();
      //注意这是在while循环里边,这个方法中会根据当前类级别的元素查找父元素,如果没有就返回null,如果
      //有父元素就返回父元素,从这里可以看出来我们的注解是支持继承的
      TypeElement parentType = findParentType(type, erasedTargetNames);
      if (parentType == null) {
        //父元素为null,直接put当前元素
        bindingMap.put(type, builder.build());
      } else {
        //从bindingMap中先获取父元素的BindingSet
        BindingSet parentBinding = bindingMap.get(parentType);
        if (parentBinding != null) {
          //父元素的BindingSet不为null,就需要为当前元素设置它的父元素
          builder.setParent(parentBinding);
          //然后put到bindingMap中
          bindingMap.put(type, builder.build());
        } else {
          //父元素的BindingSet是null,就先把当前的Entry放到队尾,继续下一次循环
          entries.addLast(entry);
        }
      }
    }
    //返回key为类级别的元素,value为BindingSet的集合
    return bindingMap;
  }
  • 接下去大家看下面方法中的parseBindView(卡塔尔和findParentType(卡塔尔,我们先看findParentType(卡塔尔是如何依照最近类级其余因素查找父成分

  private TypeElement findParentType(TypeElement typeElement, Set<TypeElement> parents) {
    TypeMirror type;
    while (true) {
      //根据当前类级别的元素查找父元素
      type = typeElement.getSuperclass();
      if (type.getKind() == TypeKind.NONE) {
        //父元素为null,直接返回null
        return null;
      }
      //parents集合中保存的是包含有我们自定义注解的类级别的元素,找到
      //父元素并且父元素是个有效的即在集合里,就返回父元素
      typeElement = (TypeElement) ((DeclaredType) type).asElement();
      if (parents.contains(typeElement)) {
        return typeElement;
      }
    }
  }
  • 再先看parseBindView(State of Qatar是怎么着分析@BindView表明到buildMap中的

  private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
      Set<TypeElement> erasedTargetNames) {
    //获取当前元素的类级别的元素
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    //生成的代码是不是可以访问,由于我们是通过target.mTextview来访问的,所以这个方法会校验@BindView修饰的
    //字段是不是private、static的,@BindView是不是在类里边,如果在类里边那么类是不是private的
    boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
        || isBindingInWrongPackage(BindView.class, element);
    //下面几行代码主要是校验@BindView修饰的字段类型,必须是一个View或者是一个接口,否则就毫无意义
    //获取@BindView修饰的字段类型,比如TextView类型
    TypeMirror elementType = element.asType();
    if (elementType.getKind() == TypeKind.TYPEVAR) {
      TypeVariable typeVariable = (TypeVariable) elementType;
      elementType = typeVariable.getUpperBound();
    }
    Name qualifiedName = enclosingElement.getQualifiedName();
    Name simpleName = element.getSimpleName();
    //校验修饰的类型是不是View的子类或接口,否则结束
    if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
      if (elementType.getKind() == TypeKind.ERROR) {
        note(element, "@%s field with unresolved type (%s) "
                + "must elsewhere be generated as a View or interface. (%s.%s)",
            BindView.class.getSimpleName(), elementType, qualifiedName, simpleName);
      } else {
        error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
            BindView.class.getSimpleName(), qualifiedName, simpleName);
        hasError = true;
      }
    }
    if (hasError) {
      return;
    }
    //获取@BindView的值,即R.id.xxx
    int id = element.getAnnotation(BindView.class).value();
    //享元模式的设计,根据当前元素的类级别的元素先从Map中获取BindingSet的内部类Builder
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    //把int类型的id包装成了QualifiedId类
    QualifiedId qualifiedId = elementToQualifiedId(element, id);
    if (builder != null) {
       //getId(qualifiedId)把QualifiedId类中的id又封装成了Id类,校验Id类是否已经存在
      String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
      if (existingBindingName != null) {
        error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
            BindView.class.getSimpleName(), id, existingBindingName,
            enclosingElement.getQualifiedName(), element.getSimpleName());
        return;
      }
    } else {
      //builder为空,说明当前类还没有对应的value,需要new一个出来,并放到builderMap中
      builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    }
    //@BindView修饰的字段的简单名称,即变量名mTextView
    String name = simpleName.toString();
    //@BindView修饰的字段的类型,比如TextView
    TypeName type = TypeName.get(elementType);
    //是否必须是字段
    boolean required = isFieldRequired(element);
    //调用BindingSet的内部类Builder中的addField方法封装解析的信息
    builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));
    //给所有含有自定义注解的类组成的Set集合中添加元素
    erasedTargetNames.add(enclosingElement);
  }

  private BindingSet.Builder getOrCreateBindingBuilder(
      Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    if (builder == null) {
      //builder为null,就构建一个BindingSet.Builder,放入集合中,并返回
      builder = BindingSet.newBuilder(enclosingElement);
      builderMap.put(enclosingElement, builder);
    }
    return builder;
  }

  static Builder newBuilder(TypeElement enclosingElement) {
    TypeMirror typeMirror = enclosingElement.asType();
    //类级别的元素是不是View的子类
    boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE);
    //类级别的元素是不是Activity的子类
    boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE);
    //类级别的元素是不是Dialog的子类
    boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE);
    TypeName targetType = TypeName.get(typeMirror);
    if (targetType instanceof ParameterizedTypeName) {
      targetType = ((ParameterizedTypeName) targetType).rawType;
    }
    //根据当前类级别的元素获取包名
    String packageName = getPackage(enclosingElement).getQualifiedName().toString();
     //substring截取后一般是MainActivity这样的字符串,然后replace的话会没有效果,如果有.则用$代替
    String className = enclosingElement.getQualifiedName().toString().substring(
        packageName.length() + 1).replace('.', '$');
    //这里可以看到要生成的包名,要生成的类名,格式如MainActivity_ViewBinding
    ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");
    //当前类是不是被final修饰
    boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
    //直接通过构造new一个Builder,第一个参数表示是不是一个class,第二个参数中含有要生成的类名和包名,其它都是布尔类型的参数
    return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
  }

小结下,在parseBindView(卡塔尔国方法中剖判出来的音讯都放在BindingSet的里边类Builder中,包涵的消息有包名、原始类名、要转换的类名、奥迪Q5.id.xxx、是否字段、变量名mTextView、变量类型TextView等消息。而BindingSet的中间类Builder又会通过调用build(State of Qatar方法创设四个BindSet实例并把那几个音讯传递过去,那正是优质的建造者情势,在BindSet中有了这几个新闻,下边大家就能够改动扶持类呀。

Color值限定

上面部分关联了ColorRes,用来界定颜色能源id,这里大家将使用ColorInt,二个用来约束Color值的申明.
在较早的TextView的setTextColor是这般完结的.

public void setTextColor(int color) {
    mTextColor = ColorStateList.valueOf(color);
    updateTextColors();
}

而是上面的措施在调用时平日会现身这种状态

myTextView.setTextColor(R.color.colorAccent);

如上,假若传递过去的参数为color的财富id就能产出颜色取错误的标题,那个标题在过去依然相比严重的.还好ColorInt并发了,校勘了这一难题.

public void setTextColor(@ColorInt int color) {
    mTextColor = ColorStateList.valueOf(color);
    updateTextColors();
}

当大家再度传播Color能源值时,就能够拿走错误的提醒.

末尾我们看binding.brewJava(sdk, debuggable卡塔尔是什么变迁扶助类的

1.回到最先的process(卡塔尔方法,大家说完了哪些创设Map集结,接着大家讲授怎么样转移扶持类

  @Override 
  public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //根据env获取一个Map集合,其中key为类级别的元素,value为BindingSet
    //BindingSet是一个自定义类,其中含有当前类中所有的注解信息
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();
      //注意下面的两行代码就是生成辅助类啦,由于需要用到JavaPoet库,后面会详细讲解
      JavaFile javaFile = binding.brewJava(sdk, debuggable);
      try {
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }
    return false;
  }

2.急需运用javapoet,由此打开那一个库大家有一些看下它是怎么着利用的

  • 诸如我们想要生成八个不足为奇的java类,在那之中有main方法,方法中有syso

  package com.example.helloworld;
  public final class HelloWorld {
    public static void main(String[] args) {
      System.out.println("Hello, JavaPoet!");
    }
  }
  • 那大家即可调用上面包车型大巴艺术来促成,能够见见又是建造者builder方式,真是无处不见啊,呼哈

  //方法级别的,定义方法名
  MethodSpec main = MethodSpec.methodBuilder("main")
       //定义修饰符
      .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
       //定义返回值
      .returns(void.class)
       //定义方法参数
      .addParameter(String[].class, "args")
       //定义方法中的syso
      .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
      .build();

  //类级别的,定义类名
  TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
      //添加修饰符
      .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
      //把上面定义的main添加到类中
      .addMethod(main)
      .build();

   //JavaFile中保存了所有的信息,包名和上面定义的类信息,方法信息
  JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
      .build();

  javaFile.writeTo(System.out);

3.简洁明了看了javapoet的选择,继续看大家的代码,而大家领会生成协助类的享有消息都在JavaFile中,我们看JavaFile是什么转换的,约等于上面包车型客车brewJava(State of Qatar

  JavaFile javaFile = binding.brewJava(sdk, debuggable);

  JavaFile brewJava(int sdk, boolean debuggable) {
    //bindingClassName是ClassName类型,它是对包名和类名的封装,这里可以看到获取包名,类和类里边的信息是
    //通过createType()方法获取的,也就是TypeSpec
    return JavaFile.builder(bindingClassName.packageName(), createType(sdk, debuggable))
        .addFileComment("Generated code from Butter Knife. Do not modify!")
        .build();
  }

  private TypeSpec createType(int sdk, boolean debuggable) {
    //类级别的,定义类名,bindingClassName是ClassName类型,它是对包名和类名的封装
    //上面获取的是包名,这里获取的是辅助类的类名
    TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
        .addModifiers(PUBLIC);
    if (isFinal) {
      result.addModifiers(FINAL);
    }
    //判断有没有父类,即生成的辅助类是不是也要从父类继承
    if (parentBinding != null) {
      //有父类,那么生成的辅助类也要从父类继承
      result.superclass(parentBinding.bindingClassName);
    } else {
      //没有父类,就让辅助类实现Unbinder
      result.addSuperinterface(UNBINDER);
    }
    //设置类似这样的字段private MainActivity target;
    if (hasTargetField()) {
      result.addField(targetTypeName, "target", PRIVATE);
    }
    //判断是不是View、Activity、Dialog的子类,来生成不同构造
    if (isView) {
      result.addMethod(createBindingConstructorForView());
    } else if (isActivity) {
      //这里我们只关注这个构造的生成,这个方法用来生成一个参数MainActivity target的构造
      result.addMethod(createBindingConstructorForActivity());
    } else if (isDialog) {
      result.addMethod(createBindingConstructorForDialog());
    }
    if (!constructorNeedsView()) {
      result.addMethod(createBindingViewDelegateConstructor());
    }
    //前面是只有一个参数MainActivity target构造
    //这里是两个参数final MainActivity target, View source的构造
    result.addMethod(createBindingConstructor(sdk, debuggable));
    if (hasViewBindings() || parentBinding == null) {
      //没有父类,那么我们前面实现了Unbinder接口,需要实现它里面的unbind方法
      result.addMethod(createBindingUnbindMethod(result));
    }
    return result.build();
  }

4.上边方法中调用的多个法子大家尚未看,个中有叁个参数的构造、八个参数的构造、unbind(卡塔尔(قطر‎方法,接下去大家挨着看

  • createBindingConstructorForActivity(State of Qatar生成一个参数MainActivity
    target的构造

  private MethodSpec createBindingConstructorForActivity() {
    MethodSpec.Builder builder = MethodSpec.constructorBuilder()
         //添加@UiThread注解
        .addAnnotation(UI_THREAD)
        .addModifiers(PUBLIC)
        //添加构造中的参数,如MainActivity target
        .addParameter(targetTypeName, "target");
    if (constructorNeedsView()) {
      builder.addStatement("this(target, target.getWindow().getDecorView())");
    } else {
      //这里是一个参数的构造不需要View,构造中添加this(target, target)
      builder.addStatement("this(target, target)");
    }
    return builder.build();
  }
  • createBindingConstructor(sdk, debuggableState of Qatar生成七个参数final
    MainActivity target, View source的结构

private MethodSpec createBindingConstructor(int sdk, boolean debuggable) {
    MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
         //添加@UiThread注解
        .addAnnotation(UI_THREAD)
        .addModifiers(PUBLIC);
    //判断是否有方法绑定,即是否有onClick()这样的回调方法
    if (hasMethodBindings()) {
      //由于是在匿名内部类方法里边调用构造函数的第一个参数,需要加上final修饰
      constructor.addParameter(targetTypeName, "target", FINAL);
    } else {
      //没有方法绑定,只需要添加构造函数中的第一个参数MainActivity target
      constructor.addParameter(targetTypeName, "target");
    }

    if (constructorNeedsView()) {
      //构造函数的第二个参数就是View source
      constructor.addParameter(VIEW, "source");
    } else {
      constructor.addParameter(CONTEXT, "context");
    }
    //有一些警告的话添加java提供的@SuppressWarnings注解
    if (hasUnqualifiedResourceBindings()) {
      constructor.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
          .addMember("value", "$S", "ResourceType")
          .build());
    }
    if (hasOnTouchMethodBindings()) {
      constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT)
          .addMember("value", "$S", "ClickableViewAccessibility")
          .build());
    }
    //有父类的情况下,需要根据情况添加如下代码
    if (parentBinding != null) {
      if (parentBinding.constructorNeedsView()) {
        constructor.addStatement("super(target, source)");
      } else if (constructorNeedsView()) {
        constructor.addStatement("super(target, source.getContext())");
      } else {
        constructor.addStatement("super(target, context)");
      }
      constructor.addCode("n");
    }
    if (hasTargetField()) {
      //会执行这里,添加this.target = target,并且换行
      constructor.addStatement("this.target = target");
      constructor.addCode("n");
    }
    //判断是不是有View绑定,肯定有@BindView
    if (hasViewBindings()) {
      if (hasViewLocal()) {
        //在构造函数中定义一个局部变量View view,用来view.setOnClickListener()
        constructor.addStatement("$T view", VIEW);
      }
      for (ViewBinding binding : viewBindings) {
        //这是在for循环里面,这个方法会间接调用findViewById和强转的操作,接下来主要看这个方法
        addViewBinding(constructor, binding, debuggable);
      }
      for (FieldCollectionViewBinding binding : collectionBindings) {
        constructor.addStatement("$L", binding.render(debuggable));
      }
      //资源绑定不为空,才会换行
      if (!resourceBindings.isEmpty()) {
        constructor.addCode("n");
      }
    }
    //资源的绑定不为空才会执行
    if (!resourceBindings.isEmpty()) {
      if (constructorNeedsView()) {
        constructor.addStatement("$T context = source.getContext()", CONTEXT);
      }
      if (hasResourceBindingsNeedingResource(sdk)) {
        constructor.addStatement("$T res = context.getResources()", RESOURCES);
      }
      for (ResourceBinding binding : resourceBindings) {
        constructor.addStatement("$L", binding.render(sdk));
      }
    }
    return constructor.build();
  }

private void addViewBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) {
    if (binding.isSingleFieldBinding()) {
      FieldViewBinding fieldBinding = binding.getFieldBinding();
      //先写target.mTextview = ,这里可以看到是直接通过.调用的,因此我们写的字段不能是private的
      CodeBlock.Builder builder = CodeBlock.builder()
          .add("target.$L = ", fieldBinding.getName());
      boolean requiresCast = requiresCast(fieldBinding.getType());
      if (!debuggable || (!requiresCast && !fieldBinding.isRequired())) {
        builder.add("source.findViewById($L)", binding.getId().code);
      } else {
        //下面的代码会组合成一行代码,Utils中的findRequiredViewAsType()会进行find和强转操作,上面介绍过了
        //Utils.findRequiredViewAsType(source, R.id.textview, "field 'mTextview'", TextView.class);
        builder.add("$T.find", UTILS);
        builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView");
        if (requiresCast) {
          builder.add("AsType");
        }
        builder.add("(source, $L", binding.getId().code);
        if (fieldBinding.isRequired() || requiresCast) {
          builder.add(", $S", asHumanDescription(singletonList(fieldBinding)));
        }
        if (requiresCast) {
          builder.add(", $T.class", fieldBinding.getRawType());
        }
        builder.add(")");
      }
      result.addStatement("$L", builder.build());
      return;
    }
  • createBindingUnbindMethod(result卡塔尔国生成unbind(卡塔尔方法,即实现Unbinder接口中的unbind(卡塔尔方法

private MethodSpec createBindingUnbindMethod(TypeSpec.Builder bindingClass) {
    //定义unbind()方法,并且添加@Override注解
    MethodSpec.Builder result = MethodSpec.methodBuilder("unbind")
        .addAnnotation(Override.class)
        .addModifiers(PUBLIC);
    if (!isFinal && parentBinding == null) {
      //@CallSuper是android提供的注解,表示调用父类
      result.addAnnotation(CALL_SUPER);
    }
    if (hasTargetField()) {
      if (hasFieldBindings()) {
        //添加MainActivity target = this.target
        result.addStatement("$T target = this.target", targetTypeName);
      }
      //添加if (target == null) throw new IllegalStateException("Bindings already cleared.")
      result.addStatement("if (target == null) throw new $T($S)", IllegalStateException.class,
          "Bindings already cleared.");
      //添加this.target = null
      result.addStatement("$N = null", hasFieldBindings() ? "this.target" : "target");
      //换行
      result.addCode("n");
      for (ViewBinding binding : viewBindings) {
        if (binding.getFieldBinding() != null) {
          //循环添加类似这样的代码来解除绑定,target.mTextview = null;
          result.addStatement("target.$L = null", binding.getFieldBinding().getName());
        }
      }
      for (FieldCollectionViewBinding binding : collectionBindings) {
        result.addStatement("target.$L = null", binding.name);
      }
    }
     //有绑定方法,如设置监听事件回调方法才会走这个if
    if (hasMethodBindings()) {
      result.addCode("n");
      for (ViewBinding binding : viewBindings) {
        addFieldAndUnbindStatement(bindingClass, result, binding);
      }
    }
    if (parentBinding != null) {
      //有父类才会换行,并且添加super.unbind()
      result.addCode("n");
      result.addStatement("super.unbind()");
    }
    return result.build();
  }

计算下,至此生成扶助类的代码就简要介绍完了,我们只是简介了下@BindView,其它的讲解都没介绍,有野趣的读者能够团结去扒Butterknife源码,我们能够在appbuildgeneratedsourceaptdebug目录下找到编写翻译时生成的扶持类,上面大家列出一个简便的

package com.zeit.butter;

import android.support.annotation.CallSuper;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import butterknife.Unbinder;
import butterknife.internal.DebouncingOnClickListener;
import butterknife.internal.Utils;

public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  private View view2131427415;

  @UiThread
  public MainActivity_ViewBinding(MainActivity target) {
    this(target, target.getWindow().getDecorView());
  }

  @UiThread
  public MainActivity_ViewBinding(final MainActivity target, View source) {
    this.target = target;

    View view;
    target.mTextview = Utils.findRequiredViewAsType(source, R.id.textview, "field 'mTextview'", TextView.class);
    view = Utils.findRequiredView(source, R.id.button, "field 'mButton' and method 'onClick'");
    target.mButton = Utils.castView(view, R.id.button, "field 'mButton'", Button.class);
    view2131427415 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.onClick();
      }
    });
    target.mImageview = Utils.findRequiredViewAsType(source, R.id.imageview, "field 'mImageview'", ImageView.class);
  }

  @Override
  @CallSuper
  public void unbind() {
    MainActivity target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");
    this.target = null;

    target.mTextview = null;
    target.mButton = null;
    target.mImageview = null;

    view2131427415.setOnClickListener(null);
    view2131427415 = null;
  }
}

CheckResult

那是三个关于再次来到结果的注释,用来解说方法,要是三个方式取得了结果,却没有利用这几个结果,就能够有不当现身,一旦现身这种不明是非,就表达您没有科学行使该措施。

@CheckResult
public String trim(String s) {
    return s.trim();
}

总结

JakeWharton的Butterknife,俗称”黄油刀”,能够扶助我们进步支付效用,非常是相称AS插件的选择,上边推荐叁个插件Android-Butterknife-Zelezny。而读书源码能够让我们知其然知其所以然,也能够学到比非常多设计方式。建议阅读第三方库或Android提供的API等源码此前,先从设计情势学起,推荐《Android源码设计形式拆解分析与实战》。

线程相关

Android中提供了多少个与线程相关的注释

  • @UiThread,平常可以等效主线程,注明情势须求在UIThread实行,举个例子View类就利用这一个表明
  • @MainThread 主线程,平常起步后创设的首先个线程
  • @WorkerThread
    工小编线程,日常为部分后台的线程,比如AsyncTask里面包车型大巴doInBackground就是那样的.
  • @BinderThread 申明方法必需求在BinderThread线程中进行,日常采纳比较少.

部分示范

new AsyncTask<Void, Void, Void>() {
        //doInBackground is already annotated with @WorkerThread
        @Override
        protected Void doInBackground(Void... params) {
            return null;
            updateViews();//error
        }
    };

@UiThread
public void updateViews() {
    Log.i(LOGTAG, "updateViews ThreadInfo=" + Thread.currentThread());
}

留心,这种情景下不会冒出谬误提醒

new Thread(){
    @Override
    public void run() {
        super.run();
        updateViews();
    }
}.start();

纵然如此updateViews会在贰个新的劳引力线程中试行,不过在compile时未有不当提示.

因为它的判别依附是,假若updateView的线程注明(这里为@UiThread卡塔尔国和run(没有线程声明State of Qatar不均等才会错误提示.倘使run方法未有线程表明,则不提示.

CallSuper

重写的法子一定要调用super方法

动用这一个评释,我们能够强逼措施在重写时必需调用父类的法门譬如Application的onCreate,onConfigurationChanged等.

Keep

在Android编写翻译生成APK的环节,大家常常必要设置minifyEnabled为true完成上面包车型大巴多少个功能

  • 混淆代码
  • 删去未有用的代码

唯独出于某某些目标,大家须求不混淆某部分代码恐怕不删除某处代码,除了配置复杂的Proguard文件之外,大家还是能动用@Keep申明.

@Keep
public static int getBitmapWidth(Bitmap bitmap) {
    return bitmap.getWidth();
}

ButterKnife

ButterKnife是贰个用来绑定View,财富和回调的进步效用的工具.小编为Jake
Wharton. ButterKnife的低价

  • 行使BindView代替冗杂的findViewById和类型调换
  • 接收OnClick表明方法来替换显式证明的佚名内部类
  • 利用BindString,BindBool,BindDrawable等申明达成资源获取

一个摘自Github的身体力行

class ExampleActivity extends Activity {
  @BindView(R.id.user) EditText username;
  @BindView(R.id.pass) EditText password;

  @BindString(R.string.login_error) String loginErrorMessage;

  @OnClick(R.id.submit) void submit() {
    // TODO call server...
  }

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.bind(this);
    // TODO Use fields...
  }
}

ButterKnife职业规律

以BindView注脚使用为例,示例代码为

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.myTextView)
    TextView myTextView;

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

1.程序在compile时,会根据注明自动生成多个类,这里为MainActivity_ViewBinder.classMainActivity_ViewBinding.class
2.当我们调用ButterKnife.bind(this);时,会寻觅当前类对应的ViewBinder类,并调用bind方法,这里会调用到MainActiivty_ViewBinder.bind方法.
3.MainActiivty_ViewBinder.bind方法其实是调用了findViewById然后在张开类型转变,赋值给MainActivity的myTextView属性

ButterKnife的bind方法

public static Unbinder bind(@NonNull Activity target) {
    return getViewBinder(target).bind(Finder.ACTIVITY, target, target);
}

ButterKnife的getViewBinderfindViewBinderForClass

@NonNull @CheckResult @UiThread
  static ViewBinder<Object> getViewBinder(@NonNull Object target) {
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());
    return findViewBinderForClass(targetClass);
  }

  @NonNull @CheckResult @UiThread
  private static ViewBinder<Object> findViewBinderForClass(Class<?> cls) {
   //如果内存集合BINDERS中包含,则不再查找
    ViewBinder<Object> viewBinder = BINDERS.get(cls);
    if (viewBinder != null) {
      if (debug) Log.d(TAG, "HIT: Cached in view binder map.");
      return viewBinder;
    }
    String clsName = cls.getName();
    if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return NOP_VIEW_BINDER;
    }
    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
      //使用反射创建实例
      Class<?> viewBindingClass = Class.forName(clsName + "_ViewBinder");
      //noinspection unchecked
      viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();
      if (debug) Log.d(TAG, "HIT: Loaded view binder class.");
    } catch (ClassNotFoundException e) {
        //如果没有找到,对父类进行查找
      if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
      viewBinder = findViewBinderForClass(cls.getSuperclass());
    } catch (InstantiationException e) {
      throw new RuntimeException("Unable to create view binder for " + clsName, e);
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Unable to create view binder for " + clsName, e);
    }
    //加入内存集合,便于后续的查找
    BINDERS.put(cls, viewBinder);
    return viewBinder;
  }

MainActivity_ViewBinder的反编写翻译源码

➜  androidannotationsample javap -c MainActivity_ViewBinder
Warning: Binary file MainActivity_ViewBinder contains com.example.admin.androidannotationsample.MainActivity_ViewBinder
Compiled from "MainActivity_ViewBinder.java"
public final class com.example.admin.androidannotationsample.MainActivity_ViewBinder implements butterknife.internal.ViewBinder<com.example.admin.androidannotationsample.MainActivity> {
  public com.example.admin.androidannotationsample.MainActivity_ViewBinder();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public butterknife.Unbinder bind(butterknife.internal.Finder, com.example.admin.androidannotationsample.MainActivity, java.lang.Object);
    Code:
       0: new           #2                  // class com/example/admin/androidannotationsample/MainActivity_ViewBinding
       3: dup
       4: aload_2
       5: aload_1
       6: aload_3                           // 创建ViewBinding实例
       7: invokespecial #3                  // Method com/example/admin/androidannotationsample/MainActivity_ViewBinding."<init>":(Lcom/example/admin/androidannotationsample/MainActivity;Lbutterknife/internal/Finder;Ljava/lang/Object;)V
      10: areturn

  public butterknife.Unbinder bind(butterknife.internal.Finder, java.lang.Object, java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: aload_2
       3: checkcast     #4                  // class com/example/admin/androidannotationsample/MainActivity
       6: aload_3                           //调用上面的重载方法
       7: invokevirtual #5                  // Method bind:(Lbutterknife/internal/Finder;Lcom/example/admin/androidannotationsample/MainActivity;Ljava/lang/Object;)Lbutterknife/Unbinder;
      10: areturn
}

MainActivity_ViewBinding的反编写翻译源码

➜  androidannotationsample javap -c MainActivity_ViewBinding
Warning: Binary file MainActivity_ViewBinding contains com.example.admin.androidannotationsample.MainActivity_ViewBinding
Compiled from "MainActivity_ViewBinding.java"
public class com.example.admin.androidannotationsample.MainActivity_ViewBinding<T extends com.example.admin.androidannotationsample.MainActivity> implements butterknife.Unbinder {
  protected T target;

  public com.example.admin.androidannotationsample.MainActivity_ViewBinding(T, butterknife.internal.Finder, java.lang.Object);
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: aload_1
       6: putfield      #2                  // Field target:Lcom/example/admin/androidannotationsample/MainActivity;
       9: aload_1
      10: aload_2
      11: aload_3                           //调用Finder.findRequireViewAsType找到View,并进行类型转换,并复制给MainActivity中对一个的变量
      12: ldc           #4                  // int 2131427412
      14: ldc           #5                  // String field 'myTextView'
      16: ldc           #6                  // class android/widget/TextView
                                            // 内部实际调用了findViewById
      18: invokevirtual #7                  // Method butterknife/internal/Finder.findRequiredViewAsType:(Ljava/lang/Object;ILjava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
      21: checkcast     #6                  // class android/widget/TextView
      24: putfield      #8                  // Field com/example/admin/androidannotationsample/MainActivity.myTextView:Landroid/widget/TextView;
      27: return

  public void unbind();
    Code:
       0: aload_0
       1: getfield      #2                  // Field target:Lcom/example/admin/androidannotationsample/MainActivity;
       4: astore_1
       5: aload_1
       6: ifnonnull     19
       9: new           #9                  // class java/lang/IllegalStateException
      12: dup
      13: ldc           #10                 // String Bindings already cleared.
      15: invokespecial #11                 // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V
      18: athrow
      19: aload_1
      20: aconst_null                       // 解除绑定,设置对应的变量为null
      21: putfield      #8                  // Field com/example/admin/androidannotationsample/MainActivity.myTextView:Landroid/widget/TextView;
      24: aload_0
      25: aconst_null
      26: putfield      #2                  // Field target:Lcom/example/admin/androidannotationsample/MainActivity;
      29: return
}

Finder的源码

package butterknife.internal;

import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.support.annotation.IdRes;
import android.view.View;

@SuppressWarnings("UnusedDeclaration") // Used by generated code.
public enum Finder {
  VIEW {
    @Override public View findOptionalView(Object source, @IdRes int id) {
      return ((View) source).findViewById(id);
    }

    @Override public Context getContext(Object source) {
      return ((View) source).getContext();
    }

    @Override protected String getResourceEntryName(Object source, @IdRes int id) {
      final View view = (View) source;
      // In edit mode, getResourceEntryName() is unsupported due to use of BridgeResources
      if (view.isInEditMode()) {
        return "<unavailable while editing>";
      }
      return super.getResourceEntryName(source, id);
    }
  },
  ACTIVITY {
    @Override public View findOptionalView(Object source, @IdRes int id) {
      return ((Activity) source).findViewById(id);
    }

    @Override public Context getContext(Object source) {
      return (Activity) source;
    }
  },
  DIALOG {
    @Override public View findOptionalView(Object source, @IdRes int id) {
      return ((Dialog) source).findViewById(id);
    }

    @Override public Context getContext(Object source) {
      return ((Dialog) source).getContext();
    }
  };

  //查找对应的Finder,如上面的ACTIVITY, DIALOG, VIEW
  public abstract View findOptionalView(Object source, @IdRes int id);

  public final <T> T findOptionalViewAsType(Object source, @IdRes int id, String who,
      Class<T> cls) {
    View view = findOptionalView(source, id);
    return castView(view, id, who, cls);
  }

  public final View findRequiredView(Object source, @IdRes int id, String who) {
    View view = findOptionalView(source, id);
    if (view != null) {
      return view;
    }
    String name = getResourceEntryName(source, id);
    throw new IllegalStateException("Required view '"
        + name
        + "' with ID "
        + id
        + " for "
        + who
        + " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
        + " (methods) annotation.");
  }

  //来自ViewBinding的调用
  public final <T> T findRequiredViewAsType(Object source, @IdRes int id, String who,
      Class<T> cls) {
    View view = findRequiredView(source, id, who);
    return castView(view, id, who, cls);
  }

  public final <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
    try {
      return cls.cast(view);
    } catch (ClassCastException e) {
      String name = getResourceEntryName(view, id);
      throw new IllegalStateException("View '"
          + name
          + "' with ID "
          + id
          + " for "
          + who
          + " was of the wrong type. See cause for more info.", e);
    }
  }

  @SuppressWarnings("unchecked") // That's the point.
  public final <T> T castParam(Object value, String from, int fromPos, String to, int toPos) {
    try {
      return (T) value;
    } catch (ClassCastException e) {
      throw new IllegalStateException("Parameter #"
          + (fromPos + 1)
          + " of method '"
          + from
          + "' was of the wrong type for parameter #"
          + (toPos + 1)
          + " of method '"
          + to
          + "'. See cause for more info.", e);
    }
  }

  protected String getResourceEntryName(Object source, @IdRes int id) {
    return getContext(source).getResources().getResourceEntryName(id);
  }

  public abstract Context getContext(Object source);
}

Otto

奥托 Bus 是五个专为Android改装的伊夫nt
Bus,在数不完品种中都有应用.由Square开源分享.

public class EventBusTest {
    private static final String LOGTAG = "EventBusTest";
    Bus mBus  = new Bus();

    public void test() {
        mBus.register(this);
    }

    class NetworkChangedEvent {

    }

    @Produce
    public NetworkChangedEvent sendNetworkChangedEvent() {
        return new NetworkChangedEvent();
    }

    @Subscribe
    public void onNetworkChanged(NetworkChangedEvent event) {
        Log.i(LOGTAG, "onNetworkChanged event=" + event);
    }
}

奥托 的干活原理

  • 利用@Produce和@Subscribe标志方法
  • 当调用bus.register方法,去探究注册对象的标识方法,并cache映射关系
  • 当post事件时,将事件与handler方法对应参加事件队列
  • 抽出事件队列,然后调用handler管理

正如为对奥托如何运用注解的解析

register的源码

public void register(Object object) {
    if (object == null) {
      throw new NullPointerException("Object to register must not be null.");
    }
    enforcer.enforce(this);
    //查找object中的Subscriber
    Map<Class<?>, Set<EventHandler>> foundHandlersMap = handlerFinder.findAllSubscribers(object);
    for (Class<?> type : foundHandlersMap.keySet()) {
      Set<EventHandler> handlers = handlersByType.get(type);
      if (handlers == null) {
        //concurrent put if absent
        Set<EventHandler> handlersCreation = new CopyOnWriteArraySet<EventHandler>();
        handlers = handlersByType.putIfAbsent(type, handlersCreation);
        if (handlers == null) {
            handlers = handlersCreation;
        }
      }
      final Set<EventHandler> foundHandlers = foundHandlersMap.get(type);
      if (!handlers.addAll(foundHandlers)) {
        throw new IllegalArgumentException("Object already registered.");
      }
    }

    for (Map.Entry<Class<?>, Set<EventHandler>> entry : foundHandlersMap.entrySet()) {
      Class<?> type = entry.getKey();
      EventProducer producer = producersByType.get(type);
      if (producer != null && producer.isValid()) {
        Set<EventHandler> foundHandlers = entry.getValue();
        for (EventHandler foundHandler : foundHandlers) {
          if (!producer.isValid()) {
            break;
          }
          if (foundHandler.isValid()) {
            dispatchProducerResultToHandler(foundHandler, producer);
          }
        }
      }
    }
  }

HandlerFinder源码

interface HandlerFinder {

  Map<Class<?>, EventProducer> findAllProducers(Object listener);

  Map<Class<?>, Set<EventHandler>> findAllSubscribers(Object listener);

  //Otto注解查找器
  HandlerFinder ANNOTATED = new HandlerFinder() {
    @Override
    public Map<Class<?>, EventProducer> findAllProducers(Object listener) {
      return AnnotatedHandlerFinder.findAllProducers(listener);
    }

    @Override
    public Map<Class<?>, Set<EventHandler>> findAllSubscribers(Object listener) {
      return AnnotatedHandlerFinder.findAllSubscribers(listener);
    }
  };

切切实实查找达成

/** This implementation finds all methods marked with a {@link Subscribe} annotation. */
  static Map<Class<?>, Set<EventHandler>> findAllSubscribers(Object listener) {
    Class<?> listenerClass = listener.getClass();
    Map<Class<?>, Set<EventHandler>> handlersInMethod = new HashMap<Class<?>, Set<EventHandler>>();

    Map<Class<?>, Set<Method>> methods = SUBSCRIBERS_CACHE.get(listenerClass);
    if (null == methods) {
      methods = new HashMap<Class<?>, Set<Method>>();
      loadAnnotatedSubscriberMethods(listenerClass, methods);
    }
    if (!methods.isEmpty()) {
      for (Map.Entry<Class<?>, Set<Method>> e : methods.entrySet()) {
        Set<EventHandler> handlers = new HashSet<EventHandler>();
        for (Method m : e.getValue()) {
          handlers.add(new EventHandler(listener, m));
        }
        handlersInMethod.put(e.getKey(), handlers);
      }
    }

    return handlersInMethod;
  }

如上就是有关Android中证明的局地总括,随笔部分内容参照他事他说加以考查自 Support
Annotations ,希望能支持我们对注脚有根基的认知,并使用到骨子里的平日支出之中。

发表评论

电子邮件地址不会被公开。 必填项已用*标注