代理模式通过创建一个代理对象来代替真实的对象,在客户端看来代理对象和真实对象并没有两样。实际的工作还是通过代理对象传到真实对象中。
但是这里说完全一样也不尽然,我们可以使用代理对象在真实对象的某个方法上加强些功能,但是主要的核心功能还是由真实对象来提供的。
代理模式可以分成如下几种:
1.虚代理:根据需要来创建开销很大的对象,该对象只有在需要的时候才会被真正创建。
2.远程代理:用来在不同的地址空间上代表同一个对象。
3.保护代理:控制对原始对象的访问。
4.智能指引:在访问对象时执行一些附加操作,比如对指向实际对象的引用计数,第一次引用一个持久对象时,将它装入内存等。

静态代理

public interface Subject {
public void request();
}
public class RealSubject implements Subject{
@Override
public void request() {
System.out.println("This is from RealSubject");
}
}
public class SubjectProxy implements Subject{
private Subject realSubject = null;
public SubjectProxy(Subject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
this.doBeforeRequest();
if(realSubject != null) {
realSubject.request();
}
this.doAfterRequest();
}

private void doBeforeRequest() {
System.out.println("This is from doBefore Request");
}

private void doAfterRequest() {
System.out.println("This is from doAfter Request");
}
}
public class Client {
public static void main(String args[]) {
Subject realSubject = new RealSubject();
Subject subject = new SubjectProxy(realSubject);
subject.request();
}
}

动态代理

上面讲的是静态代理,接下来要看到的将是动态代理,动态代理是在实现阶段不用关心代理谁,而在运行阶段才指定代理的对象。

