您现在的位置是:首页 > 正文

Android Kotlin 网络请求框架(okhttp3+retrofit2+rxjava3+rxlifecycle2)包含下载上传

2024-02-01 00:24:33阅读 2

这是我第一次发表文章,整理记录一下Android项目中最为重要的网络请求框架,采用了现流行的okhttp3+retrofit2+rxjava3+rxlifecycle2第三方库来实现,适用于post、get、上传和下载文件的网络请求

1.导入依赖库

    //rxjava
    implementation "io.reactivex.rxjava3:rxjava:3.0.0"
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
    //http
    implementation 'com.squareup.okhttp3:okhttp:4.2.2'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
    //retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.7.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.7.0'
    implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
    //gson
    implementation 'com.google.code.gson:gson:2.8.7'
    //rxlifecycle
    implementation 'com.trello.rxlifecycle2:rxlifecycle-kotlin:2.2.0'
    implementation 'com.trello.rxlifecycle2:rxlifecycle-components:2.2.0'

2.网络权限

<uses-permission android:name="android.permission.INTERNET"/>

3.OKhttp和Retrofit进行build,配置相关参数,完整代码如下

class RetrofitManager private constructor() {
    lateinit var retrofit: Retrofit

    companion object {
        //单例模式
        val instance: RetrofitManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            RetrofitManager()
        }
    }

    fun init(url: String) {
        val okHttpClient: OkHttpClient = OkHttpClient.Builder()
            .addInterceptor(HttpLoggingInterceptor().setLevel(if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE))
            .connectTimeout(60,TimeUnit.SECONDS)
            .writeTimeout(60,TimeUnit.SECONDS)
            .readTimeout(60,TimeUnit.SECONDS)
            .build()
        retrofit=Retrofit.Builder()
            .baseUrl(url)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .client(okHttpClient)
            .build()
    }
    //动态代理模式
    fun <T> createService(tClass:Class<T>):T{
        return retrofit.create(tClass)
    }
}
4.在Application的onCreate进行初始化网络请求地址,如下所示
   RetrofitManager.instance.init("http://192.168.1.1")

 5.创建请求接口

interface ApiService {
    @POST("/test/login")
    fun login(@Query("phone") mobile:String, @Query("pwd")pwd: String): Observable<ResultModel<String>>

    /**
     * 文件上传
     * address 上传路径
     */
    @Multipart
    @POST
    fun updateFile(@Url url: String,@Part address: RequestBody,@Part partList: List<MultipartBody.Part>):Observable<ResultModel<String>>

    /**
     * 文件下载服务
     * @param url 下载链接
     */
    @Streaming
    @GET
    fun download(@Header("Range")range:String,@Url url: String ):Observable<ResponseBody>
}

6.统一的回调处理
 

/**
 * 回调处理
 */
abstract class HttpResponseObserver<T>: Observer<T> {
    override fun onComplete() {
    }

    override fun onSubscribe(d: Disposable) {
    }

    override fun onNext(t: T) {
        success(t)
        finish()
    }

    override fun onError(e: Throwable) {
        if(e is HttpException){
            var msg=""
            when(e.code()){
                ResponseErrorType.INTERNAL_SERVER_ERROR.code->msg=ResponseErrorType.INTERNAL_SERVER_ERROR.msg
                ResponseErrorType.BAD_GATWAY.code->msg= ResponseErrorType.BAD_GATWAY.msg
                ResponseErrorType.NOT_FOUND.code->msg= ResponseErrorType.NOT_FOUND.msg
                ResponseErrorType.CONNECTION_TIMEOUT.code->msg= ResponseErrorType.CONNECTION_TIMEOUT.msg
                ResponseErrorType.CONNECTION_NOT_NETWORK.code->msg= ResponseErrorType.CONNECTION_NOT_NETWORK.msg
            }
            failure(e.code(),msg)
            finish()
            return
        }

        val responseErrorType: ResponseErrorType = when (e) {
            is UnknownHostException -> ResponseErrorType.CONNECTION_NOT_NETWORK
            is ConnectException -> ResponseErrorType.CONNECTION_NOT_NETWORK
            is SocketTimeoutException -> ResponseErrorType.CONNECTION_TIMEOUT
            else -> ResponseErrorType.UNEXPECTED_ERROR
        }
        failure(responseErrorType.code,responseErrorType.msg)
        finish()
    }

    /**
     * 成功的回调
     */
    abstract fun success(data: T)

    /**
     * 失败的回调
     */
    abstract fun failure(code: Int, error: String)

    /**
     * 执行结束
     */
    abstract fun finish()
}

7.写了个静态的方法类,便于调用
 

