网络
WebView的用法
借助它我们就可以 在自己的应用程序里嵌入一个浏览器,从而非常轻松地展示各种各样的网页。
新建一个 WebViewTest项目,然后修改activity_main.xml中的代码,如下所示:
<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>
|
然后修改MainActivity中的代码,如下所示:
class MainActivity : AppCompatActivity(){ override fun onCreate(savedInstanceState : Bundle?){ super.onCreate(savedInstanceState) val binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) val webView = binding.webView webView.settings.javaScriptEnabled = true webView.webViewClient = WebViewClient() webView.loadUrl("https://www.baidu.com") } }
|
调用了setJavaScriptEnabled()方法,让 WebView支持JavaScript脚本。
WebView的setWebViewClient()方法,并传入 了一个WebViewClient的实例。这段代码的作用是,当需要从一个网页跳转到另一个网页时, 我们希望目标网页仍然在当前WebView中显示,而不是打开系统浏览器。
另外还需要注意,由于本程序使用到了网络功能,而访问网络是需要声明权限的,因此我们还 得修改AndroidManifest.xml文件,并加入权限声明,如下所示:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.webviewtest">
<uses-permission android:name="android.permission.INTERNET" />
... </manifest>
|
使用HTTP访问网络
使用HttpURLConnection
首先需要获取HttpURLConnection的实例,一般只需创建一个URL对象,并传入目标的网络地 址,然后调用一下openConnection()方法即可,如下所示:
val url = URL("https://www.baidu.com") val connection = url.openConnection() as HttpURLConnection
|
在得到了HttpURLConnection的实例之后,我们可以设置一下HTTP请求所使用的方法。常用 的方法主要有两个:GET和POST。GET表示希望从服务器那里获取数据,而POST则表示希望提 交数据给服务器。写法如下:
connnection.requestMethod = "GET"
|
接下来就可以进行一些自由的定制了,比如设置连接超时、读取超时的毫秒数,以及服务器希 望得到的一些消息头等。这部分内容根据自己的实际情况进行编写,示例写法如下:
connection.connectTimeout = 8000 connection.readTimeout = 8000
|
之后再调用getInputStream()方法就可以获取到服务器返回的输入流了,剩下的任务就是对 输入流进行读取:
val input = connection.inputStream
|
最后可以调用disconnect()方法将这个HTTP连接关闭:
下面就让我们通过一个具体的例子来真正体验一下HttpURLConnection的用法。新建一个 NetworkTest项目,首先修改activity_main.xml中的代码,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="matcha_parent"> <Button android:id="@+id/secdRequestBtn" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Send Request" /> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/responseText" android:layout_width="match_parent" andorid:layout_height="wrap_content" /> </ScrollView>
</LinearLayout>
|
ScrollView。借助ScrollView控件,我们就可以以 滚动的形式查看屏幕外的内容。
接着修改MainActivity中的代码,如下所示:
class MainActivity : AppCompatActivity(){ private lateinit var binding : ActivityMainBinding override fun onCreate(savedInstanceState:Bundle?){ super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) val sendRequest = binding.sendRequest sendRequest.setOnClickListener{ Log.d("MainActivity",Click) 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 connnection.readTimeout = 8000 val input = connection.inputStream val reader = BufferedReader(InputStreamReader(intput)) reader.use{ reader.forEachLine{ response.append(it) } } showResponse(response.toString()) }catch(e:Exception){ e.printStackTrace() }finally{ connection?.disconnect() } } } private fun showResponse(response:String){ runOnUiThread{ val responseText = bindding.responseText responstText.text = response } } }
|
完整的流程就是这样。不过在开始运行之前,仍然别忘了要声明一下网络权限
如果想要提交数据给服务器, 只需要将HTTP请求的方法改成 POST,并在获取输入流之前把要提交的数据写出即可,注意,每条数据都要以键值对的形式存 在,数据与数据之间用“&”符号隔开。比如说我们想要向服务器提交用户名和密码,就可以这样 写:
connection.requestMethod = "POST" val output = DataOutputStream(connection.outputStream) output.writeBytes("username=admin&password=123456")
|
使用OkHttp
在使用OkHttp之前,我们需要先在项目中添加OkHttp库的依赖。编辑app/build.gradle文 件,在dependencies闭包中添加如下内容
dependencies { ... implementation("com.squareup.okhttp3:okhttp:4.9.0") }
|
首先需要创建一个OkHttpClient的实例,如下所示:
val client = OkHttpClient()
|
接下来如果想要发起一条HTTP请求,就需要创建一个Request对象:
val request = Request.Builder().build()
|
当然,上述代码只是创建了一个空的Request对象,并没有什么实际作用,我们可以在最终的 build()方法之前连缀很多其他方法来丰富这个Request对象。比如可以通过url()方法来设 置目标的网络地址,如下所示:
val reuqest = Request.Builder() .url("https://www.baidu.com") .build()
|
之后调用OkHttpClient的newCall()方法来创建一个Call对象,并调用它的execute()方法 来发送请求并获取服务器返回的数据,写法如下:
val response = client.newCall(request).execute()
|
Response对象就是服务器返回的数据了,我们可以使用如下写法来得到返回的具体内容:
val responseData = response.body?.string()
|
如果是发起一条POST请求,会比GET请求稍微复杂一点,我们需要先构建一个Request Body 对象来存放待提交的参数,如下所示:
val requestBody = FormBody.Builder() .add("username","admin") .add("password","123456") .build()
|
然后在Request.Builder中调用一下post()方法,并将RequestBody对象传入:
val request = Request.BUilder() .url("https://www.baidu.com") .post(requestBody) .build()
|
接下来的操作就和GET请求一样了,调用execute()方法来发送请求并获取服务器返回的数据 即可
好了,OkHttp的基本用法就先学到这里,在本章的稍后部分我们还会学习OkHttp结合Retrofit 的使用方法,到时候再进一步学习。那么现在我们先把NetworkTest这个项目改用OkHttp的方 式再实现一遍吧。
由于布局部分完全不用改动,所以直接修改MainActivity中的代码,如下所示:
class MainActivity : AppCompatActivity(){ private lateinte var binding : ActivityMainBinding override fun onCreate(savedInstanceState:Bundle?){ super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) val sendRequest = binding.sendRequestBtn sendRequest.setOnClickListener{ 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 = reponse.body?.string() if(responseData!=null){ showResponseData(responseData) }catch(e:Exception){ e.printStackTrace() } } } } private fun showResponseData(response:String){ runOnUiThread{ val responseText = binding.responseText responseText.text = response } } }
|
解析JSON格式数据
进入D:\Apache\htdocs目录下,在这里新建一个名为get_data.xml的文件,然后编辑 这个文件,并加入如下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>
|
Pull解析方式
class MainActivity : AppCompatActivity(){ 、、、 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 = reponse.body?.string() if(responseData != null){ parseXMLWithPull(responseData) } }catch(e:Exception){ e.printStackTrace() } } } 、、、 private fun parseXMLWithPull(xmlData:String){ try{ val factory = XmlPullParserFactory.newInstance() val xmlPullParser = factory.newPullParser() xmlPullParse.setInput(StringReader(xmlData)) var eventType = xmlPullParser.eventType var id = "" var name = "" var version = "" while(eventTyep != 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 -> { ifd("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() } } }
|
可以看到,这里首先将HTTP请求的地址改成了http://10.0.2.2/get_data.xml,10.0.2.2对于 模拟器来说就是计算机本机的IP地址。在得到了服务器返回的数据后,我们不再直接将其展示, 而是调用了parseXMLWithPull()方法来解析服务器返回的数据。
为了能让程序使用HTTP,我们还要进行如下配置才可以。右击res目录 →New→Directory,创建一个xml目录,接着右击xml目录→New→File,创建一个 network_config.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>
|
接下来修改AndroidManifest.xml中的代码来启用我们刚才创建的配置文件:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.networktest"> ... <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> </manifest>
|
尝试用SAX解析的方式来实现和上一小节同样的功能吧。新建一个 ContentHandler类继承自DefaultHandler,并重写父类的5个方法,如下所示:
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 = StirngBuilder() } 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" -> id.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) } } override fun endDocument(){ } }
|
修改MainActivity中的代码,如下所示:
class MainActivity : AppCompatActivity(){ 、、、 private fun sendRequestWithOkHttp(){ thread{ val client = OkHttpClient() val request = Requset.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){ parseXMLWithSAX(responseData) } } } 、、、 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() } } }
|
解析JSON格式数据
在C:\Apache\htdocs目录中新建一个get_data.json的文件,然后编辑这个文件,并加入如下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"}]
|
使用JSONObject
修改MainActivity中的代码
class MainActivity : AppCompatActivity(){ private fun sendRequestWithOkHttp(){ thread{ try{ val client = OkHttpClient() val request = Request.Builder() .url("http://10.0.2.2/get_data.json") .build() val response = client.newCall(request).execute() val responseData = response.body?.string() if(responseData != null){ praseJSONWithJSONObject(responseData) } }catch(e:Exception){ e.printStackTrace() } } } 、、、 private fun parseJSONWithJSONObject(jsonData: String){ try{ val jsonArray = JSONArray(jsonData) for(i in 0 ntil 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() } } }
|
将服务器返回的数据传入一 个JSONArray对象中。然后循环遍历这个JSONArray,从中取出的每一个元素都是一个 JSONObject对象,每个JSONObject对象中又会包含id、name和version这些数据。接下来 只需要调用getString()方法将这些数据取出
使用GSON
编辑app/build.gradle文件,在dependencies闭包中添加如 下内容:
implementation("com.google.code.gson:gson:2.8.8")
|
比如说一段JSON格式的数据如下所示:
那我们就可以定义一个Person类,并加入name和age这两个字段,然后只需简单地调用如下代 码就可以将JSON数据自动解析成一个Person对象了:
val gson = Gson() val person = gson.fromJson(jsonData, Person::class.java)
|
如果需要解析的是一段JSON数组,会稍微麻烦一点,比如如下格式的数据:
[{"name":"Tom","age":20}, {"name":"Jack","age":25}, {"name":"Lily","age":22}]
|
这个时候,我们需要借助TypeToken将期望解析成的数据类型传入fromJson()方法中,如下 所示:
val typeOf = object : TypeToken<List<Person>>() {}.type val people = gson.fromJson<List<Person>>(jsonData, typeOf)
|
好了,基本的用法就是这样,下面就让我们来真正地尝试一下吧。首先新增一个App类,并加入 id、name和version这3个字段,如下所示:
class App(val id: String, val name: String, val version: String)
|
然后修改MainActivity中的代码,如下所示:
class MainActivty : AppCompatActivity(){ 、、、 private fun sendRequestWithOkHttp(){ thread{ try{ val client = OkHttpClient() val request = Request.Builder() .url("http://10.0.2.2/get_data.json") .build() val response = client.newCall(request).execute() val responseData = response.body?.string() if(responseData != null){ praseJSONWithJSONObject(responseData) } }catch(e:Exception){ e.printStackTrace() } } } 、、、 private fun parseJSONWithGSON(jsonData:String){ val gson = Gson() 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}") } } }
|
Retrofit
新建一个RetrofitTest项目
Retrofit的基本用法
要想使用Retrofit,我们需要先在项目中添加必要的依赖库。编辑app/build.gradle文件,在 dependencies闭包中添加如下内容:
implementation("com.squareup.retrofit2:retrofit:2.6.1") implementation ("com.squareup.retrofit2:converter-gson:2.6.1")
|
新增一个App类,并加入id、name和version这3个字段,如下 所示:
class App(val id: String, val name: String, val version: String)
|
接下来,我们可以根据服务器接口的功能进行归类,创建不同种类的接口文件,并在其中定义 对应具体服务器接口的方法。不过由于我们的Apache服务器上其实只有一个获取JSON数据的 接口,因此这里只需要定义一个接口文件,并包含一个方法即可。新建AppService接口,代 码如下所示:
interface AppService{ @GET("get_data.json") fun getAppData():Call<List<App>> }
|
上述代码中有两点需要我们注意。第一就是在getAppData()方法上面添加的注解,这里使用 了一个@GET注解,表示当调用getAppData()方法时Retrofit会发起一条GET请求,请求的地 址就是我们在@GET注解中传入的具体参数。注意,这里只需要传入请求地址的相对路径即可, 根路径我们会在稍后设置。
第二就是getAppData()方法的返回值必须声明成Retrofit中内置的Call类型,并通过泛型来 指定服务器响应的数据应该转换成什么对象。由于服务器响应的是一个包含App数据的JSON数 组,因此这里我们将泛型声明成List。当然,Retrofit还提供了强大的Call Adapters功 能来允许我们自定义方法返回值的类型,比如Retrofit结合RxJava使用就可以将返回值声明成 Observable、Flowable等类型,不过这些内容就不在本节的讨论范围内了。
修改activity_main.xml中的代码,如下所示:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" >
<Button android:id="@+id/getAppDataBtn" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Get App Data" />
</LinearLayout>
|
很简单,这里在布局文件中增加了一个Button控件,我们在它的点击事件中处理具体的网络请 求逻辑即可。
现在修改MainActivity中的代码,如下所示:
package com.example.retrofittest
import android.os.Bundle import android.util.Log import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import com.example.retrofittest.databinding.ActivityMainBinding import retrofit2.Call import retrofit2.Callback import retrofit2.Response import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import retrofit2.create
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) val getAppDataBtn = binding.getAppDataBtn getAppDataBtn.setOnClickListener { Log.d("MainActivity", "Button clicked, initiating network request.") 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 onResponse(call: Call<List<App>>, response: Response<List<App>>) { if (response.isSuccessful) { 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}") } } else { Log.e("MainActivity", "Response body is null") } } else { Log.e("MainActivity", "Response failed with code: ${response.code()}") } } override fun onFailure(call: Call<List<App>>, t: Throwable) { Log.e("MainActivity", "Request failed", t) } }) } } }
|
从NetworkTest项目中复制 network_config.xml文件到RetrofitTest项目当中,然后修改AndroidManifest.xml中的代码,以及申请网络权限
处理复杂的接口地址类型
为了方便举例,这里先定义一个Data类,并包含id和content这两个字段,如下所示:
class Data(val id: String, val content: String)
|
然后我们先从最简单的看起,比如服务器的接口地址如下所示:
这是最简单的一种情况,接口地址是静态的,永远不会改变。那么对应到Retrofit当中,使用如 下的写法即可:
interface ExampleService { @GET("get_data.json") fun getData(): Call<Data> }
|
但是显然服务器不可能总是给我们提供静态类型的接口,在很多场景下,接口地址中的部分内 容可能会是动态变化的,比如如下的接口地址:
在这个接口当中,部分代表页数,我们传入不同的页数,服务器返回的数据也会不同。 这种接口地址对应到Retrofit当中应该怎么写呢?其实也很简单,如下所示:
interface ExampleService { @GET("{page}/get_data.json") fun getData(@Path("page") page: Int): Call<Data> }
|
在@GET注解指定的接口地址当中,这里使用了一个{page}的占位符,然后又在getData()方 法中添加了一个page参数,并使用@Path(“page”)注解来声明这个参数。这样当调用 getData()方法发起请求时,Retrofit就会自动将page参数的值替换到占位符的位置,从而组 成一个合法的请求地址
另外,很多服务器接口还会要求我们传入一系列的参数,格式如下:
这是一种标准的带参数GET请求的格式。接口地址的最后使用问号来连接参数部分,每个参数都 是一个使用等号连接的键值对,多个参数之间使用“&”符号进行分隔。那么很显然,在上述地址 中,服务器要求我们传入user和token这两个参数的值。对于这种格式的服务器接口,我们可 以使用刚才所学的@Path注解的方式来解决,但是这样会有些麻烦,Retrofit针对这种带参数的 GET请求,专门提供了一种语法支持
interface ExampleService { @GET("get_data.json") fun getData(@Query("u") user: String, @Query("t") token: String): Call<Data> }
|
这里在getData()方法中添加了user和token这两个参数,并使用@Query注解对它们进行声 明。这样当发起网络请求的时候,Retrofit就会自动按照带参数GET请求的格式将这两个参数构 建到请求地址当中。
学习了以上内容之后,现在你在一定程度上已经可以应对千变万化的服务器接口地址了。不过 HTTP并不是只有GET请求这一种类型,而是有很多种,其中比较常用的有GET、POST、PUT、 PATCH、DELETE这几种。它们之间的分工也很明确,简单概括的话,GET请求用于从服务器获 取数据,POST请求用于向服务器提交数据,PUT和PATCH请求用于修改服务器上的数据, DELETE请求用于删除服务器上的数据。
而Retrofit对所有常用的HTTP请求类型都进行了支持,使用@GET、@POST、@PUT、@PATCH、 @DELETE注解,就可以让Retrofit发出相应类型的请求了。
比如服务器提供了如下接口地址:
这种接口通常意味着要根据id删除一条指定的数据,而我们在Retrofit当中想要发出这种请求就 可以这样写:
interface ExampleService { @DELETE("data/{id}") fun deleteData(@Path("id") id: String): Call<ResponseBody> }
|
这里使用了@DELETE注解来发出DELETE类型的请求,并使用了@Path注解来动态指定id,这些 都很好理解。但是在返回值声明的时候,我们将Call的泛型指定成了ResponseBody,这是什 么意思呢?
由于POST、PUT 、PATCH、DELETE这几种请求类型与GET请求不同,它们更多是用于操作服 务器上的数据,而不是获取服务器上的数据,所以通常它们对于服务器响应的数据并不关心。 这个时候就可以使用ResponseBody,表示Retrofit能够接收任意类型的响应数据,并且不会对 响应数据进行解析。
那么如果我们需要向服务器提交数据该怎么写呢?比如如下的接口地址:
POST http: {"id": 1, "content": "The description for this data."}
|
使用POST请求来提交数据,需要将数据放到HTTP请求的body部分,这个功能在Retrofit中可 以借助@Body注解来完成:
interface ExampleService { @POST("data/create") fun createData(@Body data: Data): Call<ResponseBody> }
|
可以看到,这里我们在createData()方法中声明了一个Data类型的参数,并给它加上了 @Body注解。这样当Retrofit发出POST请求时,就会自动将Data对象中的数据转换成JSON格 式的文本,并放到HTTP请求的body部分,服务器在收到请求之后只需要从body中将这部分数 据解析出来即可。这种写法同样也可以用来给PUT、PATCH、DELETE类型的请求提交数据。
最后,有些服务器接口还可能会要求我们在HTTP请求的header中指定参数,比如:
GET http: User-Agent: okhttp Cache-Control: max-age=0
|
这些header参数其实就是一个个的键值对,我们可以在Retrofit中直接使用@Headers注解来 对它们进行声明
interface ExampleService {
@Headers("User-Agent: okhttp", "Cache-Control: max-age=0") @GET("get_data.json") fun getData(): Call<Data> }
|
但是这种写法只能进行静态header声明,如果想要动态指定header的值,则需要使用 @Header注解,如下所示:
interface ExampleService { @GET("get_data.json") fun getData(@Header("User-Agent") userAgent: String, @Header("Cache-Control") cacheControl: String): Call<Data>
}
|
Retrofit构造器的最佳写法
学到这里,其实还有一个问题我们没有正视过,就是获取Service接口的动态代理对象实在是 太麻烦了。先回顾一下之前的写法吧,大致代码如下所示:
val retrofit = Retrofit.Builder() .baseUrl("http://10.0.0.2/") .addConverterFactory(GsonConverterFactory.create()) .build() val appService = retrofit.create(AppService::class.java)
|
我们想要得到AppService的动态代理对象,需要先使用Retrofit.Builder构建出一个 Retrofit对象,然后再调用Retrofit对象的create()方法创建动态代理对象。如果只是写一次 还好,每次调用任何服务器接口时都要这样写一遍的话,肯定没有人能受得了。
事实上,确实也没有每次都写一遍的必要,因为构建出的Retrofit对象是全局通用的,只需要在 调用create()方法时针对不同的Service接口传入相应的Class类型即可。因此,我们可以 将通用的这部分功能封装起来,从而简化获取Service接口动态代理对象的过程。
新建一个ServiceCreator单例类,代码如下所示:
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成为了一个单例类,并在它的内部定义了一 个BASE_URL常量,用于指定Retrofit的根路径。然后同样是在内部使用Retrofit.Builder 构建一个Retrofit对象,注意这些都是用private修饰符来声明的,相当于对于外部而言它们都 是不可见的。
最后,我们提供了一个外部可见的create()方法,并接收一个Class类型的参数。当在外部调 用这个方法时,实际上就是调用了Retrofit对象的create()方法,从而创建出相应Service接 口的动态代理对象。
经过这样的封装之后,Retrofit的用法将会变得异常简单,比如我们想获取一个AppService接 口的动态代理对象,只需要使用如下写法即可:
val appService = ServiceCreator.create(AppSerVice::class.java)
|
泛型实化功能
object ServiceCreator { ... inline fun <reified T> create(): T = create(T::class.java) }
|
val appService = ServiceCreator.create<AppService>()
|