使用Kotlin开发Android项目- Kibo 【安卓巴士博文大赛】
------必备引言------
阳阳举办的第三个活动了,每次我都是积极参与,一方面是想获得奖品,另一方面也是
对自己最近一段时间学习的总结。上次讲的 Kotlin 开发 Android 使用Kotlin来开发Android只是皮毛,举了个小例子,接下来给大家讲的是自己之前做的一个 Android 项目 Kibo (新浪微博第三方客户端)。
项目概述
由于项目的内容比较多,所以本篇主要从项目的框架上来讲述。后面有可能会就细节分析。
首先来看下项目的效果吧(图片太大 巴士的 markdown 貌似不支持图片并排。。):
从图片中也可以很清楚的看出,底部和官方的差不多,为主页、消息、发现、个人四
个部分。而发布微博、分组、设置我则是使用悬浮窗实现的(主要是如果设计和官方一样的话上架会被驳回)。现在线上的 1.2.0 版本是以前的了。现在在做的是 1.3.2 了,只是一直没时间搞上线。。
言归正传,Kibo使用的框架是 MVP + Dagger2 + Rxjava + Retrofit + GreenDao ,是大家可以好好学习的一个架构,学会之后会感觉安卓开发是如此的简单,再配合 Kotlin 这门优雅的语言,你会发现写出来的代码也很好理解。
项目结构
首先我粗略介绍下框架中每部分的作用吧,结合项目的目录结构来看可能会更好点:
也许大家看了目录结构后觉得有些包可以合并在一起的,比如用户账户数据可以放到 bean 里, glide 图片工具可以放到 utils 包中,其实这些我都想过,但是后来发现这些都需要单独作为一个功能来做,所以就所索性新开了一个包来写了。
整体框架大家看完之后我就先给大家具体讲下使用的每一个优秀的设计.讲完了大伙应该就知道是如何设计的了。
Dagger2
Dagger2配合MVP使用
首先要说的是项目的两个大头,当时我学会了使用 MVP 后一直感觉在每一个 Activity 和 Fragment 中都需要 new 一个 Present 是一件很难受的事,然后就上网查了有什么方法可以解决,果然,就让我遇到了 Dagger2 。使用 MVP 时如果配合 Dagger 的话你会感觉到自己写起代码来得心应手,因为你只需要知道你需要添加什么功能,基本上你就可以按照已经搭建好的项目结构去继续开发。
由于项目中很多地方用到了注入,所以我先讲 Dagger 。
Dagger 现在不知道发展到哪一个版本了,我当时用的就是2,感觉已经可以满足我的需求了。用过之后对其的理解就是帮你实现依赖注入!so cool!
依赖注入
讲到 Dagger 不得不提的就是这个,依赖注入( Dependency Injection )是用于实现控制反转( Inversion of Control )的最常见的方式之一。
为什么需要依赖注入
控制反转用于解耦,解的究竟是谁和谁的耦?这是我在最初了解依赖注入时候产生的第一个问题。
我也不像网络上那样把代码贴出来给大家解释了,我就从项目中的举一个例子吧,而项目中使用 Dagger 是为了注入 Present 的,就从如何注入 P 讲起吧!
我们在 MVP 中使用的 P 也就是 Presenter ,如果在之前的开发中,想要在 Activity 中使用 P 来获取数据和显示 View 以及处理逻辑的话,我们最常用的方式可能就是形如下的代码段:
***ActivityPresenter ***ActivityPresenter = new ***ActivityPresenterImpl(...);
看上去没有什么问题,当然没有问题,我们也确实可以这么用,然后每个 Activity 或者 Fragment 在 onCreate 中去实例化即可。但是这其实是一种典型的硬编码!神马是硬编码呢?就是你的 Present构造方法中如果有任何参数发生变化的话,那么此时,不仅需要修改 P 的构造函数,还需要修改所有使用到这个 P 的类,这样子写代码就是所谓的硬编码啦!而我们开发中应该尽可能的避免这种写法,由此依赖注入就诞生咯!!
依赖注入先用简单的说法来描述,就是上面 Presenter 实例的获取是通过事先一个辅助类定义好,在具体使用的时候,通过这个辅助类去生成一个 Presenter 对象,这样子就好很多了,而这个辅助类就是 Dagger 中 Module。
所以使用 Dagger 能够达到的效果就是代码中不再需要 new *Presenter() 这样类似的代码实现,直接在定义 Presenter 实例时,用 @Inject 注解一下, Presenter 的实例就生成。
讲了 Dagger 的一个大概,最明了的讲解就是通过代码中贯穿的使用,下面我就通过项目中一个页面的实现来讲解下,至于 RxJava 和 Retrofit 在讲解的时候我会顺便做出解释。
我们就用最简单的个人页(第四张图)的实现来说吧,从图片中我们也可以看出来并没有什么特别的实
现,我也是用的最简单的实现方法,大家看了布局文件就明白了( fragment_me.xml ),因为内容太多了我就不贴出来具体布局代码了,其实就是两个 CardView,然后 CardView 里再放其他的。
个人页的主要的实现就是 MeFragment ,首先看到有一个注解 @Inject 直接注入 MePresent :
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) }
,这样就完成 Dagger 注入了,其实是重写了父类 BaseFragment 中的 injectPresent() 方法:
@Injectprotected var present: P? = nullprotected open fun injectPresent() { }
目的就是注入我们需要的特定的 Presenter ,然后我们就可以直接使用注入的 MePresenter(present) ,如刷新时获取数据:
override fun onRefresh() { present!!.loadData() }
我们不必再手动的去 new 一个对象,而具体是怎么实现的呢?
首先大概讲一下 Dagger 中的一些常用注解:
@Inject : 通常在需要依赖的地方使用这个注解。换句话说,你用它告诉 Dagger 这个类或者字段需要依赖注入。这样, Dagger 就会构造一个这个类的实例并满足他们的依赖。
@Module : Modules 类里面的方法专门提供依赖,所以我们定义一个类,用 @Module 注解,这样 Dagger 在构造类的实例的时候,就知道从哪里去找到需要的 依赖。 modules 的一个重要特征是它们设计为分区并组合在一起(比如说,在我们的 app 中可以有多个组成在一起的 modules)。
@Provide : 在 modules 中,我们定义的方法是用这个注解,以此来告诉 Dagger 我们想要构造对象并提供这些依赖。
@Component : Components 从根本上来说就是一个注入器,也可以说是 @Inject 和 @Module 的桥梁,它的主要作用就是连接这两个部分。
Components 可以提供所有定义了的类型的实例,比如:我们必须用 @Component 注解一个接口然后列出所有的 @Modules 组成该组件,如果缺失了任何一块都会在编译的时候报错。所有的组件都可以通过它的 modules 知道依赖的范围。
@Scope : Scopes 可是非常的有用, Dagger2 可以通过自定义注解限定注解作用域。后面会演示一个例子,这是一个非常强大的特点,因为就如前面说的一样,没必要让每个对象都去了解如何管理他们的实例。简单来说就是我们可以定义所有范围的粒度。
项目 Dagger 主要是在 di 目录下:
首选确定了一个 UIScope ,这个我是为了限定注解作用域在UI域使用。
@Documented@Scope@Retention(RetentionPolicy.RUNTIME) annotation class UIScoped
简单和 Java 代码对比下:
@Documented@Scope@Retention(RetentionPolicy.RUNTIME)public @interface UIScoped { }
其实看着感觉没多大差啦!!但是还是感觉酷酷的。(每次写东西都小跑题,还是继续关注MeFragment的实现吧:
DaggerMePresentComponent.builder() .mePresentModule(MePresentModule(uid, this)) .managerAndApiComponent((activity.application as EMApplication).daggerManagerAndApiComponent) .build() .inject(this)
在 MeFragment 完成 Dagger 的注入需要 MePresentComponent 、 MePresentModule 、 daggerManagerAndApiComponent :下面依次分析:
MePresentComponent :
@UIScoped@Component(dependencies = arrayOf(ManagerAndApiComponent::class), modules = arrayOf(MePresentModule::class))interface MePresentComponent { fun inject(meFragment: MeFragment)}
只有简单的一个 inject 方法,方法传参是 MeFragment ,注解提示依赖于 ManagerAndApiComponent 和 MePresentModule , MePresentComponent 这个类就是起到注入 MePresent 桥梁作用的东东啦!
然后我们再来看 ManagerAndApiComponent 是什么:
@Singleton @Component(modules = arrayOf(ApplicationModule::class, ManagerModule::class, ApiModule::class))interface ManagerAndApiComponent { fun provideDraftManager(): DraftManager fun provideGroupManager(): GroupManager fun provideMessageManager(): MessageManager fun provideNotifyManager(): NotifyManager fun provideStatusManager(): StatusManager fun provideStatusUploadImageManager(): StatusUploadImageManager fun provideUserManager(): UserManager /**--------------------------------------------------------------------------------------- */ fun provideAttitudeApi(): AttitudeApi fun provideCommentApi(): CommentApi fun provideGroupApi(): GroupApi fun provideInfoPageApi(): InfoPageApi fun provideLoginApi(): LoginApi fun provideMessageApi(): MessageApi fun provideNotifyApi(): NotifyApi fun provideSearchRecommendApi(): SearchRecommendSource fun provideStatusApi(): StatusApi fun provideUserApi(): UserApi fun provideUpdateApi(): UpdateApi }
从上面可以看出 ManagerAndApiComponent 有三个 Module ( ApiModule 、 ManagerModule 、 ApplicationModule ),前两个分别提供 Api 的接口和 Manager 的接口,分别获取数据和管理数据,具体可以看 ApiModule 和 ManagerModule :
ApiModule :
@Moduleclass ApiModule { @Provides @Singleton internal fun provideAttitudeApi(weiCoService: WeiCoService, weiBoService: WeiBoService): AttitudeApi { return AttitudeApiImp(weiCoService) } @Provides @Singleton internal fun provideUserApi(weiCoService: WeiCoService, weiBoService: WeiBoService): UserApi { return UserApiImp(weiCoService) } //后面都是类似的返回获取对应Api .......... }
ManagerModule :
@Moduleclass ManagerModule { @Provides @Singleton internal fun provideDraftManager(): DraftManager { return DraftManagerImp() } @Provides @Singleton internal fun provideUserManager(): UserManager { return UserManagerImp() } //省略类似的提供数据管理类(其实就是在每次获取和更改数据时存起来,方便下次获取和管理,不需要上面的Api联网获取了) ............. }
而第三个 ApplicationModule :
@Moduleclass ApplicationModule(private val mContext: Context) { @Provides internal fun provideContext(): Context { return mContext } @Provides @Singleton internal fun provideOkHttpClient(): OkHttpClient { return OkHttpClientProvider.getDefaultOkHttpClient(true) .newBuilder() .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)//设置读取超时时间 .writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)//设置写的超时时间 .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)//设置连接超时时间 .addInterceptor { chain -> var request = chain.request() request = interceptRequest(request) LogUtil.d(TAG, "request " + request.url()) chain.proceed(request) } .build() } @Provides @Singleton internal fun provideWeiBoService(okHttpClient: OkHttpClient): WeiBoService? { return WeiBoService.Factory.create(okHttpClient) } @Provides @Singleton internal fun provideWeiCoService(okHttpClient: OkHttpClient): WeiCoService? { return WeiCoService.WeiCoFactory.create(okHttpClient) } ...... }
由 ApplicationModule 可以看出主要是提供全局的 Context 和 ApiModule 中需要的两个 Service 。(通过 @Provides 都是可以认为是提供出去给别的类使用的)。
WeiboService 和 WeiCoService 讲起来涉及到 RxJava 和 Retrofit ,我就放到最后再讲吧,现在就理解成是为 ApiModule 提供数据的一个 okhttp 连接服务就行啦!
所以 MePresentComponent 中可以使用 dependcies = ManagerAndApiComponent 提供的 Api 和 Manager ,也可以注入 module = MePresentModule 中提供的 MePresentImp :
MePresentModule :
@Moduleclass MePresentModule(private val uid: Long, private val meView: MeView) { @Provides @UIScoped internal fun providePresent(userApi: UserApi, userManager: UserManager, draftManager: DraftManager): MePresent { return MePresentImp(uid, userApi, userManager, draftManager, meView) } }
因为 ManagerAndApiComponent 中提供了 Api 和 Manager 的全部接口实现,所以在 MePresentModule 中所需要的传参如 UserApi , UserManager , DraftManager 等都不需要自己传入,而我们只需要传入 uid , MeView ,而这两个参数都是在注入时会带入构造函数中的( Kotlin 的构造函数都是直接写在类定义体中的。。感觉稍微参数多点就要好几行咯)。
上上面提到的的 MePresentComponent 中定义了一个 inject() 方法,参数是 MeFragment 。然后 rebuild 一下项目,会生成一个以 Dagger 为前缀的 Component 类,这里是前面的 DaggerMePresentComponent ,所以在 MeFragment 中使用如下代码就可以:
DaggerMePresentComponent.builder() .mePresentModule(MePresentModule(uid, this)) .managerAndApiComponent((activity.application as EMApplication).daggerManagerAndApiComponent) .build() .inject(this)
需要加入依赖的 Module 和 Component ,其中 ManagerAndApiComponent 使用( (activity.application as EMApplication).daggerManagerAndApiComponent )获取是因为其是在EMApplication 中初始化的:
class EMApplication : Application() { var daggerManagerAndApiComponent: ManagerAndApiComponent? = null private set override fun onCreate() { super.onCreate() mApplication = this CrashReport.initCrashReport(mApplication, Key.BUGLY_KEY, false)//BUG上传 if (UserPrefs.get(this).token != null) { Init.instance.start(this, UserPrefs.get(this).account.uid!!) } if (SystemUtil.isMainProcess(this)) { registerTokenExpiredEvent() registerActivityLifecycleCallbacks() initNightMode() BackgroundUtil.instance.init(this) } initCrashReport() daggerManagerAndApiComponent = DaggerManagerAndApiComponent.builder() .apiModule(ApiModule()) .managerModule(ManagerModule()) .applicationModule(ApplicationModule(this)) .build() } companion object { private val DEFAULT_CHANNEL = "default" private var mApplication: Application? = null val instance: Application? get() = mApplication } //.......}
MePresentModule 需要传入参数是用户的 uid 和 MeView ,用来提供依赖的 MePresenter !
接下来就是见证奇迹的时刻,因为 Dagger2 是在编译期注入的,所以我们需要小小的
build 一下下,然后我们就可以使用 MePresent 来加载数据了!
override fun onRefresh() { present!!.loadData() }
下面简单说下 Dagger 的原理吧:
首先我们可以看出来 Module 是真正提供依赖的地方,但是要发挥作用的话还是要依靠 Component 类,而且一个 Component 类类可以包含多个 Module 类,用来提供多个依赖。
接着我们重新回顾一下上面的注入过程:首先 MeFragment 需要使用 MePresenter ,因此,我们在基类 BaseFragment 用 @Inject 对 *Presenter 进行标注 P,表明这是要注入的类。并且写了一个抽象方法 injectPresent 用于让子类来实现注入。
abstract class BaseFragment<P : BasePresent> : 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() } } protected open fun injectPresent() { } //..... }
后面的分析过程基本上可以从上面的讲解弄清楚,如果还有不清楚的可以给我提下,我再改下。
但是到此时,我们仍然没有看到实例化的过程,所以下面,就基于这个实例,分析 Dagger2 内部究竟做了什么。
Dagger2注入原理
Dagger2 与其他依赖注入框架不同,它是通过 apt 插件在编译阶段生成相应的注入代码,下面我们就具体看看 Dagger2 生成了哪些注入代码?
我们先看 MePresenter 这个类, Dagger2 会在 appbuildgeneratedsourceaptarmeabireleasecomkibodipresentmodule 目录下生成一个对应的工厂类MePresentModule_ProvidePresentFactory,看下面具体代码:
@Generated( value = "dagger.internal.codegen.ComponentProcessor", comments = "https://google.github.io/dagger")public final class MePresentModule_ProvidePresentFactory implements Factory<MePresent> { private final MePresentModule module; private final Provider<UserApi> userApiProvider; private final Provider<UserManager> userManagerProvider; private final Provider<DraftManager> draftManagerProvider; public MePresentModule_ProvidePresentFactory( MePresentModule module, Provider<UserApi> userApiProvider, Provider<UserManager> userManagerProvider, Provider<DraftManager> draftManagerProvider) { assert module != null; this.module = module; assert userApiProvider != null; this.userApiProvider = userApiProvider; assert userManagerProvider != null; this.userManagerProvider = userManagerProvider; assert draftManagerProvider != null; this.draftManagerProvider = draftManagerProvider; } @Override public MePresent get() { return Preconditions.checkNotNull( module.providePresent( userApiProvider.get(), userManagerProvider.get(), draftManagerProvider.get()), "Cannot return null from a non-@Nullable @Provides method"); } public static Factory<MePresent> create( MePresentModule module, Provider<UserApi> userApiProvider, Provider<UserManager> userManagerProvider, Provider<DraftManager> draftManagerProvider) { return new MePresentModule_ProvidePresentFactory( module, userApiProvider, userManagerProvider, draftManagerProvider); } /** Proxies {@link MePresentModule#providePresent(UserApi, UserManager, DraftManager)}. */ public static MePresent proxyProvidePresent( MePresentModule instance, UserApi userApi, UserManager userManager, DraftManager draftManager) { return instance.providePresent(userApi, userManager, draftManager); } }
对比MePresentModule :
@Moduleclass MePresentModule(private val uid: Long, private val meView: MeView) { @Provides @UIScoped internal fun providePresent(userApi: UserApi, userManager: UserManager, draftManager: DraftManager): MePresent { return MePresentImp(uid, userApi, userManager, draftManager, meView) } }
看到上面的类名,我们发现了一种对应关系,在 MePresentModule 中定义的 @Provides 修饰的方法 providePresent 会对应的生成一个工厂类,这里是 MePresentModule_ProvidePresentFactory。我们看到这个类里有一个 get() 方法,其中调用了 ApIModule 里的 get() 方法来返回我们所需要的依赖 UserApi 以及 UserManager 和DraftManager 。
看下 ApiModule 生成的 ApiModule_ProvideUserApiFactory 中的 get() 方法,就是返回一个 UserApi :
@Generated( value = "dagger.internal.codegen.ComponentProcessor", comments = "https://google.github.io/dagger")public final class ApiModule_ProvideUserApiFactory implements Factory<UserApi> { private final ApiModule module; private final Provider<WeiCoService> weiCoServiceProvider; private final Provider<WeiBoService> weiBoServiceProvider; public ApiModule_ProvideUserApiFactory( ApiModule module, Provider<WeiCoService> weiCoServiceProvider, Provider<WeiBoService> weiBoServiceProvider) { assert module != null; this.module = module; assert weiCoServiceProvider != null; this.weiCoServiceProvider = weiCoServiceProvider; assert weiBoServiceProvider != null; this.weiBoServiceProvider = weiBoServiceProvider; } @Override public UserApi get() { return Preconditions.checkNotNull( module.provideUserApi(weiCoServiceProvider.get(), weiBoServiceProvider.get()), "Cannot return null from a non-@Nullable @Provides method"); } public static Factory<UserApi> create( ApiModule module, Provider<WeiCoService> weiCoServiceProvider, Provider<WeiBoService> weiBoServiceProvider) { return new ApiModule_ProvideUserApiFactory(module, weiCoServiceProvider, weiBoServiceProvider); } /** Proxies {@link ApiModule#provideUserApi(WeiCoService, WeiBoService)}. */ public static UserApi proxyProvideUserApi( ApiModule instance, WeiCoService weiCoService, WeiBoService weiBoService) { return instance.provideUserApi(weiCoService, weiBoService); } }
所以获取了 uid , userApi , userManager , draftManager , meView 这五个参数注入到 MePresent 中,然后就可以提供 MePresent 来使用了!其他的都是类似的过程。还有一些其他层面的问题我在这不一一解答了,感觉问题还是比较多的,大家可以在网络上找到答案,或者直接提出自己的问题。总的来说去看生成的代码直接分析还是比较容易理解的。
看到这再看这张图应该会有点理解了:
MVP
MVP和MVC
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<MePresent>(), 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<Draft>? = null fun onCreate() { mDraftObservable = RxBus.get().register(EventTag.Companion.getEVENT_DRAFT_UPDATE()) mDraftObservable!!.subscribe(object : Action1<Draft>() { fun call(draft: Draft) { loadDrafts() } }) } fun loadData() { loadUser() loadDrafts() } //显示用户信息,昵称、头像、微博数。。。 private fun loadUser() { val serverObservable = mUserApi.getUserByUid(mUid) .compose(ErrorCheckerTransformer.Companion.create()) .doOnNext(object : Action1<User>() { fun call(user: User) { mUserManager.saveUser(user) } }) val localObservable = RxUtil.INSTANCE.createDataObservable(object : RxUtil.Provider<User>() { 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<User>(mMeView) { protected fun onFail(e: Throwable) { } fun onNext(user: User) { mMeView.setUser(user) } }) addSubscription(subscription) } private fun loadDrafts() { RxUtil.INSTANCE.createDataObservable(object : RxUtil.Provider<Long>() { val data: Long? get() = mDraftManager.getDraftsCount() }) .compose(SchedulerTransformer.Companion.create()) .subscribe(object : SubscriberAdapter<Long>() { 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<P : BasePresent> : 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<Long>() { val data: Long? get() = mDraftManager.getDraftsCount() }) .compose(SchedulerTransformer.Companion.create()) .subscribe(object : SubscriberAdapter<Long>() { 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 获取数据的部分吧:
RxJava
为什么使用RxJava?
上面的 loadDrafts() 方法中 :
private void loadDrafts() { RxUtil.INSTANCE.createDataObservable(new RxUtil.Provider<Long>() { @Override public Long getData() { return mDraftManager.getDraftsCount(); } }) .compose(SchedulerTransformer.Companion.<Long>create()) .subscribe(new SubscriberAdapter<Long>() { @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 <T> * @return */ public static <T> Observable<T> createDataObservable(final Provider<T> provider) { return Observable.create(new Observable.OnSubscribe<T>() { @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<T> { public T getData() throws Exception; } }
这个工具类具体是怎么使用呢?我们就以上面加载草稿数显示为例,这里我使用 Java 代码给大家解释,大家对比下就知道 Kotlin 做了哪些修改了:
private void loadDrafts() { RxUtil.createDataObservable(new RxUtil.Provider<Long>() { @Override public Long getData() { return mDraftManager.getDraftsCount(); } }) .compose(SchedulerTransformer.<Long>create()) .subscribe(new SubscriberAdapter<Long>() { @Override public void onNext(Long integer) { mMeView.setDraftCount(integer.intValue()); } }); }
首先是使用使用工具类准备创建一个被观察者 createDataObservable ,其传入的参数是实现了接口 Provider ,而 Provider 接口实现方法 getData 是获取当前草稿数量,所以泛型中的 T 现在是long ,即现在创建了一个被观察者实例 Observable<Long> ,然后使用 compose 方法,其中 SchedulerTransformer.<Long>create() 相当于将这个被观察者注册到 io 线程和主线程中执行,即相当于执行了如下代码:
observable .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread());
具体可以再 SchedulerTransformer 中看到(在包com.kibo.api.ex中可以看到这个类)。
然后又将这个 Observabe 注册到一个观察者上,这个观察者是通过 SubscriberAdapter 这个类生成的:
abstract class SubscriberAdapter<T> : Subscriber<T>() { 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
Retrofit 与okhttp 共同出自于 Square 公司, retrofit 就是对 okhttp 做了一层封装。把网络请求都交给给了 Okhttp ,我们只需要通过简单的配置就能使用 retrofit 来进行网络请求了,其主要作者是 Android 大神 JakeWharton。
我们就拿之前提到的 ApiModule 中有个 provideUserApi :
@Provides@SingletonUserApi 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<AddressResponse> @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<QueryStatusResponse> @GET("/2/statuses/show") fun getWeiboById(@Query("id") id: Long, @Query("isGetLongText") isGetLongText: Int): Observable<Status> @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<UserStatusesResponse> //省略大批代码... 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<T> 的支持,然后我们就可以使用返回的 Observable 放到 RxJava 中来使用啦!是不是特别简单,毕竟是大神搞的嘛。 Retrofit 就简单介绍到这吧,详细的大家可以查些资料来看。
GreenDao
最后再跟大家聊一聊 GreenDao,也许有的人已经在使用了,毕竟这是一个非常好用的安卓 ORM 框架,刚开始我找的时候找到了两个, Realm 和 GreenDao ,在两个都适用之后我最后还是选择了greendao 。
在项目中使用 greendao 主要是对一些数据进行缓存,而自己又不需要像使用 Android 自带的 SQLite 操作那样,许多 sql 语句都需要自己写,我们所需要做的就是定义好自己的实体属性,这样greendao 就可以根据你所添加的实体来内建一张一张的表了,并且会赋予默认值。
在最后会生成三个比较重要的文件, DaoMaster 、 DaoSession 、**Dao (可能会有多个),还有一个就是自己的实体类。而我们操作数据主要使用的就是 DaoMaster 和 DaoSession 两个类,因为项目中使用了多个 Dao ,所以我又封装了一个 DBManager 专门用来管理多个 DaoSession ,大家可以看 com.kibo.utils.db.DBManager 和 com.kibo.manager.imp 下的内容,具体的一些实现细节我就不说了。大家可以在网上搜索一些相关的文章看看。
总结
好吧,感觉写博客是个体力活,感谢上次大赛中大家给我提的使用 MarkDown 编辑,感觉是排版上好很多。再次感谢大家阅读我的文章!
写出代码和给别人讲明白真的是有点不同,花了两天才算搞定这一篇,主要现在实习并不是安卓方向,而是 Java ,要不然我就可以把我的实习所得跟大家分享下了,文章可能会有些不合理和凌乱的地方,望大家多多见谅,我会继续努力,写出更好的文章呈献给大家!另外大家如果需要项目的源码可以给我留言。
附上大赛地址:
快看!安卓巴士第二期博文大赛已经开始啦!