记一次网络请求优化过程

最近接手的项目中,同事反映获取服务器数据有点慢,不如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){
//                    return@filter 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
            loop@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){
//                            continue@loop
//                        }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的整体体验提高了不少.记录备查,以后还要多观察,多测试,多学习.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值