使用Retrofit的方式请求Socket,且Socket可以和Http无缝切换

15 篇文章 0 订阅
12 篇文章 0 订阅

前言

一般来说前端的app和服务器通讯都是用的Http,Http使用方便,请求流程好控制,但有时候app需要实时接收服务端的推送或保持长连接,这时就需要使用Socket了

java提供的Socket接口还是比较难用的,而网上有一个开源库OkScoket封装的还是挺好用的,Github地址:https://github.com/xuuhaoo/OkSocket

但即使如此,其没有一对一回调或同步请求方法,只能通过一个或几个统一的回调方法,就造成了使用比较麻烦且容易出错

而Retrofit使用比较好用,但是原生其只支持Http,所以我将其封装了一下可以不用修改Retrofit和其接口的代码就可以简单方便安全的使用

正文

下面将一下原理,如果不想看的同学可以直接翻到下面看引用方式和如何使用

我们如果无侵入无修改接口的使用可以用两种方案

1.自定义动态代理

2.根据Retrofit提供的接口自定义实现Call.Factory和Call

1.自定义动态代理

该种方式比较灵活,且代码很少,但无法利用Retrofit的其他优势,比如自定义返回值类型和解析器,比如支持RxJava就需要自己在写一套或Copy一下,比如解析对象就要自己来处理

所以不使用该方法,但我将实现的代码放出来(只支持POST和GET的注解,其他注解可以自行支持)

我们通过写相应接口的动态代理,并将自身请求的回调注册到OkSocket的统一回调中,然后自己判断id来取回调,并将其改造成同步(自旋)和异步(回调)的Call在封装适配RxJava

import io.reactivex.Observable
import io.reactivex.Observer
import io.reactivex.disposables.Disposable
import io.reactivex.exceptions.CompositeException
import io.reactivex.exceptions.Exceptions
import io.reactivex.plugins.RxJavaPlugins
import okhttp3.Request
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.http.Field
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Query
import java.lang.reflect.*
import java.net.SocketTimeoutException

/**
 * creator: lt  2021/1/23  lt.dygzs@qq.com
 * effect : 将大部分Http转为Socket
 * warning:
 */
object SocketRequest {

    /**
     * 动态代理单例对象
     */
    val instance: IPostRequest = getPostRequest()

    //获取动态代理实例对象
    private fun getPostRequest(): IPostRequest {
        val clazz = IPostRequest::class.java//拿到我们被代理接口的class对象
        return Proxy.newProxyInstance(//调用动态代理生成的方法来生成动态代理
                clazz.classLoader,//类加载器对象
                arrayOf(clazz),//因为我们的接口不需要继承别的接口,所以直接传入接口的class就行
                PostRequestHandler()//InvocationHandler接口的实现类,用来处理代理对象的方法调用
        ) as IPostRequest
    }

    class PostRequestHandler : InvocationHandler {
        override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? {
            //处理Object类的方法
            if (method.declaringClass == Any::class.java)
                return (if (args == null) method.invoke(this) else method.invoke(this, *args))
            val tMap = HashMap<String, Any>()
            method.parameterAnnotations.forEachIndexed { index, it ->
                //拿到参数的key,如果拿不到说明需要使用http请求
                val key = (it.find { it is Field } as? Field)?.value
                        ?: (it.find { it is Query } as? Query)?.value
                        ?: return method.invoke(PostRequest.getPostRequest(), args)
                tMap.put(key, args?.get(index) ?: "")
            }
            //拿到url,如果不是以斜杠开头就拼接上
            var url = method.getAnnotation(POST::class.java)?.value
                    ?: method.getAnnotation(GET::class.java)?.value
                    ?: return method.invoke(PostRequest.getPostRequest(), args)
            if (url.startsWith('/').not())
                url = "/$url"
            //处理返回值
            return when (method.returnType) {
                Call::class.java -> SocketCall<Any>(url, tMap, getParameterUpperBound(0, method.genericReturnType as ParameterizedType))
                Observable::class.java -> createObservable(url, tMap, method)
                else -> method.invoke(PostRequest.getPostRequest(), args)
            }
        }
    }

