我不是罗大锤我不是罗大锤

我不是罗大锤

我不是罗大锤我不是罗大锤

我不是罗大锤

首页首页
分类分类
标签标签
友情链接友情
日记日记
Android 网络技术Android 网络技术

Android 网络技术

&Android

允许评论

4 年前

一、准备工作

1.网络权限

在 Android 中使用网络功能需要在 AndroidManifest.xml 中声明权限:

<uses-permission android:name="android.permission.INTERENT" />

2.明文 HTTP(可选)

从 Android9.0 系统开始,应用程序默认只允许使用 HTTPS 类型的网络请求,HTTP 因为有安全隐患默认不支持,为了让程序可以使用 HTTP 明文传输,需要进行如下配置:

  1. 右键res目录 -> New -> Directory,创建一个xml目录。
  2. 右键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" />
// Kotlin
// 让浏览器支持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'
}
// Kotlin
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 数据自动转换成实体对象。

添加依赖库:

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 文件待用:

[
  {"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"}
]

同时还有一个对应的实体类,包含 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//get_data.json

这种情况下,对应到 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=&t=

这里也可以使用上面的@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>()
}
目录
一、准备工作
1.网络权限
2.明文 HTTP(可选)
二、WebView 用法
三、使用 HttpURLConnection
1.使用 POST 提交数据
四、使用 OkHttp
1.POST方法
五、网络请求回调的实现方式
1.HttpURLConnection 实现
1. 首先定义一个回调接口
2.将HTTP请求提取到公共类中并添加回调接口
3.调用 HTTP 请求方法并实现回调
2.OkHttp 实现
1.将 HTTP 请求提取到公共类中
2.调用 HTTP 请求方法并实现回调
六、最好用的网络库——Retrofit
1.Retrofit 的基本用法
2.处理复杂的接口地址类型
2.1.动态接口地址
2.2.带参数接口
2.3.常用 HTTP 请求类型
2.4.向服务器提交数据 POST
2.5.设置请求 Header
3.Retrofit 构建器的最佳写法
暂无评论

在线人数:0 人

文章总浏览量:13384

Powered byNola