欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

记一次网络请求优化过程

程序员文章站 2022-07-13 08:12:51
...

最近接手的项目中,同事反映获取服务器数据有点慢,不如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进行转换,所以没有遇到过这样的问题,也没有想到这样的方法会对性能造成这么大的影响.对于请求传参,我了解到的有以下方式:

  1. 直接将请求参数写到map对象中,直接传输,不做转换.自己之前一直采用這样的方式,在kotlin语言中,因为可变参数数量,和to语法糖的存在,这是很方便的.不会造成过多的性能影响.
  2. 调用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的整体体验提高了不少.记录备查,以后还要多观察,多测试,多学习.