App线上网络问题优化策略
在我们App开发过程中,网络是必不可少的,几乎很难想到有哪些app是不需要网络传输的,所以网络问题一般都是线下难以复现,一旦到了用户手里就会碰到很多疑难杂症,所以对于网络的监控是必不可少的,针对用户常见的问题,我们在实际的项目中也需要添加优化策略。
1 网络的基础优化
对于一些主流的网络请求框架,像OkHttp、Retrofit等,其实就是对Http协议做了封装,我们在使用的时候常见的就是POST或者GET请求,如果我们是做客户端开发,知道这些基本的内容好像也可以写代码,但是真正碰到了线上网络问题,反而摸不到头脑,其实最大的问题还是对于网络方面的知识储备不足,所以文章的开始,我们先来点基础的网络知识。
1.1 网络连接的类型
其实对于网络的连接,我们常见的就是向服务端发起请求,服务端返回对应的响应,但是在同一时刻,只能有一个方向的数据传输,这种连接方式称为半双工通信。
| 类型 | 描述 | 举例 |
|---|---|---|
| 单工 | 在通信过程中,数据只能由一方发送到另一方 | 常见的例如UDP协议;Android广播 |
| 半双工 | 在通信过程中,数据可以由一方A发送到另一方B,也可以从一方B发送到另一方A,但是同一时刻只能存在一方的数据传输 | 常见的例如Http协议 |
| 全双工 | 在任意时刻,都会存在A到B和B到A的双向数据传输 | 常见的例如Socket协议,长连接通道 |
所以在Http1.0协议时,还是半双工的协议,因为默认是关闭长连接的,如果需要支持长连接,那么就需要在http头中添加字段:“Connection:Keep-Alive”;在Http 1.1协议时,默认是开启了长连接,如果需要关闭长连接,那么需要添加http请求头字段:“Connection:close”.
那么什么时候或者场景下,需要用到长连接呢?其实很简单,记住一点即可,如果业务场景中对于消息的即时性有要求时,就需要与服务端建立长连接,例如IM聊天,视频通话等场景。
1.2 DNS解析
如果伙伴们在项目中有对网络添加trace日志,除了net timeout这种超时错误,应该也看到过UnknowHostException这种异常,这是因为DNS解析失败,没有解析获取到服务器的ip地址。
像我们在家的时候,手机或者电脑都会连接路由器的wifi,而路由器是能够设置dns服务器地址的,

但是如果设置错误,或者被攻击篡改,就会导致DNS解析失败,那么我们app的网络请求都会出现异常,所以针对这种情况,我们需要加上自己的DNS解析策略。
首先我们先看一个例子,假设我们想要请求百度域名获取一个数据,例如:
object HttpUtil {private const val BASE_URL = "https://www.baidu.comxx"fun initHttp() {val client = OkHttpClient.Builder().build()Request.Builder().url(BASE_URL).build().also {kotlin.runCatching {client.newCall(it).execute()}.onFailure {Log.e("OkHttp", "initHttp: error $it ")}}}
}
很明显,百度的域名是错误的,所以在执行网络请求的时候就会报错:
java.net.UnknownHostException: Unable to resolve host "www.baidu.comxx": No address associated with hostname
所以一旦我们的域名被劫持修改,那么整个服务就会处于宕机的状态,用户体感就会很差,因此我们可以通过OkHttp提供的自定义DNS解析器来做一个小的优化。
public interface Dns {/*** A DNS that uses {@link InetAddress#getAllByName} to ask the underlying operating system to* lookup IP addresses. Most custom {@link Dns} implementations should delegate to this instance.*/Dns SYSTEM = hostname -> {if (hostname == null) throw new UnknownHostException("hostname == null");try {return Arrays.asList(InetAddress.getAllByName(hostname));} catch (NullPointerException e) {UnknownHostException unknownHostException =new UnknownHostException("Broken system behaviour for dns lookup of " + hostname);unknownHostException.initCause(e);throw unknownHostException;}};/*** Returns the IP addresses of {@code hostname}, in the order they will be attempted by OkHttp. If* a connection to an address fails, OkHttp will retry the connection with the next address until* either a connection is made, the set of IP addresses is exhausted, or a limit is exceeded.*/List<InetAddress> lookup(String hostname) throws UnknownHostException;
}
我们看下源码,lookup方法相当于在做DNS寻址,一旦发生异常那么就会抛出UnknownHostException异常;同时内部还定义了一个SYSTEM方法,在这个方法中会通过系统提供的InetAddress类进行路由寻址,同样如果DNS解析失败,那么也会抛出UnknownHostException异常。
所以我们分两步走,首先使用系统能力进行路由寻址,如果失败,那么再走自定义的策略。
class MyDNS : Dns {override fun lookup(hostname: String): MutableList<InetAddress> {val result = mutableListOf<InetAddress>()var systemAddressList: MutableList<InetAddress>? = null//通过系统DNS解析kotlin.runCatching {systemAddressList = Dns.SYSTEM.lookup(hostname)}.onFailure {Log.e("MyDNS", "lookup: $it")}if (systemAddressList != null && systemAddressList!!.isNotEmpty()) {result.addAll(systemAddressList!!)} else {//系统DNS解析失败,走自定义路由result.add(InetAddress.getByName("www.baidu.com"))}return result}
}
这样在www.baidu.comxx 解析失败之后,就会使用www.baidu.com 域名替换,从而避免网络请求失败的问题。
1.3 接口数据适配策略
相信很多伙伴在和服务端调试接口的时候,经常会遇到这种情况:接口文档标明这个字段为int类型,结果返回的是字符串“”;或者在某些情况下,我需要服务端返回一个空数组,但是返回的是null,对于这种情况,我们在数据解析的时候,无论是使用Gson还是Moshi,都会解析失败,如果处理不得当,严重的会造成崩溃。
所以针对这种数据格式不匹配的问题,我们可以对Gson简单做一些适配处理,例如List类型:
class ListTypeAdapter : JsonDeserializer<List<*>> {override fun deserialize(json: JsonElement?,typeOfT: Type?,context: JsonDeserializationContext?): List<*> {return try {if (json?.isJsonArray == true) {Gson().fromJson(json, typeOfT)} else {Collections.EMPTY_LIST}} catch (e: Exception) {//Collections.EMPTY_LIST}}
}
如果json是List数组类型数据,那么就正常将其转换为List数组;如果不是,那么就解析为空数组。
class StringTypeAdapter : JsonDeserializer<String> {override fun deserialize(json: JsonElement?,typeOfT: Type?,context: JsonDeserializationContext?): String {return try {if (json?.isJsonPrimitive == true) {Gson().fromJson(json, typeOfT)} else {""}} catch (e: Exception) {""}}
}
对于String类型字段,首先会判断是否为基础类型(String,Number,Boolean),如果是基础类型那么就正常转换即可。
GsonBuilder().registerTypeAdapter(Int::class.java, IntTypeAdapter()).registerTypeAdapter(String::class.java, StringTypeAdapter()).registerTypeAdapter(List::class.java, ListTypeAdapter()).create().also {GsonConverterFactory.create(it)}
这样在创建GsonConverterFactory时,就可以使用我们的策略来进行数据适配,但是在测试环境下,我们不建议这样使用,因为无法发现服务端的问题,在上线之后为了规避线上问题可以使用此策略。
2 HTTPS协议
http协议与https协议的区别,就是多了一个“s”,可别小看这一个“s”,它能够保证http数据传输的可靠性,那么这个“s”是什么呢,就是SSL/TLS协议。

从上图中看,在进入TCP协议之前会先走SSL/TLS协议.
2.1 对称加密和非对称加密
既然Https能保证传输的可靠性,说明它对数据进行了加密,以往http协议数据的传输都是明文传输,数据极容易被窃取和冒充,因此后续优化中,对于数据进行了加密传输,才有了Https协议诞生。
常见的加密手段有两种:对称加密和非对称加密。
2.1.1 对称加密
首先对称加密,从名字就能知道具体的原理,看下图:

对称加密和解密的密钥是一把钥匙,需要双方约定好,发送方通过秘钥加密数据,接收方使用同一把秘钥解密获取传递的数据。
所以使用对称加密非常简单,解析数据很快,但是安全性比较差,因为双方需要约定同一个key,key的传输有被劫持的风险,而统一存储则同样存在被攻击的风险。
所以针对这种情况,应运而生出现了非对称加密。
2.1.2 非对称加密
非对称加密会有两把钥匙:私钥 + 公钥,对于公钥任何人都可以知道,发送方可以使用公钥加密数据,而接收方可以用只有自己知道的私钥解密拿到数据。

那么既然公钥所有人都知道,那么能够通过公钥直接推算出私钥吗?答案是目前不可能,未来可能会,得看全世界的密码学高手或者黑客能否解决这个问题。
总结一下两种加密方式的优缺点:
| 加密类型 | 优点 | 缺点 |
|---|---|---|
| 对称加密 | 流程简单,解密速度快 | 不安全,秘钥管理有风险 |
| 非对称加密 | 私钥只有自己知道 | 流程繁琐,解密速度慢 |
2.2 公钥的安全保障
通过2.1小节对于非对称加密的介绍,虽然看起来安全性更高了一些,但是对于公钥的传递有点儿太理想化,我们看下面的场景。

如果公钥在传输的过程中被劫持,那么发送方拿到的是黑客的公钥,后续所有的数据传输都被劫持了,所以问题来了,如何保证发送方拿到的公钥一定是接收方的?
举个简单的例子:我们在马路上捡到了一张银行卡,想把里面的钱取出来,那么银行柜台其实就是接收方,银行卡就是公钥,那么银行就会直接把钱给我们了吗?肯定不可以,要么需要身份证,要么需要密码,能够证明这个银行卡是我们自己的,所以公钥的安全性保证就是CA证书(可以理解为我们的身份证)。
那么首先接收方需要办一张身份证,需要通过CA机构生成一个数字签名,具体生成的规则如下:

那么最终发送给接收方的就是如下一张数字证书,包含的内容有:数字签名 + 公钥 + 接收方的个人信息等。

那么发送方接收到数字证书之后,就会检查数字证书是否合法,检测方式如下:

如果不是办的假证,这种可能性几乎为0,因为想要伪造一个域名的数字签名,根本不可能,CA机构也不是吃干饭的,所以只要通过证书认证了,那么就能保证公钥的安全性。

2.3 Https的传输流程
其实一个Https请求,中间包含了2次Http传输,假如我们请求 www.baidu.com 具体流程如下:
(1)客户端向服务端发起请求,要访问百度,那么此时与百度的服务器建立连接;
(2)此时服务端有公钥和私钥,公钥可以发送给客户端,然后给客户端发送了一个SSL证书,其中包括:CA签名、公钥、百度的一些信息,详情可见2.2小节最后的图;
(3)客户端在接收到SSL证书后,对CA签名解密,判断证书是否合法,如果不合法,那么就断开此次连接;如果合法,那么就生成一个随机数,作为数据对称加密的密钥,通过公钥加密发送到服务端。
(4)服务端接收到了客户端加密数据后,通过私钥解密,拿到了对称加密的密钥,然后将百度相关数据通过对称加密秘钥加密,发送到客户端。
(5)客户端通过解密拿到了服务端的数据,此次请求结束。
其实Https请求并不是完全是非对称加密,而是集各家之所长,因为对称加密密钥传递有风险,因此前期通过非对称加密传递对称加密密钥,后续数据传递都是通过对称加密,提高了数据解析的效率。
但是我们需要了解的是,Https保障的只是通信双方当事人的安全,像测试伙伴通过Charles抓包这种中间人攻击方式,还是会导致数据泄露的风险,因为通过伪造证书或者不受信任的CA就可以实现。
Android 学习笔录
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Android 性能优化篇:https://qr18.cn/FVlo89
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap
相关文章:
App线上网络问题优化策略
在我们App开发过程中,网络是必不可少的,几乎很难想到有哪些app是不需要网络传输的,所以网络问题一般都是线下难以复现,一旦到了用户手里就会碰到很多疑难杂症,所以对于网络的监控是必不可少的,针对用户常见…...
PDF 工具箱
PDF 工具箱 V9.0.0.1 程序:VB.net 运行库:NET Framework 4.5 功能简介: 1、PDF文件多文件合并,可调整顺序。 2、PDF文件拆分,将每页拆分成独立的PDF文件。 3、PDF文件添加水印,文字或图片水印&…...
大数据组件系列-Hadoop每日小问
1、谈谈对HDFS的理解?HDFS这种存储适合哪些场景? HDFS即Hadoop Distributed File System,Hadoop 分布式文件系统。它为的是解决海量数据的存储与分析的问题,它本身是源于Google在大数据方面的论文,GFS-->HDFS; HD…...
【前端】在Vue页面中引入其它vue页面 数据传输 相互调用方法等
主页面 home 从页面 headView 需求 在 home.vue 中引用 headView.Vue 方案: home.vue 代码: 只需要在home.vue 想要的地方添加 <headView></headView> <script>//聊天页面 import headView /view/headView.vueexport default {components: {headView},…...
网络通信深入解析:探索TCP/IP模型
http协议访问web 你知道在我们的网页浏览器的地址当中输入url,未必是如何呈现的吗? web浏览器根据地址栏中指定的url,从web服务器获取文件资源(resource)等信息,从而显示出web页面。web使用HTTP(…...
可靠的可视化监控平台应用在那些场景?
可视化监控平台是一种用户友好的工具,可以帮助用户实时监控IT设备的运行状态和网络流量,以及监测安全性和性能指标。它们通常采用图形化界面,使得用户能够直观地了解设备和网络的状态。 以下是一些可视化监控平台常见的应用场景:…...
从 BBR 失速到带宽探测
看一下 pacing 流失速的成因: 一段时间收不到 ack,丢了 ack 自时钟,cwnd 将耗尽,bbr 虽有 cwnd_gain(上图没有表现),但在该 cwnd_gain 下不依赖 ack 持续坚持发送多久取决于 cwnd_gain 的数值。 bbr 失速的后果在于…...
MobaXterm使用sz/rz命令下载上传文件
MobaXterm使用sz/rz命令下载上传文件 1 参考文档2 下载3 上传 1 参考文档 MobaXterm使用sz/rz命令下载上传文件 2 下载 步骤1:sz filename 步骤2:ctrl 鼠标右键 步骤3:Receive file using Z-modem 3 上传 步骤1:rz 步骤2&am…...
vue el-popover hover延时触发,el-popover 鼠标放上三秒以后触发
背景:el-popover hover只要鼠标刮过就显示 多个el-popover出现加载卡顿 解决方案 给el-popover加一个延时显示 <template><div><el-popovertrigger"hover":open-delay"3000"content"这是一个Popover"><button…...
计算机竞赛 基于深度学习的人脸识别系统
前言 🔥 优质竞赛项目系列,今天要分享的是 基于深度学习的人脸识别系统 该项目较为新颖,适合作为竞赛课题方向,学长非常推荐! 🧿 更多资料, 项目分享: https://gitee.com/dancheng-senior/…...
Android扫码连接WIFI实现
0,目标 APP中实现扫WIFI分享码自动连接WIFI功能 1,前提条件 设备需要有个扫码器(摄像头拍照识别也行),APP调用扫码器读取WIFI连接分享码。 2,增加权限 在AndroidManifest.xml中增加权限 <uses-permissi…...
TrOCR – 基于 Transformer 的 OCR 入门指南
多年来,光学字符识别 (OCR) 出现了多项创新。它对零售、医疗保健、银行和许多其他行业的影响是巨大的。尽管有着悠久的历史和多种最先进的模型,研究人员仍在不断创新。与深度学习的许多其他领域一样,OCR 也看到了变压器神经网络的重要性和影响。如今,我们拥有像TrOCR(Tran…...
MAC终端美化
先看看效果: 1.安装on-my-zsh 打开终端,输出: sh -c "$(curl -fsSL https://gitee.com/mirrors/oh-my-zsh/raw/master/tools/install.sh)"安装过程中如果出现了链接超时的错误,不要慌,就再来一次&#x…...
Matlab常用字符串操作教程
Matlab是一种功能强大的编程语言,它提供了丰富的字符串操作函数。在本教程中,我们将介绍一些常用的Matlab字符串操作函数和用法。 字符串的创建和访问: 使用单引号或双引号创建字符串:str Hello World; 或 str "Hello Worl…...
基于SSM的汽车养护管理系统
末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:采用JSP技术开发 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目&#x…...
Redis发布订阅机制学习
【IT老齐151】Redis发布订阅机制是如何实现的?_哔哩哔哩_bilibili go-redis的发布与订阅 - 知乎 (zhihu.com) 前置: 先输入 redis-server.exe 启动redis,否则对应接口不开放 再输入 redis-cli 命令启动客户端服务 1.机制示意图 当一…...
施展世界:GPT时代需要的教育,是学会如何提出好问题
来源:BV1co4y1W7h7 有很多脑力活,它实际上是伪装成脑力活的体力活,它在回答问题这个层面,那是非常的厉害,人现在肯定是比不过它了,注意了,这是回答问题的层面,但问题是谁来问问题呢&…...
Excel学习 WPS版
Excel学习 1.界面基础1.1 方格移动快捷键1.2 自动适配文字长度1.3 跨栏置中1.4 多个单元格同宽度:1.5 下拉框选择1.6 打印预览1.7 绘制边框1.8 冻结一行多行表头1.9 分割视图 2.日期相关2.1 今日日期快捷键2.2 月份提取 3.数学公式3.1 自动增长3.2 排序3.3 筛选3.4 …...
MySQL的Json类型个人用法详解
前言 虽然MySQL很早就添加了Json类型,但是在业务开发过程中还是很少设计带这种类型的表。少不代表没有,当真正要对Json类型进行特定查询,修改,插入和优化等操作时,却感觉一下子想不起那些函数怎么使用。比如把json里的…...
VUE 程序的执行过程(非常非常重要)
在Vue.js应用程序中,index.html和main.js的执行顺序是: 1. 首先,浏览器加载index.html文件。 2. 在index.html文件中,通过<script>标签引入了main.js文件。 3. 当浏览器遇到<script>标签时,它会停止解析H…...
IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...
以光量子为例,详解量子获取方式
光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
