Dagger讲完了,下面进入我们的另一大正题MVP:
MVC模式无论是做web开发还是安卓应该都比较熟悉,毕竟刚开始的时候大家都是用这种开发架构的。
刚开始用MVC(M-Model-模型、V-View-视图、C-Controller-控制器)的时候,就感觉一个Activity或者Fragment中要写很多行代码,所有的数据和View都需要在一个文件中写,感觉特别臃肿,后来真正开发的时候接触到了MVP(M-Model-模型、V-View-视图、P-Presenter-展示器),才感受到编码的美丽。
Controlller/Presenter在MVC/MVP中都起着逻辑控制处理的角色,起着控制各业务流程的作用。而 MVP与MVC最不同的一点是M与V是不直接关联的也是就Model与View不存在直接关系,这两者之间间隔着的是Presenter层,其负责调控View与Model之间的间接交互,在 Android中很重要的一点就是对UI的操作基本上需要异步进行也就是在MainThread中才能操作UI,所以对View与Model的切断分离是合理的。此外Presenter与View、Model的交互使用接口定义交互操作可以进一步达到松耦合也可以通过接口更加方便地进行单元测试。
我们的MVP的实现主要在ui目录下:
这个目录下我们可以看到Present和View,而Model在哪里呢?不要着急,分析Presenter的时候会说的!
我们仍然通过看MeFragment的实现来分析MVP架构,MeFragment相关代码:
class MeFragment : BaseFragment(), MeView, SwipeRefreshLayout.OnRefreshListener {
@BindView(R.id.iv_avatar)
internal var ivAvatar: ImageView? = null
@BindView(R.id.tv_name)
...
internal var swipeRefreshLayout: SwipeRefreshLayout? = null
private var mUser: User? = null
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater!!.inflate(R.layout.fragment_me, container, false)
ButterKnife.bind(this, view)
return view
}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
swipeRefreshLayout!!.setOnRefreshListener(this)
swipeRefreshLayout!!.setColorSchemeColors(resources.getColor(R.color.gplus_color_1),
resources.getColor(R.color.gplus_color_2),
resources.getColor(R.color.gplus_color_3),
resources.getColor(R.color.gplus_color_4))
swipeRefreshLayout!!.isRefreshing = true
present!!.loadData()//第一次加载数据
llRoot!!.visibility = View.GONE
}
override fun injectPresent() {
val uid = UserPrefs.get(activity).account.uid!!
DaggerMePresentComponent.builder()
.mePresentModule(MePresentModule(uid, this))
.managerAndApiComponent((activity.application as EMApplication).daggerManagerAndApiComponent)
.build()
.inject(this)
}
override fun setUser(user: User) {
if (user != null) {
mUser = user
ImageLoadFactory.getImageLoad().loadImageCircle(activity, ivAvatar, user.avatar_large, R.drawable.circle_image_placeholder)
tvName!!.text = user.screen_name
tvDesc!!.text = String.format("简介:%s", user.description)
tvStatusCount!!.text = CountUtil.getCounter(activity, user.statuses_count!!)
tvFollowingCount!!.text = CountUtil.getCounter(activity, user.friends_count!!)
tvFollowerCount!!.text = CountUtil.getCounter(activity, user.followers_count!!)
llRoot!!.visibility = View.VISIBLE
}
}
override fun setDraftCount(count: Int) {
tvDraftCount!!.visibility = if (count > 0) View.VISIBLE else View.GONE
tvDraftCount!!.text = CountUtil.getCounter(activity, count)
}
override fun completeRefresh() {
swipeRefreshLayout!!.isRefreshing = false
}
@OnClick(R.id.ll_draft, R.id.ll_collect, R.id.ll_following, R.id.ll_me, R.id.ll_follower, R.id.ll_status, R.id.ll_attitude, R.id.ll_myphoto, R.id.ll_more)
fun onClick(view: View) {
when (view.id) {
R.id.ll_draft -> {
//草稿
var intent = DefaultFragmentActivity.starFragmentV4(activity, getString(R.string.draft_box), DraftFragment::class.java, null)
startActivity(intent)
}
....//点击事件
}
override fun onRefresh() {
present!!.loadData()
}
}
MeView+BaseView:
interface MeView : BaseView {
fun setUser(user: User)//设置用户信息
fun setDraftCount(count: Int)//设置草稿数
fun completeRefresh()//完成刷新
}
interface BaseView {
fun showDialogLoading(isShow: Boolean, hintStringId: Int)
fun showHint(stringId: Int)
fun showHint(string: String)
fun showDialogLoading(isShow: Boolean)
fun onAuthenticationError()
fun onDefaultLoadError()
}
从上面可以看出MeFragment中实现了MeView的三个方法。
MePresent+BasePresent:
interface MePresent : BasePresent {
fun loadData()
}
interface BasePresent {
fun onCreate()
fun onDestroy()
}
MePresentImp:
class MePresentImp(private val mUid: Long, private val mUserApi: UserApi, private val mUserManager: UserManager, private val mDraftManager: DraftManager, private val mMeView: MeView) : AbsBasePresent(), MePresent {
private var mDraftObservable: Observable? = null
fun onCreate() {
mDraftObservable = RxBus.get().register(EventTag.Companion.getEVENT_DRAFT_UPDATE())
mDraftObservable!!.subscribe(object : Action1() {
fun call(draft: Draft) {
loadDrafts()
}
})
}
fun loadData() {
loadUser()
loadDrafts()
}
//显示用户信息,昵称、头像、微博数。。。
private fun loadUser() {
val serverObservable = mUserApi.getUserByUid(mUid)
.compose(ErrorCheckerTransformer.Companion.create())
.doOnNext(object : Action1() {
fun call(user: User) {
mUserManager.saveUser(user)
}
})
val localObservable = RxUtil.INSTANCE.createDataObservable(object : RxUtil.Provider() {
val data: User
get() = mUserManager.getUserByUid(mUid)
})
val subscription = Observable.concat(localObservable, serverObservable)
.compose(SchedulerTransformer.Companion.create())
.doOnTerminate(object : Action0() {
fun call() {
mMeView.completeRefresh()
}
})
.subscribe(object : ResponseSubscriber(mMeView) {
protected fun onFail(e: Throwable) {
}
fun onNext(user: User) {
mMeView.setUser(user)
}
})
addSubscription(subscription)
}
private fun loadDrafts() {
RxUtil.INSTANCE.createDataObservable(object : RxUtil.Provider() {
val data: Long?
get() = mDraftManager.getDraftsCount()
})
.compose(SchedulerTransformer.Companion.create())
.subscribe(object : SubscriberAdapter() {
fun onNext(integer: Long?) {
mMeView.setDraftCount(integer!!.toInt())
}
})
}
fun onDestroy() {
super.onDestroy()
RxBus.get().unregister(EventTag.Companion.getEVENT_DRAFT_UPDATE(), mDraftObservable)
}
}
好吧,看到MePresentImp的构造函数我终于看到了Kotlin的构造函数是多么的精(shi)妙(mao)了!!
MePresentImp我们在上面的Dagger解析中已经看过了,当传入参数mUid、mUserApi、mUserManager、mDraftManager、mMeView后会首先执行onCreate方法加载草稿数显示loadDrafts(),这是因为BaseFragment中onViewCreated方法中一旦检测到Present注入成功后就会直接执行Present的onCreate方法:
abstract class BaseFragment : Fragment(), BaseView {
private var mLoadingDialog: Dialog? = null
@Inject
protected var present: P? = null
set
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
injectPresent()
if (present != null) {
present!!.onCreate()
}
}
//省略无关代码...
}
从MePresentImp中我们可以看出,其主要功能就是连接Model和View,将获取到的数据显示出来,(楼主,你在逗我吗?哪里来的Model?被你吃了吗?)。其实Model这个业务层从代码中也可以看出来,都交给了Rxjava来处理咯:分析个简单的加载草稿数的Model:
private fun loadDrafts() {
RxUtil.INSTANCE.createDataObservable(object : RxUtil.Provider() {
val data: Long?
get() = mDraftManager.getDraftsCount()
})
.compose(SchedulerTransformer.Companion.create())
.subscribe(object : SubscriberAdapter() {
fun onNext(integer: Long?) {
mMeView.setDraftCount(integer!!.toInt())
}
})
}
感觉分析这个离MVP又有点远了,不过MVP的东西基本上就是这么实现的了:
Present作为中间件负责Model和View之间交互,持有View的接口,通过接口的形式将 View 层和 Model 层完全隔离开来。当然了,我这里其实不算MVP啦,因为我把Model业务层使用RxJava在Present中实现了,所以并没有分开写,当然也可以分开写,不过我感觉没有必要就写在一起了。总结起来就是:
在Activity/Fragment/…中注入(或者创建)一个Present并且实现View中的方法,而Present中持有Model和View接口,可以在Presenter中获取Model的数据并在View中显示,达到交互的目的。
大家如果还有什么不明白的可以看下具体项目源码,应该能很好理解的。
下面再来讲下使用RxJava实现的Model获取数据的部分吧:
上面的loadDrafts()方法中:
private void loadDrafts() {
RxUtil.INSTANCE.createDataObservable(new RxUtil.Provider() {
@Override
public Long getData() {
return mDraftManager.getDraftsCount();
}
})
.compose(SchedulerTransformer.Companion.create())
.subscribe(new SubscriberAdapter() {
@Override
public void onNext(Long integer) {
mMeView.setDraftCount(integer.intValue());
}
});
}
这种形式搭建看到compose、subscribe应该就可以猜出来这肯定是使用了RxJava了,所完成的功能就是获取草稿数量getDraftsCount并显示在MeFragment中mMeView.setDraftCount(integer!!.toInt())
首先了解下RxJava吧:我先抛个网址给大家:http://gank.io/post/560e15be2dca930e00da1083,讲的很清楚,我在这里再讲的话有点班门弄斧了,但是这里我还是要简单介绍下,因为如果直接说我的封装,有可能大家会有点接受不了。
RxJava是什么
RxJava 是一个由 Netflix 开发的 Reactive Extensions 的(Java版)实现。其他还有大量主流编程语言的实现,包括 Javascript, Python, Ruby, Go 等等
我所理解的是RxJava是对观察者模式的一种很好的封装,其实Android中是有自己的观察者模式实现的。
而有观察者模式必定会看到这两个东西:
Observables 和 Observers
一个 Observable 可以输出一个或一系列的 objects,这些 objects 会被订阅到这个Observable 的 Observer 所处理或接收。
把一个 Observer 注册到一个 Observable 上是很必要的,如果不这么做的话Observable 什么都不会输出。当 Observer 注册之后,一个 Subscription 类型的实例会创建,它可以用来取消对 Observable 的订阅,这通常在 Activities 和Fragments 的 onStop 或 onPause 方法中非常有用,我现在在MePresentImp中继承了AbsBasePresent
AbsBasePresent:
public abstract class AbsBasePresent implements BasePresent {
private CompositeSubscription mCompositeSubscription;
public AbsBasePresent() {
mCompositeSubscription = new CompositeSubscription();
}
@Override
public void onDestroy() {
mCompositeSubscription.clear();
}
protected void addSubscription(Subscription s) {
if (mCompositeSubscription == null) {
mCompositeSubscription = new CompositeSubscription();
}
mCompositeSubscription.add(s);
}
}
这个类的主要作用是将一个Subscription实例添加到CompositeSubscription中,当Present被销毁时,清空CompositeSubscription中的Subscription实例,
哎哟,感觉我讲起来好费力哦!大家还是去看上面那篇博文中的扔物线大神给大家讲解的吧。
我这里先介绍下我是如何实现的,其实就是有一个封装好的RxUtils:
public class RxUtil {
/**
* 生成Observable
* @param
* @return
*/
public static Observable createDataObservable(final Provider provider) {
return Observable.create(new Observable.OnSubscribe() {
@Override
public void call(Subscriber super T> subscriber) {
try {
subscriber.onNext(provider.getData());
subscriber.onCompleted();
} catch (Throwable e) {
subscriber.onError(e);
}
}
});
}
public static void unsubscribe(Subscription... subscriptions)
{
for (Subscription subscription : subscriptions)
{
if (subscription != null)
{
if (!subscription.isUnsubscribed())
{
subscription.unsubscribe();
}
}
}
}
public static interface Provider {
public T getData() throws Exception;
}
}
这个工具类具体是怎么使用呢?我们就以上面加载草稿数显示为例,这里我使用Java代码给大家解释,大家对比下就知道Kotlin做了哪些修改了:
private void loadDrafts() {
RxUtil.createDataObservable(new RxUtil.Provider() {
@Override
public Long getData() {
return mDraftManager.getDraftsCount();
}
})
.compose(SchedulerTransformer.create())
.subscribe(new SubscriberAdapter() {
@Override
public void onNext(Long integer) {
mMeView.setDraftCount(integer.intValue());
}
});
}
首先是使用使用工具类准备创建一个被观察者createDataObservable,其传入的参数是实现了接口Provider,而Provider接口实现方法getData是获取当前草稿数量,所以泛型中的T现在是long,即现在创建了一个被观察者实例Observable,然后使用compose方法,其中SchedulerTransformer.create()相当于将这个被观察者注册到io线程和主线程中执行,即相当于执行了如下代码:
observable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
具体可以再SchedulerTransformer中看到(在包com.kibo.api.ex中可以看到这个类)。
然后又将这个Observabe注册到一个观察者上,这个观察者是通过SubscriberAdapter这个类生成的:
abstract class SubscriberAdapter : Subscriber() {
override fun onCompleted() {
}
override fun onError(e: Throwable) {
LogUtil.d(this@SubscriberAdapter, e.message)
}
}
这个类其实比较简单,就是生成一个Subscriber的,只是直接在这里有两个方法已经先实现了,我们需要实现的还有一个OnNext方法,当然,如果有需要的话还是可以覆写这两个方法的,由于这个比较简单,只是简单的获取一个Long数据,所以就只需要覆写一个onNext方法将获取到的草稿数显示到meView上就行了。
无论何时 Observer 订阅 Observable 的消息,它都需要考虑处理3个方法:
– onNext (T) 方法用来接收 Observable 发出的 objects.
– onError (Exception) ,这个方法会在内部抛出异常的时候调用。
– onCompleted() ,这个方法会在 Observable 停止释放 objects 的时候调用
从上面我们可以看出,RxJava就是一个被观察者负责类似于获取数据的工作,而观察者就负责将观察的数据进行处理。Rxjava就先说到这吧,大家去看完那篇博文应该就能理解的差不多了,还是非常好用的!
最后再稍稍讲下Retrofit吧:
讲这个终于可以脱离大框架来讲了,这样大家也能看的清楚一点,首先介绍下:
什么是Retrofit
Retrofit与okhttp共同出自于Square公司,retrofit就是对okhttp做了一层封装。把网络请求都交给给了Okhttp,我们只需要通过简单的配置就能使用retrofit来进行网络请求了,其主要作者是Android大神JakeWharton。
我们就拿之前提到的ApiModule中有个provideUserApi:
@Provides
@Singleton
UserApi provideUserApi(WeiCoService weiCoService, WeiBoService weiBoService) {
return new UserApiImp(weiCoService);
}
我们Dagger就先不看了,我们呢就主要看这个UserApi是怎么获取数据的,首先有两个参数,分别是两个Service,这个选用了WeiCoService,我们就看看这个:
WeiCoService:
interface WeiCoService {
object WeiCoFactory {
internal var sWeiCoService: WeiCoService? = null
fun create(okHttpClient: OkHttpClient): WeiCoService? {
if (sWeiCoService == null) {
val retrofit = Retrofit.Builder()
.baseUrl(Key.WEICO_BASE_URL)
.addConverterFactory(GsonConverterFactory.create(GsonUtils.getGson()))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.callFactory(okHttpClient)
.build()
sWeiCoService = retrofit.create(WeiCoService::class.java)
}
return sWeiCoService
}
}
@GET("/2/place/nearby_pois")
fun a(@Query("lat") paramDouble1: Double, @Query("long") paramDouble2: Double, @Query("page") paramInt1: Int, @Query("count") paramInt2: Int): Observable
@GET("2/statuses/friends_timeline")
fun getFriendsWeibo(@Query("since_id") since_id: Long, @Query("max_id") max_id: Long,
@Query("count") count: Int, @Query("page") page: Int): Observable
@GET("/2/statuses/show")
fun getWeiboById(@Query("id") id: Long, @Query("isGetLongText") isGetLongText: Int): Observable
@GET("2/statuses/user_timeline")
fun getUserWeibos(@Query("uid") uid: Long, @Query("feature") feature: Int,
@Query("since_id") since_id: Long, @Query("max_id") max_id: Long,
@Query("count") count: Int, @Query("page") page: Int): Observable
//省略大批代码...
companion object {
val TAG = "Api"
}
}
到这里我们就是Retrofit的重点了,Retrofit需要定义一个接口,用来返回我们的Observable对象。
创建一个Retrofit对象
上面的工厂对象中有一个create方法就是直接创建的一个Retrofit对象并返回的:
这里的baseUrl加上下面@GET(“*”)定义的参数形成完整的请求地址;
addConverterFactory(GsonConverterFactory.create(GsonUtils.getGson()))的意思是构建了一个返回值为Gson的支持,Call对象的泛型接收另外的格式需要添加另外的支持,这里用的是自己的一个Gson处理,addCallAdapterFactory(RxJavaCallAdapterFactory.create())的意思是添加返回值是Observable的支持,然后我们就可以使用返回的Observable放到RxJava中来使用啦!是不是特别简单,毕竟是大神搞的嘛。Retrofit就简单介绍到这吧,详细的大家可以查些资料来看。
最后再跟大家聊一聊GreenDao,也许有的人已经在使用了,毕竟这是一个非常好用的安卓ORM框架,刚开始我找的时候找到了两个,Realm和GreenDao,在两个都适用之后我最后还是选择了greendao。
在项目中使用greendao主要是对一些数据进行缓存,而自己又不需要像使用Android自带的SQLite操作那样,许多sql语句都需要自己写,我们所需要做的就是定义好自己的实体属性,这样greendao就可以根据你所添加的实体来内建一张一张的表了,并且会赋予默认值。
在最后会生成三个比较重要的文件,DaoMaster、DaoSession、****Dao(可能会有多个),还有一个就是自己的实体类。而我们操作数据主要使用的就是DaoMaster和DaoSession两个类,因为项目中使用了多个Dao,所以我又封装了一个DBManager专门用来管理多个Dao,大家可以看com.kibo.utils.db.DBManager和com.kibo.manager.imp下的内容,具体的一些实现细节我就不说了。大家可以在网上搜索一些相关的文章看看。
好吧,感觉写博客是个体力活,写出代码和给别人讲明白真的是有点不同,花了两个周末才算搞定这一篇,主要现在实习并不是安卓方向,而是Java,要不然我就可以把我的实习所得跟大家分享下了,文章可能会有些不合理和凌乱的地方,望大家多多见谅,我会继续努力,写出更好的文章呈献给大家!