WebView

  1. 使用WebView控件

  2. 调用getSettings方法设置浏览器属性

  3. 传入WebViewClient实例

  4. 传入网址

  5. 声明网络权限

    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
    18
    class 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

  1. 获取HttpURLConnection实例
  2. 设置请求方法
  3. 自由定制一些功能
  4. 得到从服务器返回的输入流,并对输入流进行读取
  5. 将HTTP连接关闭
  6. 记得声明权限
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
45
46
47
48
49
50
51
class 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.sendRequest.setOnClickListener {
sendRequestWithHttpURLConnection()
}
}
private fun sendRequestWithHttpURLConnection() {
//开启线程发送网络请求
thread {
var connection:HttpURLConnection?=null
try {
val response=StringBuilder()
val url=URL("https://www.baidu.com")
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)
}
}
showResponse(response.toString())

}catch (e:Exception){
e.printStackTrace()
}finally {
//断开连接
connection?.disconnect()
}
}
}
private fun showResponse(response: String) {
runOnUiThread {
//在这里进行UI操作,将结果显示到界面上
binding.responseText.text=response
}
}
}

使用OkHttp

发送GET请求

  1. 安装依赖库

  2. 创建一个OkHttpClient()实例

  3. 创建Request对象,通过连缀丰富对象

  4. 通过newCall方法获取Call对象,并调用execute方法发送请求并获取服务器返回的数据

  5. 得到返回数据的具体内容

    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
    private 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请求

  1. 安装依赖库

  2. 创建一个OkHttpClient()实例

  3. 创建一个RequestBody对象

  4. 创建Request对象,并调用post方法,通过连缀丰富对象

  5. 通过newCall方法获取Call对象,并调用execute方法发送请求并获取服务器返回的数据

  6. 得到返回数据的具体内容

    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
    23
    private 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解析方式

  1. 创建XmlPullParserFactory实例,并得到得到XmlPullParser对象

  2. 将服务器返回的XML数据设置进去

  3. 开始解析,用geteventType()得到当前解析的事件

  4. 在while循环中不断进行解析,调用next获得下一个解析事件

  5. 在循环内通过getName获得名字,用nextText获取具体内容

  6. 为了让程序可以使用HTTP,还要在xml文件中添加配置

  7. 在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
    45
    private 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解析方式

  1. 新建一个类继承DefaultHandler,并重写五种方法
  2. 创建SAXParserFactory实例
  3. 获取XMLReader对象
  4. 将编写的实例类设置到XMLReader中
  5. 调用parse方法开始执行
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
45
46
47
48
49
50
51
52
53
54
55
56
57
class MyHandle:DefaultHandler() {
private var nodeName=""
private lateinit var id:StringBuilder
private lateinit var name:StringBuilder
private lateinit var version:StringBuilder
//开始XML解析时调用
override fun startDocument() {
id=StringBuilder()
name=StringBuilder()
version=StringBuilder()

}
//开始解析某个节点时调用
override fun startElement(
uri: String?,
localName: String?,
qName: String?,
attributes: Attributes?
) {
// localName记录当前节点名称
nodeName= localName!!
Log.d("MyHandle", "uri is $uri")
Log.d("MyHandle", "localName is $localName")
Log.d("MyHandle", "qName is $qName")
Log.d("MyHandle", "attributes is $attributes")


}
//获取节点内内容时调用
override fun characters(ch: CharArray?, start: Int, length: Int) {
//根据当前节点名判断加到哪个StringBuilder对象中
when(nodeName){
"id"->id.append(ch,start,length)
"name"->name.append(ch,start,length)
"version"->version.append(ch,start,length)

}
}
//完成解析某个节点时调用
override fun endElement(uri: String?, localName: String?, qName: String?) {
if ("app"==localName){
//trim()方法是用于去除字符串两端的空白字符
Log.d("MyHandle", "id is ${id.toString().trim()} ")
Log.d("MyHandle", "name is ${name.toString().trim()} ")
Log.d("MyHandle", "version is ${version.toString().trim()} ")
//最后将StringBuilder内容清空
id.setLength(0)
name.setLength(0)
version.setLength(0)
}
}
//完成xml解析时调用
override fun endDocument() {
super.endDocument()
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 private fun parseXMLWithSAX(xmlData: String) = try {
val factory=SAXParserFactory.newInstance()
val xmlReader=factory.newSAXParser().xmlReader
val handler= MyHandle()

//将MyHandle的实例设置到XMLReader中
xmlReader.contentHandler=handler
//开始执行解析
xmlReader.parse(InputSource(StringReader(xmlData)))

}catch (e:Exception){
e.printStackTrace()
}
}


解析JSON格式数据

使用JSONObject

  1. 定义一个数组,将返回的数据传入到JSONArray对象中
  2. 循环遍历数组,取出各个元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private fun parseJSONWithJSONObject(jsonData: String) {
try {
//定义一个JSON数组
val jsonArray=JSONArray(jsonData)
//循环遍历
for (i in 0 until jsonArray.length()){
//取出元素,每个取出的元素都是一个jsonObject对象
val jsonObject=jsonArray.getJSONObject(i)
val id=jsonObject.getString("id")
val name=jsonObject.getString("name")
val version=jsonObject.getString("version")
Log.d("MainActivity", "id is $id")
Log.d("MainActivity", "name is $name")
Log.d("MainActivity", "version is $version")

}
}catch (e:Exception){
e.printStackTrace()
}

}

使用GSON

优点:可以将一段JSON格式的字符串自动映射成一个对象,不需要再手动编写代码解析

