最近再一次偶然的机会在github上见到了这样一个repo http://www.github.com/donnfelker/android-bootstrap 能让你迅速搭建起基本ui和框架.但是基本上没有什么文档,非常可惜.环境搭好 了,却不知道在哪里加代码. 于是我玩几天准备把我的理解写一下,以供找不到文 档的同学可以快速上个手.
Android Bootstrap 其实是一堆框架的集合, 让你迅速搭好android 开发的基本 框架. 里面包括
- Fragments
- Account Manager
- android-maven-plugin
- Dagger
- ActionBarSherlock
- Menu Drawer
- Robotium
- Parse API
很多是UI的框架我就不解释了, 如 Fragments, ActionBarSherlock. 但是我想 讲的是
- 依赖注入框架 Dagger
- UI testing 框架 Robotium
- backend服务Parse.
- android maven
本章要介绍两个注入框架 Dagger 和 butterknife
这又是一个依赖注入的框架,个人觉得依赖注入的模式貌似是为java专门准备的.使 得木纳的 java 代码结构变得灵活清爽, 松耦合, 易测试. 而 注入方式个人也比较喜欢 annotation 的方式而不是讨厌的 xml,把所有的依赖 配置都放到一个文件里并不无不妥, 但是都放到 xml 里, OMG, 放到可读性最屎 的 xml 里, 找所有依赖配置都要去翻这个难读得 xml…想着就头疼. 当项目变 大时, 一大波 xml 来袭………Orz
简单来说就是好莱坞原则
不要call我, 我会call你的.
对于好莱坞agent来说,他知道什么时候用什么演员,因 此,演员只需要留下联系方式, 也就是注入, 等待agent call他.
因此, 也叫控制反转.
其实, 也就是更优雅的实现组合模式, 传统的组合模式会需要 new 这些依赖, 也就是要各式各样的factory, 而依赖注入也就是说给你传进去.
比如我开咖啡店, 我要卖不同的咖啡种类, 雀巢的银桥的丝袜的 什么
espresso,amerino之类的. 我是
个非常抠塞的奸商, 我不想为每一种咖啡专门买一个昂贵的专用咖啡机. 经过研究发现这些
咖啡机只存在一些不同, 比如不同的加热方式, 滴漏方式,filter或者
水泵流量或温度不同.
所以,我决定实现一个 configurable 的 coffeemaker.
package coffee;
import dagger.Lazy;
import javax.inject.Inject;
class CoffeeMaker {
@Inject Lazy<Heater> heater; // Don't want to create a possibly costly heater until we need it.
@Inject Pump pump;
public void brew() {
heater.get().on();
pump.pump();
System.out.println(" [_]P coffee! [_]P ");
heater.get().off();
}
}
这是我的咖啡机.提供一个煮的按钮,可以看到, 组装咖啡机 的水泵和加热器都是注入进来的. 那他们是在哪构造的呢.
而作为老板的我,要怎样用这个咖啡机呢, 按一下”煮”按钮, 当然. 但是在那之 前,我们先要决定如何组装一个想要的咖啡机.
class CoffeeApp implements Runnable {
@Inject CoffeeMaker coffeeMaker;
@Override public void run() {
coffeeMaker.brew();
}
public static void main(String[] args) {
ObjectGraph objectGraph = ObjectGraph.create(new DripCoffeeModule());(ref:graph)
CoffeeApp coffeeApp = objectGraph.get(CoffeeApp.class);
coffeeApp.run();
}
}
客户说要americano,所以老板我给咖啡机装成滴漏式, 如代码第 (graph) 行. 构造Graph意思相当于要构造滴漏式咖啡机的图, 图会根据Module里provider组 件以及以及被Inject的地方建立联系. 也就是说, 根据用户的需求用不同的组件 蓝图来构造咖啡机.
下面来看组件式在哪被初始化的.
interface Heater {
void on();
void off();
boolean isHot();
}
class ElectricHeater implements Heater {
boolean heating;
@Override public void on() {
System.out.println("~ ~ ~ heating ~ ~ ~");
this.heating = true;
}
@Override public void off() {
this.heating = false;
}
@Override public boolean isHot() {
return heating;
}
}
这是电加热器的接口实现, 他的初始化方法会放到一个module里的 @provide
标记的方法里. 这个被标记的方法会再 Heater 被注入的地方被调用.
import dagger.Module;
import dagger.Provides;
import javax.inject.Singleton;
@Module(
injects = CoffeeApp.class,
includes = PumpModule.class
)
class DripCoffeeModule {
@Provides @Singleton Heater provideHeater() {
return new ElectricHeater();
}
}
看到这样的好处了吧, 很清爽的把Module中得Heater和Pump注入到CoffeeApp中,
不需要setter注入,也不需要构造函数注入, 只需要将组件的构造函数声明为 @Inject
, 或者放
到一个Module里的provider中, 就可以在咖啡机中 @Inject
该组件.
说了这些应该大概知道 dagger 要怎么玩乐吧,那么我们 首先来看一下 androidbootstrap 的 src 目录结构好了.
├── main
│ └── java
│ └── com
│ └── donnfelker
│ └── android
│ └── bootstrap
│ ├── AndroidModule.java
│ ├── BootstrapApplication.java
│ ├── BootstrapModule.java (ref:module)
│ ├── BootstrapServiceProvider.java
│ ├── RootModule.java
│ ├── authenticator
│ │ ├── AccountAuthenticatorService.java
│ │ ├── ApiKeyProvider.java
│ │ ├── BootstrapAccountAuthenticator.java
│ │ ├── BootstrapAuthenticatorActivity.java
│ │ ├── LogoutService.java
│ │ └── SherlockAccountAuthenticatorActivity.java
│ ├── core
│ │ ├── AvatarLoader.java
│ │ ├── BootstrapService.java
│ │ ├── CheckIn.java
│ │ ├── Constants.java
│ │ ├── GravatarUtils.java
│ │ ├── ImageUtils.java
│ │ ├── Location.java
│ │ ├── News.java
│ │ ├── PauseTimerEvent.java
│ │ ├── ResumeTimerEvent.java
│ │ ├── StopTimerEvent.java
│ │ ├── TimerPausedEvent.java
│ │ ├── TimerService.java
│ │ ├── TimerTickEvent.java
│ │ ├── UserAgentProvider.java
│ │ └── ViewSummary.java
│ ├── evernote
│ ├── ui
│ │ ├── AlternatingColorListAdapter.java
│ │ ├── AsyncLoader.java
│ │ ├── BarGraphDrawable.java
│ │ ├── BootstrapActivity.java
│ │ ├── BootstrapFragmentActivity.java
│ │ ├── BootstrapPagerAdapter.java
│ │ ├── BootstrapTimerActivity.java
│ │ ├── CarouselActivity.java
│ │ ├── CheckInsListAdapter.java
│ │ ├── CheckInsListFragment.java
│ │ ├── HeaderFooterListAdapter.java
│ │ ├── ItemListFragment.java
│ │ ├── NewsActivity.java
│ │ ├── NewsListAdapter.java
│ │ ├── NewsListFragment.java
│ │ ├── TextWatcherAdapter.java
│ │ ├── ThrowableLoader.java
│ │ ├── UserActivity.java (ref:activity)
│ │ ├── UserListAdapter.java (ref:adapter)
│ │ ├── UserListFragment.java (ref:fragment)
│ │ └── view
│ │ └── CapitalizedTextView.java
│ └── util
│ ├── Ln.java
│ ├── SafeAsyncTask.java
│ └── Strings.java
└── test
└── java
└── com
└── donnfelker
└── android
└── bootstrap
└── core
└── core
├── BootstrapApiClientUtilTest.java
└── BootstrapServiceTest.java
好吧, 这样一眼就应该能看到 BootstrapModule 肯定是 依赖注入用的组件对不 对. 比如说我现在做的应用是关于 Evernote的, 在 Evernote 提供的 android SDK 中有一个最重要的类EvernoteSession, 因为当初始化后并登陆, 你就可以 用这个 Session 来调用所有 evernote API.
因此, 我把它看成一个插件, 也就 是说, 我什么时候要用到 evernote 的时候, 我只需要 @Inject 这个 session 即可. 那么, 这时候, 我只需要吧 EvernoteSession 的构造方法放到 这个 Module 里了.
public class BootstrapModule {
...
@Singleton @Provides EvernoteSession provideEvernoteSession(final Context context) {
return EvernoteSession.getInstance(context, Constants.Evernote.CONSUMER_KEY, Constants.Evernote.CONSUMER_SECRET, Constants.Evernote.EVERNOTE_SERVICE);
}
}
再来看 src 目录, 很有意思, 在 ui
下有三组 xxxActivity ,xxxListAdapter,
xxxFragment. 这三个类是这样的
- xxxActivity: 负责单个view的显示.
- xxxListAdapter: 负责List内容的更新.
- xxxListFragment: 这是继承 actionbarsherlock 的 SherlockFragment.负责
组装数据以及处理事件.
点开UserActivity, 会看见开头有这么个 annotation @InjectView
@InjectView(R.id.iv_avatar) protected ImageView avatar;
按最老套的获取 view 会这样写:
ImageView avatar;
...
@Override public void onCreate(){
avatar = (ImageView)findViewById(R.id.title);
...
是不是觉得以前的写法弱爆了. 当然这是最基本的 view inject, 还有 Click Listener Injection 等更高阶的用法 可以继续参考文档