android-gradle-深入浅出(二:项目结构)

  • Android
  • Gradle
  • Android Studio

这篇主要介绍使用Gradle的Android项目的目录结构,以及在Gradle中的配置

Gradle采用约定优于配置的原则,最简单方式是使用一个默认的目录结构。当然目录结构是可以自己修改的。

默认情况下,android gradle项目的代码都在src目录下,src下会有两个目录main和androidTest,其中main目录下是项目代码,androidTest目录下是测试代码。
src/main/
src/androidTest/

在main和androidTest目录下面,针对不同类型的code,又不会不同的目录。
对于Java和Android的Gradle插件,Java代码和Java资源分别对应了java目录和resources目录。Android插件还需要一些其他的目录和文件,比如
AndroidManifest.xml
res/
assets/
aidl/
rs/
jni/

下面我们来看一下如何更改这些默认的配置。

Android工程,默认情况下,java文件和resource文件分别在src/main/java和src/main/res目录下,在build.gradle文件,andorid{}里面添加下面的代码,便可以将java文件和resource文件放到src/java和src/resources目录下。(注意,如果你在gradle文件中修改了,只需要重新sync一下,gradle会自动创建新的目录,并将对应的文件移动到新的目录下)

1
2
3
4
5
6
7
8
9
10
sourceSets {
main {
java {
srcDir 'src/java'
}
resources {
srcDir 'src/resources'
}
}
}

更简便的写法是

1
2
3
4
sourceSets {
main.java.srcDirs = ['src/java']
main.resources.srcDirs = ['src/resources']
}

数组里其实是可以指定多个目录,这样就可以将代码和资源放置到多个目录下。
在看一个Android项目的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
android {
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
androidTest.setRoot('tests')
}
}

这里的main code其实使用的就是Android Gradle的默认值,而androidTest不再使用默认的androidTest目录,而是使用tests目录。

android-gradle-深入浅出(一)

  • Android
  • Gradle
  • Android Studio

gradle是Android开发中引入的全新的构建系统,因为全新的构建系统主要是出于下面的目的:

  1. 方便复用代码和资源
  2. 构建多种版本的apk更见简单,不论是为多渠道构建不同的apk还是构建不同环境的apk(debug,release)
  3. 方便配置,扩展,自定义构建过程
  4. 良好的IDE集成

为什么选择Gradle?

Gradle主要有以下几个有点:

  1. 使用领域驱动语言(DSL)来描述构建逻辑
  2. 构建脚本使用Groovy,可以方便的定制构建逻辑
  3. 内建的依赖管理系统,使用Maven或者Ivy
  4. 非常灵活。可以使用最佳实践但并不强制
  5. 良好的IDE支持

Gradle项目使用项目根目录下的build.gradle文件来配置

一个最简单的Java项目的build.gradle如下

1
apply plugin: 'java'

这表示改项目会使用Gradle的java插件,该插件是Gradle默认提供的,java插件提供了构建java应用和运行测试的功能。

最简单的Android项目build.gralde如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.11.1'
}
}
apply plugin: 'android'
android {
compileSdkVersion 19
buildToolsVersion "19.0.0"
}

该Android构建文件包含3个部分:
buildscript { … } 配置驱动构建过程的代码,意思就是buildscript中的配置仅仅在构建过程中起作用,也就是构建程序所使用的配置。
上面的代码中就声明了我们的构建程序会使用Maven中央仓库,并且有一个classpath依赖是指向一个Maven库的。这个Maven库就是Android Gradle插件,版本号是0.11.1。

随后就是使用这个Android插件,和之前使用Java插件一样。

最后,android { … }配置了Android项目构建时需要的所有参数。
默认情况下,只需要指定compileSdkVersion和buildtoolsVersion,compileSdkVersion指定使用哪个版本的sdk编译,buildToolsVersion指定使用哪个版本的构建工具。

