簡單高效的開發Android應用--KTea庫的入門和進階

Ktea是kotlin開發的Android庫, 它可讓Android開發更簡單更快速更容易維護. 它很容易入門和使用, 方便構建高質量的應用, 減小崩潰和內存泄露.git

github.com/mervynlove/…

引入

  • 在項目module下的build.gradle中添加
dependencies {
    //... 其餘庫的引用
    implementation 'com.mengwei:ktea:1.1.1'
}

複製代碼
  • 在項目的Application的類中初始化KTea參數
class MyApp : Application() {
    private val activitys: LinkedList<Activity> = LinkedList()

    override fun onCreate() {
        super.onCreate()

        // 初始化KTea的主要配置參數
        Settings.init {
            appCtx = this@MyApp //設置應用Module的ApplicationContext
            baseUrl = "http://..." //網絡請求時的baseUrl
            activityStack = activitys //傳入一個activity任務棧
            isDEBUG = BuildConfig.DEBUG //當是debug模式時,會打印日誌;release時是不打印的
            jsonObjectStyle = ... //這行根據需求定義, 可不寫, 具體使用方法看下面的教程
        }
    }
}
複製代碼

入門

KTea的首要目標是提供一種更簡潔更高效更安全的Android開發方式, 讓咱們從一個小例子開始, 來看看KTea的使用:github

http {
    url = "getRequest/demo"
    onSuccess = {
        //這裏是請求成功請求到的數據
        logger(it)
    }
    onError = {
        //這裏是請求失敗返回的錯誤信息
        logger(it)
    }
}
複製代碼

網絡請求是Android開發中常見的任務, 在Ktea中能夠經過http函數來完成這個任務, 上面的代碼就是一個網絡請求的例子.json

  • url參數類型是String,它的值能夠是完整的網址路徑, 也能夠是配置了baseUrl後相對的路徑.
  • onSuccess參數類型是一個lambda表達式, 在這裏你能夠接收到請求成功後的數據, it是返回的實際數據, 同時在這裏已經切換到了UI線程, 你不須要處理線程的切換.
  • onError參數類型也是一個lambda表達式, 在這裏返回請求失敗的信息, 固然這裏也切換到了UI線程.

你喜歡這樣的代碼麼? Ktea處理網絡請求這樣的常見任務不但能夠節省大量的樣板代碼, 並且已經作好了線程的切換, 你不須要再去關心.安全

如何把請求數據解析爲Entity對象

若是返回的是json格式的數據, 你可使用AndroidStudio的GsonFormat插件先自動生成一個Entity類, 而後在onSuccess中把String數據直接轉換成對應的Entity對象:bash

// 解析形如這樣的jsonobject格式: {key1:value1, key2:value2, ...}

// 對應json的entity
class xxxEntity {
    private String key1;
    private String key2;
    ...get/set 方法
}

onSuccess = {
        val entity = it.toEntity<xxEntity>()
    }
複製代碼

一樣, 若是是JsonArray的數據, 你能夠直接轉換成Entity的List服務器

// 解析形如這樣的jsonarray數據 : [{...},{...},...]
onSuccess = {
        val entityList = it.toEntityList<xxEntity>()
    }
複製代碼

toEntity和toEntityList是ktea的內置函數, 能夠對String對象直接使用, 不過必須保證該String對象必須是xxEntity對應jsonobject或者jsonarray格式.網絡

下面讓咱們來看一個開發中更常見的例子, 來展現Ktea中網絡請求組件的使用:架構

  • 請求方式是Post
  • 須要給服務器提交參數
  • 參數中的密碼要用MD5加密
  • 想要返回的數據直接就是entity對象

想讓數據接收到的直接是entity對象, 咱們可使用另一個函數httpEntityapp

fun login(mobile: String, password: String) {
    // 網絡請求代碼
    httpEntity<LoginEntity> {
        url = "user/login"
        method = Method.POST
        params = mutableMapOf("mobile" to mobile, "password" to password.MD5())
        onSuccess = {
            it //這裏的it就是LoginEntity對象
        }
        onError = { logger(it) }
    }

}
複製代碼

咱們看到這裏有兩個新的參數名稱: methodparams ,分別表明請求的方式和請求參數.ide

  • method參數默認是get方式, 因此get請求時可寫可不寫, 可是當post請求時就必需要寫了.
  • params是一個Map對象, 保存網絡請求的參數, 可使用mutableMapOf函數構建一個Map對象而且初始化賦值, 這樣只須要一行代碼就能夠同時完成建立對象和賦值的操做.

