使用网络技术

使用网络技术

Gary哥哥的哥哥

2021.2.26

这里我们使用网络技术丰富我们的应用程序,本章节主要讲解如何在手机端使用HTTP和服务器进行网络交互,并对服务器返回的数据进行解析

WebView的用法

有时候我们有一些特殊的需求,比如在应用程序当中展示一些网页

  • 这里就要使用到WebView这个空间了

很简单的:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <WebView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/webView"/>

</LinearLayout>
package com.workaholiclab.webviewtest

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.webkit.WebViewClient
import kotlinx.android.synthetic.main.activity_main.*

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://1.semantic-ui.com/")

    }
}

最后记得注册哦!

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.workaholiclab.webviewtest">

    <uses-permission android:name="android.permission.INTERNET"/>
    <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">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

只要确保你的计算机可以上网就可以用模拟器弹出这个网址

下面我们需要对HTTP协议做一些真正的网络开发工作了!

使用HTTP访问网络

这里HTTP全部的原理或许你要学习计算机网络专业课程或者网络开发才能学会,但这里作为Android开发,我们只会简单介绍。

  • 不求甚解

前面其实就是我们像那个网站发出了一条HTTP的请求,它把网页的html代码返回回来,显示在我们的APP上

接下来我们重点看看通过手动发送HTTP请求的方式,更加深入地理解这个过程

使用HttpURLConnection

在android上面发送HTTP请求一般有两种方法,HttpURLConnection和HttpClient,不过由于后者的API过多,扩展过于困难,现在都一般建议我们用前者

  • 首先,获取HttpUrlConnection实例,一般这需要创建一个URL对象,并且出传入目标的网络地址,然后调用一下openConnection()方法即可。
val url = URL("https://www.baidu.com/")
val connection = url.openConnection() as HttpURLConnection
  • 在得到HttpURLConnection的实例之后,我们可以设置一下HTTP的请求方法,常用的是GET和POST
    • Get代表想获取数据
    • POST想提交数据
connection.requestMethod = "GET"
  • 接下来就可以进行一些自由的定制了
    • 比如设置连接超时
    • 读取超时的毫秒数
    • 以及服务器希望的到的一些消息
connection.connectionTimeout = 8000
connection.readTimeout = 8000
  • 之后再调用getInputStream()方法就可以获取到服务器返回的输入流,剩下的任务就是对输入流进行读取
val input = connection.inputStream
  • 最后关闭这个连接
connection.disconnect()

下面新建一个项目来熟悉一下

别忘了注册先!!!

<uses-permission android:name="android.permission.INTERNET"/>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Send Request"
        android:textAllCaps="false"
        android:id="@+id/sendRequestBtn"
        />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/responseText"/>
    </ScrollView>

</LinearLayout>
package com.workaholiclab.networktest

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import java.io.BufferedReader
import java.io.InputStreamReader
import java.lang.Exception
import java.lang.StringBuilder
import java.net.HttpURLConnection
import java.net.URL
import kotlin.concurrent.thread

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)

                    }
                }
                println(response.toString())
                showResponse(response.toString())
            }catch (e:Exception) {
                e.printStackTrace()
            }finally {
                connection?.disconnect()
            }
        }
    }

    private fun showResponse(response: String) {
        runOnUiThread{
            //在这里进行UI操作
            responseText.text = response
        }
    }
}

如果想要提交数据给服务器也很简单 如下所示即可:

  • 向服务器提交用户名和密码:
connection.requestMethod = "POST"
val output = DataOutputStream(connection.outputStream)
output.writeBytes("username=admin&password=123456")

注意每条数据都要以KV对的形式存在,数据与数据之间用&隔开

使用OkHTTP

在开源如此盛行的今天,OkHttp已经成为Android开发者网络通讯库的首选

  • 添加依赖文件
implementation 'com.squareup.okhttp3:okhttp:4.1.0'

至于你看博客的时候OkHttp库最新版本是多少,可以访问它的github主页

全部步骤如下面代码所示

//创建实例
val client = OkHttpClient()
//发起一条Http请求,创建一个Request对象
//val request = Request.Builder().build()//这样子只能创建一个空的request,应该像下面一样:
//赋值
val request = Request.Builder().url("https://www.baidu.com").build()
//newCall()来创建一个Call对象
val response=client.newCall(request).execute()
//Request对象就是服务器返回的数据了,我们采用如下写法来得到返回的数据
val responseData=response.body?.string()

