使用Kotlin开发Android项目-Kibo(一)

来源:互联网 发布:万能端口检测工具 编辑:程序博客网 时间:2024/06/05 06:10

项目概述

由于项目的内容比较多,所以本篇主要从项目的框架上来讲述。
首先来看下项目的效果吧:

从图片中也可以很清楚的看出,底部和官方的差不多,为主页、消息、发现、个人四
个部分。而发布微博、分组、设置我则是使用悬浮窗实现的(主要是如果设计和官方一样的话上架会被驳回)。现在线上的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时如果配合Dagger2的话你会感觉到自己写起代码来得心应手,因为你只需要知道你需要添加什么功能,基本上你就可以按照已经搭建好的项目结构去继续开发。

由于项目中很多地方用到了注入,所以我先讲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会在app\build\generated\source\apt\armeabi\release\com\kibo\di\present\module目录下生成一个对应的工厂类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来使用了!其他的都是类似的过程。还有一些其他层面的问题我在这不一一解答了,感觉问题还是比较多的,大家可以在网络上找到答案,或者直接提出自己的问题。总的来说去看生成的代码直接分析还是比较容易理解的。

看到这再看这张图应该会有点理解了: