文章目录
- 引言
- 实战
-
- 1、引入开源库
- 2、简单封装
- 3、开始使用
-
- MVP架构模式
- MVVM架构模式 看[这里](https://blog.csdn.net/sange77/article/details/103959389)
引言
目前做APP网络API请求Retrofit+OkHttp+Kotlin协程应该是比较流行的,相比之前Retrofit+RxJava 有了太多的优势,Rx可以做的事情,协程一样可以做,而且可以做到更方便,更简洁。还不会用协程的童鞋可以看下这篇[Kotlin:玩转协程],接下来我们进行网络请求框架的实战。
实战
1、引入开源库
在app moudle 下build.gradle引入下列库。
dependencies {//OkHttp3implementation "com.squareup.okhttp3:okhttp:3.12.0"//Retrofit网络请求api "com.squareup.retrofit2:retrofit:2.6.2"api "com.squareup.retrofit2:converter-gson:2.6.2"implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2"//Kotlin Coroutines 协程api "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3"
}
最新版本,请查阅官方地址:
- OkHttp3:https://github.com/square/okhttp
- Retrofit:https://github.com/square/retrofit
- Kotlin Coroutines 协程:https://github.com/Kotlin/kotlinx.coroutines
2、简单封装
接口请求工厂类 ApiFactory.kt
import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
import okhttp3.FormBody
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit/*** 接口请求工厂* @author ssq*/
object ApiFactory {
// 日志拦截器private val mLoggingInterceptor: Interceptor by lazy {
LoggingInterceptor() }// OkHttpClient客户端private val mClient: OkHttpClient by lazy {
newClient() }/*** 创建API Service接口实例*/fun <T> create(baseUrl: String, clazz: Class<T>): T = Retrofit.Builder().baseUrl(baseUrl).client(mClient).addConverterFactory(GsonConverterFactory.create(MGson.getInstance())).addCallAdapterFactory(CoroutineCallAdapterFactory()).build().create(clazz)/*** OkHttpClient客户端*/private fun newClient(): OkHttpClient = OkHttpClient.Builder().apply {
connectTimeout(30, TimeUnit.SECONDS)// 连接时间:30s超时readTimeout(10, TimeUnit.SECONDS)// 读取时间:10s超时writeTimeout(10, TimeUnit.SECONDS)// 写入时间:10s超时if (BaseApplication.isDebugMode) addInterceptor(mLoggingInterceptor)// 仅debug模式启用日志过滤器}.build()/*** 日志拦截器*/private class LoggingInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val builder = StringBuilder()val startTime = System.nanoTime()val response: Response = with(chain.request()) {
builder.append(method() + "\n")builder.append("Sending request\n" + url())if (method() == "POST") {
builder.append("?")when (val body = body()) {
is FormBody -> {
for (j in 0 until body.size()) {
builder.append(body.name(j) + "=" + body.value(j))if (j != body.size() - 1) {
builder.append("&")}}}
// is MultipartBody -> {}}}builder.append("\n").append(headers())chain.proceed(this)}builder.append("Received response in " + (System.nanoTime() - startTime) / 1e6 + "ms\n")builder.append("code" + response.code() + "\n")LogUtil.v(builder.toString())return response}}
}
api接口 ApiServiceKt.kt
/*** api接口* @author ssq* @JvmSuppressWildcards 用来注解类和方法,使得被标记元素的泛型参数不会被编译成通配符?*/
@JvmSuppressWildcards
interface ApiServiceKt {
/*** 下载文件* @param fileUrl 文件地址 (这里的url可以是全名,也可以是基于baseUrl拼接的后缀url)* @return*/@Streaming@GETfun downloadFileAsync(@Url fileUrl: String): Deferred<ResponseBody>/*** 上传图片* @param url 可选,不传则使用默认值* @param imgPath 图片路径* @param map 参数*/@Multipart@POSTfun uploadImgAsync(@Url url: String = "${
ApiConstant.UPLOAD_IMAGE_URL}Upload.php", @PartMap imgPath: Map<String, RequestBody>, @QueryMap map: Map<String, Any>): Deferred<Response<UploadImgEntity>>/*** 通用异步请求 只需要解析BaseBean*/@FormUrlEncoded@POST("Interfaces/index")fun requestAsync(@FieldMap map: Map<String, Any>): Deferred<Response<BaseBean>>
}
Retrofit 管理类 RetrofitManagerKt.kt
import android.content.Context
import android.webkit.MimeTypeMap
import com.blankj.utilcode.util.SPUtils
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.MediaType
import okhttp3.RequestBody
import okhttp3.ResponseBody
import retrofit2.Response
import java.io.File
import java.net.URLEncoder
import java.util.*/*** Retrofit 管理类* @author ssq*/
object RetrofitManagerKt {
// 接口API服务val apiService by lazy {
ApiFactory.create(ApiConstant.BASE_URL, ApiServiceKt::class.java) }/*** 执行网络请求(结合kotlin 协程使用)* @param deferred 请求的接口* @param isValidateCode 是否验证code,如:登录是否过期* @param context 为null时,登录过期不跳登录页*/suspend fun <T : BaseBean> request(deferred: Deferred<Response<T>>, isValidateCode: Boolean = false, context: Context? = null): T? = withContext(Dispatchers.Default) {
try {
val response = deferred.await()if (response.isSuccessful) {
// 成功val body = response.body()if (isValidateCode && body != null) {
validateCode(context, body.code)}body ?: throw NullBodyException()} else {
// 处理Http异常ExceptionUtil.catchHttpException(response.code())null}} catch (e: Exception) {
// 这里统一处理错误ExceptionUtil.catchException(e)null}}/*** 下载文件*/suspend fun downloadFile(fileUrl: String): ResponseBody? = withContext(Dispatchers.IO) {
try {
apiService.downloadFileAsync(fileUrl).await()} catch (e: Exception) {
// 这里统一处理错误ExceptionUtil.catchException(e)null}}/*** 上传图片文件* @param file 图片文件* @param type 用途类型*/suspend fun uploadImage(file: File, type: Int): UploadImgEntity? =request(apiService.uploadImgAsync(imgPath = getUploadImgBodyMap(file), map = getUploadImgMap(type)))/*** 生成上传图片请求的文件参数* @param file 上传文件*/fun getUploadImgBodyMap(file: File): HashMap<String, RequestBody> {
val requestBodyMap = hashMapOf<String, RequestBody>()val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(file.path)) ?: "image/jpeg"val fileBody = RequestBody.create(MediaType.parse(mimeType), file)// (注意:okhttp3 请求头不能为中文)如果url参数值含有中文、特殊字符时,需要使用 url 编码。requestBodyMap["myfiles\"; filename=\"${
URLEncoder.encode(file.name, "utf-8")}"] = fileBodyreturn requestBodyMap}/*** 生成上传图片请求参数* @param type 用途类型*/fun getUploadImgMap(type: Int): HashMap<String, Any> {
val map = hashMapOf<String, Any>()map["type"] = typemap["time"] = System.currentTimeMillis()return map}
}
异常工具类 ExceptionUtil.kt
import android.accounts.NetworkErrorException
import android.content.res.Resources
import androidx.annotation.StringRes
import com.blankj.utilcode.util.ToastUtils
import com.google.gson.stream.MalformedJsonException
import retrofit2.HttpException
import java.net.SocketTimeoutException
import java.net.UnknownHostException/*** 异常工具类* @author ssq*/
object ExceptionUtil {
/*** 处理异常*/fun catchException(e: Exception) {
e.printStackTrace()val msg = when (e) {
is HttpException -> {
catchHttpException(e.code())return}is SocketTimeoutException -> R.string.common_error_net_time_outis UnknownHostException, is NetworkErrorException -> R.string.common_error_netis NullPointerException, is ClassCastException, is Resources.NotFoundException,is MalformedJsonException -> R.string.common_error_do_something_failis NullBodyException -> R.string.common_error_server_body_nullelse -> R.string.common_error_do_something_fail}ToastUtils.showShort(msg)}/*** 处理网络异常*/fun catchHttpException(errorCode: Int) {
if (errorCode in 200 until 300) return// 成功code则不处理showToast(catchHttpExceptionCode(errorCode), errorCode)}/*** toast提示*/private fun showToast(@StringRes errorMsg: Int, errorCode: Int) {
ToastUtils.showShort("${
BaseApplication.instance.getString(errorMsg)}:$errorCode ")}/*** 处理网络异常*/private fun catchHttpExceptionCode(errorCode: Int): Int = when (errorCode) {
in 500..600 -> R.string.common_error_serverin 400 until 500 -> R.string.common_error_requestelse -> R.string.common_error_request}
}
提示文字 string.xml
<string name="common_error_net">网络异常,请检查网络连接!</string><string name="common_error_net_time_out">网络超时</string><string name="common_error_do_something_fail">操作异常</string><string name="common_error_request">请求错误</string><string name="common_error_server">服务器错误</string><string name="common_error_server_body_null">服务器错误:body为空</string>
3、开始使用
MVP架构模式
- Activity文件,Activity为主协程域,在界面销毁时取消协程。
/*** 地址列表页* "CoroutineScope by MainScope()" 协程生命周期管理。在onDestroy()中调用cancel()取消协程。调用launch{}启动协程* @author ssq*/
class AddressListActivity : BaseListActivity<AddressListPresenter>(), CoroutineScope by MainScope(), AddressListContract.View {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)mPresenter = AddressListPresenter(this)mPresenter?.deleteAddress(data.id, position)}override fun onDestroy() {
super.onDestroy()cancel()// 取消协程}
}
- Presenter文件,从Activity 把协程域传递给Presenter
class AddressListPresenter(scope: CoroutineScope) : BasePresenterKt<AddressListContract.View>(), CoroutineScope by scope, AddressListContract.Presenter {
override fun deleteAddress(id: String, position: Int) = launch {
val map = HashMap<String, Any>()map["id"] = idRetrofitManagerKt.request(RetrofitManagerKt.apiService.requestAsync(map), true, mContext)?.also {
if (it.code == 0) {
mView?.deleteAddressSuccess(position)} else {
ToastUtils.showShort(it.message)}}}
}
-------2020年4月25日更新----------
有需要Demo源码的朋友看这里,第三方库版本是基于4月25日最新的
可以将源码下载下来参考,或是直接使用。加快你的项目开发!如果可以顺便点个Star 非常感谢了?
MVP架构源码GitHub地址
MVVM架构模式 看这里
至此,主要逻辑就是这些!