    private fun getParameterUpperBound(index: Int, type: ParameterizedType): Type {
        val types = type.actualTypeArguments
        require(!(index < 0 || index >= types.size)) { "Index " + index + " not in range [0," + types.size + ") for " + type }
        val paramType = types[index]
        return if (paramType is WildcardType) {
            paramType.upperBounds[0]
        } else paramType
    }

    private fun createObservable(url: String, tMap: HashMap<String, Any>, method: Method): Observable<Any?> =
            SocketObservable(SocketObservable.CallExecuteObservable(SocketCall(url, tMap, getParameterUpperBound(0, method.genericReturnType as ParameterizedType))))
}


class SocketObservable<T>(private val upstream: Observable<Response<T>>) : Observable<T>() {
    override fun subscribeActual(observer: Observer<in T>) {
        upstream.subscribe(BodyObserver(observer))
    }

    private class BodyObserver<R>(val observer: Observer<in R>) : Observer<Response<R>> {
        private var terminated = false
        override fun onSubscribe(disposable: Disposable) {
            observer.onSubscribe(disposable)
        }

        override fun onNext(response: Response<R>) {
            if (response.isSuccessful) {
                val body = response.body()
                if (body != null) {
                    observer.onNext(body)
                    return
                }
            }
            terminated = true
            val t: Throwable = retrofit2.HttpException(response)
            try {
                observer.onError(t)
            } catch (inner: Throwable) {
                Exceptions.throwIfFatal(inner)
                RxJavaPlugins.onError(CompositeException(t, inner))
            }
        }

        override fun onComplete() {
            if (!terminated) {
                observer.onComplete()
            }
        }

        override fun onError(throwable: Throwable) {
            if (!terminated) {
                observer.onError(throwable)
            } else {
                // This should never happen! onNext handles and forwards errors automatically.
                val broken: Throwable = AssertionError(
                        "This should never happen! Report as a bug with the full stacktrace.")
                broken.initCause(throwable)
                RxJavaPlugins.onError(broken)
            }
        }
    }

    class CallExecuteObservable<T>(private val call: Call<T>) : Observable<Response<T>>() {
        override fun subscribeActual(observer: Observer<in Response<T>>) {
            // Since Call is a one-shot type, clone it for each new observer.
            val disposable = CallDisposable(call)
            observer.onSubscribe(disposable)
            if (disposable.isDisposed) {
                return
            }
            var terminated = false
            try {
                val response = call.execute()
                if (!disposable.isDisposed) {
                    observer.onNext(response)
                }
                if (!disposable.isDisposed) {
                    terminated = true
                    observer.onComplete()
                }
            } catch (t: Throwable) {
                Exceptions.throwIfFatal(t)
                if (terminated) {
                    RxJavaPlugins.onError(t)
                } else if (!disposable.isDisposed) {
                    try {
                        observer.onError(t)
                    } catch (inner: Throwable) {
                        Exceptions.throwIfFatal(inner)
                        RxJavaPlugins.onError(CompositeException(t, inner))
                    }
                }
            }
        }

        private class CallDisposable constructor(private val call: Call<*>) : Disposable {
            @Volatile
            private var disposed = false
            override fun dispose() {
                disposed = true
                call.cancel()
            }

            override fun isDisposed(): Boolean {
                return disposed
            }
        }
    }
}