httpEntity函數和http函數使用時的區別是須要一個泛型類型, 用於解析json數據對應的Entity類型, 這樣在onSuccess下就能夠直接接收到entity類型的對象.

string.MD5是ktea的內置函數, 能夠把string類型進行MD5加密, 返回加密後的字符串, 類似的內置函數還有string.AESEncrypt, string.Base64等可使用.

在開發中, 你能夠靈活運用ktea中的多個網絡請求函數來知足須要, 除了上面介紹的httphttpEntity, 還有和httpEntity相似的httpEntityList ,它能夠把JsonArray字符串數據直接解析成Entity的List, 使用方法和httpEntity同樣.

httpEntityList<xxEntity> {
    ...
    onSuccess = {
            it //這裏的it就是一個xxEntity的List集合
        }
}
複製代碼

如何處理嵌套的json數據

爲了知足網絡請求中更復雜的任務, 下面來介紹另一個函數httpBase, 它用來把嵌套Json數據解析成Entity實體類.

{
  "status": 1,
  "msg": "success",
  "datas": {...}
}
複製代碼

形如上面的json格式, 也是開發中常見的服務器返回json串, 它的外層是狀態碼和提示信息, 實際須要的數據在"datas"的內層, 下面咱們來解決這個問題.

  • 給ktea配置時設置一個jsonObjectStyle對象來聲明json的格式
Settings.init {
    ...
    jsonObjectStyle= JsonStyle().apply {
        statusName = "status"
        dataName = "datas"
        messageName = "msg"
        successStatusValue = "1"
    }
}
複製代碼

根據實際服務器請求數據的格式, 把外層的json鍵名稱寫上以後, 就可使用httpBase函數了.

httpBase<xxxEntity> {
    ...
    onSuccess = {
       it // 這裏就是json內層的datas數據解析出來entity對象
    }
}
複製代碼

httpBase函數和httpEntity函數使用上沒有區別, 只不過須要設置一個JsonStyle對象來指定外層的鍵名, 這樣就能夠把內層的json數據而不是整個json數據解析成entity對象了.

jsonObjectStyle只須要在ktea初始化時設置一次便可, 以後使用httpBase函數時都不須要設置.

一樣, 和httpBase函數對應的還有一個httpBaseList函數, 用來把內層的jsonarray直接解析成entity的List.

httpBaseList<Entity> { ...  }
複製代碼

如何處理請求頭約定

移動端網絡請求的複雜性在於服務器端請求約定沒有固定的標準, 咱們無法控制. 每一個服務器端的代碼都是不一樣的開發人員編寫, 因此每一個公司幾乎都不相同. 在ktea中還涵蓋了一些其餘常見問題的解決方法.

  • 諸如adviceID/時間戳等的參數在請求頭中提交. 除token外的其餘須要在請求頭中提交的參數均可以在HttpHead.params賦值. HttpHead.params是一個Map對象, 能夠經過key/value的形式賦值提交參數, 這樣請求的時候就會把請求參數添加到請求頭中.
HttpHead.params["ADVICEID"] = "..."
HttpHead.params["TIMESTAMPS"] = "..."
複製代碼
  • 有的請求須要在請求頭中添加token信息, token和上面一類參數的不一樣在於token須要保存在本地以方便app關閉再打開時還能獲取到token. 若是須要在請求頭中添加token數據, 能夠在獲取token後這樣寫:
// 在這裏給token賦值
Token.token= "TOKEN" to "..."
複製代碼

上面的代碼會在token賦值後把token保存在本地, 當app關閉再打開時一樣不會丟失, 可是重啓app後須要判斷一下token是否存在, 每次重啓只須要一次判斷便可, 不用每次請求時都調用這個方法. 因此最佳的作法是在啓動頁或者我的中心頁面(具體看需求)調用一次:

class SplashActivity : BaseActivity() {
    ...

    if (Token.token == null) {
            gotoLoginActivity()
        } else {
            gotoMainActivity()
    }
}
複製代碼

修改請求頭參數的HttpHead.params和Token.token都是單例的全局變量, 只須要賦值一次便可, 你能夠根據需求在合適的時機進行賦值和修改, 而不用每次網絡請求都進行賦值. 可是當這些數據變化時, 好比時間戳, 這樣就須要在使用網絡請求函數前進行賦值.

