Kotlin实现配置化网络请求

Kotlin实现配置化网络请求

这里写图片描述

Kotlin官方提供一个DSL的典型应用场景,Anko致力直接用Kotlin配置页面布局和视图的属性。将布局文件代码化能够带来许多如类型安全、解析效率、代码重用等好处,而Anko让代码布局和XML一样简洁清晰。

受到Anko的启发,让我萌生了把Android中网络请求纷繁复杂配置信息也封装成配置化方式,实现如下方式的网络请求。

Http.get {

    url = "http://api.openweathermap.org/data/2.5/weather"

    headers {
        "Content-Type" - 'application/json'
        "pragma-token" - '33162acxxxxxx5032ad21e0e79ff70d'
    }

    params {
        "q" - "shanghai"
        "appid" - "d7a98cf22463b1c0c3df4adfe5abbc77"
    }

    onSuccess { bytes ->
        // handle data
    }

    onFail { error ->
        // handle error
    }
}

目前该框架已经完成,后面还会继续完善,项目地址Kolley

奔着这个目标,我把之前自己简单封装的Volley库翻出来,用Kotlin重新封装一下。经过分析总体过程大概如下:

  • 基础代码转Kotlin
  • 重定义原子Request
  • Request构造配置化
  • 提供RESTful方法

基础代码转Kotlin

之前的框架是参考android-async-http做的封装,用okhttp作为网络请求引擎,图片请求缓存模块使用的jakewharton提供的disklrucache,这两块都可以复用,先将这部分代码直接转成Kotlin实现。

这不需要花太多的功夫,将java代码复制过来以后,直接使用Android Studio的快速转换功能,转换后可能会有一些语法上的错误,稍微处理一下就可以了,得到类似的内容。

class OkHttpStack @JvmOverloads constructor(client: OkHttpClient = OkHttpClient()) : HurlStack() {
    private val mFactory: OkUrlFactory

    init {
        mFactory = OkUrlFactory(client)
    }

    @Throws(IOException::class)
    override fun createConnection(url: URL): HttpURLConnection {
        return mFactory.open(url)
    }
}

重定义原子Request

需要在Volley提供的Request基础上继承一个BaseRequest预处理一些信息,如params。

class ByteRequest(method: Int, url: String, errorListener: Response.ErrorListener? = Response.ErrorListener {})
: BaseRequest<ByteArray>(method, url, errorListener) {
    override fun parseNetworkResponse(response: NetworkResponse?): Response<ByteArray>? {
        return Response.success(response?.data, HttpHeaderParser.parseCacheHeaders(response))
    }
}

abstract class BaseRequest<D>(method: Int, url: String, errorListener: Response.ErrorListener? = Response.ErrorListener {})
: Request<D>(method, url, errorListener) {
    protected val DEFAULT_CHARSET = "UTF-8"

    internal var _listener: Response.Listener<D>? = null
    protected val _params: MutableMap<String, String> = HashMap() // used for a POST or PUT request.

    /**
     * Returns a Map of parameters to be used for a POST or PUT request.
     * @return
     */
    public override fun getParams(): MutableMap<String, String> {
        return _params
    }

    override fun deliverResponse(response: D?) {
        _listener?.onResponse(response)
    }

    protected fun log(msg: String) {
        if (BuildConfig.DEBUG) {
            Log.d(this.javaClass.simpleName, msg)
        }
    }
}

Request构造配置化

上一步封装的Request必须在构造器中提供一些参数,并且像Listener这样的参数不能直接传递表达式,为配置化调用的封装提供了一定的困难。需要重新封装一个Request构造器,再在最后交给执行队列的时候创建真正的Request传递给它,这样让所有网络请求需要的配置信息都可以很方便的构造。

open class BaseRequestWapper() {
    internal lateinit var _request: ByteRequest
    var url: String = ""
    var method: Int = Request.Method.GET
    private var _start: (() -> Unit) = {}
    private var _success: (ByteArray) -> Unit = {}
    private var _fail: (VolleyError) -> Unit = {}
    private var _finish: (() -> Unit) = {}
    protected val _params: MutableMap<String, String> = HashMap() // used for a POST or PUT request.

    protected val _headers: MutableMap<String, String> = HashMap()
    var tag: Any? = null

    fun onStart(onStart: () -> Unit) {
        _start = onStart
    }

    fun onFail(onError: (VolleyError) -> Unit) {
        _fail = onError
    }

    fun onSuccess(onSuccess: (ByteArray) -> Unit) {
        _success = onSuccess
    }

    fun onFinish(onFinish: () -> Unit) {
        _finish = onFinish
    }

    fun params(makeParam: RequestPairs.() -> Unit) {
        val requestPair = RequestPairs()
        requestPair.makeParam()
        _params.putAll(requestPair.pairs)
    }

