该项目所用到的框架有:
- ButterKnife
- Retrofit 2.0(okhttp)
- Rxjava
- Jackson
- Ormlite
- Mosby
接下来会分别讨论使用各个框架后的一些感受。
1. ButterKnife
使用注解@,实现变量与方法与Anroid View的绑定。
消除findViewById
,使用注解@BindView
替代
将多个Views分组成一个列表或数组,一次性操作他们完成actions,setters或properties(PS:这部分功能没见到github上有示例代码)
使用@OnClick
注解实现listner的绑定,消除匿名内部类
使用注解将变量与resource中的资源绑定
示例展示
//代码示例
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...
}
}
Android ButterKnife Zelezny插件
同时还可以在AS中添加相关的ButterKnife插件Android ButterKnife Zelezny可以直接自动根据layout生成ButterKnife样板代码。
2.RxJava+Retrofit+Jackson
这三个在项目中一般是组合使用的,所以放在一个小节说了
RxJava(RxAndroid)
一个基于事件流编程的,用来处理异步任务的第三方库。
首先假设一个需求场景,我们需要通过网络请求得到一串数据,然后将得到的数据写入外存中的一个文件中。我们的处理应该是怎样的?
不用RxJava
//进行网络请求
httpClient.setCallBack(new CallBack(){
void doOnSuccess(final Data data){
//因为写入外存文件是耗时操作,所以要新开线程。
new Thread(new Runnable(){
public void run(){
//将数据写入外存
FileUtils.wirtToFile(data);
}
}).start();
}).dopost(url);
}
通过模拟实现一个非常平常的异步任务需求,我们可以看到,代码已经存在多层嵌套了,匿名内部类里嵌套匿名内部类,代码易读性较差,相对的,当出了Bug或者需求改变了,在多层嵌套中进行调试和新添需求是十分痛苦且容易出错的。
使用RxJava
//第一步,通过网络请求获得Data
httpService.getData()
//第二步,将Data转化为Byte数组
.map(new Func1<Data,Byte[]>(){
public Byte[] call(Data data){
return data.toString.getByte();
}
})
//在新线程中处理写入任务
.observerOn(Scheduler.newThread())
//第三部,处理Byte数组,将数据写入文件
.subscribe(new Action1<Byte[]>(){
public void call(Byte[] byte){
writeByteToFile(byte);
}}
})
我们可以看到,在通过网络请求得到Data示例后,之后的数据解析转化,吸入外存的耗时操作都是在一个线性的编码中进行的,而不是像上一种实现方法需要嵌套再嵌套,考虑到实际需求中可能有更多层的加工以及操作,使用RxJava的结构明显更加的清晰易读。
Retrofit
首先Retrofit是一个网络请求的框架,底层使用的是Okhttp框架,使用注解简化网络请求中的部分配置。
使用Retrofit
第一步:
创建一个接口类,并做好网络请求部分参数配置
//创建业务请求
public interface MovieService {
@GET("v2/movie/top250")
Observable<HttpResult<List<Subjects>>> getMovie(@Query("start") int start, @Query("count") int count);
}
第二步:
创建Retrofit,实例化业务请求类,并发送请求
//示例化Retrofit
Retrofit retrofit1 = new Retrofit.Builder()
.baseUrl(BASEURL)
//将结果使用JackJson转化为Bean
//.addConverterFactory(JacksonConverterFactory.create())
//使网络请求接口可以返回Observable
.//addCallAdapterFactory(RxJavaCallAdapterFactory.create())
//将结果转化为String
.addConverterFactory(ScalarsConverterFactory.create())
.build();
//实例化业务请求
MovieService movieService1 = retrofit1.create(MovieService.class);
//发送请求
movieService1.getMovieString(0,2).enqueue(new retrofit2.Callback<String>() {
@Override
public void onResponse(retrofit2.Call<String> call, retrofit2.Response<String> response) {
Log.d(TAG,"onResponse:"+response.body().toString());
}
@Override
public void onFailure(retrofit2.Call<String> call, Throwable t) {
Log.d(TAG,"onFailure:"+t.getMessage());
}
});
使用Okhttp
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://api.douban.com/v2/movie/top250?start=0&count=2")
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
subscriber.onError(e);
subscriber.onCompleted();
}
@Override
public void onResponse(Call call, Response response) throws IOException
{
subscriber.onNext(response.body().string());
subscriber.onCompleted();
}
});
对比结果
从逻辑上来看Retrofit并没有单纯使用Okhttp那么清晰,而且代码也更多了,但是优点在于,Retrofit进行了一定的解耦,在有多个请求时,使用接口加注解的方式更加方便,而且能够非常简便的集成我们之前说得RxJava和在实际项目中经常用到的Json解析,所以在一定体量的项目下,使用Retrofit能够省很多事,并且也有利于后期的维护,因为每个代码块已经分别解耦了。
3.Jackson
至于Jackson我觉得做过网络请求的同学应该都很熟悉了,毕竟现在毕竟流行的网络数据传输用的都是Json。而Retrofit还支持很多其他的转化,如下:
Gson: com.squareup.retrofit2:converter-gson
Jackson: com.squareup.retrofit2:converter-jackson
Moshi: com.squareup.retrofit2:converter-moshi
Protobuf: com.squareup.retrofit2:converter-protobuf
Wire: com.squareup.retrofit2:converter-wire
Simple XML: com.squareup.retrofit2:converter-simplexml
Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars
至于公司为什么选用Jackson,我觉得也许是Jackson的性能比较不错吧,在这里不深究了。
各json解析库性能比较
4.Ormlite
Object Relational Mapping Lite(Ormlite)是为持久化Java对象到Sql数据库中,而提供一些简单轻量的功能,同时避免了繁琐和额外开销的标准化ORM包。
Ormlite基本使用
AndroidStudio中引入
compile 'com.j256.ormlite:ormlite-core:4.48'
compile 'com.j256.ormlite:ormlite-android:4.48'
配置Bean类
//使用注解进行数据库表配置
//设置表名为tb_test
@DatabaseTable(tableName = "tb_test")
public class TestDbBean {
//将该变量绑定为name列
@DatabaseField(columnName = "name")
private String name;
//设置该字段为id字段,且自增
@DatabaseField(generatedId = true)
private int id;
public TestDbBean() {
}
public TestDbBean(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "TestDbBean{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
配置Dao类
public class TestDbDao {
private Context context;
public TestDbDao(Context context) {
this.context = context;
}
public void DbTestAdd()
{
MyDatabaseHelper helper = MyDatabaseHelper.getHelper(context);
Dao<TestDbBean,Integer> dao = helper.getTestDao();
try {
TestDbBean testBean = new TestDbBean();
testBean.setName("myy");
dao.create(testBean);
testBean.setName("myy1");
dao.create(testBean);
testBean.setName("myy2");
dao.create(testBean);
} catch (SQLException e) {
e.printStackTrace();
}finally {
//helper关闭后无法再次使用,需要获取新的helper
helper.close();
}
}
public void DbTestDelete()
{
MyDatabaseHelper helper = MyDatabaseHelper.getHelper(context);
Dao<TestDbBean,Integer> dao = helper.getTestDao();
try {
dao.deleteById(2);
} catch (SQLException e) {
e.printStackTrace();
}finally {
helper.close();
}
}
public void DbTestUpdate()
{
MyDatabaseHelper helper = MyDatabaseHelper.getHelper(context);
Dao<TestDbBean,Integer> dao = helper.getTestDao();
try {
TestDbBean testBean = new TestDbBean();
testBean.setName("myy_update");
testBean.setId(3);
dao.update(testBean);
// 这是更新ID用的
// dao.updateId()
} catch (SQLException e) {
e.printStackTrace();
}finally{
helper.close();
}
}
public List<TestDbBean> DbTestQueryAll()
{
MyDatabaseHelper helper = MyDatabaseHelper.getHelper(context);
Dao<TestDbBean,Integer> dao = helper.getTestDao();
List<TestDbBean> beans = null;
try {
beans = dao.queryForAll();
} catch (SQLException e) {
e.printStackTrace();
}finally {
helper.close();
}
return beans;
}
}
实现DBHelper并继承OrmLiteSqliteOpenHelper类
public class MyDatabaseHelper extends OrmLiteSqliteOpenHelper {
private static final String DB_NAME = "db_test";
private static final int DB_VERSION = 2;
private Dao<TestDbBean,Integer> dao;
public MyDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase database, ConnectionSource connectionSource) {
try{
//创建数据库表
TableUtils.createTable(connectionSource,TestDbBean.class);
}catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource, int oldVersion, int newVersion) {
try {
TableUtils.dropTable(connectionSource,TestDbBean.class,true);
onCreate(database,connectionSource);
} catch (SQLException e) {
e.printStackTrace();
}
}
private static MyDatabaseHelper instance;
public static MyDatabaseHelper getHelper(Context context) {
if(instance==null) {
synchronized (MyDatabaseHelper.class){
if(instance==null) {
instance = new MyDatabaseHelper(context);
}
}
}
return instance;
}
public Dao<TestDbBean,Integer> getTestDao()
{
if(dao==null)
{
try {
dao = getDao(TestDbBean.class);
} catch (SQLException e) {
e.printStackTrace();
}
}
return dao;
}
@Override
public void close() {
super.close();
//关闭后就无法使用了,置空以便gc回收
dao=null;
instance=null;
}
}
相对于使用原生的数据库操作方式,Ormlite简化了数据库的创建,(使用@注解对bean进行配置即可完成表的配置)对于创建,增删改查等操作,使用TableUtils就能完成,非常方便。
5.Mosby
Mosby是一个服务于Android的MVP库。它旨在使用一个清晰的MVP架构,帮助我们开发一个时髦的Android应用。并且,Mosby还能通过引入ViewState和维持Presenter来处理横竖屏切换带来的问题(切换后数据丢失)。
Ps:不得不吐槽Mosby库的名字居然来自于一部电视剧《How I Met Your Mother》,原谅我没听说过
引入
dependencies {
compile 'com.hannesdorfmann.mosby:mvp:2.0.1'
compile 'com.hannesdorfmann.mosby:viewstate:2.0.1'
}
设计View接口
View在App中对应于Activity或者Fragment等完成相对独立的业务逻辑的显示类。设计出来的View接口将对Presenter调用,应该只暴露更新相关UI的接口,至于接口的粒度可以根据具体业务进行划分。
具体代码借用官方的例子:
Mosby官方例子
public interface LoginView extends MvpView {
// Shows the login form
public void showLoginForm();
// Called if username / password is incorrect
public void showError();
// Shows a loading animation while checking auth credentials
public void showLoading();
// Called if sign in was successful. Finishes the activity. User is authenticated afterwards.
public void loginSuccessful();
}
实现View
public class LoginFragment extends MvpViewStateFragment<LoginView, LoginPresenter>
implements LoginView {
@InjectView(R.id.username) EditText username;
@InjectView(R.id.password) EditText password;
@InjectView(R.id.loginButton) ActionProcessButton loginButton;
@InjectView(R.id.errorView) TextView errorView;
@InjectView(R.id.loginForm) ViewGroup loginForm;
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_login, container, false);
}
@Override public ViewState createViewState() {
return new LoginViewState();
}
@Override public LoginPresenter createPresenter() {
return new LoginPresenter();
}
@OnClick(R.id.loginButton) public void onLoginClicked() {
// Check for empty fields
String uname = username.getText().toString();
String pass = password.getText().toString();
loginForm.clearAnimation();
// Start login
presenter.doLogin(new AuthCredentials(uname, pass));
}
// Called first time the fragment starts
@Override public void onNewViewStateInstance() {
showLoginForm();
}
@Override public void showLoginForm() {
LoginViewState vs = (LoginViewState) viewState;
vs.setShowLoginForm();
errorView.setVisibility(View.GONE);
setFormEnabled(true);
loginButton.setLoading(false);
}
@Override public void showError() {
LoginViewState vs = (LoginViewState) viewState;
vs.setShowError();
loginButton.setLoading(false);
if (!isRestoringViewState()) {
// Enable animations only if not restoring view state
loginForm.clearAnimation();
Animation shake = AnimationUtils.loadAnimation(getActivity(), R.anim.shake);
loginForm.startAnimation(shake);
}
errorView.setVisibility(View.VISIBLE);
}
@Override public void showLoading() {
LoginViewState vs = (LoginViewState) viewState;
vs.setShowLoading();
errorView.setVisibility(View.GONE);
setFormEnabled(false);
loginButton.setLoading(true);
}
private void setFormEnabled(boolean enabled) {
username.setEnabled(enabled);
password.setEnabled(enabled);
loginButton.setEnabled(enabled);
}
// Called when login was successful
@Override public void loginSuccessful() {
getActivity().finish();
}
}
继承Presenter
继承了MvpBasePresenter,并在这个类里完成相关业务逻辑的组装
public class LoginPresenter extends MvpBasePresenter<LoginView> {
private AccountManager accountManager;
private EventBus eventBus;
@Inject public LoginPresenter(AccountManager accountManager,
EventBus eventBus) {
this.accountManager = accountManager;
this.eventBus = eventBus;
}
public void doLogin(AuthCredentials credentials) {
if (isViewAttached()) {
getView().showLoading();
}
// Kind of "callback"
subscriber = new Subscriber<Account>() {
@Override public void onCompleted() {
if(isViewAttached()){
getView().loginSuccessful();
}
}
@Override public void onError(Throwable e) {
if (isViewAttached()){
getView().showError();
}
}
@Override public void onNext(Account account) {
eventBus.post(new LoginSuccessfulEvent(account));
}
};
// do the login
accountManager.doLogin(credentials).subscribe(subscriber);
}
}
总体来说MVP架构中的事件链如下:
View触发事件->调用Presenter中的业务方法->Presenter从View中获取相关状态->Presenter调用Model,如:网络请求,数据库操作等->得到结果,调用View更新UI
ViewState
如果要引入ViewState解决横竖屏切换问题只需要用MvpLceViewStateFragment 代替MvpLceFragment然后实现createViewState()和getData()就好。
从使用后的体验来看,其实Mosby所做的事情大部分是一种规范作用,并且还是比较轻量级的,当然Mosby还提供了对应于LEC结构(Loading-Error-Content)的类和接口,封装好了对应于LEC的接口,只需要继承并实现就好。