目录
WebView
WebView用法
WebView控件:借助它可以在自己的应用程序里嵌入一个浏览器,从而非常轻松地展示各种各样的网页。但它不会包含功能全面的网络浏览器的任何功能,例如导航控件或地址栏。WebView 默认只显示网页
使用Android studio新建一个WebViewTest项目,然后修改activity_main.xml中的代码,添加一个WebBiew控件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
这里的写法很简单,给它设置了一个id,并让它充满整个屏幕
然后修改MainActivity中的代码
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
webView.settings.javaScriptEnabled=true
webView.webViewClient = WebViewClient()
webView.loadUrl("https://www.baidu.com")
}
}
这里通过WebView的getSettings()方法可以设置一些浏览器的属性,这里并没有设置过多的属性,只是调用了setJavaScriptEnabled()方法,让WebView支持JavaScript脚本
接下来是调用了WebView的setWebViewClient()方法,并传入了一个WebViewClient的实例。这段代码的作用是,当需要从一个网页跳转到另一个网页时,希望目标网页仍然在当前WebView中显示,而不是打开系统浏览器
最后一步调用WebView的loadUrl()方法,并将网址传入,即可展示相应网页的内容
运行程序,就可以看到百度网页
可能会出现的报错:Webpage not available
需要在项目文件中的AndroidManifest.xml文件添加如下声明权限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
具体可看这篇博客:https://blog.csdn.net/zgd826237710/article/details/95520785
使用HTTP访问网络
对于HTTP,它的工作原理特别简单,就是客户端向服务器发出一条HTTP请求,服务器收到请求
之后会返回一些数据给客户端,然后客户端再对这些数据进行解析和处理就可以了
而前面说的WebView是已经在后台处理好了发送HTTP请求、接收服务器响应、解析返回数据,以及最终的页面展示这几步工作
HttpURLConnection
HttpURLConnection:发送HTTP请求
使用Android studio创建一个新项目
然后修改activity_main.xml中的代码,添加一个button控件用于发送HTTP请求;并且使用ScrollView控件,在里面添加一个TextView控件用来将服务器返回的数据显示出来
修改MainActivity中的代码
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()
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操作,将结果显示到界面上
responseText.text = response
}
}
}
在“Send Request”按钮的点击事件里调用了sendRequestWithHttpURLConnection()方法,在这个方法中先是开启了一个子线程
在这个线程里首先获取HttpURLConnection的实例,一般只需创建一个URL对象,并传入目标的网络地址,然后调用一下openConnection()方法并用as转换为HttpURLConnection对象
接下来又设置了连接超时和读取超时的毫秒数,之后再调用getInputStream()方法就可以获取到服务器返回的输入流了
接着利用BufferedReader对服务器返回的流进行读取,并将结果传入showResponse()方法中。在showResponse()方法里,由于Android是不允许在子线程中进行UI操作,所以使用一个对异步消息处理机制进行封装的runOnUiThread()方法,然后在这个方法的Lambda表达式中进行操作,将返回的数据显示到界面上
使用connection.requestMethod = "GET/POST"可以设置HTTP请求所使用的方法
运行程序,并点击“Send Request”按钮
这是服务器返回的HTML代码,只是通常情况下浏览器会将这些代码解析成漂亮的网页后再展示出来
OkHttp
并不是只能使用HttpURLConnection,完全没有任何其他选择,事实上在开源盛行的今天,有许多出色的网络通信库都可以替代原生的HttpURLConnection,而其中OkHttp无疑是做得最出色的一个
在使用OkHttp之前,需要先在项目中添加OkHttp库的依赖。编辑app/build.gradle文件,在dependencies闭包中添加如下内容:
dependencies {
...
implementation 'com.squareup.okhttp3:okhttp:4.1.0'
}
添加上述依赖会自动下载两个库:一个是OkHttp库,一个是Okio库,后者是前者的通信基础。
修改MainActivity中的代码
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
···
sendRequestBtn.sendRequestWithOkHttp {
sendRequestWithOkHttp()
}
}
...
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()
}
}
}
}
在sendRequestWithOkHttp按钮中调用sendRequestWithOkHttp方法,在sendRequestWithOkHttp方法中处理发送逻辑,并且开启一个子线程
首先创建一个OkHttpClient的实例;再创建一个Request对象用于发起一条HTTP请求,然后通过url()方法来设置目标的网络地址
之后调用OkHttpClient的newCall()方法来创建一个Call对象,并调用它的execute()方法来发送请求并获取服务器返回的数据,这个对Response对象就是服务器返回的数据,使用response.body?.string()这种写法来得到返回的具体内容
最后仍然调用了前面的showResponse()方法,将服务器返回的数据显示到界面上
运行效果和使用HttpURLConnection发送请求的效果一样
网络请求回调的实现方式
使用回调机制的原因
一个Android应用程序很可能会在许多地方都使用到网络功能,而发送HTTP请求的代码基本是相同的,如果每次都去编写一遍发送HTTP请求的代码,这显然不是一个程序员正常的做法。所以通常情况下应该将这些通用的网络操作提取到一个公共的类里,并提供一个通用方法,当想要发起网络请求的时候,只需简单地调用一下这个方法即可
但是需要注意,网络请求通常属于耗时操作,如果想当然的在公共类内部开启一个线程就解决问题了,那就会很容易报错。因为如果公共类中的方法中开启一个线程来发起HTTP请求,服务器响应的数据是无法进行返回的。这是由于所有的耗时逻辑都是在子线程里进行的,公共类中的方法会在服务器还没来得及响应的时候就执行结束了,当然也就无法返回响应的数据了
这时使用回调机制就可以了,和前面一样会有HttpURLConnection、OkHttp的回调
需要注意的是,不管是使用HttpURLConnection还是OkHttp,最终的回调接口都还是在子线程中运行的,因此不可以在这里执行任何的UI操作,除非借助runOnUiThread()方法来进行线程转换
使用HttpURLConnection回调
首先需要定义一个接口,比如将它命名成HttpCallbackListener,代码如下所示:
interface HttpCallbackListener {
fun onFinish(response: String)
fun onError(e: Exception)
}
在接口中定义了两个方法:onFinish()方法表示当服务器成功响应我们请求的时候调用,onError()表示当进行网络操作出现错误的时候调用
这两个方法都带有参数,onFinish()方法中的参数代表服务器返回的数据,而onError()方法中的参数记录着错误的详细信息。
在新建一个文件命名成HttpUtil,如下所示:
object HttpUtil {
fun sendHttpRequest(address: String, listener: HttpCallbackListener) {
thread {
var connection: HttpURLConnection? = null
try {
val response = StringBuilder()
val url = URL(address)
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()
}
}
}
}
首先给sendHttpRequest()方法添加了一个HttpCallbackListener参数,并在方法的内部开启了一个子线程,然后在子线程里执行具体的网络操作
注意,子线程中是无法通过return语句返回数据的,因此将服务器响应的数据传入了HttpCallbackListener的onFinish()方法中,如果出现了异常,就将异常原因传入onError()方法中
现在sendHttpRequest()方法接收两个参数,因此在调用它的时候还需要将HttpCallbackListener的实例传入,如下所示:
HttpUtil.sendHttpRequest(address, object : HttpCallbackListener {
override fun onFinish(response: String) {
// 得到服务器返回的具体内容
}
override fun onError(e: Exception) {
// 在这里对异常情况进行处理
}
})
这样当服务器成功响应的时候,就可以在onFinish()方法里对响应数据进行处理了。类似地,如果出现了异常,就可以在onError()方法里对异常情况进行处理
这样,就巧妙地利用回调机制将响应数据成功返回给调用方了
使用OkHttp回调
使用OkHttp回调会比HttpURLConnection要简单得多
接着在HttpUtil中加入一个sendOkHttpRequest()方法,如下所示:
object HttpUtil {
...
fun sendOkHttpRequest(address: String, callback: okhttp3.Callback) {
val client = OkHttpClient()
val request = Request.Builder()
.url(address)
.build()
client.newCall(request).enqueue(callback)
}
}
sendOkHttpRequest()方法中有一个okhttp3.Callback参数,这个是OkHttp库中自带的回调接口,类似于刚才自己编写的HttpCallbackListener接口
然后在client.newCall()之后没有像之前那样一直调用execute()方法,而是调用了一个enqueue()方法,并把okhttp3.Callback参数传入。这是因为,OkHttp在enqueue()方法的内部已经开好子线程了,然后会在子线程中执行HTTP请求,并将最终的请求结果回调到okhttp3.Callback当中
在调用sendOkHttpRequest()方法的时候就可以这样写:
HttpUtil.sendOkHttpRequest(address, object : Callback {
override fun onResponse(call: Call, response: Response) {
// 得到服务器返回的具体内容
val responseData = response.body?.string()
}
override fun onFailure(call: Call, e: IOException) {
// 在这里对异常情况进行处理
}
})