    fun headers(makeHeader: RequestPairs.() -> Unit) {
        val requestPair = RequestPairs()
        requestPair.makeHeader()
        _headers.putAll(requestPair.pairs)
    }

    fun excute() {
        var url = url
        if (Request.Method.GET == method) {
            url = getGetUrl(url, _params) { it.toQueryString() }
        }
        _request = ByteRequest(method, url, Response.ErrorListener {
            _fail(it)
            _finish()
        })
        _request._listener = Response.Listener {
            _success(it)
            _finish()
        }
        if (tag != null) {
            _request.tag = tag
        }
        Http.getRequestQueue().add(_request)
        _start()
    }

    private fun getGetUrl(url: String, params: MutableMap<String, String>, toQueryString: (map: Map<String, String>) ->
    String): String {
        return if (params == null || params.isEmpty()) url else "$url?${toQueryString(params)}"
    }

    private fun <K, V> Map<K, V>.toQueryString(): String = this.map { "${it.key}=${it.value}" }.joinToString("&")
}

代码中将网络请求需要的所有信息全部包装了一层,这样在调用的时候就可以很方便的逐个设置每个参数(当然会有一些默认值),最后在excute()方法中全部设置给真正的Request。这个封装保证了下面的调用方式:

url = "http://api.openweathermap.org/data/2.5/weather"
params {
    "q" - "shanghai"
    "appid" - "d7a98cf22463b1c0c3df4adfe5abbc77"
}
onSuccess { bytes ->
    // handle data
}
...

PS:上面params是的书写方式,使用了Kotlin的操作符重载功能,具体实现可以下载源码看下。

提供RESTful方法

实现到上一步,已经准备的差不多了,接下来还需要最后一步,提供RESTful请求方法。

object Http {
    private var mRequestQueue: RequestQueue? = null
    fun init(context: Context) {
        // Set up the network to use OKHttpURLConnection as the HTTP client.
        // getApplicationContext() is key, it keeps you from leaking the
        // Activity or BroadcastReceiver if someone passes one in.
        mRequestQueue = Volley.newRequestQueue(context.applicationContext, OkHttpStack(OkHttpClient()))
    }

    fun getRequestQueue(): RequestQueue {
        return mRequestQueue!!
    }

    val request: (Int, BaseRequestWapper.() -> Unit) -> Request<ByteArray> = { method, request ->
        val baseRequest = BaseRequestWapper()
        baseRequest.method = method
        baseRequest.request()
        baseRequest.excute()
        baseRequest._request
    }


    val post = request.partially1(Request.Method.POST)
    val put = request.partially1(Request.Method.PUT)
    val delete = request.partially1(Request.Method.DELETE)
    val head = request.partially1(Request.Method.HEAD)
    val options = request.partially1(Request.Method.OPTIONS)
    val trace = request.partially1(Request.Method.TRACE)
    val patch = request.partially1(Request.Method.PATCH)
}

上面的request: (Int, BaseRequestWapper.() -> Unit) -> Request<ByteArray>方法为网络请求提供了入口、保证了配置化代码都可以在{}中调用、完成了真正网络请求添加到执行队列。用户可以通过http.requset(method){}方式发起各种请求。

val get = request.partially1(Request.Method.GET)等提供了RESTful方法的封装,实现Http.get{}的方便调用。

后续

关于图片请求模块的实现,其实也是异曲同工,虽然更加复杂一点,但是具体思路是一样的。有兴趣的可以下载源码查看实现,也欢迎提交代码。

图片请求的方式

Image.display {
    url = "http://7xpox6.com1.z0.glb.clouddn.com/android_bg.jpg"
    imageView = mImageView
    options {
        // these values are all default value , you do not need specific them if you do not want to custom
        imageResOnLoading = R.drawable.default_image
        imageResOnLoading = R.drawable.default_image
        imageResOnFail = R.drawable.default_image
        decodeConfig = Bitmap.Config.RGB_565
        scaleType = ImageView.ScaleType.CENTER_CROP
        maxWidth = ImageDisplayOption.DETAULT_IMAGE_WIDTH_MAX
        maxHeight = ImageDisplayOption.DETAULT_IMAGE_HEIGHT_MAX
    }
}
Image.load {
    url = "http://7xpox6.com1.z0.glb.clouddn.com/android_bg.jpg"
    options {
        scaleType = ImageView.ScaleType.CENTER_CROP
        maxWidth = ImageDisplayOption.DETAULT_IMAGE_WIDTH_MAX
        maxHeight = ImageDisplayOption.DETAULT_IMAGE_HEIGHT_MAX
    }
    onSuccess { bitmap ->
        _imageView2?.setImageBitmap(bitmap)
    }
    onFail { error ->
        log(error.toString())
    }
}

原文链接: Kotlin实现配置化网络请求

参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值