//如果是POST的话会麻烦一点点,如下:
//先构建Request Body对象来存放待提交的数据
val requestBody = FormBody.Builder().add("username","admin").add("password","123456").build()
//调用post方法将RequestBody对象传入
val requestPost=Request.Builder().url("https://www.baidu.com").post(requestBody).build()
//后面就和Get一样调用execute()方法来发送并请求获取服务器返回的数据即可

下面来对上面之前那个项目做修改,用OkHttp库来做

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()
        }
    }
}

private fun showResponse(response: String) {
    runOnUiThread{
        //在这里进行UI操作
        responseText.text = response
    }
}

解析XML格式数据

这里我们需要解决一个问题,这些数据到底是以什么样子的形式在网络上进行传输的呢?

  • 网络上传输数据的常用格式为XML和JSON

  • 下面我们先来看一下XML吧

搭建一个Web服务器非常简单,这里我们选择使用Apache服务器,如果你是Windows就要手动搭建,如果是Mac或者Linux的话默认是安装好的,只需要启动即可,具体方法可自行上网查阅,这里以Windows来讲解:

详细下载步骤请见课本p436,或到网上搜索

<apps>
    <app>
        <id>1</id>
        <name>Google Maps</name>
        <version>1.0</version>
    </app>
    <app>
        <id>3</id>
        <name>Google Play</name>
        <version>2.3</version>
    </app>
</apps>

下面看看Android如何解析这段XML

Pull解析方式

我们下面依然在OkHttpTest工程上继续修改

  • 下面这段代码确实比较烦人,耐心点领会
class MainActivity : AppCompatActivity() {
    private val keyMain="MainActivity"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        fun testOkHttp() {
            //创建实例
            val client = OkHttpClient()
            //发起一条Http请求,创建一个Request对象
            //val request = Request.Builder().build()//这样子只能创建一个空的request,应该像下面一样:
            //赋值
            val request = Request.Builder().url("https://www.baidu.com").build()
            //newCall()来创建一个Call对象
            val response=client.newCall(request).execute()
            //Request对象就是服务器返回的数据了,我们采用如下写法来得到返回的数据
            val responseData=response.body?.string()

            //如果是POST的话会麻烦一点点,如下:
            //先构建Request Body对象来存放待提交的数据
            val requestBody = FormBody.Builder().add("username","admin").add("password","123456").build()
            //调用post方法将RequestBody对象传入
            val requestPost=Request.Builder().url("https://www.baidu.com").post(requestBody).build()
            //后面就和Get一样调用execute()方法来发送并请求获取服务器返回的数据即可
        }

        sendRequestBtn.setOnClickListener {
            sendRequestWithOkHttp()
        }

    }

    private fun sendRequestWithOkHttp() {
        thread {
            try {
                val client = OkHttpClient()
                val request = Request.Builder()

                    //指定访问服务器地址是计算机本机
                    .url("http://10.0.2.2/get_data.xml")
                    .build()
                val response = client.newCall(request).execute()
                val responseData= response.body?.string()
                if(responseData!=null){
//                    showResponse(responseData)
                    //解析XML
                    parseXMLWithPull(responseData)
                }
            }catch (e:Exception){
                e.printStackTrace()
            }
        }
    }

    private fun parseXMLWithPull(xmlData: String) {
        try{
            val factory = XmlPullParserFactory.newInstance()
            val xmlPullParser = factory.newPullParser()
            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(keyMain,"id is $id")
                            Log.d(keyMain,"name is $name")
                            Log.d(keyMain,"version is $version")
                        }
                    }
                }
                eventType=xmlPullParser.next()
            }
        }catch (e:Exception){
            e.printStackTrace()
        }
    }

    private fun showResponse(response: String) {
        runOnUiThread{
            //在这里进行UI操作
            responseText.text = response
        }
    }


}

