Kotlin入门(32)网络接口访问
手机上的资源毕竟有限,为了获取更丰富的信息,就得到辽阔的互联网大海上冲浪。对于app自身,也要经常与服务器交互,以便获取最新的数据显示到界面上。这个客户端与服务端之间的信息交互,基本使用http协议进行通信,即app访问服务器的http接口来传输数据。http接口调用在java代码中可不是一个轻松的活,开发者若用最基础的httpurlconnection来编码的话,至少要考虑以下场景的处理:
1、http的请求方式是什么,是get还是post还是put还是delete?
2、http的连接超时时间是多少,请求应答的超时时间又是多少?
3、http头部的语言和浏览器信息该设置为什么?
4、http传输的数据内容采取的是哪种编码方式?
5、http的应答数据如果是压缩过的,又要如何解压?
6、http的输入输出流需要注意哪些方面?
7、http如何分块传输较大的数据信息?
瞧瞧上面层出不穷的功能要求,如果开发者事必躬亲逐个编码,那可真是要累得够呛。因此,各种意图取代httpurlconnection的网络交互框架如雨后春笋般涌现出来,既有老资格的如httpclient,又有后起之秀如android-async-http、volley、okhttp、retrofit等等,可谓是百花齐放、百家争鸣。当然,这些网络框架是需要学习成本的,使用起来也不如想象中的那么容易;它们只是在技术上各有千秋,并非终极的解决方案,往往是你方唱罢我登台,各领风骚几年然后歇菜。
其实http交互原本无需这样大动干戈,常见的接口调用仅仅是app往服务器发送一串请求信息,然后服务器返回给app一串处理结果,这种简单的业务场景已经足够应付大多数app的网络通信需求。所以大道至简,kotlin把网络交互看作是跟文件读写一样的i/o操作,后端地址就像是个文件路径,那么请求服务器的数据犹如读取文件内容。文本分为文本文件和二进制文件两种,则http接口对应获取文本数据和获取二进制数据两种,于是整个网络请求便简化为数据的存跟取了。
具体到详细的kotlin编码,文件对象由“file(文件路径)”构建,而http对象由“url(网络地址)”构建,获取接口数据则有readtext和readbytes两个方法,前者用于获取文本形式的应答数据,后者用于二进制形式的应答数据如图片文件、音频文件等等。仅仅一个readtext方法真的能完成繁杂的http接口调用操作吗?下面我们通过一个具体的接口访问案例,探讨一下如何使用kotlin代码实现http接口调用。
智能手机普遍提供了定位功能,可是系统自带的定位服务只能获得用户所在的经纬度信息,而这枯燥的经纬度数字令人不知所云,肯定要把经纬度转换为详细的地址信息才方便用户理解。将经纬度转换为详细地址,就要访问谷歌地图提供的地址查询接口了,该接口的地址形如“http://maps.google.cn/maps/api/geocode/json?请求参数信息”,app把经纬度数据作文请求参数传入,对方会返回一个包含地址信息的json串,通过解析json串即可获得当前的详细地址。由于访问网络需要在分线程进行,因此接口访问代码必须放在doasync代码块中,下面给出根据经纬度获取详细地址的kotlin代码片段:
private val mapsurl = "http://maps.google.cn/maps/api/geocode/json?latlng={0},{1}&sensor=true&language=zh-cn"
//位置监听器侦听到定位变化事件,就调用该函数请求详细地址
private fun setlocationtext(location: location?) {
if (location != null) {
doasync {
//根据经纬度数据从谷歌地图获取详细地址信息
val url = messageformat.format(mapsurl, location.latitude, location.longitude)
val text = url(url).readtext()
val obj = jsonobject(text)
val resultarray = obj.getjsonarray("results")
var address = ""
//解析json字符串,其中formatted_address字段为具体地址名称
if (resultarray.length() > 0) {
val resultobj = resultarray.getjsonobject(0)
address = resultobj.getstring("formatted_address")
}
//获得该地点的详细地址之后,回到主线程把地址显示在界面上
uithread { findaddress(location, address) }
}
} else {
tv_location.text = "$mlocation\n暂未获取到定位对象"
}
}
//在主线程中把定位信息连同地址信息都打印到界面上
private fun findaddress(location: location, address: string) {
tv_location.text = "$mlocation\n定位对象信息如下: " +
"\n\t时间:${dateutil.nowdatetime}" +
"\n\t经度:${location.longitude},纬度:${location.latitude}" +
"\n\t高度:${location.altitude}米,精度:${location.accuracy}米" +
"\n\t地址:$address"
}
上述代码看起来显然简明扼要,寥寥数行便搞定了完整的功能实现。如果使用java代码实现该功能,首先http调用就得提供底层的接口访问代码,其次分线程请求网络又得专门写个继承自asynctask的任务处理代码,末了activity这边厢还得实现该任务的完成事件,真是兴师动众、劳民伤财。由此可见kotlin的网络交互是革命性的,方式虽然简单,却足以应付大部分的网络通信需求,并且运行效果与java代码几无差别,例如调用地图接口查询地址信息,无论采用java编码还是kotlin编码,界面效果都如下图所示。
上面利用readtext方法就完成了文本数据的接口调用,当时提到了readbytes可用于获取二进制数据如图片文件,那么获取网络图片是否也同样方便呢?下面我们继续探讨如何使用kotlin代码读取网络图片。
获取网络图片的基本流程同文本格式的接口访问,一样先通过url类构建http对象,然后在doasync代码块中调用http对象的readbytes方法获得图片的字节数组。将字节数组转换为位图对象,这在前面的文章《kotlin入门(27)文件读写操作》已经加以介绍,即利用bitmapfactory工具的decodebytearray方法实现转换操作。转换好的位图当然可以在主线程直接显示出来,也可以先保存为图片文件,等到需要的时候再去读取。前面描述如何把位图保存为图片文件时,由于bitmap相关类并未提供简单的图片保存方法,因此当时保存位图文件还着实颇费了一番功夫。现在保存网络图片反而无需如此折腾,这是因为获取网络图片得到了字节数组,字节数组保存为文件可是相当方便的噢,只要调用file对象的writebytes方法,短短一行就保存好图片了。介绍完了网络图片的存取流程,最终的kotlin编码一如既往地简单明了,下面展示了一个验证码动态显示的页面代码:
class httpimageactivity : appcompatactivity() {
private val imageurl = "http://222.77.181.14/validatecode.aspx?r="
override fun oncreate(savedinstancestate: bundle?) {
super.oncreate(savedinstancestate)
setcontentview(r.layout.activity_http_image)
iv_image_code.setonclicklistener { getimagecode() }
getimagecode()
}
//获取网络上的图片验证码
private fun getimagecode() {
iv_image_code.isenabled = false
doasync {
val url = "$imageurl${dateutil.getformattime()}"
val bytes = url(url).readbytes()
//把字节数组解码为位图数据
val bitmap = bitmapfactory.decodebytearray(bytes, 0, bytes.size)
//也可通过下面三行代码把字节数组写入文件,即生成一个图片文件
val path = getexternalfilesdir(environment.directory_downloads).tostring() + "/"
val file_path = "$path${dateutil.getformattime()}.png"
file(file_path).writebytes(bytes)
//获得验证码图片数据,回到主线程把验证码显示在界面上
uithread { finishget(bitmap) }
}
}
//在主线程中显示获得到的验证码图片
private fun finishget(bitmap: bitmap) {
iv_image_code.setimagebitmap(bitmap)
iv_image_code.isenabled = true
}
}
看到了吧,即使是完整的activity代码,kotlin也只需数十行而已。倘若使用java完成同样的功能,除了http底层与asynctask的编码之外,还得补充bitmap对象的图片保存代码。也就是说,java代码需要额外添加三个工具类的实现代码,光光这一点,kotlin的效率就令人赞叹。而且,短小精悍的kotlin代码并未造成任何功能缺失,以上面的图片验证码页面为例,使用java编码和使用kotlin编码,最终的显示效果都如下图所示。