WebView
可以在应用中嵌入一个浏览器,从而展示各种网页
使用HTTP请求访问网络
HttpURLConnection
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
thread { val string = StringBuilder() val url = URL("https://zfxt.top/sentence") val connection = url.openConnection() as HttpURLConnection connection.requestMethod = "GET" connection.connectTimeout=8000 connection.readTimeout=8000 val input = connection.inputStream val reader = input.bufferedReader()
reader.use { string.append(it.readText()) }
runOnUiThread { binding.responseText.text = string } connection.disconnect() }
|
同时他也需要声明网络权限:
<uses-permission android:name="android.permission.INTERNET" />
如果要像服务器发送post请求,那么就打开output流,然后输入数据,记得每个数据必须是键值对的形式,不同的数据需要用&隔开。
OKhttp
先声明一个客户端val client = OKHttpClient()
然后声明一条请求request
1 2 3 4 5 6 7 8 9 10
| val request = Request().Builder() .url() . .build()
val requestBody = FormBody.Build()...build() request=Request().Buildder(). ... .post(requestBody).build()
|
而且request是分离的,低耦合。
获取响应val response = client.newCall(request).execute()
获取返回对象`val data = response.body?.string()
解析XML和JSON格式数据
xml
pull解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| private fun parseXMLWithPull(xmlData: String) { try { val factory = XmlPullParserFactory.newInstance() val xmlPullParse = factory.newPullParser() xmlPullParse.setInput(StringReader(xmlData)) var eventType = xmlPullParse.eventType var id = "" var name = "" var version = "" while (eventType != XmlPullParser.END_DOCUMENT) { val nodeName = xmlPullParse.name when (eventType) { XmlPullParser.START_TAG -> { when (nodeName) { "id" -> id = xmlPullParse.nextText() "name" -> name = xmlPullParse.nextText() "version" -> version = xmlPullParse.nextText() } } XmlPullParser.END_TAG -> { if ("app" == nodeName) { Log.d("MainActivity", "id is $id") Log.d("MainActivity", "name is $name") Log.d("MainActivity", "version is $version") } } } eventType = xmlPullParse.next() } } catch (e: Exception) { e.printStackTrace() } }
|
sax解析
不细讲,用的时候再学
json解析
不细讲,自己理解
还有GSON的使用。这些都是不难的东西,不需要特别理解。只要看一眼就会明白
网络请求回调的实现方法
如果每次每次发送请求都写一遍发送HTTP请求的代码,就显得代码非常的难看。
所以通常将这些通用的网络操作提取到一个公共的类里,并提供一个公用方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| object HttpUtil { fun sendHttpRequest(address: String): String { var connection: HttpURLConnection? = null try { val response = StringBuilder() val url = URL(address) connection = url.openConnection() as HttpURLConnection connection.connectTimeout = 8000 connection.readTimeout = 8000 val input = connection.inputStream val reader = BufferedReader(InputStreamReader(input)) reader.use { reader.forEachLine { response.append(it) } } return response.toString() } catch (e: Exception) { e.printStackTrace() return e.message.toString() } finally { connection?.disconnect() } } }
|
如上述代码所述,将一个网络请求封装起来。然后每次调用这个方法即可获得返回的数据
但是,一个子线程是无法通过return返回数据回到主线程的。主线程并不会阻塞的等待他返回。所以往往会采用回调的方式去解决这个问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| interface HttpCallbackListener { fun onFinish(response: String) fun onError(e: Exception) } object HttpUtil { fun sendHttpRequest(address: String, listener: HttpCallbackListener) { thread { var connection: HttpURLConnection? = null try { val response = StringBuilder() val url = URL(address) connection = url.openConnection() as HttpURLConnection connection.connectTimeout = 8000 connection.readTimeout = 8000 val input = connection.inputStream val reader = BufferedReader(InputStreamReader(input)) reader.use { reader.forEachLine { response.append(it) } } listener.onFinish(response.toString()) } catch (e: Exception) { e.printStackTrace() listener.onError(e) } finally { connection?.disconnect() } } } }
|
重新写的方法他就可以使用回调。他相比于原来的方法,他多了一个参数,也就是传入一个HttpCallbackListener
的实例类,然后通过这个实例中重写的方法去实现回调。
当你在主线程中要使用到这个网络请求并回调时可以像下面这样写:
1 2 3 4 5 6 7 8
| HttpUtil.sendHttpRequest(address, object : HttpCallbackListener { override fun onFinish(response: String) { } override fun onError(e: Exception) { } })
|
你调用了网络请求,然后重写了这个方法,在子线程中,onFinish方法会阻塞直到返回数据,然后你在这边定义的方法中获取返回的数据。
OKhttp
如果使用OKhttp的话,他的回调会更加简单,因为他内置了自行的方法。
1 2 3 4 5 6 7
| fun sendOkHttpRequest(address: String, callback: okhttp3.Callback) { val client = OkHttpClient() val request = Request.Builder() .url(address) .build() client.newCall(request).enqueue(callback) }
|
他这里没有使用execute,而是使用enqueue,他会帮我们在子线程中执行这个网络请求,并且请求完成后,返回到这个callback中。
如果要使用的话,也是一样要重写方法
1 2 3 4 5 6 7 8 9
| HttpUtil.sendOkHttpRequest(address, object : Callback { override fun onResponse(call: Call, response: Response) { val responseData = response.body?.string() } override fun onFailure(call: Call, e: IOException) { } })
|
这样子也很简单
Retrofit网络框架
他同样也是Square开发的网络库,与OKhttp相比,他的定位是应用层的网络通信库,而OKhttp是底层通信的实现。
他有哪些优点:
- 可以配置一个根路径,然后具体的请求接口只需要使用相对路径即可
- 他可以对服务器接口进行归类,把同一类的服务器接口定义到同一个接口文件中。
- 相比于GSON中对于list数据,需要声明type,这里的强大泛型类,可以直接使用
Retrofit导入如下的包1 2
| implementation 'com.squareup.retrofit2:retrofit:2.6.1' implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
|
他是基于OKhttp开发的,所以他会把retrofit,okhttp,okio都下载下来,而第二个包则会辅助把gson一起下载下来,他会自动解析gson。
给一个样例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class App(val id: String, val name: String, val version: String)
interface AppService{ @GET("get_data.json") fun getAppData():Call<List<App>> }
fun main(){ val retrofit = Retrofit.Builder() .baseUrl("https://zfxt.top/") .addConverterFactory(GsonConverterFactory.create()) .build() val appService = retrofit.create(AppService::class.java) appService.getAppData().enqueue(object : Callback<List<App>>{ override fun onResponse(call: Call<List<App>>, response: Response<List<App>>) { val list = response.body() if(list!=null){ for(app in list){ println(app.id) println(app.name) println(app.version) } } }
override fun onFailure(call: Call<List<App>>, t: Throwable) { t.printStackTrace() }
}) }
|
他会自动切换线程去完成操作,不用你去考虑切换线程和回调的关系。
这里他访问的接口是https://zfxt.top/get_data.json
。但是如果我们访问的路径中有动态变化的参数的话,我们需要传递参数进去,如:
路径占位符,也可能是符合restfulAPI的写法
1 2 3 4
| interface ExampleService { @GET("{page}/get_data.json") fun getData(@Path("page") page: Int): Call<Data> }
|
如果要传递参数
1 2 3 4 5
| interface ExampleService { @GET("get_data.json") fun getData(@Query("u") user: String, @Query("t") token: String): Call<Data> }
|
有时候我们对于返回的数据并不是特别关心其具体内容,可能只是一个普通的字符串,所以用不着去解析他,可以用Call来返回数据。他可以接收任意类型的参数。
如果我们要发送post请求并带上body。那么带上@Body注解很有用:
1 2 3 4
| interface ExampleService { @POST("data/create") fun createData(@Body data: Data): Call<ResponseBody> }
|
body注解中的内容会被转化为json格式的文本发送出去
我们还可以在方法上添加@Headers:如@Headers("User-Agent: okhttp", "Cache-Control: max-age=0")
他里面都是键值对,但是如果我们要使用动态的header的话:
1 2 3 4 5
| interface ExampleService { @GET("get_data.json") fun getData(@Header("User-Agent") userAgent: String, @Header("Cache-Control") cacheControl: String): Call<Data> }
|
我们还需要传递一个参数进去,为字符串格式
retrofit的最佳写法
把retrofit封装为一个单例类
在其中定义一个方法,接收各种泛型,然后创建:
1 2 3 4 5 6 7 8 9
| object ServiceCreator { private const val BASE_URL = "http://10.0.2.2/" private val retrofit = Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .build()
fun <T> create(serviceClass: Class<T>): T = retrofit.create(serviceClass) }
|
因为泛型在编译后会被擦除,所以我们这里只有传递参数进去才可以实现创建。但如果使用实化,也就是使用inline fun <reified T> create(): T = create(T::class.java)
代替上述代码中的create放啊发。他就可以通过泛型直接创建。