网络技术(Kotlin)
WebView
使用WebView控件
调用getSettings方法设置浏览器属性
传入WebViewClient实例
传入网址
声明网络权限
1
2
3
4<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class MainActivity : AppCompatActivity() {
private lateinit var binding:ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding=ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
binding.webView.settings.javaScriptEnabled=true
//当需要从一个网页跳转到另一个网页时,目标网页仍在WebView中展示,而不是打开系统浏览器
binding.webView.webViewClient= WebViewClient()
binding.webView.loadUrl("https://www.baidu.com")
}
}1
<uses-permission android:name="android.permission.INTERNET"/>
使用HTTP访问网络
HttpURLConnection
- 获取HttpURLConnection实例
- 设置请求方法
- 自由定制一些功能
- 得到从服务器返回的输入流,并对输入流进行读取
- 将HTTP连接关闭
- 记得声明权限
1 | class MainActivity : AppCompatActivity() { |
使用OkHttp
发送GET请求
安装依赖库
创建一个OkHttpClient()实例
创建Request对象,通过连缀丰富对象
通过newCall方法获取Call对象,并调用execute方法发送请求并获取服务器返回的数据
得到返回数据的具体内容
1
implementation("com.squareup.okhttp3:okhttp:4.11.0")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18private fun sendRequestWithOkHttp() {
//开启线程发送网络请求
thread {
try {
val client=OkHttpClient()
val request=Request.Builder()
.url("https://www.baidu.com")
.build()
val response=client.newCall(request).execute()
val responseData=response.body?.string()
if (responseData!=null){
showResponse(responseData)
}
}catch (e:Exception){
e.printStackTrace()
}
}
}
发送POST请求
安装依赖库
创建一个OkHttpClient()实例
创建一个RequestBody对象
创建Request对象,并调用post方法,通过连缀丰富对象
通过newCall方法获取Call对象,并调用execute方法发送请求并获取服务器返回的数据
得到返回数据的具体内容
1
implementation("com.squareup.okhttp3:okhttp:4.11.0")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23private fun sendRequestWithOkHttp() {
//开启线程发送网络请求
thread {
try {
val client=OkHttpClient()
val requestBody=FormBody.Builder()
.add("username","admin")
.add("password","123456")
.build()
val request=Request.Builder()
.url("https://www.baidu.com")
.post(requestBody)
.build()
val response=client.newCall(request).execute()
val responseData=response.body?.string()
if (responseData!=null){
showResponse(responseData)
}
}catch (e:Exception){
e.printStackTrace()
}
}
}
解析XML数据
Pull解析方式
创建XmlPullParserFactory实例,并得到得到XmlPullParser对象
将服务器返回的XML数据设置进去
开始解析,用geteventType()得到当前解析的事件
在while循环中不断进行解析,调用next获得下一个解析事件
在循环内通过getName获得名字,用nextText获取具体内容
为了让程序可以使用HTTP,还要在xml文件中添加配置
在AndroidManifest中启动配置文件
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
36
37
38
39
40
41
42
43
44
45private fun parseXMLWithPull(xmlData:String){
try {
//创建XmlPullParserFactory实例
val factory=XmlPullParserFactory.newInstance()
//借助XmlPullParserFactory实例得到XmlPullParser对象
val xmlPullParser=factory.newPullParser()
//将服务器返回的XML数据设置进去
xmlPullParser.setInput(StringReade r(xmlData))
//开始解析
//用geteventType()得到当前解析的事件
var eventType=xmlPullParser.eventType
var id=""
var name=""
var version=""
//当eventType!=XmlPullParser.END_DOCUMENT说明解析工作还未完成
while(eventType!=XmlPullParser.END_DOCUMENT){
//通过getName得到当前节点的名字
val nodeName=xmlPullParser.name
when (eventType) {
//开始解析某节点
XmlPullParser.START_TAG->{
when(nodeName){
//通过nextText()获取节点具体内容
"id"->id=xmlPullParser.nextText()
"name"->name=xmlPullParser.nextText()
"version"->version=xmlPullParser.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")
}
}
}
//调用next()方法获取下一个解析事件
eventType=xmlPullParser.next()
}
}catch (e:Exception){
e.printStackTrace()
}
}1
2
3
4
5
6
7
8<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system"/>
</trust-anchors>
</base-config>
</network-security-config>1
android:networkSecurityConfig="@xml/neiwork_config"
SAX解析方式
- 新建一个类继承DefaultHandler,并重写五种方法
- 创建SAXParserFactory实例
- 获取XMLReader对象
- 将编写的实例类设置到XMLReader中
- 调用parse方法开始执行
1 | class MyHandle:DefaultHandler() { |
1 | private fun parseXMLWithSAX(xmlData: String) = try { |
解析JSON格式数据
使用JSONObject
- 定义一个数组,将返回的数据传入到JSONArray对象中
- 循环遍历数组,取出各个元素
1 | private fun parseJSONWithJSONObject(jsonData: String) { |
使用GSON
优点:可以将一段JSON格式的字符串自动映射成一个对象,不需要再手动编写代码解析
- 添加依赖库
- 获取Gson对象
- 如果是一个JSON数据,可以调用fromJson方法,如果是一段Json数组需要借助TypeToken将期望解析成的数据类型传入fromJson方法
1 | implementation ("com.google.code.gson:gson:2.10.1") |
1 | private fun parseJSONWithGSON(jsonData: String) { |
网络请求回调的实现方式
采用回调的原因:所有耗时逻辑在子线程中进行,sendHttpRequest()方法在服务器还没来得及响应的时候就执行结束
HttpURLConnection方法
将请求方法放入到单例类中
定义一个接口,并定义方法
给方法添加一个接口参数,并开启一个子线程
在线程中回调接口方法
调用sendRequestWithHttpURLConnection()方法,调用时还需将接口实例传入
1
2
3
4interface HttpCallbackListener {
fun onFinish(response:String)
fun onError(e: Exception)
}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
32object HttpUtil {
fun sendRequestWithHttpURLConnection(adress: String, listener: HttpCallbackListener) {
//开启线程发送网络请求
thread {
var connection: HttpURLConnection? = null
try {
val response = StringBuilder()
val url = URL(adress)
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()
}
}
}
}1
2
3
4
5
6
7
8HttpUtil.sendRequestWithHttpURLConnection(adress,object:HttpCallbackListener{
override fun onFinish(response:String){
//得到服务器返回的具体内容
}
override fun onError(e:Exception){
//在这里对异常情况进行处理
}
})
OkHttp方法
- 将请求方法放入到单例类中
- 传入okhttp3.Callback参数,这是oKHttp库自带的回调接口
- 在newCall后调用enqueue方法,并传入接口参数
- 调用sendRequestWithOkHttp()方法
1 | fun sendRequestWithOkHttp(adress: String, callback: okhttp3.Callback) { |
1 | HttpUtil.sendRequestWithOkHttp(adress,object:Callback{ |
最好用的网络库:Retrofit
基本用法
添加依赖库
定义接口,使用@GET注解表示调用方法时Retrofit会发起一条GET请求,请求地址就是注解中传入的具体参数
构建Retrofit对象,baseUrl用于指定所以Retrofit请求的根路径,addConverterFactory用于指定Retrofit在解析数据时所使用的转换库,这俩方法必须调用
创建接口的动态代理对象
调用接口方法返回一个Call<List
>对象,再调用它enqueue方法(发送请求时Retrofit会自动在内部开启线程,整个操作无需考虑线程切换问题) 调用response.body()方法获取Retrofit解析后的对象,也就是List
类型数据,最后遍历List 记得声明权限
如果服务器接口是HTTP,则需要进行网络安全配置,并启动配置
1
2
3
4// Retrofit 核心库
implementation ("com.squareup.retrofit2:retrofit:2.9.0")
// Gson 转换器
implementation ("com.squareup.retrofit2:converter-gson:2.9.0")1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25binding.getAppDataBtn.setOnClickListener {
val retrofit=Retrofit.Builder()
.baseUrl("http://10.0.2.2/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val appService=retrofit.create(AppService::class.java)
appService.getAppData().enqueue( object :retrofit2.Callback<List<App>> {
override fun onResponse(
call: retrofit2.Call<List<App>>,
response: retrofit2.Response<List<App>>
) {
val list=response.body()
if(list!=null){
for (app in list){
Log.d("MainActivity", "id is ${app.id}")
Log.d("MainActivity", "name is ${app.name}")
Log.d("MainActivity", "version is ${app.version}")
}
}
}
override fun onFailure(call: retrofit2.Call<List<App>>, t: Throwable) {
t.printStackTrace()
}
})
}
处理复杂的接口地址类型
class Data(val id:String,val content:String)
接口地址是静态
1
2
3
4
5GET http://example.com/get_data.json
interface ExampleService{
fun getData():Call<Data>
}接口地址是动态变化的
page部分代表页数,传入不同页数,返回数据也不同。
1
2
3
4
5GET http://example.com/<page>/get_data.json
interface ExampleService{
fun getData( page:Int):Call<Data>
}接口要求传入一系列参数
这是一个标准的带参数的GET请求格式,接口地址的最后使用问号连接参数,每个参数都是使用等号连接符连接的键值对,多个参数之间用&分隔
1
2
3
4
5GET http://example.com/get_data.json?u=<user>&t=<token>
interface ExampleService{
fun getData( user:String, token:String):Call<Data>
}多种请求类型
GET请求用于从服务器获取数据,POST请求用于从服务器提交数据,PUT和PATCH请求用于修改服务器上的数据,DELETE请求用于删除服务器上的数据
DELETE请求
使用ResponseBody的原因:POST,PUT,PATCH和DELETE与GET不同,它们更多用于对数据进行操作,而不是获取数据,所以对服务器响应的数据并不关心,ResponseBody表示能接收任意类型的响应数据,但不对数据进行解析。
1
2
3
4
5DELETE http://example.com/data/<id>
interface ExampleService{
fun deleteData( id:String):Call<ResponseBody>
}POST请求
1
2
3
4
5
6POST http://example.com/data/create
{"id":1,"content":"The description for this data."}
interface ExampleService{
fun createData( data:Data):Call<ResponseBody>
}@Body注解作用:当发出请求时,会自动将Data对象中的数据转化成JSON格式文本,并放到HTTP请求的body部分,服务器收到请求后只需从body中将这一部分数据解析出来即可,这种写法也可用来给PUT,PATCH和DELETE请求提交数据
在HTTP请求的header中指定参数
1
2
3GET http://example.com/get_data.json
User-Agent:okhttp
Cache-Control:max-age=0这些header参数其实就是一个个键值对
静态header声明:
1
2
3
4
5interface ExampleService{
fun getData():Call<Data>
}动态header声明:
1
2
3
4interface ExampleService{
fun getData( userAgent:String, cacheControl:String):Call<Data>
}
Retrofit构建器的最佳写法
新建一个单例类
将构建Retrofit写法放入
使用泛型实化
调用
1
2
3
4
5
6
7
8
9object 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)
}1
val appService=ServiceCreator.create<AppService>()