進階

還有更多的網絡請求功能並不能一一講解,須要在使用中去慢慢發現和體會. 下面咱們來介紹一些更重要的內容.

在實際開發中, 若是把網絡請求的代碼直接放在activity中不但違背了類的單一性原則而讓維護代碼很是困難, 更重要的是還會產生一個很嚴重的問題: 內存泄露. 爲了不內存泄露, 就須要額外的代碼來處理, 並且由於不少人對內存泄漏的不瞭解, 當應用表現偶爾崩潰時每每沒法定位緣由.

如何構建穩定, 易維護的高質量應用

構建易維護應用的關鍵在於減小代碼之間的耦合, 遵照類的職能單一性原則. 爲此, 移動端的架構設計引入了流行的MVP和MVVM的模式.

MVP的缺點很明顯, 它須要額外建立契約接口類和其餘分層的類並聲明功能重複的方法在各層來回調用, 當頁面原本只須要添加一個很小的功能時卻要同時修改不少的類, 須要大量的煩人的代碼, 大大加劇開發負擔.

我不喜歡MVVM模式的緣由在於databinding須要在xml文件中寫功能代碼, 我仍是但願xml能維持單一的佈局功能, 這樣出現問題時就能夠在儘可能小的範圍進行排查.

那麼, Ktea如何實現讓開發簡單方便, 又易於維護呢?
  • BaseViewModel是ktea中viewmodel層的基類, viewmodel層的職責是更新數據並在數據變化時進行通知, 它並不持有activity對象, 它持有的是當前activity生命週期變化的對象.
  • LiveData在數據變化時會通知訂閱它的對象, 它是鏈接viewmodel和activity的橋樑.

下面咱們來看如何使用它們:

class NewsListModel:BaseViewModel() {
    val errorLiveData by lazy { MutableLiveData<String>() }
    val newsLiveData by lazy { MutableLiveData<List<NewsEntity>>() }

    fun getNews(pageNum: Int) {

        jobs + httpBaseList<NewsEntity> {
            url = "article/listNews"
            params = mutableMapOf("pageNum" to pageNum, "pageSize" to 20)
            onSuccess = { newsLiveData.value = it }
            onError = { errorLiveData.value = it }
        }

    }
}
複製代碼

ViewModel層老是繼承BaseViewModel並由一系列的請求數據的函數和LiveData組成. 經常使用的LiveData是MutableLiveData, 泛型指定數據源對象的類型, 經過給LiveData.value賦值來通知數據的變化. 在ViewModel中咱們只須要獲取數據並通知給LiveData便可, 不須要和任何的類和模塊交互.

咱們在ViewModel中並無持有activity的引用, 那麼咱們如何去更新UI呢? 因此咱們必須在activity訂閱LiveData, 這樣當數據變化時, 咱們就會獲得通知.

class NewsListActivity : BaseActivity() {

    private val model by lazy { getViewModel<NewsListModel>() }
    private val adapter by lazy { NewsAdapter() }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_news_list)
    }

    override fun initData() {
        recyclerView.layoutManager = LinearLayoutManager(this)
        recyclerView.adapter = adapter
        // 訂閱livedate的數據變化通知
        model.errorLiveData.observe(this) {
            dismissLoading()
            errorToast(it)
        }
        model.newsLiveData.observe(this) {
            adapter.update(it)
            dismissLoading()
        }
        //獲取數據
        model.getNews(index)
        showLoading()
    }
}
複製代碼

Ktea中定義了BaseViewModel和BaseActivity類, 它們加入了不少簡化開發和提升性能的代碼, 在開發中繼承它們會幫助你省了不少麻煩.

要在activity中獲得viewmodel對象必須經過函數getViewModel獲取而不能直接建立, 由於viewmodel須要獲取當前activity的生命週期對象.

上面的代碼還演示了經過livedata的observe方法訂閱livedata的數據變化來更新UI的方法.

最後

上面只是介紹了KTea庫裏不多的一部分功能, 之後會陸續提供更多的Ktea教程來針對性的深刻介紹每一個模塊的使用技巧.

Ktea庫是我開源的第一個庫, 開源以前已經在實際項目中使用了一段時間, 提升開發效率方面在團隊中也獲得了證實.

相關文章
相關標籤/搜索