小心:

  1. 只需要使用android插件,再使用Java插件会报错.
  2. 在local.properties中使用sdk.dir属性指定SDK路径,或者你可以使用ANDROID_HOME环境变量。

Otto源码分析

  • android
  • square
  • Otto
  • event bus
  • 源码分析

构造函数

使用Otto通常是通过一个Provider提供一个Bus单例。
首先我们来分析一下Bus的构造函数,Bus类的构造函数最终都会调用Bus(ThreadEnforcer enforcer, String identifier, HandlerFinder handlerFinder)这个构造函数。
其中enforcer用来限制执行register,unregister以及post event的线程,如果执行这些函数的线程不是enforcer指定的线程,就会抛出异常。
identifier相当于给Bus起的一个名字,在toString方法中使用。
handlerFinder是整个event bus的核心,用于在register,unregister的时候寻找所有的subscriber和producer。handlerFinder不需要用户指定,默认使用HandlerFinder接口中定义的常量ANNOTATED,ANNOTATED本身就是HandlerFinder的匿名实现。

注册

如果一个类对某些事件感兴趣,需要调用register方法来注册监听这些事件,监听通过在方法上使用@Subscribe来实现,Otto通过方法的参数来决定是否调用该方法。
register方法首先会调用handlerFinder的findAllProducers(object)方法去找到所有使用了@Produce注解的方法。findAllProducers其实是委托AnnotatedHandlerFinder.findAllProducers方法。在AnnotatedHandlerFinder中,定义了一个静态变量SUBSCRIBERS_CACHE

1
2
private static final Map<Class<?>, Map<Class<?>, Method>> PRODUCERS_CACHE =
new HashMap<Class<?>, Map<Class<?>, Method>>();

PRODUCERS_CACHE 的key是监听类,就是调用bus.register()的类,value本身又是一个map,这个map的key是事件的class,value是生产事件的方法。
比如下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MainActivity extends Activity {
@Inject Bus bus;
@Override
public void onResume() {
bus.register(this);
}
@Produce public ClickEvent produceClick() {
// TODO: React to the event somehow!
return new ClickEvent();
}
}

在PRODUCERS_CACHE中就会有一条记录,它的key是MainActivity.class,value对应的map中,key是ClickEvent.class,value是produceClick Method对象。
SUBSCRIBERS_CACHE:
MainActivity.class -》
ClickEvent.class -》produceClick

当调用AnnotatedHandlerFinder的findAllProducers方法时,会先根据传入的对象的类型,检查是否已经被缓存到PRODUCERS_CACHE,如果没有的话,就会调用loadAnnotatedMethods,利用反射去寻找所有使用了@Produce注解的方法,并且将结果缓存到PRODUCERS_CACHE中。最后,会从PRODUCERS_CACHE中取出监听类的所有Produce方法,遍历这些方法,为一个方法构建一个EventProducer对象,并将这个EventProducer对象放到一个以事件的class作为key的map中,然后返回这个map。EventProducer类包含了Produce方法和该方法所属的对象,并且提供了调用Produce方法的功能。

回到Bus的Register方法,调用完findAllProducers方法之后,会遍历传入的监听类的Produce方法,并且根据Produce方法的返回值类型,来检查是否已经有对应的Subscribe存在,如果有的话,就会调用Subscribe方法,并将Producer的返回值传入。

1
2
3
4
5
6
Set<EventHandler> handlers = handlersByType.get(type);
if (handlers != null && !handlers.isEmpty()) {
for (EventHandler handler : handlers) {
dispatchProducerResultToHandler(handler, producer);
}
}

这里需要注意的是,Bus对象两个map类型的常量,用来缓存所有事件的Producer和Subscriber。

1
2
3
4
5
6
7
/** All registered event handlers, indexed by event type. */
private final ConcurrentMap<Class<?>, Set<EventHandler>> handlersByType =
new ConcurrentHashMap<Class<?>, Set<EventHandler>>();
/** All registered event producers, index by event type. */
private final ConcurrentMap<Class<?>, EventProducer> producersByType =
new ConcurrentHashMap<Class<?>, EventProducer>();

