Android网络工具概述。接收、发送、检查和模拟服务器
当应用程序很复杂,有很多对网络的请求时,各种工具和库可以帮助你与网络打交道并搜索问题。
今天,所有的应用程序都以这样或那样的方式与互联网相连。在开发简单的应用程序时,作为一项规则,没有任何问题。另一件事是当应用程序很复杂,对网络有许多请求时。在这种情况下,各种工具和库可以帮助你在网络上工作并寻找问题。我们将在这篇文章中研究它们。我希望你会发现一些新的东西,尽管有些东西对你来说似乎很熟悉。
备注和假设
首先,我想提请你注意一些评论和假设:
- 在这篇文章中,我们将看看具体的例子,这些例子的代码可以插入到Android Studio中,运行并看看会发生什么。
- 本文将重点关注通过HTTP/HTTPS与互联网的合作。我们将不考虑WebSocket 和流媒体协议。
- 此外,我们也不会考虑特定类型的数据库,例如,像glide 和picasso那样的下载图片。
- 我们将从简单的事情逐渐复杂化。
- 代码不会根据MVP和MVVM模式等被分成若干层。这样做是为了简化例子和文章的整体内容。
- 不要忘记把权限放在清单中*。*
- 这篇文章是为中级以上的开发者准备的,所以我们不会纠缠于非常简单的事情;出于同样的原因,我们也不会去研究每一个例子或库。我们只对这些工具进行概述,以拓宽你的视野。
在Android上接收和发送数据的方法
选择一个例子
例如,我们将使用GitHub上的谷歌账户,因为该API是开放的,不需要额外的token请求和其他凭证。
让我们进入该页面。这就是它的样子。
注意有心的描述。"谷歌❤️开源"。现在让我们转到这个页面的API。
它看起来是这样的。
注意描述中的心脏:它是存在的,一切都很正常,而且数据是对应的。
我们已经选择了与一个开放的API合作。现在我们用它来测试下面要讨论的工具。
HttpUrlConnection
让我们在安卓设备上获得这个字符串。首先,让我们采取本地 HttpUrlConnection 工具,并尝试使用它。
我们不需要添加任何库。
创建一个活动,并把下面的代码放在里面。
Kotlin
class HttpUrlConnectionActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Thread { request() }.start()
}
private fun request() {
val url = URL("https://api.github.com/orgs/google")
val connection = url.openConnection() as HttpURLConnection
val response = connection.inputStream.bufferedReader().use(BufferedReader::readText)
Log.d("HttpExample", "response: $response")
}
}
运行该应用程序并进入LogCat。
D: response: {“login”:”google”,”id”:1342004,”node_id”:”MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=”,”url”:”https://api.github.com/orgs/google","repos_url":"https://api.github.com/orgs/google/repos","events_url":"https://api.github.com/orgs/google/events","hooks_url":"https://api.github.com/orgs/google/hooks","issues_url":"https://api.github.com/orgs/google/issues","members_url":"https://api.github.com/orgs/google/members{/member}","public_members_url":"https://api.github.com/orgs/google/public_members{/member}","avatar_url":"https://avatars.githubusercontent.com/u/1342004?v=4","description":"Google ❤️ Open Source”,”name”:”Google”,”company”:null,”blog”:”https://opensource.google/","location":null,"email":"opensource@google.com","twitter_username":"GoogleOSS","is_verified":true,"has_organization_projects":true,"has_repository_projects":true,"public_repos":2237,"public_gists":0,"followers":0,"following":0,"html_url":"https://github.com/google","created_at":"2012-01-18T01:30:18Z","updated_at":"2021-12-30T01:40:20Z","type":"Organization"}
在日志中,我们得到了和浏览器中一样的行。注意有心的那一行。正如你所看到的,只花了5-10行代码就得到了一个响应。
这个例子并没有处理来自服务器的错误和响应。我们敢打赌,一切工作都很正常。
优点和缺点。
- + 这个本地工具总是可以使用的。
- + 不需要拉动额外的库。
- + 安卓系统从第一天起就存在,所以一切都尽可能的测试,所有的bug都被修复。
- - 难以用于捕获异常。
- - 难以用于建立复杂的查询和传递头文件。在这里,我们实际上是在处理字符串。
这个工具在一些库的引擎盖下,包括跨平台的库,所以要理解其工作的本质,你需要了解它。
现在让我们继续讨论那些本质上与本地HttpUrlConnection 工具做同样事情的库,但使开发者的生活更容易。
Okhttp
让我们从 Square 的Okhttp库开始。
让我们添加一个导入。
implementation ‘com.squareup.okhttp3:okhttp:4.0.1’
创建一个活动并插入以下代码。
Kotlin
class OkhttpActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Thread { request() }.start()
}
private fun request() {
val request = Request.Builder().url("https://api.github.com/orgs/google").build()
val response = OkHttpClient().newCall(request).execute().body?.string()
Log.d("HttpExample", "response: $response")
}
}
我们得到了与上一个版本中使用HttpUrlConnection时相同的带心响应。
D: response: {"login":"google","id":1342004,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=","url":"https://api.github.com/orgs/google","repos_url":"https://api.github.com/orgs/google/repos","events_url":"https://api.github.com/orgs/google/events","hooks_url":"https://api.github.com/orgs/google/hooks","issues_url":"https://api.github.com/orgs/google/issues","members_url":"https://api.github.com/orgs/google/members{/member}","public_members_url":"https://api.github.com/orgs/google/public_members{/member}","avatar_url":"https://avatars.githubusercontent.com/u/1342004?v=4","description":"Google ❤️ Open Source","name":"Google","company":null,"blog":"https://opensource.google/","location":null,"email":"opensource@google.com","twitter_username":"GoogleOSS","is_verified":true,"has_organization_projects":true,"has_repository_projects":true,"public_repos":2237,"public_gists":0,"followers":0,"following":0,"html_url":"https://github.com/google","created_at":"2012-01-18T01:30:18Z","updated_at":"2021-12-30T01:40:20Z","type":"Organization"}
你可以看到,答案又是在几行中给出的。从视觉上看,它与上一个版本没有什么不同。但这是在如此简单的情况下。在更复杂的情况下,差异将是明显的。这个库已经简化了头文件的传递方式。你可以在这里读到更多。
优点和缺点。
- + 传递头文件和正文的方法被简化了。
- - 额外的库稍微增加了应用程序的大小。
改造
让我们来看看Retrofit,它也来自Square。这个库所做的事情与 Okhttp 基本上是一样的。但当你需要进行复杂的 URL 查询时,它有明显的优化。
我们增加了依赖性。
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.1.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.8'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
请注意,除了 Retrofit 库之外,还添加了 RxJava 及其适配器。这是因为Retrofit给出的不是一个字符串,而是一个RxJava对象,比如Single。这使得多线程的工作更加容易。Retrofit还可以给出LiveData和Flow。
用以下代码制作一个Activity。
Kotlin
class RetrofitActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_retrofit)
findViewById<Button>(R.id.buttonActivityRetrofit).setOnClickListener {
request()
}
}
private fun request() {
Retrofit.Builder()
.baseUrl("https://api.github.com")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.build()
.create(RetrofitService::class.java)
.user
.subscribeOn(Schedulers.io())
.subscribe { response ->
Log.d("HttpExample", "response: $response")
}
}
interface RetrofitService {
@get:GET("/orgs/google")
val user: Single<String>
}
}
我们得到了答案。
D: response: {"login":"google","id":1342004,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=","url":"https://api.github.com/orgs/google","repos_url":"https://api.github.com/orgs/google/repos","events_url":"https://api.github.com/orgs/google/events","hooks_url":"https://api.github.com/orgs/google/hooks","issues_url":"https://api.github.com/orgs/google/issues","members_url":"https://api.github.com/orgs/google/members{/member}","public_members_url":"https://api.github.com/orgs/google/public_members{/member}","avatar_url":"https://avatars.githubusercontent.com/u/1342004?v=4","description":"Google ❤️ Open Source","name":"Google","company":null,"blog":"https://opensource.google/","location":null,"email":"opensource@google.com","twitter_username":"GoogleOSS","is_verified":true,"has_organization_projects":true,"has_repository_projects":true,"public_repos":2237,"public_gists":0,"followers":0,"following":0,"html_url":"https://github.com/google","created_at":"2012-01-18T01:30:18Z","updated_at":"2021-12-30T01:40:20Z","type":"Organization"}
正如你所看到的,Retrofit并没有插入整个URL。它被打散了。部分路径被移至RetrofitService接口。这既使开发在代码量方面变得复杂,又在进行复杂的URL查询时简化了开发。除此之外,结果都是一样的。
优点和缺点。
- + 可以转换为Gson,你需要;添加.addConverterFactory(GsonConverterFactory.create(gson))。
- + 赠送RxJava、liveData和Flow对象。要做到这一点,你需要添加,例如,RxJava2CallAdapterFactory.create()。
- +创建复杂的URL,并在那里传递查询参数和头信息,这就更容易了。
- - 更复杂的结构和更多的代码。
我们已经介绍了在Android上与网络打交道的最流行的方法。接下来,我们将看看从移动设备到服务器的数据路径的下一个阶段--代码检查的工具和库。
检查来自服务器的请求和响应的方法
Android Studio网络检查器
有些时候,代码中似乎没有问题,服务器也在工作,但没有达到预期的结果。或者错误来自服务器。也许问题出在头部或错误的URL链接上。为了检测这一点,你需要使用可以检查流量的工具--例如,Android Studio Network Inspector。
这是最容易检查的工具。它不需要任何额外的库和设置。其算法如下。
- 转到Android Studio中的应用检查选项卡。在以前的Android Studio版本中,Network Inspector位于Profiler标签中。
- 与你的设备建立一个连接。
- 在你的设备上,向服务器提出请求,例如,上面提到的那些请求之一。
- 用鼠标选择出现的曲线。
- 进入 "请求 "选项卡,查看请求参数。
- 转到 "响应 "选项卡,查看标题和传入的数据。
下面的截图显示,我们看到来自Github的响应与之前一样。
优点和缺点。
- + 不需要额外的库和设置。
- - 你不能记录请求和响应。
接下来,考虑一下HttpLoggingInterceptor 库,当你不仅要查看请求和响应,还要记录和存储它们时,就需要这个库。
HttpLoggingInterceptor
这是Square最受欢迎的检查库。
添加一个库。
implementation 'com.squareup.okhttp3:logging-interceptor:4.2.1'
用下面的代码创建一个Activity。
Kotlin
class HttpLoggingInterceptorActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
request()
}
private fun request() {
val interceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
override fun log(message: String) {
Log.i("HttpExample", "message: $message")
}
})
interceptor.level = HttpLoggingInterceptor.Level.BODY
val client: OkHttpClient = OkHttpClient.Builder()
.addInterceptor(interceptor)
.build()
Retrofit.Builder()
.baseUrl("http://api.github.com")
.client(client)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.build()
.create(RetrofitService::class.java)
.user
.subscribeOn(Schedulers.io())
.subscribe { response ->
Log.d("HttpExample", "response: $response")
}
}
interface RetrofitService {
@get:GET("/orgs/google")
val user: Single<String>
}
}
请看LogCat中的答案。
I: message: {"login":"google","id":1342004,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=","url":"https://api.github.com/orgs/google","repos_url":"https://api.github.com/orgs/google/repos","events_url":"https://api.github.com/orgs/google/events","hooks_url":"https://api.github.com/orgs/google/hooks","issues_url":"https://api.github.com/orgs/google/issues","members_url":"https://api.github.com/orgs/google/members{/member}","public_members_url":"https://api.github.com/orgs/google/public_members{/member}","avatar_url":"https://avatars.githubusercontent.com/u/1342004?v=4","description":"Google ❤️ Open Source","name":"Google","company":null,"blog":"https://opensource.google/","location":null,"email":"opensource@google.com","twitter_username":"GoogleOSS","is_verified":true,"has_organization_projects":true,"has_repository_projects":true,"public_repos":2237,"public_gists":0,"followers":0,"following":0,"html_url":"https://github.com/google","created_at":"2012-01-18T01:30:18Z","updated_at":"2021-12-30T01:40:20Z","type":"Organization"}
D: response: {"login":"google","id":1342004,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzNDIwMDQ=","url":"https://api.github.com/orgs/google","repos_url":"https://api.github.com/orgs/google/repos","events_url":"https://api.github.com/orgs/google/events","hooks_url":"https://api.github.com/orgs/google/hooks","issues_url":"https://api.github.com/orgs/google/issues","members_url":"https://api.github.com/orgs/google/members{/member}","public_members_url":"https://api.github.com/orgs/google/public_members{/member}","avatar_url":"https://avatars.githubusercontent.com/u/1342004?v=4","description":"Google ❤️ Open Source","name":"Google","company":null,"blog":"https://opensource.google/","location":null,"email":"opensource@google.com","twitter_username":"GoogleOSS","is_verified":true,"has_organization_projects":true,"has_repository_projects":true,"public_repos":2237,"public_gists":0,"followers":0,"following":0,"html_url":"https://github.com/google","created_at":"2012-01-18T01:30:18Z","updated_at":"2021-12-30T01:40:20Z","type":"Organization"}
我们得到了2个相同的日志。在这个简单的例子中,这可能看起来毫无意义。然而,当你有数以百计的请求分散在一千多个不同的文件中时,在应用程序中的一个位置记录请求和响应是有意义的。
优点和缺点。
- + 有可能积累日志。
- + 你可以编辑头文件。
- - 增加了应用程序的大小,像任何库一样。
所以,在HttpLoggingInterceptor库的帮助下,你不仅可以查看请求和响应,还可以将它们输出到LogCat中,如果有必要,还可以将它们写入文件或发送到服务器中。接下来,说说以前流行的Stetho工具。
Stetho
在Android Studio Network Inspector出现之前,Stetho被积极用于检查。这是一个来自Facebook的库。它本质上是Android Studio Network Inspector和HttpLoggingInterceptor的混合体。它可以将日志输出到Google Chrome浏览器和记录。我们将不详细讨论它,因为它正变得不受支持。最新版本的浏览器不能加载信息(见下面的截图),但仍然支持日志记录。也许你可以从它那里得到一些有用的功能。在这里阅读更多关于这个库的信息。
Wireshark
到目前为止,我们已经考虑了内部工具和库,也就是说,当一个应用程序的源代码可用时,可以应用这些工具。然而,有时会出现无法获得源代码的情况。Wireshark工具将帮助你解决这个问题。
我们将考虑一个没有加密的流量的例子,也就是HTTP而不是HTTPS。下面我将解释原因。
下载并安装 Wireshark。
接下来,进入AndroidManifest并添加一行。
<application
...
android:usesCleartextTraffic="true"
...
</application>
将地址改为api.github.com/orgs/google…
Kotlin
class WiresharkActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Thread { request() }.start()
}
private fun request() {
val request = Request.Builder().url("http://api.github.com/orgs/google").build()
val response = OkHttpClient().newCall(request).execute().body?.string()
Log.d("HttpExample", "response: $response")
}
}
打开Wireshark程序。找到你的网络并双击它。
在搜索框中,我们输入过滤词HTTP。我们就只剩下两行了。
接下来,在模拟器上运行该程序(!)。它不会在真实设备上工作。
我们看到有一个请求被发送到了api.github.com。然而,没有收到响应,因为301永久移动,因为流量被重定向到api.github.com,而且已经收到了来自它的响应。
在Wireshark中,你可以看到所有已经发送和接收的数据和头信息。对HTTPS也可以这样做。要做到这一点,你需要对流量进行解密。
优点和缺点
- + 你可以查看低层次的流量,精确到比特。
- + 你不需要访问应用程序的源代码。
- - 难以使用安全的HTTPS协议来进行解密。
- - 需要在模拟器上运行,因为它从电脑而不是移动设备上捕获流量。
- - 使用起来相当复杂。
这就结束了我们对检查工具的研究,接下来是在没有真正的服务器来测试应用程序的情况下创建模拟服务器的方法。
创建模拟服务器的方法
Postman
有时有必要测试与服务器的通信,当后端还没有准备好或没有从内部访问服务器来改变参数以找到错误。在这种情况下,你可以创建你自己的模拟服务器并使用它。
下载 邮递员 并运行它。要做到这一点,您需要登录您的帐户。
点击集合 标签,创建一个新的集合。
转到模拟服务器选项卡,选择已经创建的集合,创建一个新的模拟服务器。
接下来,你需要复制创建的服务器的URL。
回到 "集合 "标签,添加一个新的查询。
以下是这个例子的内容。
-
选择GET方法。
-
插入复制的URL。
-
添加一个斜线和一些后缀;例如,我把**/status**。
-
选择回答代码OK 200。
-
选择JSON响应类型。
-
插入任何JSON格式的答案,例如**{"status":"good"}。**
检查是否一切正常。要做到这一点。
-
回到 "请求 "选项卡。
-
在请求行中插入完整的URL,然后点击 "发送"。
-
在页面的底部,寻找答案。
这个服务器不仅在本地计算机上创建,而且在互联网上也可以使用。你可以通过在浏览器中粘贴一个字符串并看到答案来检查它。
现在剩下的就是在安卓设备上获取JSON。要做到这一点,在上面的例子中,OkhttpActivity插入了URL并重新启动了应用程序。
Kotlin
class PostmanActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Thread { request() }.start()
}
private fun request() {
val url = URL("https://7bb01ebf-d698-4c65-94c0-7625cf817d06.mock.pstmn.io/status")
val connection = url.openConnection() as HttpURLConnection
val response = connection.inputStream.bufferedReader().use(BufferedReader::readText)
Log.d("HttpExample", "response: $response")
}
}
我们得到了我们期望得到的答案。
D: response: {
"status": "good"
}
优点和缺点
- + 当prod-server还没有准备好的时候,你可以使用模拟服务器。
- + 当你需要改变服务器端的参数,但不能访问其代码时,可以使用。
- - 它是一个不完整的服务器,所以不可能完全模拟服务器的工作。例如,你不能发送照片和其他位数据。
正如你所看到的,Postman的模拟服务器功能在开发安卓应用的许多情况下都非常有用。
总结
所以今天的安卓开发者要求所有的应用程序都支持网络。一般来说,在简单的情况下,这并不困难。然而,在拥有100多万行代码和数百个不同请求的应用程序中,情况就完全不同了。在这种情况下,你必须使用各种技巧和非标准的方法来达到预期的效果。我在上面提供的库和工具,是为了帮助你解决一些问题。