下面是核心代码的讲解,其他部分和OkHttp的内容是一样的

    private fun sendRequestWithOkHttp() {
        thread {
            try {
                val client = OkHttpClient()
                val request = Request.Builder()
                    //这里吧HTTP请求·的地址改成了下面这个xml
                    //指定访问服务器地址是计算机本机
                    .url("http://10.0.2.2/get_data.xml")
                    //10.0.2.2对于模拟器来说就是计算机本机的IP地址
                    .build()
                val response = client.newCall(request).execute()
                val responseData= response.body?.string()
                if(responseData!=null){
//                    showResponse(responseData)
                    //解析XML,不再用展示
                    parseXMLWithPull(responseData)
                }
            }catch (e:Exception){
                e.printStackTrace()
            }
        }
    }

    private fun parseXMLWithPull(xmlData: String) {
        try{
            //首先创建XmlPullParserFactory实例
            val factory = XmlPullParserFactory.newInstance()
            //接住实例得到XmlPullParser对象
            val xmlPullParser = factory.newPullParser()
            //调用setInput方法将服务器返回的XML数据设置进去
            xmlPullParser.setInput(StringReader(xmlData))
            //解析的过程当中可以通过getEventType获取当前解析的事件
            var eventType = xmlPullParser.eventType
            var id =""
            var name =""
            var version = ""
            //然后在while循环当中不断解析,如果解析不等于XmlPullParser.END_DOCUMENT说明解析工作还没有完成那个,调用next方法
            while (eventType!=XmlPullParser.END_DOCUMENT){
                //获取当前节点的名字
                val nodeName = xmlPullParser.name
                when(eventType){
                    //开始解析某个节点
                    XmlPullParser.START_TAG->{
                        when(nodeName){
                            //发现对应的就调用nextText方法来获取节点内的具体内容
                            "id" -> id =xmlPullParser.nextText()
                            "name"-> name=xmlPullParser.nextText()
                            "version" -> version=xmlPullParser.nextText()
                        }
                    }
                    //完成某个节点的解析
                    XmlPullParser.END_TAG ->{
                        //每当解析完一个app将其打印出来
                        if("app"==nodeName){
                            Log.d(keyMain,"id is $id")
                            Log.d(keyMain,"name is $name")
                            Log.d(keyMain,"version is $version")
                        }
                    }
                }
                eventType=xmlPullParser.next()
            }
        }catch (e:Exception){
            e.printStackTrace()
        }
    }

为了能让程序使用HTTP,我们还需要进行如下配置

  • 在res文件目录下新建xml文件夹,新建一个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>

这个文件的意思就是允许我们以明文的方式在网络上传播数据,而HTTP使用的就是明文传输

还有记得注册文件:(最后那句!)

<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">

SAX方法

虽然比Pull方法复杂一些,但是语义方面会更加清楚。

具体内容见课本p441页!!!

解析JSON文件和GSON文件

这个在之前的SpringBoost和Java的课程就有所介绍,因此这里也不展开来讲了

具体内容见课本p444页!!!

网络请求回调

其实我们之前的OkHttp和HttpURLConnection都是很有问题的,因为一个应用程序很可能会在许多地方都使用网络功能,而发送HTTP请求的代码基本是相同的,如果每次都编写,是很麻烦的。因此我们会写一些接口来解决。

object HttpUtil {
    fun sendHttpRequest(address: String):String{
        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)

                }
            }
            return response.toString()
        }catch (e: Exception) {
            e.printStackTrace()
            return e.message.toString()
        }finally {
            connection?.disconnect()
        }
    }
}

因此,每当发起一条HTTP请求的时候就可以写成:

val address="https://www.baidu.com"
val response = HttpUtil.sendHttpRequest(address)

但是网络请求一般情况下都是好事的操作,而上面这个方法的内部,并没有开启一个线程,就可能导致主线程被阻塞

  • 但这个方法里面开启一个线程来发起HTTP请求,服务器响应的数据是没有办法进行返回的,由于耗时逻辑在至县城里面,这个方法会在服务器还没来得及响应的时候就执行结束了,当然也就无法返回响应数据了

在这里我们就需要运用到编程语言的回调机制来解决了

  • 首先我们需要定义一个接口
interface HttpCallbackListener {
    fun onFinish(response:String)
    fun onError(e:Exception)
}

response:String 代表服务器返回的数据

e:Exception 记录错误的详细信息

  • 接着修改我们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)

                    }
                }
                //回调onFinishI()方法
                listener.onFinish(response.toString())
            }catch (e: Exception) {
                e.printStackTrace()
                //回调onError()方法
                listener.onError(e)
            }finally {
                connection?.disconnect()
            }
        }
    }
}
  1. 我们多了一个HttpCallbackListener的参数,并在方法的内部开启了一个子线程
  2. 然后在子线程当中执行具体的网络操作
    • 注意:子线程是无法通过return语句返回数据的,因此我们将服务器响应的数据传入onFinish()方法
    • 异常传入onError()方法
  3. 现在两个参数当中,在调用的时候,还需要传入一个HttpCallbackListener的实例
  • 调用如下(记得传入一个实例)
