Kotlin的魔能机甲——KtArmor(三)

2,017 阅读5分钟

前言

继上篇说到, KtArmor-MVP的插件使用。我们可以快速创建基本的模板代码,但是在编写业务代码时候,不熟悉KtArmor-MVP框架, 不知其然,无法驾驭这个魔能机甲 。所以这篇我先从BaseActivity 开始说起,介绍KtArmor—MVP 的用法,“深入源码”解析,带你走进 KtArmor-MVP。

Activity

KtArmor-MVP 框架主要包含3个主要的Activity

  • BaseActivity
  • ToolbarActivity
  • MvpActivity

它们之间继承关系如下:

MvpActivity > ToolbarActivity > BaseActivity

然后我们来看看他们具体的实现

BaseActivity

abstract class BaseActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 在界面未初始化之前调用的初始化窗口
        initWidows()

        if (initArgs(intent.extras)) {
            setContentView(getLayoutId())

            initBefore()
            initView()
            initListener()
            initData()
        } else {
            finish()
        }
    }

    open fun initArgs(bundle: Bundle?): Boolean = true

    open fun initWidows() {}

    abstract fun getLayoutId(): Int

    open fun initBefore() {}

    open fun initView() {}

    open fun initListener() {}

    open fun initData() {}
}

BaseActivity 基本的模板结构,定义了基本的Activity 初始化的方法。可以继承BaseActivity,复写对应方法进行扩展。下面是方法具体描述:

  • initWidows: 在界面(setContentView)未初始化之前调用的初始化窗口方法
  • initArgs: 初始化界面参数方法(Activity 之间跳转传递参数), 该方法 默认返回 True, 显示Activity, 否则返回False, 不显示Activity。
  • getLayoutId:初始化 Activity 的 layout 布局
  • initBeforeinitView() 之前, setContentView()方法 之后的初始化方法。
  • initView:初始化控件view 方法.
  • initListener:初始化 控件view 相关 listener 方法。
  • initData:初始化数据方法

可以适用于 APP 启动页面简单展示页面等, 不涉及到Presenter 的 Activity

ToolbarActivity

abstract class ToolbarActivity : BaseActivity() {

    var toolbarTitle: String = ""
        set(value) {
            field = value
            supportActionBar?.title = value
        }

    override fun initView() {
        super.initView()
        initToolbar()
    }

    /**
     *  Toolbar id must be toolbar
     */
    private fun initToolbar() {
        findViewById<Toolbar>(R.id.toolbar)?.let { toolbar ->
            setSupportActionBar(toolbar)
            supportActionBar?.let {
                it.setDisplayHomeAsUpEnabled(true)
                it.setDisplayShowHomeEnabled(true)
            }
        }
    }

    override fun onOptionsItemSelected(item: MenuItem?): Boolean {
        when (item?.itemId) {
            //将滑动菜单显示出来
            android.R.id.home -> {
                finish()
                return true
            }
        }
        return super.onOptionsItemSelected(item)
    }
}

ToolbarActivity 继承 BaseActivity, 方便于显示 Toolbar,在项目中挺常用的,所以就封装这个Toolbar基本用法。

  • Toolbar title 的显示
  • Toolbar 返回键(android.R.id.home)的关闭操作。
  • toolbarTitle: 可以更改 toolbar 对应的 title

在 Activity 的 xml 引入 Toolbar控件, 并且 id 必须为 toolbar,否则不会调用 initToolbar初始化方法 !!!

MvpActivity

abstract class MvpActivity<P : BaseContract.Presenter> : ToolbarActivity(), BaseContract.View {

    lateinit var presenter: P

    override fun initBefore() {
        presenter = bindPresenter()
    }

    abstract fun bindPresenter(): P

    override fun showError(@StringRes msgRes: Int) {
        showError(getString(msgRes))
    }

    override fun showError(msg: String) {
        toast(msg)
        hideLoading()
    }

    override fun showLoading() {}

    override fun hideLoading() {}

    override fun onDestroy() {
        super.onDestroy()

        if (::presenter.isInitialized) {
            presenter.detachView()
        }
    }
}

MvpActivity 同样是继承ToolbarActivity, 实现了基本 BaseContract.View, 管理着 Presenter 生命周期。子类需要实现 bindPresenter()方法,传递对应的 Presenter。 然后就可以调用 Presenter 进行后续的操作。

  • 封装了 Presenter 初始化,销毁
  • 复写 showError(), showLoading(), hideLoading() 等方法。(简单toast 了)

::presenter.isInitialized 意思是判断 Presenter 是否懒加载初始化, 防止未初始化,抛异常。
后续可能会通过泛型T, 反射生成Presenter,减少重复操作

Fragment

BaseFragmentMvpFragment 的实现和 Activity 实现异曲同工,这里就不过多介绍了~

Presenter

abstract class BasePresenter<V : BaseContract.View>(view: V) : BaseContract.Presenter{

    val view: V?
        get() = mViewRef.get()

    // View 接口类型的弱引用
    private var mViewRef = WeakReference(view)

    val presenterScope: CoroutineScope by lazy {
        CoroutineScope(Dispatchers.Main + Job())
    }