从定义我们可以看出,一种事件只能有一个Producer,却可以有多个Subscriber。

找到了所有的producers之后,就是调用handlerFinder.findAllSubscribers(object)来寻找object中使用@Subscribe注解的方法,过程和findAllProducers类似,唯一的不同是一个事件可以有多个subscriber,因此findAllSubscribers的返回值类型是Map, Set>。其中EventHandler包含了subscribe方法和订阅事件的对象的信息。

找到监听类所有的subscribe方法之后,就需要查看bus中时候有和这些subscribe方法对应的producer方法,如果有的话,就会使用调用subscribe方法。这也就是文档上说的,一旦有新的subscriber订阅了某一事件,并且该事件有对应的producer,那么subscriber方法就会被立即调用,并且传入producer方法的返回值。

发送事件

post(Obejct event)方法用来发送事件给所有订阅者,它接收一个Object类型的参数,说明Otto的事件可以是任意类型的对象。post方法首先会获取所有event对象的父类

1
Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass());

然后遍历这些父类,找到他们的所有订阅者,发送事件。这表明任何订阅了event对象父类的订阅者也都会收到event事件。值得注意的是Otto使用ThreadLocal类型来存放事件队列ThreadLocal> eventsToDispatch,这样极大的简化了多线程模式下的开发。

取消订阅

unregister方法,做的事情和register刚好想法,从缓存中清除所有和当前监听对象相关的producers和subscribers。

Otto使用入门

  • android
  • square
  • Otto
  • event bus

介绍

Otto是square公司出的一个事件库(pub/sub模式),用来简化应用程序组件之间的通讯。
Otto 修改自Google的Guava库,专门为Android平台进行了优化。

使用

Otto本身是为Android平台专门开发的,使用的时候最好是使用单例模式。

1
Bus bus = new Bus();

bus对象只有作为单例共享的时候才足够高效,推荐使用依赖注入框架来注入单例对象或者采用类似的机制。

发布事件

发布一个事件很简单,调用post方法就可以,post方法可以接受任何类型对象

1
bus.post(new AnswerAvailableEvent(42));

订阅事件

订阅只需要在方法上加上@Subscribe注解,同时在适当的地方调用register方法:

1
2
3
@Subscribe public void answerAvailable(AnswerAvailableEvent event) {
// TODO: React to the event somehow!
}

注意subscribe方法接收的参数类型需要和post参数的类型一致或者是post参数类型的父类。

1
bus.register(this);

一旦调用了register方法,Otto就会通过反射去寻找所有带有@Subscribe或者@Produce注解的方法,并将这些方法缓存下来。只有在调用了register之后,该类里面标注了@Subscribe或者@Produce的方法才会在适当的时候被调用。另外,当不需要订阅事件的时候,可以调用unregister来取消订阅。

生产者

有时候当订阅某个事件的时候,希望能够获取当前的一个值,比如订阅位置变化事件的时候,希望能拿到当前的位置信息。Otto中@Produce正是扮演了这么一个生产者的角色。
@Produce也是用于方法,并且这个方法的参数必须为空,返回值是你要订阅的事件的类型。

1
2
3
4
@Produce public AnswerAvailableEvent produceAnswer() {
// Assuming 'lastAnswer' exists.
return new AnswerAvailableEvent(this.lastAnswer);
}

使用@Produce之后,也需要调用bus.register()。调用了register方法之后,所有之前订阅AnswerAvailableEvent事件的方法都会被执行一次,参数就是produceAnswer方法的返回值,之后任何新的订阅了AnswerAvailableEvent事件的方法,也都会立即调用produceAnswer方法。

线程限制

可以指定@Subscribe和@Produce标注的回调方法所运行的线程,默认是在MainThread中执行。

