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

hyzhan43發表於2019-08-10

前言

繼上篇說到, 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 原始碼傳送門

感謝閱讀,下次再見

相關文章