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…...
【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机
这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机,因为在使用过程中发现 Airsim 对外部监控相机的描述模糊,而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置,最后在源码示例中找到了,所以感…...
STM32---外部32.768K晶振(LSE)无法起振问题
晶振是否起振主要就检查两个1、晶振与MCU是否兼容;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容(CL)与匹配电容(CL1、CL2)的关系 2. 如何选择 CL1 和 CL…...
日常一水C
多态 言简意赅:就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过,当子类和父类的函数名相同时,会隐藏父类的同名函数转而调用子类的同名函数,如果要调用父类的同名函数,那么就需要对父类进行引用&#…...
在 Spring Boot 项目里,MYSQL中json类型字段使用
前言: 因为程序特殊需求导致,需要mysql数据库存储json类型数据,因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...
