简单高效的开发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库是我开源的第一个库, 开源以前已经在实际项目中使用了一段时间, 提升开发效率方面在团队中也获得了证实.

相关文章
相关标签/搜索