HttpUtil.sendHttpRequest(address,object : HttpCallbackListener{
            override fun onFinish(response:String){
                //得到服务器返回的内容
            }
            override fun onError(e:Exception){
                //对异常情况进行处理
            }
        })

当服务器成功响应的时候,就可以在onFinish方法中响应数据进行处理了

我们看到,使用HttpURLConnection好像挺麻烦的,那么OkHttp就会非常简单了

object HttpUtil {
    fun sendOkHttpRequest(address: String,callback:okhttp3.Callback){
        val client = OkHttpClient()
        val request = Request.Builder().url(address).build()
        client.newCall(request).enqueue(callback)
    }
}

可以看到,这里没有像以前一样使用execute(),而是调用enqueue方法,并把okhttp3.Callback参数传入,相信你可以看出来了,exqueue内部已经帮我们开好子线程了,然后会在子线程中执行HTTP请求,并将最终的请求结果回调到okhttp3.Callback当中

调用如下:

HttpUtil.sendOkHttpRequest("https://www.baidu.com", object : Callback {
    override fun onFailure(call: Call, e: IOException) {
        //对异常进行处理
    }

    override fun onResponse(call: Call, response: Response) {
        //得到服务器返回的具体内容
        val responseData = response.body?.string()
    }
})

记住,无论你用哪个,最终的回调接口都还是在子线程中运行的,不能进行UI操作,除非记住runOnUiThread()方法进行线程转换

Retrofit(最好用的网络库)

同样是Square公司开发的网络库,但它的定位于OkHttp完全不同。OkHttp侧重于底层的通信实现,Retrofit侧重于上层接口的封装,使得我们可以用面向对象的思维进行网络操作。详细你可以见它的git护不住也

  • 我们新建一个RetrofitTest项目来试一下吧

基本用法

首先我们先来谈一下,Retrofit的基本设计思想

它的设计是基于以下几个事实的。

  1. 同一款应用程序中所发起的网络请求绝大多数指向的是同一个服务器域名。这个很好理解,因为任何公司的客户端和服务器都是配套的,很难想象一个客户端去多个服务器获取不同的数据
  2. 服务器提供的接口通常是可以根据功能来柜内的。
    • 新增用户,修改,查询可以归成一类
    • 上架书本,销售书本,查询可供销售的书本可以归成一类
  3. 最后,开发者肯定更加习惯于迪奥哟经一个接口,获取它的返回值的编码方式,担当调用的是服务器的接口时候,很难想象想象该如何使用这样的编码方式

Retrofit就是基于以上的事实来进行设计的

  1. 首先我们可以配置好一个根路径,然后在指定服务器接口地址时只需要使用相对路径即可,这样就不用每次都指定完整的URL地址了
  2. 允许我们对服务器接口进行归类,将功能同属一类的接口定义到同一个接口文件当中,从而使得代码更加合理
  3. 我们完全不关心网络通信的细节,只需要在接口文件中声明一些列方法和返回值,然后通过注解的方式制定该方法对应哪个服务器接口以及需要的参数
    • 当我们程序调用该方法的时候,Retrofit会自动的向对应服务器接口发起请求,并将响应的数据解析成返回值声明的类型,这使得我们可以用面向对象的思维来进行操作

下面,我们快速体验一下吧

  • dependencies闭包中添加下面内容:
implementation 'com.squareup.retrofit2:retrofit:2.6.1'
implementation 'com.squareup.retrofit2:converter-gson:2.6.1'

上面第一条依赖会将OkHttp,Okio这几个库都一起下载下来

因为是Android开发,理所当然使用GSON

  • 我们继续使用书本上JSON当中的数据接口,由于Retrofit会借助GSON将JSON数据转换成对象,因此在这里同样需要新增一个App类,加入属性字段
class App(val id: String,val name:String,val version:String) 
  • 接下来,我们可以根据服务器接口的功能进行归类,创建不同种类的接口文件,并在其中定义对应具体服务器接口方法。不过我们之前Apache服务器上面其实只有一个获取JSON数据的接口(书本上JSON部分,这里没讲解了,可回去看书),因此这里只需要定义一个接口文件,包含一个方法即可:
interface AppService {
    @GET("get_data.json")
    fun getAppData():Call<List<App>>
}

