一、准备工作
1.网络权限
在Android中使用网络功能需要在AndroidManifest.xml中声明权限:
<uses-permission android:name="android.permission.INTERENT" />
2.明文HTTP(可选)
从Android9.0系统开始,应用程序默认只允许使用HTTPS类型的网络请求,HTTP因为有安全隐患默认不支持,为了让程序可以使用HTTP明文传输,需要进行如下配置:
- 右键res目录 -> New -> Directory,创建一个xml目录。
- 右键xml目录 -> New -> File,创建一个network_config.xml文件。
<?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>
修改AndroidManifest.xml
<application
...
android:networkSecurityConfig="@xml/network_config">
</application>
2022/11/27更新:
现在可以直接在 AndroidManifest.xml 文件中加入如下字段即可:
<application
...
android:usesCleartextTraffic="true">
...
</application>
二、WebView用法
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
// 让浏览器支持JavaScript脚本
webView.settings.javaScriptEnabled=true
// 当网页跳转时使目标网页仍在当前WebView中显示,而不是打开浏览器
webView.webViewClient = WebViewClient()
webView.loadUrl("http://www.baidu.com")
三、使用HttpURLConnection
private fun sendRequestWithHttpURLConnection() {
// 开启线程发起网络请求
thread {
var connection: HttpURLConnection? = null
try {
val response = StringBuilder()
val url = URL("https://www.baidu.com")
// 获取HttpURLConnection实例
connection = url.openConnection() as HttpURLConnection
// 设置请求方式
connection.requestMethod = "GET"
// 设置超时毫秒树
connection.connectTimeout = 8000
connection.readTimeout = 8000
// 获取服务器返回的输入流
val input = connection.inputStream
// 下面对获取到的输入流进行读取
val reader = BufferedReader(InputStreamReader(input))
reader.use {
reader.forEachLine {
response.append(it)
}
}
// 将读取到的字符串显示在TextView中
showResponse(response.toString())
} catch(e: Exception) {
e.printStackTrace()
} finally {
// 关闭HTTP连接
connection?.disconnect()
}
}
}
//将读取到的字符串显示在TextView中
private fun showResponse(response: String) {
runOnUiThread {
// 在这里可以执行UI操作,将结果显示在页面上
textView.text = response
}
}
1.使用POST提交数据
connection.requestMethod = "POST"
val output = DataOutputStream(connection.outputStream)
output.writeBytes("username=admin&password=123456")
四、使用OkHttp
OkHttp是许多出色的网络通信库中做的最出色的一个,是由大名鼎鼎的Square公司开发的,出了OkHttp之外,还开发了Retrofit、Picasso等知名开源项目。
OKHttp不仅在接口封装上做的简单易用,就连在底层实现上也是自成一派,比起原生的HTTPURLConnection可以说是有过之而无不及。
现在已经成了广大Android开发者首选的网络通信库,OKHttp项目地址是:OKHttp
在使用OKHttp之前需要先在项目中添加OkHttp库的依赖,编辑app/build.gradle:
dependencies {
...
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
}
private fun sendRequestWithOkHttp() {
thread {
try {
// 首先创建OkHttpClient实例
val client = OkHttpClient()
// 想要发起HTTP请求,就要创建一个Request对象
val request = Request.Builder()
.url("https://www.baidu.com")
.build()
// 创建Call对象,execute()发送请求
val response = client.newCall(request).execute()
// 获取返回具体内容
val responseData = response.body?.string()
if(responseData != null) {
// 将返回结果字符串显示在TextView中
showResponse(responseData)
}
} catch(e: Exception) {
e.printStackTrace()
}
}
}
1.POST方法
val requestBody = FormBody.Builder()
.add("username", "admin")
.add("password", "123456")
.build()
val request = Request.Builder()
.url("https://www.baidu.com")
// 放入Post数据
.post(requestBody)
.build()
五、网络请求回调的实现方式
因为每一个应用程序很可能会在很多地方都使用到网络功能,而发送HTTP请求的代码基本是相同的,如果我们每次都去写一遍发送HTTP请求的代码,这显示是非常差劲做法。
因此,通常情况下我们应该将这些通用的网络操作提取到一个公共的类中,并提供一个方法。而且由于HTTP请求是耗时操作,如果没有在请求内部开启线程的话就很有可能导致ANR。
但是如果在请求内部直接开启一个线程来进行HTTP请求的话,服务器响应的数据是无法进行返回的,这是由于所有的耗时操作都是在子线程中进行的,子线程会在服务器还没有来得及响应的时候就结束了,所以我们还需要使用回调机制。
1.HttpURLConnection实现
1. 首先定义一个回调接口
interface HttpCallbackListener {
fun onFinish(response: String)
fun onError(e: Exception)
}
2.将HTTP请求提取到公共类中并添加回调接口
object HttpUtil {
fun sendHttpRequest(address: String, listener: HttpCallbackListener) {
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)
}
}
// 回调onFinish()方法
listener.onFinish(response.toString)
} catch(e: Exception) {
e.printStackTrace()
// 回调onError方法
listener.onError(e)
} finally {
connection?.disconnect()
}
}
}
}
3.调用HTTP请求方法并实现回调
HttpUtil.sendHttpRequest(address, object : HttpCallbackListener {
override fun onFinish(response: String) {
// 得到服务器返回的具体内容
}
override fun onError(e: Exception) {
// 在这里对异常情况进行处理
}
})
2.OkHttp实现
1.将HTTP请求提取到公共类中
object HttpUtil {
...
// okhttp3.Callback是OkHttp自带的回调接口
fun sendOkHttpRequest(address: String, callback: okhttp3.Callback) {
val client = OkHttpClient()
val request = Request.Builder()
.url(address)
.build()
// enqueue在内部开启子线程
client.newCall(request).enqueue(callback)
}
}
2.调用HTTP请求方法并实现回调
HttpUtil.sendOkHttpRequest(address, object : Callback {
override fun onResponse(call: Call, response: Response) {
// 得到服务器返回的具体内容
}
override fun onFailure(call: Call, e: IOException) {
// 在这里对异常情况进行处理
}
})
六、最好用的网络库——Retrofit
Retrofit也是Square公司开发的网络库,但是它和OkHttp定位完全不同,OkHttp侧重底层通信的实现,而Retrofit侧重上层接口的封装,事实上Retrofit就是在OkHttp的基础上进一步开发出来的应用层网络通信库。Github地址:Retrofit。
Retrofit会借助GSON将JSON数据自动转换成POJO对象。
添加依赖库:
dependencies {
// 自动导入:Retrofit、OkHttp、Okio
implementation "com.squareup.retrofit2:retrofit:2.6.1"
// Retrofit转换库,借助GSON解析数据,所以这里也会下载GSON
// 除了GSON,Retrofit还支持Jackson、Moshi等
implementation "com.squareup.retrofit2:converter-gson:2.6.1"
}
这里约定一个JSON文件待用(get_data.json):
[{"id" : "5", "version" : "5.5", "name" : "Clash of Clans"},
{"id" : "6", "version" : "7.0", "name" : "Boom Beach"},
{"id" : "7", "version" : "3.5", "name" : "Clash Royale"}]
同时还有一个对应的POJO类,包含id、name、version:
class App(val id: String, val name: String, val version: String)
1.Retrofit的基本用法
我们根据服务器接口的功能进行归类,创建不同种类的接口文件,并在其中定义对应具体服务器接口的方法。下面定义一个获取上方约定的JSON文件的一个接口,创建AppService接口:
interface AppService {
// 表示调用getAppData()时Retrofit发起一条GET请求
// 请求地址就是传入的具体参数,只需填写相对地址
@GET("get_data.json")
// 返回值必须声明成Retrofit内置的Call
fun getAppData(): Call<List<App>> // 通过泛型来指定响应数据转换成什么对象
}
修改MainActivity代码:
fun onCreate(...) {
...
btn.setOnClickListener {
// 构建Retrofit对象
val retrofit = Retrofit.Builder().run {
// 指定所有Retrofit接口中请求的根路径
baseUrl("http://10.0.2.2/")
// Retrofit在解析数据时使用的转换库
addConverterFactory(GsonConverterFactory.create())
build()
}
// 传入具体Service接口的Class类型
val appService = retrofit.create(AppService::class.java)
// 调用AppService接口中的getAppData()方法,返回Call<List<App>>
// 再调用Call的enqueue()方法发起请求,请求结果回调到Callback中
// 请求时,会自动在内部开启子线程,数据回调后会自动切换到主线程
appService.getAppData().enqueue(object : Callback<List<App>> {
override fun onResponse(call: Call<List<App>>,
response: Response<List<App>>) {
// 得到Retrofit解析后的对象(List<App>)
val list = response.body()
if (list != null) {
for(app in list) {
// 遍历list,并打印出App类中数据
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: Call<List<App>>, t: Throwable) {
t.printStackTrace()
}
})
}
}
2.处理复杂的接口地址类型
为了方便举例,这里先定义一个Data类,包含id和content两个字段:
class Data(val id: String, val content: String)
2.1.动态接口地址
在很多场景下,接口中地址的部分内容可能是会动态变化的,比如如下的接口地址:
GET http://example.com/
这种情况下,对应到Retrofit接口类中可以如下编写:
interface ExampleService {
// 添加{page}占位符
@GET("{page}/get_data.json")
// 使用@Path("page")来声明这个参数
// 调用getData()时,自动将page参数替换到占位符
fun getData(@Path("page") page: Int): Call<Data>
}
2.2.带参数接口
很多接口还会要求传入一系列参数,格式如下:
GET http://example.com/get_data.json?u=
这里也可以使用上面的@Path注解来解决,但是会有些麻烦,专门的语法支持如下:
interface ExampleService {
@GET("get_data.json")
// @Query注解会在发起请求时自动将这两个参数构建到请求地址中
fun getData(@Query("u") user: String, @Query("t") token: String): Call<Data>
}
2.3.常用HTTP请求类型
Retrofit支持@GET、@POST、@PUT、@PATCH、@DELETE常用注解。比如提供了如下接口:
DELETE http://example.com/data/
上面接口意味着根据id删除一条指定数据,想发出这种请求可以这样写:
interface ExampleService {
@DELETE("data/{id}")
// ResponseBody表示对服务器响应数据不关心,可以接受任意类型数据
// 并且不对响应数据进行解析
fun deleteData(@Path("id") id: String): Call<ResponseBody>
}
2.4.向服务器提交数据POST
比如有有如下提交数据的接口:
POST http://example.com/data/creaet
{“id” : 1, “content” : “The description for this data.”}
使用POST来提交数据,需要将数据放到HTTP请求的body部分:
interface ExampleService {
@POST("data/create")
// 给Data类加上@Body注解,这样发出POST请求时
// 会自动将Data中的数据转成JSON格式的文本
// 这种写法同样适用于PUT、PATCH、DELETE
fun createData(@Body data: Data): Call<ResponseBody>
}
2.5.设置请求Header
有些服务器接口可能要求在HTTP请求的header中指定参数,比如:
GET http://example.com/get_data.json
User-Agent: okhttp
Cache-Control: max-age=0
这些header就是一个个键值对,可以直接使用@Headers注解:
interface ExampleService {
// 这种写法只能进行静态header声明
@Headers("User-Agent: okhttp", "Cache-Control: max-age=0")
@GET("get_data.json")
fun getData(): Call<Data>
}
也可以使用@Header注解进行动态指定header的值:
interface ExampleService {
@GET("get_data.json")
// 发起请求时会自动将两个参数的值设置到对应的header中
fun getData(@Header("User-Agent") userAgent: String,
@Header("Cache-Control") cacheControl: String): Call<Data>
}
3.Retrofit构建器的最佳写法
如果每一次使用Retrofit时都要写一遍上面的获取Service接口的动态代理对象,那实在是太麻烦了。Retrofit对象是全局通用的,只需要在调用create()方法时针对不同的Service接口传入相应的Class类即可,因此我们可以将通用的部分功能封装起来。新建一个ServiceCreator单例类:
object ServiceCreator {
private const val BASE_URL = "http://10.0.2.2/"
private val retrofit = Retrofit.Builder().run {
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)
}
fun main() {
// 获取AppService接口动态代理对象
val appService = ServiceCreator.create<AppService>()
}