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
//他的流程是:
//先设置一个Url地址,然后通过url打开一个连接并强转为HttpURLConnection并保存为一个connection对象
//为这个对象设置请求方法,超时时间如.然后获取他的输入流,然后读取即可。
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()
//使用use他会读取完成后自动关闭流
reader.use {
string.append(it.readText())
}
//因为这是在一个子线程上,是不允许修改ui的,所以要在主线程上执行。
runOnUiThread {
binding.responseText.text = string
}
connection.disconnect()
}

同时他也需要声明网络权限:
<uses-permission android:name="android.permission.INTERNET" />
如果要像服务器发送post请求,那么就打开output流,然后输入数据,记得每个数据必须是键值对的形式,不同的数据需要用&隔开。

OKhttp

  1. 先声明一个客户端val client = OKHttpClient()

  2. 然后声明一条请求request

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //Get请求
    val request = Request().Builder()
    .url()
    .//可以连缀很多方法修饰其内容
    .build()
    //Post请求稍微麻烦一点,需要构建一个对象
    val requestBody = FormBody.Build()...build()
    request=Request().Buildder().
    ...
    .post(requestBody).build()

    而且request是分离的,低耦合。

  3. 获取响应val response = client.newCall(request).execute()

  4. 获取返回对象`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)
}
}
// 回调onFinish()方法
listener.onFinish(response.toString())
} catch (e: Exception) {
e.printStackTrace()
// 回调onError()方法
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是底层通信的实现。
他有哪些优点:

  1. 可以配置一个根路径,然后具体的请求接口只需要使用相对路径即可
  2. 他可以对服务器接口进行归类,把同一类的服务器接口定义到同一个接口文件中。
  3. 相比于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
//GET http://example.com/get_data.json?u=<user>&t=<token>
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放啊发。他就可以通过泛型直接创建。