object HttpHelper {
    @JvmStatic
    fun login(account: String, pwd: String) =
        RetrofitManager.instance.createService(ApiService::class.java)
            .login(account,pwd)
            .compose(RxJavaScheduler.compose())
    @JvmStatic
    fun updateFile(url:String,address: RequestBody, partList: List<MultipartBody.Part>)=
        RetrofitManager.instance.createService(ApiService::class.java)
            .updateFile(url,address, partList)
            .compose(RxJavaScheduler.compose())
    @JvmStatic
    fun downFile(url: String, range:Long,file:File,downloadCallback: DownloadCallBack) {
        //断点续传时请求的总长度
        var totalLength = "-"
        if (file.exists()) {
            totalLength += file.length()
        }
        RetrofitManager.instance.createService(ApiService::class.java)
            .download("bytes=" + java.lang.Long.toString(range) + totalLength,url)
            .compose(RxJavaScheduler.compose())
            .subscribe(object : Observer<ResponseBody>{
                override fun onSubscribe(d: Disposable) {
                }

                override fun onNext(responseBody: ResponseBody) {
                    var randomAccessFile: RandomAccessFile? = null
                    var inputStream: InputStream? = null
                    var total = range
                    var responseLength: Long = 0
                    try {
                        val buf = ByteArray(2048)
                        var len = 0
                        responseLength = responseBody.contentLength()
                        inputStream = responseBody.byteStream()
                        randomAccessFile = RandomAccessFile(file, "rwd")
                        if (range == 0L) {
                            randomAccessFile.setLength(responseLength)
                        }
                        randomAccessFile.seek(range)
                        var progress = 0
                        var lastProgress = 0
                        while (inputStream.read(buf).also { len = it } != -1) {
                            randomAccessFile.write(buf, 0, len)
                            total += len.toLong()
                            lastProgress = progress
                            progress = (total * 100 / randomAccessFile.length()).toInt()
                            if (progress > 0 && progress != lastProgress) {
                                downloadCallback.onProgress(
                                    randomAccessFile.length(),
                                    total,
                                    progress
                                )
                            }
                        }
                        downloadCallback.onCompleted()
                    } catch (e: Exception) {
                        downloadCallback.onError(e.message)
                        e.printStackTrace()
                    } finally {
                        try {
                            randomAccessFile?.close()
                            inputStream?.close()
                        } catch (e: Exception) {
                            e.printStackTrace()
                        }
                    }
                }

                override fun onError(e: Throwable) {
                    downloadCallback.onError(e.toString())
                }

                override fun onComplete() {
                }

            })
    }
}

其中的下载回调接口代码如下

interface DownloadCallBack {
    fun onProgress(totalLength: Long, currentLength: Long, progress: Int)
    fun onCompleted()
    fun onError(msg: String?)
}
object RxJavaScheduler {
    fun <T> compose(): ObservableTransformer<T, T> {
        return ObservableTransformer {
                it.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
        }
    }
}

8.使用到的枚举和实体

enum class ResponseErrorType(val code:Int, val msg:String) {
    INTERNAL_SERVER_ERROR(500,"服务器出错,请重试"),
    BAD_GATWAY(502,"服务器出错,请重试"),
    NOT_FOUND(404,"服务器找不到资源,请检查路径"),
    CONNECTION_TIMEOUT(408,"连接超时,请重试"),
    CONNECTION_NOT_NETWORK(499,"当前网络不可用,请检查网络"),
    UNEXPECTED_ERROR(700,"未知错误");
}
/**
 * 结果返回实体类
 * @param code 错误号
 * @param message 消息
 * @param result 结果实体对象
 */
open class ResultModel<T>(

	@field:SerializedName("code")
	val code: Int? = null,

	@field:SerializedName("msg")
	val message: String? = null,

	@field:SerializedName("result")
	val result: T? = null

)

9.最后在界面中调用

/**
     * post请求
     */
    fun login(){
        HttpHelper.login("1","1").bindUntilEvent(this,ActivityEvent.DESTROY)
            .subscribe(object : HttpResponseObserver<ResultModel<String>>() {
                override fun success(data: ResultModel<String>) {
                }
                override fun failure(code: Int, error: String) {
                }

                override fun finish() {
                    //这里可以dismiss加载框
                }

            })
    }

    /**
     * 多文件上传
     */
    fun uploadFile(files:List<File>){
        val parts = ArrayList<MultipartBody.Part>()
        files.forEach{
            val body=RequestBody.create("multipart/form-data".toMediaTypeOrNull(),it)
            val part=MultipartBody.Part.createFormData("file","${System.currentTimeMillis()}.jpg",body)
            parts.add(part)
        }
        HttpHelper.updateFile("/test/updateFile","myFile".toRequestBody("text/html;charset=utf-8".toMediaTypeOrNull()),parts)
            .bindUntilEvent(this,ActivityEvent.DESTROY)
            .subscribe(object :HttpResponseObserver<ResultModel<String>>(){
                override fun success(data: ResultModel<String>) {
                }

                override fun failure(code: Int, error: String) {
                }

                override fun finish() {
                }

            })

    }
    /**
     * 下载文件
     */
    private var range: Long = 0
    private var pg : Int = 0
    fun downFile(downUrl:String){
        val file = File(getApkPath(), "App.apk")
        if (file.exists()) {
            range = SPUtils.getInstance().getLong(this, "downRange", 0)
            pg = (range * 100 / file.length()).toInt()
            if (range == file.length()) {
                Log.e("Tag","已经下载完成")
                return
            }
        }
        HttpHelper.downFile(downUrl,range,file,object :DownloadCallBack{
            override fun onProgress(totalLength: Long, currentLength: Long, progress: Int) {

            }

            override fun onCompleted() {
            }

            override fun onError(msg: String?) {
            }

        })

    }
    fun getApkPath(): String {
        var directoryPath = ""
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {//判断外部存储是否可用
            directoryPath = getExternalFilesDir("apk")!!.absolutePath
        } else {//没外部存储就使用内部存储
            directoryPath = filesDir.toString() + File.separator + "apk"
        }
        val file = File(directoryPath)
        Log.e("APK路径","APK路径$directoryPath")
        if (!file.exists()) {//判断文件目录是否存在
            file.mkdirs()
        }
        return directoryPath
    }

10,总结:代码已全部贴上,比较简单,但是够用,而且支持扩展,最主要的是这个框架的使用提高了网络请求的处理效率,我这点代码是demo,可能存在一些问题,望包容和指出!!!

网站文章