    fun launchUI(block: suspend CoroutineScope.() -> Unit, error: ((Throwable) -> Unit)? = null) {
        presenterScope.launch {
            tryCatch({
                block()
            }, {
                error?.invoke(it) ?: view?.showError(it.toString())
            })
        }
    }

    override fun detachView() {
        mViewRef.clear()
        // 取消掉 presenterScope创建的所有协程和其子协程。
        presenterScope.cancel()
    }
}
  • 绑定 View 的初始化, 销毁, 采用 弱引用方式, 防止内存泄露。
  • launchUI 封装 协程,切换到 Main线程方法,并进行tryCatch 捕获异常。
  • presenterScope 的销毁,绑定到 对应 UI界面 的 onDestory方法,防止内存泄露。

Model

abstract class BaseModel {

    suspend fun <R> launchIO(block: suspend CoroutineScope.() -> R) = withContext(Dispatchers.IO) {
        block()
    }
}

BaseModel 相对简单, 封装了协程切换 IO 线程的操作.

后续可能添加相关 DB 相关操作

Retrofit

class MyRetrofitConfig : BaseRetrofitConfig() {

    override val baseUrl: String
        get() = API.BASE_URL
        
    override val readTimeOut: Long
        get() = //TODO

    override val writeTimeOut: Long
        get() = //TODO

    override val connectTimeOut: Long
        get() = //TODO

    override fun initRetrofit(): Retrofit {
    
        // 默认实现 BaseRetrofit.init()
        return Retrofit.Builder()
            .baseUrl(KtArmor.retrofit.baseUrl)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(CoroutineCallAdapterFactory())
            .client(KtArmor.retrofit.initOkHttpClient())
            .build()
            
    }

    override fun initOkHttpClient(): OkHttpClient {
        // 可以传递 Interceptor 进行网络请求拦截
        return BaseOkHttpClient.init(TokenInterceptor.create())
    }
}

Retrofit 相关网络操作, 可以继承BaseRetrofitConfig类, 在这里可以配置自己的参数进行扩展, 相关参数如下:

  • baseUrl: 网络请求的 baseUrl
  • initRetrofit: 为初始化 Retrofit方法,可以返回自定Retrofit
    • 默认实现BaseRetrofit.init()
  • initOkHttpClient 初始化 OkHttp的方法,可以返回自定Okhttp
    • 默认实现BaseOkHttpClient.init()
    • init()方法可以传入对应的 Interceptor, 进行拦截网络操作.
    • readTimeOut, writeTimeOut, connectTimeOut 可以复写网络连接超时属性

SharedPreferences

KtArmor-MVP 通过代理方式,封装了 SharedPreferences基本操作.

  • 通过定义一个变量,并通过 by Preference 代理
  • 传递 Sp的 key 和对应的 defaultValue

例如

var account by Preference(Key.ACCOUNT, "")

定义了一个 account 变量,传递对应Sp 存储的key,和默认值 “” (空串, 说明account 是 String 类型)
然后直接当正常变量使用, 如下直接赋值 就可以修改 Sp 中keyKey.ACCOUNT 的值了。代码如下

account = "123"

tryCatch

        // 传统的 tryCatch
        try{
            // TODO
        }catch (e: Exception){
            // TODO 
        }
        
        //  KtArmor-MVP 扩展
        tryCatch({
            // TODO
        })

        //  KtArmor-MVP 扩展
        tryCatch({
            // TODO
        }, {
            // TODO
        })

扩展函数

  • Toast 扩展, 不重复showToast,多次点击会替换
    • 支持:Context,Activity, Fragment 扩展
    • 扩展参数string (或 @StringRes ), duration
    • 全局 Toast, 通过 Toasts.show(xx)
  • sp, dp 相互转化
    • 支持:Float to Float, Int to Int
  • Log
    • 支持:string.showLog()
    • 示例
      "我是Log".showLog()
      
  • R.color.xxx -> Color IntR.drawable.xxx -> Drawable 扩展
    • 支持:Context,View 下扩展
    • 示例:
      val defaultColor: Int = context.getColorRef(R.color.xxx)
      val defaultDrawable: Drawable? = context.getDrawableRef(R.drawable.xxx)
      
  • startActivity 参照 anko 的 startActivity (fuzhi)
    • 支持:Context, Fragment 下扩展
    • 示例:
      startActivity<XXXActivity>(key to value)
      
  • View相关扩展
    • TextView 扩展
      • 示例
        // 直接获取 TextView 的 text 值
        mTvAccount.str()
        
    • View 显示隐藏扩展
      • 示例
        mIvImage.visible()
        
    • 显示,关闭软键盘扩展
      • 支持:View, Activity
      • 示例
        activity.hideKeyboard()
        
  • ...

最后

以上是KtArmor-MVP 的全部内容,后续框架有更新,也会更新相关文档。

还是那句话,KtArmor-MVP 封装了基本 MVP结构的框架,是一款小而美的框架,麻雀虽小五章俱全。封装了基础的功能,小的项目,或者测试项目可以直接拿来用,省时省力。希望大家喜欢~

最后,若有不妥,望小伙伴们指出。

Kotlin的魔能机甲——KtArmor(一)

Kotlin的魔能机甲——KtArmor插件篇(二)

KtArmor-MVP 源码传送门

感谢阅读,下次再见