1
2
3
// 这两个方法是等价的
Bus bus1 = new Bus();
Bus bus2 = new Bus(ThreadEnforcer.MAIN);

如果不关心在哪个线程执行,可以使用ThreadEnforcer.ANY,甚至可以使用自己实现的ThreadEnforcer接口。

深入浅出RxJava(一:基础篇)

原文链接

RxJava正在Android开发者中变的越来越流行。唯一的问题就是上手不容易,尤其是大部分人之前都是使用命令式编程语言。但是一旦你弄明白了,你就会发现RxJava真是太棒了。

这里仅仅是帮助你了解RxJava,整个系列共有四篇文章,希望你看完这四篇文章之后能够了解RxJava背后的思想,并且喜欢上RxJava。

基础

RxJava最核心的两个东西是Observables(被观察者,事件源)和Subscribers(观察者)。Observables发出一系列事件,Subscribers处理这些事件。这里的事件可以是任何你感兴趣的东西(触摸事件,web接口调用返回的数据。。。)

一个Observable可以发出零个或者多个事件,知道结束或者出错。每发出一个事件,就会调用它的Subscriber的onNext方法,最后调用Subscriber.onNext()或者Subscriber.onError()结束。

Rxjava的看起来很想设计模式中的观察者模式,但是有一点明显不同,那就是如果一个Observerble没有任何的的Subscriber,那么这个Observable是不会发出任何事件的。

Hello World

创建一个Observable对象很简单,直接调用Observable.create即可

1
2
3
4
5
6
7
8
9
Observable<String> myObservable = Observable.create(
new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> sub) {
sub.onNext("Hello, world!");
sub.onCompleted();
}
}
);

这里定义的Observable对象仅仅发出一个Hello World字符串,然后就结束了。接着我们创建一个Subscriber来处理Observable对象发出的字符串。

1
2
3
4
5
6
7
8
9
10
Subscriber<String> mySubscriber = new Subscriber<String>() {
@Override
public void onNext(String s) { System.out.println(s); }
@Override
public void onCompleted() { }
@Override
public void onError(Throwable e) { }
};

这里subscriber仅仅就是打印observable发出的字符串。通过subscribe函数就可以将我们定义的myObservable对象和mySubscriber对象关联起来,这样就完成了subscriber对observable的订阅。

1
myObservable.subscribe(mySubscriber);

一旦mySubscriber订阅了myObservable,myObservable就是调用mySubscriber对象的onNext和onComplete方法,mySubscriber就会打印出Hello World!

更简洁的代码

是不是觉得仅仅为了打印一个hello world要写这么多代码太啰嗦?我这里主要是为了展示RxJava背后的原理而采用了这种比较啰嗦的写法,RxJava其实提供了很多便捷的函数来帮助我们减少代码。

首先来看看如何简化Observable对象的创建过程。RxJava内置了很多简化创建Observable对象的函数,比如Observable.just就是用来创建只发出一个事件就结束的Observable对象,上面创建Observable对象的代码可以简化为一行

1
Observable<String> myObservable = Observable.just("Hello, world!");

java

接下来看看如何简化Subscriber,上面的例子中,我们其实并不关心OnComplete和OnError,我们只需要在onNext的时候做一些处理,这时候就可以使用Action1类。

1
2
3
4
5
6
Action1<String> onNextAction = new Action1<String>() {
@Override
public void call(String s) {
System.out.println(s);
}
};

subscribe方法有一个重载版本,接受三个Action1类型的参数,分别对应OnNext,OnComplete, OnError函数。

1
myObservable.subscribe(onNextAction, onErrorAction, onCompleteAction);

java

这里我们并不关心onError和onComplete,所以只需要第一个参数就可以

1
2
myObservable.subscribe(onNextAction);
// Outputs "Hello, world!"

java

上面的代码最终可以写成这样

1
2
3
4
5
6
7
Observable.just("Hello, world!")
.subscribe(new Action1<String>() {
@Override
public void call(String s) {
System.out.println(s);
}
});

使用java8的lambda可以使代码更简洁

1
2
Observable.just("Hello, world!")
.subscribe(s -> System.out.println(s));

Android开发中,强烈推荐使用retrolambda这个gradle插件,这样你就可以在你的代码中使用lambda了。

变换

让我们做一些更有趣的事情吧!

比如我想在hello world中加上我的签名,你可能会想到去修改Observable对象:

1
2
Observable.just("Hello, world! -Dan")
.subscribe(s -> System.out.println(s));

如果你能够改变Observable对象,这当然是可以的,但是如果你不能修改Observable对象呢?比如Observable对象是第三方库提供的?比如我的Observable对象被多个Subscriber订阅,但是我只想在对某个订阅者做修改呢?
那么在Subscriber中对事件进行修改怎么样呢?比如下面的代码:

1
2
Observable.just("Hello, world!")
.subscribe(s -> System.out.println(s + " -Dan"));

这种方式仍然不能让人满意,因为我希望我的Subscribers越轻量越好,因为我有可能会在mainThread中运行subscriber。另外,根据响应式函数编程的概念,Subscribers更应该做的事情是“响应”,响应Observable发出的事件,而不是去修改。

如果我能在某些中间步骤中对“Hello World!”进行变换是不是很酷?

操作符(Operators)

操作符就是为了解决对Observable对象的变换的问题,操作符用于在Observable和最终的Subscriber之间修改Observable发出的事件。RxJava提供了很多很有用的操作符。

比如map操作符,就是用来把把一个事件转换为另一个事件的。

1
2
3
4
5
6
7
8
Observable.just("Hello, world!")
.map(new Func1<String, String>() {
@Override
public String call(String s) {
return s + " -Dan";
}
})
.subscribe(s -> System.out.println(s));

使用lambda可以简化为

1
2
3
Observable.just("Hello, world!")
.map(s -> s + " -Dan")
.subscribe(s -> System.out.println(s));

是不是很酷?map()操作符就是用于变换Observable对象的,map操作符返回一个Observable对象,这样就可以实现链式调用,在一个Observable对象上多次使用map操作符,最终将最简洁的数据传递给Subscriber对象。

map操作符进阶

map操作符更有趣的一点是它不必返回Observable对象返回的类型,你可以使用map操作符返回一个发出新的数据类型的observable对象。

比如上面的例子中,subscriber并不关心返回的字符串,而是想要字符串的hash值

1
2
3
4
5
6
7
8
Observable.just("Hello, world!")
.map(new Func1<String, Integer>() {
@Override
public Integer call(String s) {
return s.hashCode();
}
})
.subscribe(i -> System.out.println(Integer.toString(i)));

很有趣吧?我们初始的Observable返回的是字符串,最终的Subscriber收到的却是Integer,当然使用lambda可以进一步简化代码:

1
2
3
Observable.just("Hello, world!")
.map(s -> s.hashCode())
.subscribe(i -> System.out.println(Integer.toString(i)));

前面说过,Subscriber做的事情越少越好,我们再增加一个map操作符

1
2
3
4
Observable.just("Hello, world!")
.map(s -> s.hashCode())
.map(i -> Integer.toString(i))
.subscribe(s -> System.out.println(s));

不服?

是不是觉得我们的例子太简单,不足以说服你?你需要明白下面的两点:

1.Observable和Subscriber可以做任何事情
Observable可以是一个数据库查询,Subscriber用来显示查询结果;Observable可以是屏幕上的点击事件,Subscriber用来响应点击事件;Observable可以是一个网络请求,Subscriber用来显示请求结果。

2.Observable和Subscriber是独立于中间的变换过程的。
在Observable和Subscriber中间可以增减任何数量的map。整个系统是高度可组合的,操作数据是一个很简单的过程。