  1. 添加依赖库
  2. 获取Gson对象
  3. 如果是一个JSON数据,可以调用fromJson方法,如果是一段Json数组需要借助TypeToken将期望解析成的数据类型传入fromJson方法
1
implementation ("com.google.code.gson:gson:2.10.1")
1
2
3
4
5
6
7
8
9
10
11
private fun parseJSONWithGSON(jsonData: String) {
val gson=Gson()
val typeOf=object :TypeToken<List<App>>() {}.type
val appList=gson.fromJson<List<App>>(jsonData,typeOf)
for(app in appList){
Log.d("MainActivity", "id is ${app.id}")
Log.d("MainActivity", "name is ${app.name}")
Log.d("MainActivity", "version is ${app.version}")
}

}

网络请求回调的实现方式

采用回调的原因:所有耗时逻辑在子线程中进行,sendHttpRequest()方法在服务器还没来得及响应的时候就执行结束

HttpURLConnection方法

  1. 将请求方法放入到单例类中

  2. 定义一个接口,并定义方法

  3. 给方法添加一个接口参数,并开启一个子线程

  4. 在线程中回调接口方法

  5. 调用sendRequestWithHttpURLConnection()方法,调用时还需将接口实例传入

    1
    2
    3
    4
    interface 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
    32
    object 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
    8
    HttpUtil.sendRequestWithHttpURLConnection(adress,object:HttpCallbackListener{
    override fun onFinish(response:String){
    //得到服务器返回的具体内容
    }
    override fun onError(e:Exception){
    //在这里对异常情况进行处理
    }
    })

OkHttp方法

  1. 将请求方法放入到单例类中
  2. 传入okhttp3.Callback参数,这是oKHttp库自带的回调接口
  3. 在newCall后调用enqueue方法,并传入接口参数
  4. 调用sendRequestWithOkHttp()方法
1
2
3
4
5
6
7
8
fun sendRequestWithOkHttp(adress: String, callback: okhttp3.Callback) {
val client = OkHttpClient()
val request = Request.Builder()
.url(adress)
.build()
//enqueue()内部帮我们开好了子线程,然后子线程中执行Http请求
client.newCall(request).enqueue(callback)
}
1
2
3
4
5
6
7
8
9
HttpUtil.sendRequestWithOkHttp(adress,object:Callback{
override fun onResponse(call:Call,response:Response){
//得到服务器返回的具体内容
val responseData=response.body?.string()
}
override fun onFailure(call:Call,e:IOException){
//在这里对异常情况进行处理
}
})

最好用的网络库:Retrofit

基本用法

  1. 添加依赖库

  2. 定义接口,使用@GET注解表示调用方法时Retrofit会发起一条GET请求,请求地址就是注解中传入的具体参数

  3. 构建Retrofit对象,baseUrl用于指定所以Retrofit请求的根路径,addConverterFactory用于指定Retrofit在解析数据时所使用的转换库,这俩方法必须调用

  4. 创建接口的动态代理对象

  5. 调用接口方法返回一个Call<List>对象,再调用它enqueue方法(发送请求时Retrofit会自动在内部开启线程,整个操作无需考虑线程切换问题)

  6. 调用response.body()方法获取Retrofit解析后的对象,也就是List类型数据,最后遍历List

  7. 记得声明权限

  8. 如果服务器接口是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
    25
    binding.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
    5
    GET http://example.com/get_data.json
    interface ExampleService{
    @GET("get_data.json")
    fun getData():Call<Data>
    }
  • 接口地址是动态变化的

    page部分代表页数,传入不同页数,返回数据也不同。

    1
    2
    3
    4
    5
    GET http://example.com/<page>/get_data.json
    interface ExampleService{
    @GET("{page}/get_data.json")
    fun getData(@Path("page") page:Int):Call<Data>
    }
  • 接口要求传入一系列参数

    这是一个标准的带参数的GET请求格式,接口地址的最后使用问号连接参数,每个参数都是使用等号连接符连接的键值对,多个参数之间用&分隔

    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>
    }
  • 多种请求类型

    GET请求用于从服务器获取数据,POST请求用于从服务器提交数据,PUT和PATCH请求用于修改服务器上的数据,DELETE请求用于删除服务器上的数据

  • DELETE请求

    使用ResponseBody的原因:POST,PUT,PATCH和DELETE与GET不同,它们更多用于对数据进行操作,而不是获取数据,所以对服务器响应的数据并不关心,ResponseBody表示能接收任意类型的响应数据,但不对数据进行解析。

    1
    2
    3
    4
    5
    DELETE http://example.com/data/<id>
    interface ExampleService{
    @DELETE("data/{id}")
    fun deleteData(@Path("id") id:String):Call<ResponseBody>
    }
  • POST请求

    1
    2
    3
    4
    5
    6
    POST http://example.com/data/create
    {"id":1,"content":"The description for this data."}
    interface ExampleService{
    @POST("data/create")
    fun createData(@Body data:Data):Call<ResponseBody>
    }

    @Body注解作用:当发出请求时,会自动将Data对象中的数据转化成JSON格式文本,并放到HTTP请求的body部分,服务器收到请求后只需从body中将这一部分数据解析出来即可,这种写法也可用来给PUT,PATCH和DELETE请求提交数据

  • 在HTTP请求的header中指定参数

    1
    2
    3
    GET http://example.com/get_data.json
    User-Agent:okhttp
    Cache-Control:max-age=0

    这些header参数其实就是一个个键值对

    静态header声明:

    1
    2
    3
    4
    5
    interface ExampleService{
    @Headers("User-Agent:okhttp","Cache-Control:max-age=0")
    @GET("get_data.json")
    fun getData():Call<Data>
    }

    动态header声明:

    1
    2
    3
    4
    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)
    }
    1
    val appService=ServiceCreator.create<AppService>()