public interface Subject {
public void doSomething();
}
public class RealSubject implements Subject {
@Override
public void doSomething() {
System.out.println("This is from RealSubject");
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class DynamicProxy <T> {
public static <T> T newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) {
return (T) Proxy.newProxyInstance(loader, interfaces, h);
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHanlder implements InvocationHandler {
private Object proxy = null;
public MyInvocationHanlder(Object proxy) {
this.proxy = proxy;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(this.proxy, args);
}
}
public class Client {
public static void main(String args[]) {
Subject realsubject = new RealSubject();
MyInvocationHanlder handler = new MyInvocationHanlder(realsubject);
Subject subject = DynamicProxy.newProxyInstance(realsubject.getClass().getClassLoader(),
realsubject.getClass().getInterfaces(),handler);
subject.doSomething();
}
}

有时候系统中存在有大量的细粒度的对象,并且这些细粒度的对象有很多存在重复的对象,
还有一种情况是系统中的大量细粒度对象整体上并不重复,但是在每个对象的部分数据上存在重复。
为了避免这种现象我们需要通过缓存包含重复数据的对象,让这些对象只出现一次,从而减少内存的消耗。

因此我们需要对这些细粒度的对象的组成进行分类,分出哪些数据是不变的并且重复出现的,称之为内部变量,哪些数据是经常变化的,我们称之为外部变量,我们需要缓存的是哪些不变且重复的内部变量,而哪些变化的外部变量就不需要缓存了,一般我们会把外部状态分离出来,放到外部让应用在使用的时候进行维护并在需要的时候传递给享元对象使用。
这就是享元模式的基本思想,它的设计重点在于分离变与不变。

public class Flyweight {
private long id;
private String name;
private String sex;
private String classNo;

public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getClassNo() {
return classNo;
}
public void setClassNo(String classNo) {
this.classNo = classNo;
}
public Flyweight(String key){
String[] internalkey = key.split("-");
this.sex = internalkey[0];
this.classNo = internalkey[1];
}
}
public class ConcreFlyweight extends Flyweight {
private String key;
public ConcreFlyweight(String key) {
super(key);
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}

public String toString() {
return "Id = "+super.getId() +" Name = "+super.getName()
+" Sex = "+super.getSex() +" Class No."+ super.getClassNo();
}
}
public class FlyweightFactory {

private static class FlyweightFactoryHolder {
public static FlyweightFactory instance = new FlyweightFactory();
}
private FlyweightFactory(){}

public static FlyweightFactory getInstance() {
return FlyweightFactoryHolder.instance;
}

private Map<String,Flyweight> flyweightCache = new HashMap<String,Flyweight>();
public Flyweight getFlyweight(String key) {
Flyweight obj = flyweightCache.get(key);
if(obj == null) {
obj = new ConcreFlyweight(key);
flyweightCache.put(key, obj);
System.out.println("新建一个对象,添加到缓存器");
}else{
System.out.println("直接使用缓存器中现有的");
}
return obj;
}
}
public class Client {
public static void main(String args[]) {
for(int i =0 ; i<2 ;i++) {
for(int j =0;j<10;j++) {
if(i==0) {
FlyweightFactory.getInstance().getFlyweight("Boy-Class"+j);
}else{
FlyweightFactory.getInstance().getFlyweight("Girl-Class"+j);
}
}
}
Flyweight fly = FlyweightFactory.getInstance().getFlyweight("Boy-Class6");
fly.setId(1001);
fly.setName("jimmy");
System.out.println(fly.toString());

Flyweight fly2 = FlyweightFactory.getInstance().getFlyweight("Boy-Class60");
fly2.setId(1003);
fly2.setName("jimmy2");
System.out.println(fly2.toString());
}
}

概述

中介者模式的提出是为了解决:一个系统中多个对象相互交互,使得整个系统变得复杂,对象之间耦合紧密,导致系统不利于维护和扩展这个现象。
在中介者模式中用一个中介类来封装对象之间的交互。这样其他对象就不需要维护对象之间的关系了。扩展关系的时候也只需要扩展或者修改中介者对象就可以了。

在中介者模式中,需要交互的对象称为同事类。它们一般都持有中介者对象的引用。但是也可以将中介者设计为一个单例,在同事类需要中介者引用的时候通过getInstance获取中介者引用。
同时由于中介者需要维护同事对象之间的关系所以也应该拥有各个同事类的引用。但是也可以在中介者处理方法中通过创建,或者获取,或者通过参数传入的方式获取同事对象。
在中介者模式中一旦一个同事发生了变化,需要主动通知中介者,让中介者去处理该变化与其他同事对象的交互。

简化的中介者模式

去掉同事类的父类,只要需要交互就可以成为同事类
不需要中介者接口,直接通过单例模式获取中介者的引用。
同事对象不再持有中介者,而是在需要的时候直接获取中介者对象,中介者也不再持有同事对象,在中介方法中通过创建,获取,参数传递的形式得到同事对象的引用。

public class CDReader {
private String data = null;
public void readData(String data) {
this.data = data;
Mediator.getInstance().changed(this);
}
public String getData() {
// TODO Auto-generated method stub
return this.data;
}
}
public class CPU {   
private String audioData = null;
private String videoData = null;
public void processData(String data) {
String [] datas = data.split(":");
audioData = datas[0];
videoData = datas[1];
Mediator.getInstance().changed(this);
}
public String getAudioData() {
return audioData;
}
public String getVideoData() {
return videoData;
}
}
public class MediaPlayer {   
public void playSound(String audio) {
System.out.println(audio);
}
public void playVideo(String video) {
System.out.println(video);
}
}
public class Mediator {

private Mediator() {}
private static class MediatorHolder{
public static Mediator mInstance = new Mediator();
}
public static Mediator getInstance() {
return MediatorHolder.mInstance;
}
private String data = null;
private String audioData = null;
private String videoData = null;
public void changed(Object colleage) {
if(colleage instanceof CDReader) {
data = ((CDReader) colleage).getData();
new CPU().processData(data);
}else if (colleage instanceof CPU) {
audioData = ((CPU) colleage).getAudioData();
videoData = ((CPU) colleage).getVideoData();
MediaPlayer player = new MediaPlayer();
player.playSound(audioData);
player.playVideo(videoData);
}
}
}
public class Client {   
public static void main(String args[]) {
new CDReader().readData("这是音乐:这是视频");
}
}

工厂模式的定义:

定义一个用于创建对象的接口,让子类决定实例化哪个类,工厂方法使得一个类的实例化延迟到其子类。
上面的定义比较官方,其实就个人的理解工厂方法就相当于一个黑盒,我们传入一个所需产品的识别标示,工厂就会自动产生我们所需要的产品,我们无须理解其中繁杂的创建过程,只要知道所产生产品所能提供的方法就可以了。
还有个优点就是,只要产品的接口不变,那么上层的应用就无须修改,我们如果需要扩展或者需要修改具体的实现只要动中间层即可,比如我们要扩展只要增加产品类即可,如果需要修改只需要修改产品类即可。

工厂模式的变体:

将工厂简化为静态工厂

决定产生何种产品的决定因素可以是如下几种方式:

1.工厂类的传入参数
2.读取配置文件
3.某个运行时参数或者状态量

工厂方法的返回值,可以是接口,抽象类,具体类。

public interface AbsProduct {
public void doSomething();
}
public class ProductImplA implements AbsProduct {
@Override
public void doSomething() {
System.out.println("This Message is Come from " + ProductImplA.class.getSimpleName());
}
}
public class ProductImplB implements AbsProduct {
@Override
public void doSomething() {
System.out.println("This messsage is come from "+ ProductImplB.class.getSimpleName());
}
}
public class ProductImplC implements AbsProduct {
@Override
public void doSomething() {
System.out.println("This massage is come from "+ ProductImplC.class.getSimpleName());
}
}
public interface AbsFactory {
public <T extends AbsProduct> T createProduct(Class<T> clzz);
}
public class FactoryImpl implements AbsFactory {
@Override
public <T extends AbsProduct> T createProduct(Class<T> clzz) {

AbsProduct product = null;
try {
product = (T) Class.forName(clzz.getName()).newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return (T) product;
}
}
public class FactoryTestMain {
public static void main(String args[]) {
AbsFactory factory = new FactoryImpl();
AbsProduct product;
product= factory.createProduct(ProductImplA.class);
product.doSomething();
product = factory.createProduct(ProductImplB.class);
product.doSomething();
product = factory.createProduct(ProductImplC.class);
product.doSomething();
}
}

工厂方法还有种比较好用的变体,其实也算不上什么变体,就是在工厂内部定义完成操作的方法,然后我们扔进去一个类型,指定要什么类型的对象来完成这个操作,这时候外部无须获取产品类型,直接调用工厂里面的方法即可
工厂父类一般是抽象方法,主要有两种类型,一个是抽象工厂方法用于产生抽象产品,另一类是使用抽象产品完成的操作方法。这样产品类就不需要抛头露面了。结构如下:

public abstract class AbstractFactory {
protected AbstractProduct product = null;
protected int productType = -1;
public abstract AbstractProduct createFactory();
public abstract void setProductType(int type);
public void doSomeThing() {
product = createFactory();
if(product != null) {
product.doSomethingA();
product.doSomethingB();
}
}
}
public interface AbstractProduct {
public void doSomethingA();
public void doSomethingB();
}
public class ConcreFactory  extends AbstractFactory{
@Override
public AbstractProduct createFactory() {
if(super.productType == 0) {
return new ConcreProductA();
}else if(super.productType == 1) {
return new ConcreProductB();
}
return null;
}

@Override
public void setProductType(int type) {
super.productType = type;
}
}
public class ConcreProductA implements AbstractProduct{
public void doSomethingA() {
System.out.println("ConcreProductA doSomething A");
}
public void doSomethingB() {
System.out.println("ConcreProductA doSomething B");
}
}
public class ConcreProductB implements AbstractProduct{
public void doSomethingA() {
System.out.println("ConcreProductB doSomething A");
}
public void doSomethingB() {
System.out.println("ConcreProductB doSomething B");
}
}
public class Client {

public static void main(String args[]) {
ConcreFactory factory = new ConcreFactory();
factory.doSomeThing();

factory.setProductType(0);
factory.doSomeThing();

factory.setProductType(1);
factory.doSomeThing();

factory.setProductType(2);
factory.doSomeThing();
}
}

抽象工厂

虽然工厂模式与抽象工厂模式都带有“工厂”两个字,但是二者所要解决的问题是不同的,工厂模式主要解决的问题是屏蔽产品类创建的繁杂过程,只要知道想获得什么的时候传入什么,以及产生的产品有哪些方法即可,而抽象工厂模式解决的是如何产生一个产品簇,这种产品往往有一定约束条件的的,与其称之为抽象工厂方法,而不如称其为方案工厂方法,因为往往产生的是一整套配套的方案。

下面我们用一个萝卜一个坑的例子来演示抽象工厂模式:

public interface AbsHole {
public void printHoleSize();
}
public interface AbsTurnips {
public void printTuripsSize();
}
public interface AbstractFactory {
public AbsHole createHole();
public AbsTurnips createTurnips();
}
public class BigHole implements AbsHole {
public void printHoleSize() {
System.out.println("大坑");
}
}
public class BigTurnips implements AbsTurnips {
public void printTuripsSize() {
System.out.println("大萝卜");
}
}
public class SmallHole implements AbsHole {
public void printHoleSize() {
System.out.println("小坑");
}
}
public class SmallTurnips implements AbsTurnips {
public void printTuripsSize() {
System.out.println("小萝卜");
}
}
public class ConcreBigFactory implements AbstractFactory {

public AbsHole createHole() {
return new BigHole();
}

public AbsTurnips createTurnips() {
return new BigTurnips();
}
}
public class ConcreSmallFactory implements AbstractFactory {

public AbsHole createHole() {
return new SmallHole();
}

public AbsTurnips createTurnips() {
return new SmallTurnips();
}
}
public class Client {

public static void main(String args[]) {
AbstractFactory factory = new ConcreBigFactory();
factory.createHole().printHoleSize();
factory.createTurnips().printTuripsSize();

factory = new ConcreSmallFactory();
factory.createHole().printHoleSize();
factory.createTurnips().printTuripsSize();
}
}

原型模式:

原型模式是一种创建型模式,它一般用在需要快速创建大量相似的对象的情景下。
它与用new 创建对象的区别如下:
原型模式是一种内存二进制拷贝,所以比使用new 产生对象的方式来得快速高效。所以在需要重复地创建相似对象时可以考虑使用原型模式。比如需要在一个循环内创建对象,并且对象创建过程比较复杂或者循环次数很多的情况,使用原型模式不但可以简化创建过程,而且可以使系统的整体性能提高很多。
原型模式它创建对象的时候并不调用所创建对象的构造方法。这个是优点同时也是原型模式的缺点。
在使用原型模式的时候还需要注意的是浅拷贝和深拷贝
一般对于成员变量是基本数据类型,以及String类型的时候只需要使用浅拷贝即可,而对于那些成员变量包含数组,对象的类型,要注意使用深拷贝,否则那些成员变量是非基本变量的成员,在复制出来的对象中只是一个引用,这些引用指向的还是原对象,这样就会导致修改拷贝出的对象时,会对原对象产生干扰。
在原型模式中需要实现Cloneable接口。Cloneable它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。

public class PersonInfo implements Cloneable{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
protected Object clone() {
PersonInfo info = null;
try {
info = (PersonInfo) super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return info;
}
}
public class CustomInfo implements Cloneable{

private long id = 0;
private PersonInfo mPersonInfo = null;
public CustomInfo() {
System.out.println("调用构造方法");
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public PersonInfo getmPersonInfo() {
return mPersonInfo;
}
public void setmPersonInfo(PersonInfo mPersonInfo) {
this.mPersonInfo = mPersonInfo;
}
public Object clone() {
CustomInfo info = null;
try {
info = (CustomInfo) super.clone();
info.setmPersonInfo((PersonInfo) this.mPersonInfo.clone());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return info;
}
public String toString() {
return "id = "+id +" name = "+ mPersonInfo.getName()+
" age = " +mPersonInfo.getAge()+" hashCode = "+ this.hashCode();
}
}
import java.util.Random;
public class Client {
public static void main(String args[]) {
CustomInfo info = new CustomInfo();
info.setId(new Random().nextLong());
PersonInfo pinfo = new PersonInfo();
pinfo.setAge(new Random().nextInt(100));
pinfo.setName("Jimmy");
info.setmPersonInfo(pinfo);
System.out.println(info.toString());
for(int i=0;i<10;i++) {
CustomInfo cinfo = (CustomInfo) info.clone();
cinfo.setId(2222222222222l);
System.out.println(cinfo.toString());
}
System.out.println(info.toString());
}
}

应用场景:

如果创建某个对象将会耗费过多的资源,或者需要某个对象作为某种设备或者其他对象的管理者的时候,比如对项目配置文件或者数据库进行读写的情况。

单例模式的定义:

确保某个类只有一个实例,并且自行实例化后向系统提供这个实例,单例的主要思想是通过将构造方法私有化从而将创建对象的权利回收,不让外部其他对象显式调用构造方法创建对象。

单例写法:

单例有五写法:
分别是饿汉式,懒汉式,双重判空检查法,内部类,枚举法:

  • 饿汉式: 在有多个虚拟机的情况下有可能会创建多个实例
    public class SingletonHungry {
    private SingletonHungry(){}
    private static final SingletonHungry mInstance = new SingletonHungry();
    public static final SingletonHungry getInstance() {
    return mInstance;
    }
    public void sayHello() {
    System.out.print("Hello I am "+ SingletonHungry.class.getSimpleName());
    }
    }

懒汉式:在多线程的情况有可能会创建多个实例,解决方法:加synchronized

public class SingletonLayzy {

private static SingletonLayzy mInstance = null;
private SingletonLayzy() {}
public static synchronized SingletonLayzy getInstance() {
if( mInstance == null ) {
mInstance = new SingletonLayzy();
}
return mInstance;
}
public void sayHello() {
System.out.println("Hello I am " + SingletonLayzy.class.getSimpleName());
}
}

双重校验锁:

public class SingletonDoubleCheck {

private static volatile SingletonDoubleCheck mInstance = null;
private void SingletonDoubleCheck() {
}
public static SingletonDoubleCheck getInstance() {
if(mInstance == null) {
synchronized(SingletonDoubleCheck.class) {
if(mInstance == null) {
mInstance = new SingletonDoubleCheck();
}
}
}
return mInstance;
}
public void sayHello() {
System.out.println("Hello I am "+ SingletonDoubleCheck.class.getSimpleName());
}
}

类级内部类

public class SingletonHolder {
private SingletonHolder() {
}
private static class InnerClass {
private static SingletonHolder mInstance = new SingletonHolder();
}
public static SingletonHolder getInstance() {
return InnerClass.mInstance;
}
public void sayHello() {
System.out.println("Hello I am "+ SingletonHolder.class.getSimpleName());
}
}

枚举单例:

public enum SingletonEnum {
INSTANCE;
private SingletonEnum() {

}
public String sayHello () {
return "Hello I am " + SingletonEnum.class.getSimpleName();
}
}

单例池:

public class SingletonPool {

public static final String SINGLETONE_KEY_PREFIX = "sigleton_prefix";
private static final int MAX_POOL_SIZE = 10;
private static int mCurrentItemId = 0;
private static Map<String, SingletonPool> mSingletonPools = new HashMap<>();

private SingletonPool() {

}

public static SingletonPool getInstance() {
String key = SINGLETONE_KEY_PREFIX + mCurrentItemId;
SingletonPool item = mSingletonPools.get(key);
if ( item == null ) {
item = new SingletonPool();
System.out.println("New Item");
mSingletonPools.put(key, item);
} else {
System.out.println("Get From Cache");
}
mCurrentItemId = (mCurrentItemId + 1) % MAX_POOL_SIZE;
return item;
}

public String sayHello() {
return "Hello I am " + this.toString();
}
}

总结:单例共有五种写法分别是懒汉,饿汉,双重校验锁,类级内部类,枚举。
饿汉:因为加载类的时候就创建实例,所以线程安全。缺点是不能延时加载,同时多个ClassLoader存在时不能保持只有一个实例。
懒汉:需要加锁才能实现多线程同步,但是效率会降低。优点是延时加载。
双重校验锁:instance = new singletone()这种代码在不同编译器上的行为和实现方式不可预知,不推荐使用
类级内部类:延迟加载,减少内存开销。
枚举:不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,但是失去了类的一些特性。

1.开闭原则:

对扩展开放,对修改关闭

也就是说在增加新特性的时候最好考虑通过扩展已有系统的方式来实现需求的变化,而不是通过直接通过修改系统已有源码的方式。

也就是说一旦程序开发完成,程序中一个类的实现只应该因为错误而修改,新的或者改变的特性应该通过新建不同的类的实现,新建的类可以通过继承的方式来重用已有的代码。

开闭原则的必要性:

因为一个已有的系统一般都是事先经过千万次测试的稳定系统,如果直接对这些稳定系统进行修改,很有可能直接影响到原有系统的稳定性。

同时如果部分系统有测试用例的情况下,每修改一个代码有可能需要修改大量的用例代码

而通过继承可以保证不影响原有系统稳定性的情况下实现变化,这种情况下只需对新增变化进行各项测试即可。

如何使用:

通过抽象类或者接口对可扩展性作适当的限定,这其实就是为了限定整个系统的可扩展范围。

对象引用避免使用具体的实现类,尽可能使用接口或者抽象类。这样可以保证整个系统的可扩展性。(里氏替换原则)

抽象层必须保持稳定。并且预知了所有可能的扩展,在任何扩展下都不会变化。

2.里氏替换原则

所有父类能够出现的地方,之类都应该可以出现。

用在实际就是在类中调用其他类的时候尽可能使用父类或者接口。

里氏替换原则的核心思想就是通过抽象建立规范,具体的实现在运行时替换掉抽象,从而实现功能扩展。

3.依赖倒置原则

模块之间的依赖关系需要通过抽象发生,实现类之间不发生直接的依赖关系。其依赖关系是通过接口或者抽象类来产生的。

实现细则:

每个类都应该有接口或者抽象类,或者两者都存在

变量的类型尽量是接口或者是抽象类

任何类都不允许从具体类中派生

一般使用接口定义公开的属性和方法,抽象类负责准确地实现业务逻辑。

比如下图所示通过AbstractClass1 和2 建立起依赖关系。而不是通过具体的之类来直接建立关系。

4.接口隔离原则

类之间的依赖关系应该建立在最小的接口上,在项目中需要按照职责将臃肿的接口拆封成更小的和更具体的接口,这样客户只需要知道他们感兴趣的方法,

把不需要的接口剔除掉,最好做到一个接口只服务于一个子模块或者业务逻辑。

5.迪米特法则

一个类应该对自己需要耦合或者调用的类知道得最少。

6. 单一职责原则

接口或者类,尽量要做到单一职责,类的设计尽量要做到只有一个原因引起变化,而对于方法则需要满足一个方法尽可能只做一件事情。

英文原文:

http://fernandocejas.com/2015/07/18/architecting-android-the-evolution/
http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/


中文翻译:

http://android.jobbole.com/81153/
https://zhuanlan.zhihu.com/p/20001838

对应代码:

https://github.com/android10/Android-CleanArchitecture
https://github.com/zhengxiaopeng/MVVM_Android-CleanArchitecture
https://github.com/zhengxiaopeng/Rocko-Android-Demos/tree/master/architecture/MVVM_Android-CleanArchitecture

对于好的文章真的会有一种忍不住要收藏的感觉,现在就无耻得转载这篇文章分享给大家:

开始

我们知道编写高质量软件是既困难又复杂的:不仅是满足需求方面,还要健壮、可维护、可测试,并且足够灵活以适应增长和变化。这就是“代码整洁之道”的来源,并可以成为开发任何软件应用程序的良好方法。
思想很简单:代码整洁之道代表构建系统的一组实践:

  • 独立于框架。
  • 可测试性。
  • 独立于UI。
  • 独立于数据库。
  • 独立于任何外部代理。

我们并不要求一定要用四环结构(如图所示),这只是一个示例图解,但是要考虑的是依赖项规则:源码依赖项只能向内指向,内环里的所有项不能了解外环所发生的东西。

以下是更好地理解和熟悉本方法的一些相关词汇:

  • Entities:是指一款应用的业务对象
  • Use cases:是指结合数据流和实体中的用例,也称为Interactor
  • Interface Adapters: 这一组适配器,是负责以最合理的格式转换用例(use cases)和实体(entities)之间的数据,表现层(Presenters )和控制层(Controllers ),就属于这一块的。
  • Frameworks and Drivers: 这里是所有具体的实现了:比如:UI,工具类,基础框架,等等。

想要更具体,更生动丰富的解释,可以参考这篇文章或者这个视频。

场景

我会设置一个简单的场景来开始:创建一个简单的小app,app中显示从云端获取的一个朋友或用户列表。当点击其中任何一个时,会打开一个新的窗口,显示该用户的详细信息。
这里我放了一段视频,大家看看这个视频 (需翻墙)大概就可以对我所描述的东西了解个大概了。

Android应用架构

这一对象遵循关注分离原则,也就是通过业务规则让内环操作对外环事物一无所知,这样一来,在测试时它们就不会依赖任何的外部元素了。
要达到这个目的,我的建议就是把一个项目分成三个层次,每个层次拥有自己的目的并且各自独立于堆放运作。
值得一提的是,每一层次使用其自有的数据模型以达到独立性的目的(大家可以看到,在代码中需要一个数据映射器来完成数据转换。如果你不想把你的模型和整个应用交叉使用,这是你要付出的代价)。
以下是图解,大家感受下:

表现层 (Presentation Layer)

表现层在此,表现的是与视图和动画相关的逻辑。这里仅用了一个Model View Presenter(下文简称MVP),但是大家也可以用MVC或MVVM等模式。这里我不再赘述细节,但是需要强调的是,这里的fragment和activity都是View,其内部除了UI逻辑以外没有其他逻辑,这也是所有渲染的东西发生的地方。
本层次的Presenter由多个interactor(用例)组成,Presenter在 android UI 线程以外的新线程里工作,并通过回调将要渲染到View上的数据传递回来。

领域层 (Domain Layer)

这里的业务规则是指所有在本层发生的逻辑。对于Android项目来说,大家还可以看到所有的interactor(用例)实施。这一层是纯粹的java模块,没有任何的Android依赖性。当涉及到业务对象时,所有的外部组件都使用接口。

数据层 (Data Layer)

应用所需的所有数据都来自这一层中的UserRepository实现(接口在领域层)。这一实现采用了Repository Pattern,主要策略是通过一个工厂根据一定的条件选取不同的数据来源。
比如,通过ID获取一个用户时,如果这个用户在缓存中已经存在,则硬盘缓存数据源会被选中,否则会通过向云端发起请求获取数据,然后存储到硬盘缓存。
这一切背后的原理是由于原始数据对于客户端是透明的,客户端并不关心数据是来源于内存、硬盘还是云端,它需要关心的是数据可以正确地获取到。

错误处理

这是一个长期待解决的讨论话题,如果大家能够分享各自的解决方案,那真真是极好的。
我的策略是使用回调,这样的话,如果数据仓库发生了变化,回调有两个方法:onResponse()和onError(). onError方法将异常信息封装到一个ErrorBundle对象中: 这种方法的难点在于这其中会存在一环扣一环的回调链,错误会沿着这条回调链到达展示层。因此会牺牲一点代码的可读性。另外,如果出现错误,我本来可以通过事件总线系统抛出事件,但是这种实现方式类似于使用C语言的goto语法。在我看来,当你订阅多个事件时,如果不能很好的控制,你可能会被弄得晕头转向。
测试

测试方法

关于测试方面,我根据不同的层来选择不同的方法:

展示层 ( Presentation Layer) : 使用android instrumentation和 espresso进行集成和功能测试
领域层 ( Domain Layer) : 使用JUnit和Mockito进行单元测试;
数据层 ( Data Layer) : 使用Robolectric ( 因为依赖于Android SDK中的类 )进行集成测试和单元测试。

代码展示

我猜你现在在想,扯了那么久的淡,代码究竟在哪里呢? 好吧,这就是你可以找到上述解决方案的github链接。还要提一点,在文件夹结构方面,不同的层是通过以下不同的模块反应的:

presentation: 展示层的Android模块
domain: 一个没有android依赖的java模块
data: 一个数据获取来源的android模块。
data-test: 数据层测试,由于使用Robolectric 存在一些限制,所以我得再独立的java模块中使用。

结论

正如 Bob大叔 所说:“Architecture is About Intent, not Frameworks” ,我非常同意这个说法,当然了,有很多不同的方法做不同的事情(不同的实现方法),我很确定,你每天(像我一样)会面临很多挑战,但是遵循这些方法,可以确保你的应用会:

易维护
易测试
高内聚
低耦合

参考资料

Source code: android10/Android-CleanArchitecture · GitHub
The clean architecture by Uncle Bob
Architecture is about Intent, not Frameworks
Model View Presenter
Repository Pattern by Martin Fowler
Android Design Patterns Presentation

在上一篇博文中我们给出了一个例子,在layout中添加data 节点,并在里面用variable标签声明一个变量,然后在java文件中声明一个对应的变量,将其绑定到layout中。
那接下来的部分我们就继续了解下,可以绑定哪些类型:

在绑定某个类型之前需要将当前类型导入到layout布局中,要实现这个目标必须依赖于import标签:
  • 使用Import在布局中导包

使用DataBinding在布局中可以导入类:
基本的格式如下:

<data>
<import type="android.os.Bundle"/>
</data>

这就和Java中导包一样,没特殊的地方:
在类名有冲突的时候,还可以使用别名来区别二者,具体的别名使用方法如下:

<import type="android.os.Bundle"/>
<import type="com.idealist.testDatabing.Bundle"
alias="selfBundle"/>

基本上包括自定义类型在内的全部类型都可以在layout中声明:

导入的类型还可以在表达式中使用static属性和方法:

<TextView
android:text="@{MyUtils.log(user.lastName)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

像JAVA一样,java.lang.*是自动导入的。

导入包后就可以使用这些包声明对应的变量,变量声明方式如下:
  • Variables 变量
    这个就和变量定义一个样,type指定变量类型,name 指定变量名,对于bean类型必须有get/set方法
<variable name="name"  type="String"/>
  • 当属性名需要用到单引号的时候外面引号需要用双引号,反之,当内部用单引号的时候外部需要用双引号,这个很多语言都有这个用法。

    android:text='@{map["firstName"]}'
    android:text="@{map[`firstName`]}"
  • 在变量中还可以用到对应的资源

    android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
    android:text="@{@plurals/banana(bananaCount)}"
  • 集合的用法:
    集合泛型要特别注意左尖括号需要使用转译:

    <data class="CollectionsBinding">
    <import type="java.util.Map" />
    <import type="java.util.List" />
    <import type="android.util.SparseArray" />
    <variable name="list" type="List&lt;String>" />
    <variable name="sparse" type="SparseArray&lt;String>"/>
    <variable name="map" type="Map&lt;String, String>"/>
    <variable name="index" type="int" />
    <variable name="key" type="String" />
    </data>

    List 用法:

    <TextView
    android:text="@{list[index]}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

    SparseArray 用法:

    <TextView
    android:text="@{sparse[index]}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

    Map 用法:

    <TextView
    android:text="@{map[key]}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
    <TextView
    android:text='@{map["firstName"]}'
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
    <TextView
    android:text="@{map[`firstName`]}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
  • Include 用法:
    注意下面的例子中在name.xml以及contact.xml两个layout文件中必需要有user variable

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:bind="http://schemas.android.com/apk/res-auto">
    <data>
    <variable name="user" type="com.example.User"/>
    </data>
    <LinearLayout
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <include layout="@layout/name"
    bind:user="@{user}"/>
    <include layout="@layout/contact"
    bind:user="@{user}"/>
    </LinearLayout>
    </layout>

    除了上述的用法,目前还支持设置默认值给某个属性

    android:text="@{String.valueOf(user.age),default=`defaultValue`}"
除了直接使用变量外还可以使用通过运算符进行运算后的值
  • 支持的运算符:

    算术运算符 + - / * %
    逻辑运算符 && ||
    二进制运算符 & | ^
    字符串连接 +
    一元运算 + - ! ~
    移位运算 >> >>> <<
    比较运算 == > < >= <=
    instanceof
    null
    强制转换
    方法调用
    数据访问 []
    三元运算 ?:
    除了上述基本的运算外下面还有一些特殊的操作符:

?? - 左边的对象如果它不是null,选择左边的对象;或者如果它是null,选择右边的对象:

android:text="@{user.displayName ?? user.lastName}"


* 自定义 Binding 类名称

在上一篇博文中介绍到会自动生成ActivityMainBinding ,这个其实是根据布局来创建的,我们上个例子中布局文件名为activity_main,将当中的下划线去掉然后下划线分割的两端首字母大写,在最后加上Binding
就构成了ActivityMainBinding。但是我们还可以自己指定一个类名:

<data class="com.idealist.customName">
...
</data>

<data class="CustomBinding"></data>在apppackage/databinding下生成CustomBinding;
<data class=".CustomBinding"></data>在apppackage下生成CustomBinding;
<data class="com.example.CustomBinding"></data>` 明确指定包名和类名。

* 实时反馈数据的变化

我们上面介绍的可以将数据和layout上的数据进行绑定,但是当绑定数据变化后并不能立刻反映到View杀害能够。Data Binding的真正强大的地方是当数据变化时,可以通知对应的Data对象。
有三种不同的数据变化通知机制:Observable对象、ObservableFields以及observable Collections。下面就来介绍下这个用法:

继承BaseObservable的方式

实现android.databinding.Observable接口的类可以为绑定对象添加一个监听器用于监听所有你想监听的对象的变化。
要实现上述的绑定可以分成两步:
1 继承BaseObservable的基类来创建Observable对象。
2 为getter添加Bindable注释。

下面是一个例子:
这个和上一篇博文介绍的一样就不多解释了:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class = "CustomBindingName">
<import type="com.idealist.databindingdemo.bean.UserBean"/>
<variable
name="user"
type="UserBean"/>
</data>
<LinearLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.idealist.databindingdemo.MainActivity">

<TextView
android:gravity="center"
android:text="@{user.firstName}"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<TextView
android:gravity="center"
android:text="@{user.lastName}"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<Button
android:text="Change Name to IOS"
android:id="@+id/changeName"
android:layout_width="match_parent"
android:onClick="onChangeName1"
android:layout_height="wrap_content" />

<Button
android:text="Change Name to Android"
android:id="@+id/changeName1"
android:layout_width="match_parent"
android:onClick="onChangeName2"
android:layout_height="wrap_content" />
</LinearLayout>
</layout>

这里的Userbean 继承自BaseObservable并且在对应的get方法上添加了@Bindable注释。并在set方法中添加notifyPropertyChanged(BR.lastName);来通知变化

public class UserBean extends BaseObservable{

@Bindable
public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
@Bindable
public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}

private String firstName;
private String lastName;

public UserBean(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}

接着就可以在MainActivity中调用set方法来设置对应的值,一旦bean的值一改变对应TextView 上的文本就立刻改变。
需要注意的是上面的BR是编译阶段生成的一个类,用 @Bindable 标记过 getter 方法会在 BR 中生成一个 entry。
当数据发生变化时还是需要手动发出通知。 通过调用 notifyPropertyChanged(BR.firstName) 可以通知系统 BR.firstName 这个 entry 的数据已经发生变化,需要更新 UI。

public class MainActivity extends AppCompatActivity {

private UserBean userBean = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
CustomBindingName binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
userBean = new UserBean("Android", "OS");
binding.setUser(userBean);
}

public void onChangeName1(View view) {
userBean.setFirstName("IOS");
userBean.setLastName("Jobs");
}

public void onChangeName2(View view) {
userBean.setFirstName("Android");
userBean.setLastName("OS");
}
}

运行结果如下:

ObservableFiled

这种方法也可以实现Observable 对象的效果,它的好处是不用在set方法中添加notifyXXXXX
首先我们在原来例子的基础上添加了一个用于显示年龄的TextView

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class = "CustomBindingName">
<import type="com.idealist.databindingdemo.bean.UserBean"/>
<variable
name="user"
type="UserBean"/>
</data>
<LinearLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.idealist.databindingdemo.MainActivity">

<TextView
android:gravity="center"
android:text="@{user.firstName}"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<TextView
android:gravity="center"
android:text="@{user.lastName}"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:gravity="center"
android:text="@{String.valueOf(user.age)}"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<Button
android:text="Change Name to IOS"
android:id="@+id/changeName"
android:layout_width="match_parent"
android:onClick="onChangeName1"
android:layout_height="wrap_content" />

<Button
android:text="Change Name to Android"
android:id="@+id/changeName1"
android:layout_width="match_parent"
android:onClick="onChangeName2"
android:layout_height="wrap_content" />
</LinearLayout>
</layout>

在原来的Bean中添加如下代码:

public ObservableInt age = new ObservableInt();

public ObservableInt getAge() {
return age;
}

public void setAge(ObservableInt age) {
this.age = age;
}

除了ObservableInt,还提供了如下的类型,基本上所有的基本类型都提供了:

public ObservableInt age = new ObservableInt();
public ObservableBoolean age1 = new ObservableBoolean();
public ObservableChar age2 = new ObservableChar();
public ObservableByte age3 = new ObservableByte();
public ObservableDouble age4 = new ObservableDouble();
public ObservableFloat age5 = new ObservableFloat();
public ObservableLong age6 = new ObservableLong();
public ObservableShort age7 = new ObservableShort();
public ObservableField<String> age8 = new ObservableField<>();

接下来就是通过如下的设置bean值,一旦bean值被设置,就会立刻反应到View上:

public class MainActivity extends AppCompatActivity {

private UserBean userBean = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
CustomBindingName binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
userBean = new UserBean("Android", "OS");
binding.setUser(userBean);
}

public void onChangeName1(View view) {
userBean.setFirstName("IOS");
userBean.setLastName("Jobs");
userBean.getAge().set(23);
}

public void onChangeName2(View view) {
userBean.setFirstName("Android");
userBean.setLastName("OS");
userBean.getAge().set(26);
}
}

除了上述的基本类型ObServerable 还支持List Map等集合类型:

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);

在layout文件中,通过String键可以访问map:

<data>
<import type="android.databinding.ObservableMap"/>
<variable name="user" type="ObservableMap<String, Object>"/>
</data>

<TextView
android:text='@{user["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text='@{String.valueOf(1 + (Integer)user["age"])}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

ObservableArrayList用于键是整数:

ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);

在layout文件中,通过索引可以访问list:

<data>
<import type="android.databinding.ObservableList"/>
<import type="com.example.my.app.Fields"/>
<variable name="user" type="ObservableList<Object>"/>
</data>

<TextView
android:text='@{user[Fields.LAST_NAME]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

* 在DataBinding中使用View的id

在Bind数据后,我们有时候会需要为某个按钮添加一个响应事件,这时候我们可以这样做:
如下布局中有两个按钮,id分别为changeName以及changeName1,我们下面将要为这两个按钮添加点击事件:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class = "CustomBindingName">
<import type="com.idealist.databindingdemo.bean.UserBean"/>
<variable
name="user"
type="UserBean"/>
</data>
<LinearLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.idealist.databindingdemo.MainActivity">

<TextView
android:gravity="center"
android:text="@{user.firstName}"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<TextView
android:gravity="center"
android:text="@{user.lastName}"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:gravity="center"
android:text="@{String.valueOf(user.age)}"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<Button
android:text="Change Name to IOS"
android:id="@+id/changeName"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<Button
android:text="Change Name to Android"
android:id="@+id/changeName1"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</layout>

下面就是为按钮添加事件的办法,很简单吧感觉比findviewById简单不少:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

private UserBean userBean = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
CustomBindingName binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
userBean = new UserBean("Android", "OS");
binding.setUser(userBean);
binding.changeName.setOnClickListener(this);
binding.changeName1.setOnClickListener(this);
}

@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.changeName:
userBean.setFirstName("IOS");
userBean.setLastName("Jobs");
userBean.getAge().set(23);
break;
case R.id.changeName1:
userBean.setFirstName("Android");
userBean.setLastName("OS");
userBean.getAge().set(26);
break;
}
}
}
Event Binding (事件绑定)

下面例子是实现的是按钮的二值状态切换:

首先定义一个接口

public interface UserPressEvent {
void press(View view);
void unPress(View view);
}

布局中使用:

<variable
name="event"
type="com.idealist.UserPressEvent"/>

android:onClick="@{user.isPressed? event.unPress : event.press}"

在Activity实现该接口UserPressEvent:

@Override
public void press(View view) {
user.isPressed(true);
}

@Override
public void unPress(View view) {
user.isPressed(false);
}
自定义转换

有时候转换应该是自动的在特定类型之间。例如,设置背景的时候:

<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

这里,背景需要Drawable对象,但颜色是一个整数。不管何时有Drawable并且返回值是一个整数,那么整数类型会被转换为ColorDrawable。这个转换是通过使用带有BindingConversion注解的静态方法完成的1

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
属性

对于一个属性,Data Binding会试图找到对应的setAttribute方法。
比如下面的例子:

<com.idealist.databinding.view.GithubCard
android:layout_width="match_parent"
android:layout_height="200dp"
android:gravity="center"
app:age="27"
app:firstName="@{@string/firstName}"
app:lastName="@{@string/lastName}"/>

这里并没有在declare-styleable 中定义 但是我们在自定义GithubCard中有age,firstName,lastName这些属性的setter方法所以可以直接使用上述的方式进行设置:

下面再来看个在网上找的一个例子,下面先列出代码然后展开分析:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="com.liangfeizc.databinding.sample.attributesetter.AttributeSettersActivity"/>
<variable
name="activity"
type="AttributeSettersActivity"/>
<variable
name="imageUrl"
type="String"/>
</data>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<com.liangfeizc.avatarview.AvatarView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:error="@{@drawable/error}"
app:imageUrl="@{imageUrl}"
app:onClickListener="@{activity.avatarClickListener}"/>

</LinearLayout>
</layout>

public class AttributeSettersActivity extends BaseActivity {
private ActivityAttributeSettersBinding mBinding;

public View.OnClickListener avatarClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(AttributeSettersActivity.this, "Come on", Toast.LENGTH_SHORT).show();
mBinding.setImageUrl(Randoms.nextImgUrl());
}
};

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

mBinding = DataBindingUtil.setContentView(this, R.layout.activity_attribute_setters);
mBinding.setActivity(this);
mBinding.setImageUrl(Randoms.nextImgUrl());
}

@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
Log.d(App.TAG, "load image");
Picasso.with(view.getContext()).load(url).error(error).into(view);
}
}

首先在AttributeSettersActivity onCreate方法中建立DataBinding后会将Activity,和ImageUri传到布局上,
我们在AttributeSettersActivity中看到如下方法:

@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
Log.d(App.TAG, "load image");
Picasso.with(view.getContext()).load(url).error(error).into(view);
}

这个是用来干嘛的呢?它的意思是:
如果AvatarView中的 imageUrl和 error 参数都存在并且由于imageUrl是string类型error是drawable 类型则就会调用上面定义的绑定适配器,并将对应的参数传入。在匹配适配器的时候,会忽略自定义的命名空间。

今天要讲的是DataBinding,在Google 2015 IO大会上推出了DataBinding 这个新特性, 它是以一个support库的形式发布,所以可以在所有的Android平台上使用它。但是它对Android Studio 以及Gradle的版本有一定的要求。
Android Studio 至少是1.3.0-beta1 Gradle至少是1.3以上。这个对大家来说应该是没啥大问题吧,如果没有满足这两个条件,大家Google下就可以解决了。
刚开始我们先搭建一个很简单很简单的例子,让大家属性下环境的搭建:

  • 首先检查下gradle版本是否正确,在项目根目录下检查build.gradle,这是我的配置:

    buildscript {
    repositories {
    jcenter()
    }
    dependencies {
    classpath 'com.android.tools.build:gradle:2.1.0'
    // NOTE: Do not place your application dependencies here; they belong
    // in the individual module build.gradle files
    }
    }

    allprojects {
    repositories {
    jcenter()
    }
    }

    task clean(type: Delete) {
    delete rootProject.buildDir
    }
  • 在模块的build.gradle 中 使用如下配置,打开DataBinding支持:

    dataBinding {
    enabled true
    }

    下面是我的模块build.gradle

    apply plugin: 'com.android.application'

    android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"

    dataBinding {
    enabled true
    }

    defaultConfig {
    applicationId "com.idealist.databindingdemo"
    minSdkVersion 15
    targetSdkVersion 23
    versionCode 1
    versionName "1.0"
    }
    buildTypes {
    release {
    minifyEnabled false
    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
    }
    }

    dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.3.0'
    }

    到此为止已经完成了环境的配置。

  • 修改布局文件:用一个layout节点将原先的布局给包裹住,layout中分成两类,一类是data,一类是布局:
    重点是data的配置:
    在data节点中variable是必备的属性,name是这个变量的名字,type一般是包含Bean所处包名的全路径。当然也包含import属性。
    定义好data节点后就可以使用${} 对 bean进行访问了,比如这里的@{user.lastName},这时候会调用UserBean的getlastName()方法,所以一般Bean都需要有get/set方法

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
    <import type="com.idealist.databindingdemo.bean.UserBean"/>
    <variable
    name="user"
    type="UserBean"/>
    </data>
    <LinearLayout
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.idealist.databindingdemo.MainActivity">

    <TextView
    android:gravity="center"
    android:text="@{user.firstName}"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

    <TextView
    android:gravity="center"
    android:text="@{user.lastName}"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />
    </LinearLayout>
    </layout>
  • 下面是Userbean的源码

    public class UserBean {
    public String getFirstName() {
    return firstName;
    }

    public void setFirstName(String firstName) {
    this.firstName = firstName;
    }

    public String getLastName() {
    return lastName;
    }

    public void setLastName(String lastName) {
    this.lastName = lastName;
    }

    private String firstName;
    private String lastName;

    public UserBean(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
    }
    }
  • 点击运行,这时候会产生一个ActivityMainBinding类位于/DataBindingDemo/app/build/intermediates/classes/debug/com/idealist/databindingdemo/databinding/ActivityMainBinding.java
    这个名字是根据布局生成的,这个类名可以指定的,后面会介绍这点。
    接下来就可以使用这个Binding将数据绑定到布局上了,修改MainActivity如下所示:

    public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    UserBean userBean = new UserBean("Android", "OS");
    binding.setUser(userBean);
    }
    }

    点击运行,会看到如下的结果: