记一次网络请求优化过程
最近接手的项目中,同事反映获取服务器数据有点慢,不如iOS版的流畅,观察OKHttp日志发现,某页面的数据请求耗时700ms左右,但是页面从空白到完成数据展示,感觉耗时在2.3秒左右,在代码中插入耗时计算代码发现,从Presenter构建到请求返回的onNext耗时在2000+ms,初步减去正常的OKHttp耗时,另外有1500ms左右的不明耗时,出现在代码逻辑中.逐步增加耗时计算代码的埋点,缩小测量范围后,定位到具体的代码块,分析代码上下文后,发现2个代码逻辑的耗时点.记录如下.
额外的线程切换耗时
原代码如下
@SuppressLint("CheckResult")
@Suppress("UNCHECKED_CAST")
inline fun <A : Any> RxPresenter<*>.requestApi1(
@NonNull crossinline fun0: ((HttpService?) -> Observable<A>?),
@NonNull observerImp: ObserverImp<in A>) {
doAsync {//使用anko切换到子线程
try {
if (DisposalApp.app?.isLoginLose() == true) {
if (observerImp.shouldApiHandler) {
observerImp?.onError(ExitException())
return@doAsync
}
}
//下面的compose subscribeOn(io).observeOn(main)
var observer = fun0.invoke(HttpManager.getProjectWorkHttpService())?.compose(ScheduleObserverTransformer.instance)
//下面是没有完全注释的,完全无用的try catch 和filter
try {
// observer = observer?.compose(bindToLifecycle())
observer?.filter {
// val currentSubscription= currentSubscription()
// if (currentSubscription!=null){
// if (countDownCheck){
// [email protected] false
// }
// }
return@filter true
}
} catch (e: Exception) {
e.printStackTrace()
observerImp?.onError(ExitException())
return@doAsync
}
val disposable = observer?.subscribe({
if (it == null) {
observerImp?.onError(Throwable("连接错误"))
} else
observerImp?.onNext(it as A)
}, {
observerImp?.onError(it)
})
if (disposable == null) {
observerImp.onError(NullPointerException("请求失败,请检查参数是否正确"))
return@doAsync
}
addSubscribe(disposable)
} catch (e: Exception) {
observerImp.onError(NullPointerException("请求失败,请检查参数是否正确"))
e.printStackTrace()
}
}
}
个人觉得外围的doAsync{}
是完全没有必要且浪费资源的,未完全注释掉的try-catch也毫无用处,随即修改为如下:
@SuppressLint("CheckResult")
@Suppress("UNCHECKED_CAST")
inline fun <A : Any> RxPresenter<*>.requestApi(
@NonNull crossinline fun0: ((HttpService?) -> Observable<A>?),
@NonNull observerImp: ObserverImp<in A>) {
try {
if (DisposalApp.app?.isLoginLose() == true) {
if (observerImp.shouldApiHandler) {
observerImp.onError(ExitException())
return
}
}
val observer = fun0.invoke(HttpManager.getProjectWorkHttpService())?.compose(ScheduleObserverTransformer.instance)
val disposable = observer?.subscribe({
if (it == null) {
observerImp.onError(Throwable("连接错误"))
} else
observerImp.onNext(it as A)
}, {
observerImp.onError(it)
})
if (disposable == null) {
observerImp.onError(NullPointerException("请求失败,请检查参数是否正确"))
return
}
addSubscribe(disposable)
} catch (e: Exception) {
observerImp.onError(NullPointerException("请求失败,请检查参数是否正确"))
e.printStackTrace()
}
}
后测试,请求速度加快300ms左右.
使用反射转化object对象为Map<String,String>
经过上面的优化后,发现在Presenter创建到请求发起前,仍有800ms左右的耗时,进一步缩小耗时测量范围后发现,前任开发者使用了如下的一个方法转化object对象为Map<String,String>格式作为请求参数.
/**
* 将所有属性转换为map
* retrofit表单提交使用
*/
fun toMap(): Map<String, String> {
val map = hashMapOf<String, String>()
try {
val properties = this.javaClass.kotlin.memberProperties
[email protected]for (p in properties) {
p.getter.isAccessible=true
when (p.returnType.javaType) {
Int::class.javaPrimitiveType,
Int::class.javaObjectType -> {
val str= p.getter.call(this) as? Int
if (str!=null){
map[p.name] = str.toString()
}
}
Short::class.javaPrimitiveType,
Short::class.javaObjectType -> {
val str= p.getter.call(this) as? Short
if (str!=null){
map[p.name] = str.toString()
}
}
Boolean::class.javaPrimitiveType,
Boolean::class.javaObjectType -> {
val str= p.getter.call(this) as? Boolean
if (str!=null){
map[p.name] = str.toString()
}
}
Long::class.javaPrimitiveType,
Long::class.javaObjectType -> {
val str= p.getter.call(this) as? Long
if (str!=null){
map[p.name] = str.toString()
}
}
Double::class.javaPrimitiveType,
Double::class.javaObjectType -> {
val str= p.getter.call(this) as? Double
if (str!=null){
map[p.name] = str.toString()
}
}
String::class.java -> {
val str= p.getter.call(this) as? String
if (str!=null){
map[p.name] = str
}
}
else -> {
// if (p==null){
// [email protected]
// }else{
// map.put(p.name, "")
// }
}
}
}
}catch (e:Exception){}
return map
}
此方法不同的对象调用时耗时在200-700+ms不等.众所周知,运行时的反射对性能是有一定影响的,此方法在每个网络请求会被调用2次,因为之前自己是直接传入map对象作为请求参数,而不是使用object进行转换,所以没有遇到过这样的问题,也没有想到这样的方法会对性能造成这么大的影响.对于请求传参,我了解到的有以下方式:
- 直接将请求参数写到map对象中,直接传输,不做转换.自己之前一直采用這样的方式,在kotlin语言中,因为可变参数数量,和to语法糖的存在,这是很方便的.不会造成过多的性能影响.
- 调用retrofit的
addConverterFactory
添加Converter.Factory
实现requestBodyConverter
方法进行自动转换.该项目也有配置,但是因为需要修改HTTPServeice的注解,项目较大,不便修改.
后想到了使用Gson进行object到Map的转化,修改代码如下:
companion object {
val gson = Gson()
val type = object : TypeToken<Map<String, String>>() {
}.type
}
/**
* 将所有属性转换为map
* retrofit表单提交使用
*/
fun toMap(): Map<String, String> {
return gson.fromJson<Map<String, String>>(gson.toJson(this, this.javaClass), type)
}
修改完成后发现部分对象在进入到gson.toJson(this, this.javaClass)
时出现卡死,内存暴涨,频繁GC,观察传入的对象类型:
data class LabelEntity(val labCode:String,val controller: RefreshController?):MultipartBean(controller){
init {
pageIndex=1
pageSize=1000
}
}
发现复杂的,不必上传的成员变量controller
,会不会是这个对象的问题呢?将该对象排除序列化后,速度恢复正常.问题解决,最后的代码如下:
companion object {
val gson = GsonBuilder().enableComplexMapKeySerialization().disableInnerClassSerialization().addSerializationExclusionStrategy(object : ExclusionStrategy {
//排除不用序列化的复杂对象类型
override fun shouldSkipClass(clazz: Class<*>?): Boolean {
return clazz == RefreshController::class.java
}
override fun shouldSkipField(f: FieldAttributes?): Boolean {
return f?.name == "controller"
}
}).create()
val type = object : TypeToken<Map<String, String>>() {
}.type
}
/**
* 将所有属性转换为map
* retrofit表单提交使用
*/
fun toMap(): Map<String, String> {
return gson.fromJson<Map<String, String>>(gson.toJson(this, this.javaClass), type)
}
解决这个问题后,创建Presenter到请求发起前的耗时由700+ms减少到20+ms.
结语
修改了上面的2个问题后,请求的响应时间减少了1000+ms,耗时基本为OKHttp日志统计的耗时+几十ms,App的整体体验提高了不少.记录备查,以后还要多观察,多测试,多学习.
上一篇: 网络 协议 相关知识杂记
推荐阅读
-
你以为你以为的就是你以为的吗?记一次服务器点对点通知的联调过程
-
记一次SQL优化
-
网站性能优化之HTTP请求过程简述
-
记一次mq无法正常生产消息的事故排查过程
-
记一次网络攻击处理
-
记一次vue长列表的内存性能分析和优化
-
记一次大厂的面试过程
-
记录一次排查使用HttpWebRequest发送请求的发生“基础连接已关闭:接收时发生错误”异常问题的过程
-
记一次Elasticsearch OOM的优化过程——基于segments force merge 和 store type 转为 niofs
-
C#利用 HttpWebRequest 类发送post请求,出现“套接字(协议/网络地址/端口)只允许使用一次”问题