网络通信
WebView的用法
- 在layout文件下添加控件
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
- 在Activity中进行设置
// 调用getSettings可以设置一些浏览器属性
// setJavaScriptEnabled(true)表示让WebView支持JavaScript脚本
webView.settings.setJavaScriptEnabled(true)
// 调用setWebViewClient方法并传入WebViewClient实例
// 使得跳转网页时,目标网页仍然在WebView中显示
webView.webViewClient = WebViewClient()
// 传入网址
webView.loadUrl("https://www.baidu.com")
- 在AndroidManifest中加入权限声明
<uses-permission android:name="android.permission.INTERNET" />
使用HTTP访问网络
使用HttpURLConnection
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
sendRequestBtn.setOnClickListener {
sendRequestWithHttpURLConnection()
}
}
private fun sendRequestWithHttpURLConnection() {
// 开启线程来发起网络请求
thread {
var connection: HttpURLConnection? = null
try {
// 接受返回信息
val response = StringBuilder()
// 创建URL对象并传入网址
val url = URL("https://www.baidu.com")
// 调用openConnection获取HttpURLConnection实例
connection = url.openConnection() as HttpURLConnection
// 设置连接超时和读取超时的毫秒数
connection.connectTimeout = 8000
connection.readTimeout = 8000
// 调用getInputStream获取返回的输入流
val input = connection.inputStream
showResponse(input.toString())
// 下面对获取到的输入流进行读取
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) {
// 因为在不能在子线程中修改UI,所有要使用异步消息处理机制
runOnUiThread {
// 在这里进行UI操作,将结果显示到界面上
responseText.text = response
}
}
}
如果忘记声明权限就运行程序了,要把APP卸载掉,再重新运行程序才能生效。
使用OkHttp
先在app/build.gradle
中添加依赖
implementation 'com.squareup.okhttp3:okhttp:4.2.2'
修改之前的代码用OkHttp的方式实现
private fun sendRequestWithOkHttp() {
thread {
try {
// 创建OkHttpClient实例
val client = OkHttpClient()
// 创建Request用于发送HTTP请求
val request = Request.Builder()
.url("https://www.baidu.com")
.build()
// 调用client.newCall创建Call对象
// 并调用execute来发送请求并获取服务器返回的数据
val response = client.newCall(request).execute()
// 得到返回的具体内容
val responseData = response.body?.string()
if (responseData != null) {
showResponse(responseData) }
} catch (e: Exception) {
e.printStackTrace()
}
}
}
解析XML格式数据
- 下载Apache HTTP Servier
http://www.apachelounge.com/download/ - 解压后打开\Apache24\conf\httpd.conf文件修改
ServerRoot(apache目录)改为 Apache24的路径
ServerName (服务器名称) 改为127.0.0.1 - 安装apache
安装/卸载命令(以管理员身份运行CMD)
安装命令:”E:\apache\Apache24\bin\httpd.exe” -k install -n apache
卸载命令:”E:\apache\Apache24\bin\httpd.exe” -k uninstall -n apache
(每改一次.conf文件,都要卸载后重新安装) - 打开任务管理器,服务,开启apache
- 打开浏览器输入127.0.0.1出现It works!证明安装成功。
进入htdocs目录下,新建get_data.xml文件加入以下内容
<apps>
<app>
<id>1</id>
<name>Google Maps</name>
<version>1.0</version>
</app>
<app>
<id>2</id>
<name>Chrome</name>
<version>2.1</version>
</app>
<app>
<id>3</id>
<name>Google Play</name>
<version>2.3</version>
</app>
</apps>
打开浏览器输入127.0.0.1/get_data.xml即可看到内容。
Pull解析方式
修改url为http://10.0.2.2/get_data.xml
10.0.2.2对于模拟器来说就是本机的IP地址
加入parseXMLWithPull(responseData)方法
parseXMLWithPull方法如下
private fun parseXMLWithPull(xmlData: String) {
try {
// 创建XmlPullParserFactory实例来获得XmlPullParser对象
val factory = XmlPullParserFactory.newInstance()
val xmlPullParser = factory.newPullParser()
// 将服务器返回的数据设置进去
xmlPullParser.setInput(StringReader(xmlData))
// 获得当前解析事件
var eventType = xmlPullParser.eventType
var id = ""
var name = ""
var version = ""
// eventType != XmlPullParser.END_DOCUMENT说明解析没有完成
while (eventType != XmlPullParser.END_DOCUMENT) {
// 获取节点名
val nodeName = xmlPullParser.name
when (eventType) {
// 开始解析某个节点
XmlPullParser.START_TAG -> {
when (nodeName) {
// 获取节点内容
"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")
}
}
}
// 进入下一个解析事件
eventType = xmlPullParser.next()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
在Logcat中查看解析出来的数据
SAX解析方式
新建一个类继承DefaultHandler,重写五个方法。
- startDocument:开始XML解析时调用
- startElement:开始解析某个节点时调用
- characters:获取节点中内容时调用
- endElement:完成解析某个节点时调用
- endDocument:完成整个解析时调用
class ContentHandler : DefaultHandler() {
// 定义对象
private var nodeName = ""
private lateinit var id: StringBuilder
private lateinit var name: StringBuilder
private lateinit var version: StringBuilder
override fun startDocument() {
// 进行初始化
id = StringBuilder()
name = StringBuilder()
version = StringBuilder()
}
override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) {
// 记录当前节点名
nodeName = localName
}
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("ContentHandler", "id is ${id.toString().trim()}")
Log.d("ContentHandler", "name is ${name.toString().trim()}")
Log.d("ContentHandler", "version is ${version.toString().trim()}")
// 最后要将StringBuilder清空掉
id.setLength(0)
name.setLength(0)
version.setLength(0)
}
}
override fun endDocument() {
}
在Activity中加入parseXMLWithSAX(responseData)
private fun parseXMLWithSAX(xmlData: String) {
try {
// 创建SAXParserFactory对象来获取xmlReader对象
val factory = SAXParserFactory.newInstance()
val xmlReader = factory.newSAXParser().xmlReader
// 获取ContentHandler实例
val handler = ContentHandler()
// 将ContentHandler的实例设置到XMLReader中
xmlReader.contentHandler = handler
// 开始执行解析
xmlReader.parse(InputSource(StringReader(xmlData)))
} catch (e: Exception) {
e.printStackTrace()
}
}
解析JSON格式数据
新建get_data.json文件
[
{
"id": "5",
"version": "5.5",
"name": "Clans of Clans"
},
{
"id": "6",
"version": "7.0",
"name": "Boom Beach"
},
{
"id": "7",
"version": "3.5",
"name": "Clans Royale"
}
]
使用JSONObject
修改url为http://10.0.2.2/get_data.json
加入parseJSONWithJSONObject(responseData)方法
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
导入依赖implementation 'com.google.code.gson:gson:2.8.5'
新增App类
class App(val id: String, val name: String, val version: String)
在Activity中加入parseJSONWithGSON(responseData)方法
private fun parseJSONWithGSON(jsonData: String) {
// 创建Gson实例
val gson = Gson()
// 借助TypeToken将希望解析成的数据类型传入fromJson方法
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}")
}
网络请求回调的实现方式
新建object HttpUtil类加入以下方法
fun sendOkHttpRequest(address: String, callback: okhttp3.Callback) {
val client = OkHttpClient()
val request = Request.Builder()
.url(address)
.build()
// enqueue方法会在内部开启子线程执行HTTP请求
// 并将请求结果回调到okhttp3.Callback中
client.newCall(request).enqueue(callback)
}
调用时就可以写成
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
添加依赖
implementation 'com.squareup.retrofit2:retrofit:2.6.1'
implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
新建APP类
class App(val id: String, val name: String, val version: String)
新建AppService接口
interface AppService {
// 表示调用getAppData时Retrofit会发起一条GET请求
// 请求的地址就是@GET中传入的参数
// 这里只需要传入相对路径
@GET("get_data.json")
// getAppData的返回值必须声明成Retrofit中内置的Call类型
// 并通过泛型来指定数据应该转换成什么对象
fun getAppData(): Call<List<App>>
}
在布局文件中添加一个Button
在Activity中修改代码
getAppDataBtn.setOnClickListener {
// 创建retrofit对象
val retrofit = Retrofit.Builder()
// 指定跟路径
.baseUrl("http://10.0.2.2/")
// 指定解析数据时使用的转换库
.addConverterFactory(GsonConverterFactory.create())
.build()
// 调用create方法并传入Service接口对应的Class类型,创建一个该接口的动态代理对象
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) {
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()
}
})
}
处理复杂的接口地址类型
假设地址为http://example.com/
- 当含有页数时,如
http://example.com/<page>
// @Path将page参数自动替换到{page}的位置来表示页数
@GET("{page}/get_data.json")
fun getData(@Path("page")page: Int):Call<Data>
- 当含有参数时,如
http://example.com/get_data.json?u=<user>&t=<token>
@GET("get_data.json")
fun getData(@Query("user") user: String, @Query("token") token: String): Call<Data>
- 当要删除数据时,如
http://example.com/data/<id>
// ResponseBody表示能够接受任意类型的数据,并不会对其进行解析
@DELETE("data/{id}")
fun deleteData(@Path("id") id: String): Call<ResponseBody>
- 提交数据时,如
http://example.com/data/create
// 使用@Body将数据放到HTTP请求的body部分
@POST("data/create")
fun createData(@Body data: Data): Call<ResponseBody>
- 有些服务器接口还可能会要求我们在HTTP请求的header中指定参数,如
http://example.com/get_data.json
User-Agent:okhttp
Cache-Control:max-age=0
@GET("get_data.json")
fun getData(@Header("User-Agent") userAgent: String, @Header("Cache-Control") cacheControl: String): Call<Data>
Retrofit构建器的最佳写法
创建单例类
object ServiceCreator {
// 指定根路径
private const val BASE_URL = "http://10.0.2.2/"
// 构建retrofit对象
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
// 创建相应Service接口的动态代理对象
fun <T> create(serviceClass: Class<T>): T = retrofit.create(serviceClass)
// 进一步简化代码
inline fun <reified T> create(): T = create(T::class.java)
}
之后就可以使用如下方法来获取AppService接口的动态代理对象了
val appService = ServiceCreator.create<AppService>()