注意,使用网络需要在权限中声明
当我们需要在应用程序里显示一些网页时就可以使用这个控件。
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")
}
}
原理:客户端向服务器发出一条HTTP请求,服务器收到请求之后返回一些数据给客户端,客户端再将这些数据进行解析和处理。
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
//设置HTTP请求所使用的方法,GET表示从服务器获取,POST表示向服务器提交
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)
}
}
showResponse(response.toString())
}catch (e : Exception){
e.printStackTrace()
}finally {
//关闭HTTP连接
connection?.disconnect()
}
}
}
private fun showResponse(response: String) {
//该方法就是对异步消息处理机制进行了一层封装,背后原理是一样的
runOnUiThread {
responseText.text = response
}
}
如果想要提交数据:
connection.requestMethod = "POST"
val output = DataOutputStream(connection.outputStream)
ouyput.writeBytes("username=admin&password=123456")
private fun sendRequestWithOkHttp() {
thread {
try {
//获取OkHttpClient实例
val client = OkHttpClient()
//创建Request对象
val request = Request.Builder()
.url("http://10.0.2.2/get_data.json")
.build()
//调用OkHttpClient的newCall方法来创建一个Call对象,并调用其execute方法发送请求并获取服务器返回的数据
val response = client.newCall(request).execute()
val responseData = response.body?.string()
if (responseData!=null){
parseJSONWithGSON(responseData)
}
}catch (e : Exception){
e.printStackTrace()
}
}
}
如果是发起一条POST请求,需要构建一个Request Body对象来存放待提交的参数:
val requestBody = FormBody.Builder()
.add("username","admin")
.add("password","123456")
.build()
private fun parseXMLWithPull(xmlData: String) {
try {
//创建XmlPullParserFactory实例,并借其得到xmlPullParser对象
val factory = XmlPullParserFactory.newInstance()
val xmlPullParser = factory.newPullParser()
//将服务器返回的XML数据setInput里
xmlPullParser.setInput(StringReader(xmlData))
var eventType = xmlPullParser.eventType
var id = ""
var name = ""
var version = ""
//只要解析工作没完成就继续
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()
}
}
SAX用法比Pull复杂一些,但语义方面会更加清楚。
private fun parseXMLWithSAX(xmlData: String) {
try {
val factory = SAXParserFactory.newInstance()
val xmlReader = factory.newSAXParser().xmlReader
val handler = ContentHandler()
xmlReader.contentHandler = handler
xmlReader.parse(InputSource(StringReader(xmlData)))
}catch (e:Exception){
e.printStackTrace()
}
}
class ContentHandler : 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?
) {
nodeName = localName
Log.d("ContentHandler", "uri is $uri")
Log.d("ContentHandler", "localName is $localName")
Log.d("ContentHandler", "qName is $qName")
Log.d("ContentHandler", "attributes is $attributes")
}
//获取节点内容时调用
override fun characters(ch: CharArray?, start: Int, length: Int) {
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){
Log.d("ContentHandler", "id is ${
id.toString().trim()}")
Log.d("ContentHandler", "name is ${
name.toString().trim()}")
Log.d("ContentHandler", "version is ${
version.toString().trim()}")
id.setLength(0)
name.setLength(0)
version.setLength(0)
}
}
//完成XML解析时调用
override fun endDocument() {
super.endDocument()
}
}
//我们在服务器中定义了一个数组,传入其中
private fun parseJSONWithJSONObject(jsonData: String) {
try {
//将数据传入JSONArray
val jsonArray = JSONArray(jsonData)
//对jsonArray中的每一个元素依次取出我们需要的数据
for (i in 0 until jsonArray.length()){
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的字符串自动映射成一个对象。
private fun parseJSONWithGSON(jsonData: String) {
val gson = Gson()
//获取数组中对象的类型
val typeOf = object : TypeToken<List<App>>(){
}.type
//根据解析出的类型将JSON数据自动解析成对象
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}")
}
}
一般我们应该将通用的网络操作提取到一个公共的类里,并提供一个公共的方法,每当发起网络请求时,只需要简单地调用一下这个方法即可。
如何在使用线程的提升返回响应的数据?
利用回调机制。
在HTTPURLConnection中,传入接口
//发送请求时同时传入该接口,使得
intertface HttpCallbackListener{
//服务器成功响应时调用该方法
fun onFinish(response : String)
//网络操作出现错误时调用该方法
fun onError(e : Exception)
}
在OKHTTP中,有自带的回调接口okhttp3.Callback。将execute方法改为enqueue方法,并把okhttp3.Callback参数传入即可。
注意,回调接口是在子线程中运行的,切忌在其中进行UI操作。
从Android 9.0开始,应用程序默认只允许使用HTTPS类型的网络请求,HTTP类型的网络请求被认为因为有安全隐患默认不再被支持。
新建一个xml,并配置进application中。
<?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>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:networkSecurityConfig="@xml/network_config">
...
</application>
Retrofit是在OkHttp的基础上进一步开放出来的应用层网络通信库,它的定位与OkHttp不同,前者侧重上册接口的封装,后者侧重底层通信的实现。
Retrofit的设计基于以下几个事实
Retrofit可以设置根路径,然后再指定服务器接口时只需使用相对路径即可
Retrofit允许我们对服务器接口进行分类,将功能同属于一类的服务器接口定义到同一个接口文件中,从而让代码架构变得更加合理。
我们不用关心网络通信的细节,只需要在接口文件中声明一系列方法和返回值 ,然后通过注解的方式指定该方法对应哪个服务器接口,以及需要提供哪些参数。当我们调用该方法是,Retrofit会自动向对应的服务器接口发起请求,并将相应的数据解析成返回值声明的类型。这就使得我们可以用更加面向对象的思维来进行网络操作。
//具体功能种类名开头,Service结尾
interface AppService {
//GET注解表示会发对该路径(相对路径)发起GET请求
@GET("get_data.json")
//这里的返回值必须声明为Retrofit内置的Call类型,并通过泛型来指定服务器响应的数据应该转换成什么对象
fun getAppData() : Call<List<App>>
}
object ServiceCreator {
private const val BASE_URL = "http://10.0.2.2/"
//使用Retrofit.Builder构建Retrofit对象
val retrofit = Retrofit.Builder()
//指定根路径
.baseUrl(BASE_URL)
//指定Retrofit解析数据用的转换库
.addConverterFactory(GsonConverterFactory.create())
.build()
//利用泛型实化获取泛型的类型信息
inline fun <reified T> create() : T = retrofit.create(T::class.java)
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
getAppDataBtn.setOnClickListener {
val appService = ServiceCreator.create<AppService>()
//Retrofit在发起请求时自动开启子线程,数据回调到Callback后又会自动切换回主线程
appService.getAppData().enqueue(object :Callback<List<App>>{
override fun onFailure(call: Call<List<App>>, t: Throwable) {
t.printStackTrace()
}
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}")
}
}
}
})
}
}
}
灵活运用@Path,@Query注解
@Headers注解
@Header注解
GET | POST | PUT | PATCH | DELETE |
---|---|---|---|---|
从服务器获取数据 | 向服务器提交数据 | 修改服务器上的数据(全部更新) | 修改服务器上的数据(局部更新) | 删除服务器上的数据 |