class SocketCall<T>(
        val url: String,
        val tMap: HashMap<String, Any>,
        val trueReturnType: Type) : Call<T> {
    private var canceled = false
    private var isExecuted = false//是否运行过,一个call对象只允许运行一次
    private var requestId = 0

    /**
     * 检查网络三十秒,如果没有连接成功就 sync抛异常,async就回调false
     * 传入[asyncCallback]表示async
     */
    private fun checkConnect(asyncCallback: ((Boolean) -> Unit)? = null) {
        if (SocketManage.manager.isConnect) {
            asyncCallback?.invoke(true)
            return
        }
        SocketManage.connect()
        val time = System.currentTimeMillis()
        if (asyncCallback == null) {
            while (true) {
                if (SocketManage.currConnStatus == 3)
                    return
                if (System.currentTimeMillis() - time > 30000)
                    throw SocketTimeoutException()
                try {
                    Thread.sleep(100)
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
        } else {
            ThreadPool.submitToCacheThreadPool {
                while (true) {
                    if (SocketManage.currConnStatus == 3) {
                        asyncCallback(true)
                        return@submitToCacheThreadPool
                    }
                    if (System.currentTimeMillis() - time > 30000) {
                        asyncCallback(false)
                        return@submitToCacheThreadPool
                    }
                    try {
                        Thread.sleep(100)
                    } catch (e: Exception) {
                        e.printStackTrace()
                    }
                }
            }
        }
    }

    override fun execute(): Response<T> {
        checkConnect()
        if (isExecuted) throw IllegalStateException("只能执行一次")
        isExecuted = true
        val data = TcpSendData(url, tMap)
        requestId = data.request_id
        "send2 : $url ${tMap.toJson()}".w("SocketManage22")
        var a: Any? = null
        var t: Throwable? = null
        var notFinish = true
        SocketManage.addListener(requestId, trueReturnType) { any: Any?, throwable: Throwable? ->
            a = any
            t = throwable
            notFinish = false
        }
        //发送请求
        SocketManage.manager.send(data)
        var whileNumber = 0
        while (notFinish && !canceled) {
            try {
                whileNumber++
                if (whileNumber % 20 == 0)
                    SocketManage.handlerTimeOutedListener()
                Thread.sleep(50)
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
        t?.let { throw it }
        return if (a == null) throw ServerException("服务端返回的result是空") else Response.success(a as T)
    }

    override fun enqueue(callback: Callback<T>) {
        checkConnect {
            if (!it) {
                HandlerPool.post {
                    callback.onFailure(this, SocketTimeoutException())
                }
                return@checkConnect
            }
            if (isExecuted) throw IllegalStateException("只能执行一次")
            isExecuted = true
            val data = TcpSendData(url, tMap)
            "send : $url ${tMap.toJson()}".w("SocketManage22")
            requestId = data.request_id
            SocketManage.addListener(requestId, trueReturnType) { any: Any?, throwable: Throwable? ->
                    if (any != null)
                        callback.onResponse(this, Response.success(any as T))
                    else
                        callback.onFailure(this, throwable ?: ServerException("服务端返回的result是空2"))
            }
            //发送请求
            SocketManage.manager.send(data)
        }
    }

    override fun clone(): Call<T> = SocketCall(url, tMap, trueReturnType)

    override fun isExecuted(): Boolean = isExecuted

    override fun cancel() {
        canceled = true
        // 取消请求
        SocketManage.removeListener(requestId)
    }

    override fun isCanceled(): Boolean = canceled

    override fun request(): Request? = null

}

object : SocketActionAdapter() {
            override fun onSocketReadResponse(info: ConnectionInfo?, action: String?, data: OriginalData?) {
                data ?: return
...
                        //处理回调
                        val (_, type, listener) = listenerMap.remove(requestId) ?: return
                        try {
                            val any = Gson().fromJson<Any?>(body.getJSONObject("body").toString(), type)
                            HandlerPool.post {
                                if (any == null) listener(null, ServerException("服务端返回的result是空")) else listener(any, null)
                            }
                        } catch (t: Throwable) {
                            HandlerPool.post {
                                listener(null, t)
                            }
                        }
                        handlerTimeOutedListener()
...

具体可以参考我之前仿照Retrofit实现的Http动态代理:模仿Retrofit封装一个使用更简单的网络请求框架_滔lt的博客-CSDN博客

2.根据Retrofit提供的接口自定义实现Call.Factory和Call

ps:该方案被废弃,因为其比较受限制,而且无法充分利用到Retrofit的功能,并且限制Http比较死,用Socket需要写很多死代码

通过查看Retrofit框架,发现传入的OkHttpClient其实就是一个Call.Factory的实现类,所以只需要自行实现Call.Factory,我们也可以来控制Call的创建,只要Call能创建出来,其他的如Observable也只是对Call的包装

在通过和方法1一样的回调处理,即可实现效果

关键代码如下:

abstract class SocketCallAdapter(private val manager: IConnectionManager) : Call.Factory {
    
    override fun newCall(request: Request): Call {
        ...
        return SocketCall(
                manager,
                this,
                url,
                map
        )
    }
}

/**
 * creator: lt  2021/2/27  lt.dygzs@qq.com
 * effect : 用于Socket请求的Call
 * warning:
 */
internal class SocketCall(
        private val manager: IConnectionManager,
        private val adapter: SocketCallAdapter,
        private val url: String,
        private val tMap: HashMap<String, Any>) : Call {
  //和第一种实现相同

  ...

}

3.根据我from的Retrofit库提供的能力来拦截Retrofit.Call的创建

使用该方式可以更灵活且更容易复用Retrofit的能力和插件

参考更易于使用的Retrofit(不用写注解)_滔lt的博客-CSDN博客第七条和源码:GitHub - ltttttttttttt/Retrofit_SocketCallAdapter: Retrofit可以直接使用OkSocket来进行网络请求,Retrofit内的东西都不需要修改,只需要将OkHttpClient换成此即可
ps:其实源码很简单,就几个文件且内容都很少,而且想自定义拦截简直太方便了

使用方式

在根项目的build.gradle文件中加入:

allprojects {
    repositories {
...
        maven { url 'https://jitpack.io' }
    }
}

app的build.gradle中加上

dependencies{
    ...
    implementation 'com.github.ltttttttttttt:Retrofit_SocketCallAdapter:1.1.8'
    implementation "com.github.ltttttttttttt:retrofit:1.3.0"
    implementation 'org.jetbrains.kotlin:kotlin-reflect:1.4.30'
    //todo 默认用的'com.github.ltttttttttttt:retrofit:',所以如果需要用到gson的拦截器之类的,但是其中包含的有原版的retrofit的引用,会导致冲突,所以可以使用下面的方法来去掉本引用的某个远程依赖
    //implementation 'com.squareup.retrofit2:converter-gson:2.7.0' exclude module: 'retrofit'
    //todo 因为使用了com.github.ltttttttttttt:retrofit,所以无法使用默认的Retrofit库,因为提供了默认Retrofit库没有的功能,但其原有功能此库都有,所以还是可以使用默认Retrofit库的所有功能的
}

代码中使用(也可以参考Github内的InitRetrofit类):

OkSocket的初始化方式参考:https://github.com/xuuhaoo/OkSocket/wiki/Connection  使用也很简单

其他使用方式和Retrofit相同(但只支持get的query和post的field转为socket请求,其他方式可以评论留言,如果你可以实现直接提交更新!(欢迎大佬来补充更新))

val manager = OkSocket.open(ConnectionInfo(xxx,xxx))
...//初始化OkSocket的manager

val mId = AtomicInteger(0)
val handler = Handler(Looper.getMainLooper())
val socketAdapter = object : SocketAdapter(manager) {
                    //从响应数据中获取请求id和body数据
                    override fun getResponseIdAndBodyBytes(data: OriginalData): Pair<Int, ByteArray>? {
                        val jb = JSONObject(String(data.bodyBytes))
                        if (!jb.has("id")) return null
                        return jb.getInt("id") to jb.getString("body").toByteArray()
                    }

                    //返回当前Socket在逻辑意义上是否和服务端连通了
                    override fun socketIsConnect(): Boolean = manager.isConnect

                    //根据url和请求参数生成用于发送的数据和Id
                    override fun createSendDataAndId(url: String, requestParametersMap: HashMap<String, Any>, returns: (ISendable, Int) -> Unit) {
                        val id = mId.incrementAndGet()
                        val data = TcpSendData(id, url, requestParametersMap)//这里需要你使用自己定义的数据发送类
                        returns(data, id)
                    }

                    //在主线程回调数据
                    override fun handlerCallbackRunnable(runnable: Runnable) {
                        handler.post(runnable)
                    }
                }


        //Socket的动态代理
        val r = Retrofit.Builder()
                .baseUrl(HttpConfig.ROOT_URL.toString())
                .addConverterFactory(GsonConverterFactory.create())
                .client(OkHttpClient())
                //这里设置了Retrofit.Call的生成拦截,可以重写SocketServiceMethodFactory.createServiceMethod方法返回null表示自己用Socket处理不了而使用Http请求
                .setServiceMethodFactory(SocketServiceMethodFactory(manager,socketAdapter ))
                .build()
                .create(HttpFunctions::class.java)

源码地址:GitHub - ltttttttttttt/Retrofit_SocketCallAdapter: Retrofit可以直接使用OkSocket来进行网络请求,Retrofit内的东西都不需要修改,只需要将OkHttpClient换成此即可

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值