通常命名都是具体功能种类名开头+Service结尾

  • 发起一条GET请求,请求地址就是我们在@GET注释中传入的具体参数。注意这里只需要传入请求地址的相对路径即可,根路径我们会在后面补上!
  • getAppData方法返回值必须声明为Call类型,并通过泛型来制定服务器响应的数据应该转换成什么对象。由于服务器响应的是一个包含App数据的JSON数,因此这里我们将泛型声明为List。
  • 当然Retrofit还提供了强大的Call Adapters功能来语序我们自定义方法返回值的类型,但暂时这个不在讨论范围之类
  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Get App Data"
        android:textAllCaps="false"
        android:id="@+id/getAppData"/>

</LinearLayout>
  • MainActivity
package com.workaholiclab.retrofittest

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.android.synthetic.main.activity_main.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class MainActivity : AppCompatActivity() {
    private val mkey ="MainActivity"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        getAppData.setOnClickListener { 
            val retrofit = Retrofit.Builder().baseUrl("http://10.0.2.2/").addConverterFactory(GsonConverterFactory.create()).build()
            val appService = retrofit.create(AppService::class.java)
            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(mkey,"id is ${app.id}")
                            Log.d(mkey,"name is ${app.name}")
                            Log.d(mkey,"version is ${app.version}")
                        }
                    }
                }

            })
        }
    }
}
  • 最后别忘了加上network_config.xml文件和注册文件!
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.workaholiclab.retrofittest">

    <uses-permission android:name="android.permission.INTERNET"/>
    <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">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

处理复杂的接口地址类型

上一小节,我们通过实例程序向一个非常简单的服务器接口地址发送请求:http:/10.0.2.2/get_data.jason.

然而在真实开发的过程当中,我们服务器提供的接口地址不可能一直那么简单,可能是千变万化的,下面来看看Retrofit如何来解决吧

  • 先定义一个Data类
class Data(val id:String,val name:String, val version:String)
  • 传入不同的page,GET http:/10.0.2.2//get_data.jason.
interface ExampleService {
    @GET("{page}/get_data_json")
    fun getData(@Path("page")page:Int): Call<Data>
}
  • http://10.0.2.2/get_data.jason?u=&t=

多个参数用&分割开来

@GET("get_data.json")
fun getData2(@Query("u")user:String,@Query("t")token:String):Call<Data>
  • HTTP不单只有GET请求,还有POST(提交),PUT & PATCH(修改服务器数据),DELETE}(删除)

  • Delete http://example.com/data/

@DELETE("data/{id}")
fun deleteData(@Path("id")id:String):Call<ResponseBody>

为什么泛型指定为ResponseBody,其实除了GET这种方法,其他方法对服务器上的数据都不关心,用ResponseBody表示可以接收任意类型的响应数据,并且不会对响应数据进行解析

  • POST一条数据上去
@POST("data/create")
fun postData(@Body data:Data):Call<ResponseBody>

Retrofit会自动将Data对象自动转化成为JSON数据,并放到HTTP的body部分

  • 有些服务器接口还要求我们在HTTP请求的header中指定参数

GET http://example.com/get_data.json

User-Agent: okhttp

Cache-Control: max-age=0

  • 静态指定header值写法
@Headers("User-Agent:okhttp","Cache-Control:max-age=0")
@GET("get_data.json")
fun getData3():Call<Data>
  • 动态指定header值写法
@GET("get_data.json")
fun getData4(@Header("User-Agent")userAgent:String,
             @Header("Cache-Control")cacheControl:String):Call<Data>

Retrofit构建器的最佳写法

我们之前获取Service的接口的动态代理对象实在是太麻烦了

  • 之前写法如下面所示:
Retrofit.Builder().baseUrl("http://10.0.2.2/")
				.addConverterFactory(GsonConverterFactory.create()).build()
val appService = retrofit.create(AppService::class.java)

由于Retrofit对象是全局通用的,只需要在调用create方法是针对不同的Service接口传入响应的Class类型即可,因此我们把这一部分的功能封装起来

object ServiceCreator {
    private const val BASE_URL="http://10.0.2.2/"
    
    private val retrofit = Retrofit.Builder().baseUrl(BASE_URL).addConverterFactory(GsonConverterFactory.create()).build()
    
    fun <T> create(serviceClass: Class<T>):T= retrofit.create(serviceClass)
}

上面代码仍然有优化空间,我们采用泛型实化来优化,

  • 优化:
object ServiceCreator {
    private const val BASE_URL="http://10.0.2.2/"

    private val retrofit = Retrofit.Builder().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)
}
  • 调用
val appService = ServiceCreator.create(AppService::class.java)

协程编写高效并发文件

详见课本p461页!!!

网络技术博客完更

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值