这是我第一次发表文章,整理记录一下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,可能存在一些问题,望包容和指出!!!