From e7aa8f71cda27f213632e9765dac0a3b6185713a Mon Sep 17 00:00:00 2001 From: yimiao Date: Mon, 19 Jul 2021 11:12:33 +0800 Subject: [PATCH] =?UTF-8?q?author:wangyimiao=20desc:=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=BD=91=E7=BB=9C=E8=AF=B7=E6=B1=82=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 27 +++-- .../ExampleInstrumentedTest.java | 26 ----- .../com/yinuo/commonlibtest/MainActivity.java | 5 +- .../yinuo/commonlibtest/ExampleUnitTest.java | 17 --- commonLib/build.gradle | 19 ++++ commonLib/src/main/assets/baseUrl.properties | 2 + .../com/yinuo/commonlib/CommonApplication.kt | 17 +++ .../com/yinuo/commonlib/net/BaseObserve.kt | 98 ++++++++++++++++++ .../yinuo/commonlib/net/RequestCallBack.kt | 10 ++ .../com/yinuo/commonlib/net/RequestManager.kt | 51 +++++++++ .../yinuo/commonlib/net/bean/BaseResponse.kt | 21 ++++ .../yinuo/commonlib/net/bean/RequestParam.kt | 9 ++ .../net/interceptor/BaseUrlInterceptor.kt | 83 +++++++++++++++ .../net/interceptor/HttpCommonInterceptor.kt | 70 +++++++++++++ yinuoapp.jks | Bin 0 -> 2469 bytes 15 files changed, 401 insertions(+), 54 deletions(-) delete mode 100644 app/src/androidTest/java/com/yinuo/commonlibtest/ExampleInstrumentedTest.java delete mode 100644 app/src/test/java/com/yinuo/commonlibtest/ExampleUnitTest.java create mode 100644 commonLib/src/main/assets/baseUrl.properties create mode 100644 commonLib/src/main/java/com/yinuo/commonlib/CommonApplication.kt create mode 100644 commonLib/src/main/java/com/yinuo/commonlib/net/BaseObserve.kt create mode 100644 commonLib/src/main/java/com/yinuo/commonlib/net/RequestCallBack.kt create mode 100644 commonLib/src/main/java/com/yinuo/commonlib/net/RequestManager.kt create mode 100644 commonLib/src/main/java/com/yinuo/commonlib/net/bean/BaseResponse.kt create mode 100644 commonLib/src/main/java/com/yinuo/commonlib/net/bean/RequestParam.kt create mode 100644 commonLib/src/main/java/com/yinuo/commonlib/net/interceptor/BaseUrlInterceptor.kt create mode 100644 commonLib/src/main/java/com/yinuo/commonlib/net/interceptor/HttpCommonInterceptor.kt create mode 100644 yinuoapp.jks diff --git a/app/build.gradle b/app/build.gradle index 9fbfb0a..667fb79 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,17 +1,19 @@ plugins { id 'com.android.application' + id 'kotlin-android' + id 'kotlin-android-extensions' + id 'kotlin-kapt' } android { - compileSdkVersion 30 - buildToolsVersion "30.0.3" + compileSdkVersion rootProject.ext.android.compileSdkVersion + buildToolsVersion rootProject.ext.android.buildToolsVersion defaultConfig { - applicationId "com.yinuo.commonlibtest" - minSdkVersion 23 - targetSdkVersion 30 - versionCode 1 - versionName "1.0" + minSdkVersion rootProject.ext.android.minSdkVersion + targetSdkVersion rootProject.ext.android.targetSdkVersion + versionCode rootProject.ext.android.versionCode + versionName rootProject.ext.android.versionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -26,6 +28,14 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + + dataBinding { + enabled = true + } + + viewBinding { + enabled = true + } } dependencies { @@ -34,7 +44,4 @@ dependencies { implementation 'com.google.android.material:material:1.2.1' implementation 'androidx.constraintlayout:constraintlayout:2.0.1' implementation project(path: ':commonLib') - testImplementation 'junit:junit:4.+' - androidTestImplementation 'androidx.test.ext:junit:1.1.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' } \ No newline at end of file diff --git a/app/src/androidTest/java/com/yinuo/commonlibtest/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/yinuo/commonlibtest/ExampleInstrumentedTest.java deleted file mode 100644 index 82e98bd..0000000 --- a/app/src/androidTest/java/com/yinuo/commonlibtest/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.yinuo.commonlibtest; - -import android.content.Context; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - assertEquals("com.yinuo.commonlibtest", appContext.getPackageName()); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/yinuo/commonlibtest/MainActivity.java b/app/src/main/java/com/yinuo/commonlibtest/MainActivity.java index f3ade52..f8320a4 100644 --- a/app/src/main/java/com/yinuo/commonlibtest/MainActivity.java +++ b/app/src/main/java/com/yinuo/commonlibtest/MainActivity.java @@ -4,11 +4,14 @@ import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; +import com.yinuo.commonlibtest.databinding.ActivityMainBinding; + public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); + ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); } } \ No newline at end of file diff --git a/app/src/test/java/com/yinuo/commonlibtest/ExampleUnitTest.java b/app/src/test/java/com/yinuo/commonlibtest/ExampleUnitTest.java deleted file mode 100644 index cf66f31..0000000 --- a/app/src/test/java/com/yinuo/commonlibtest/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.yinuo.commonlibtest; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/commonLib/build.gradle b/commonLib/build.gradle index 07004a9..060c04b 100644 --- a/commonLib/build.gradle +++ b/commonLib/build.gradle @@ -23,16 +23,23 @@ android { debug { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + buildConfigField 'String', 'BASE_URL_CONFIG_PATH', '"baseUrl.properties"' } release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + buildConfigField 'String', 'BASE_URL_CONFIG_PATH', '"baseUrl.properties"' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + + androidExtensions { + // kotlin序列化 + experimental = true + } } dependencies { @@ -47,6 +54,18 @@ dependencies { // 添加kotlin依赖 implementation rootProject.ext.dependencies.kotlin implementation rootProject.ext.dependencies.kotlin_android + // 添加okhttp依赖 + implementation rootProject.ext.dependencies.okhttp + // 添加retrofit依赖 + implementation rootProject.ext.dependencies.retrofit + // 添加retrofit转化gson能力依赖 + implementation rootProject.ext.dependencies.converter_gson + // 添加rxjava依赖 + implementation rootProject.ext.dependencies.rxjava + // 添加rxAndroid依赖 + implementation rootProject.ext.dependencies.rxandroid + // 添加retrofit和rxjava适配器 + implementation rootProject.ext.dependencies.retrofit_rxjava //luban图片压缩 implementation rootProject.ext.dependencies.luban diff --git a/commonLib/src/main/assets/baseUrl.properties b/commonLib/src/main/assets/baseUrl.properties new file mode 100644 index 0000000..8f9b83a --- /dev/null +++ b/commonLib/src/main/assets/baseUrl.properties @@ -0,0 +1,2 @@ +debug1 = https://www.wanandroid.com +debug2 = https://www.baidu.com diff --git a/commonLib/src/main/java/com/yinuo/commonlib/CommonApplication.kt b/commonLib/src/main/java/com/yinuo/commonlib/CommonApplication.kt new file mode 100644 index 0000000..7fc9f2e --- /dev/null +++ b/commonLib/src/main/java/com/yinuo/commonlib/CommonApplication.kt @@ -0,0 +1,17 @@ +package com.yinuo.commonlib + +import android.annotation.SuppressLint +import android.content.Context + +@SuppressLint("StaticFieldLeak") +object CommonApplication { + private var commonContext: Context? = null + + fun intLibs(context: Context) { + commonContext = context + } + + fun getContext(): Context? { + return commonContext + } +} \ No newline at end of file diff --git a/commonLib/src/main/java/com/yinuo/commonlib/net/BaseObserve.kt b/commonLib/src/main/java/com/yinuo/commonlib/net/BaseObserve.kt new file mode 100644 index 0000000..1eda43a --- /dev/null +++ b/commonLib/src/main/java/com/yinuo/commonlib/net/BaseObserve.kt @@ -0,0 +1,98 @@ +package com.common.commonlib.net + +import android.net.ParseException +import android.util.Log +import com.google.gson.JsonParseException +import io.reactivex.Observable +import io.reactivex.Observer +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import org.json.JSONException +import retrofit2.HttpException +import java.net.ConnectException +import java.net.SocketTimeoutException +import java.net.UnknownHostException +import javax.net.ssl.SSLHandshakeException + +/** + * 请求基类 + */ +open class BaseObserve { + /** + * 初始化服务类 + */ + fun initService(clazz: Class): T { + return RequestManager.INSTANCE.create(clazz) + } + + /** + * 调用接口 + * @param observable 可订阅 + * @param callBack 请求回调 + */ + fun observe(observable: Observable, callBack: RequestCallBack?) { + observable + .subscribeOn(Schedulers.io()) + .unsubscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : Observer { + override fun onSubscribe(d: Disposable) { + } + + override fun onNext(t: R) { + callBack?.onResult(t) + } + + override fun onError(e: Throwable) { + e.message?.let { Log.e(TAG, it) } + callBack?.onError(dealError(e)) + } + + override fun onComplete() { + callBack?.onComplete() + } + }) + } + + /** + * 处理错误 + * @param e 异常 + */ + private fun dealError(e: Throwable): String? { + if (e is HttpException) { + return when (e.code()) { + UNAUTHORIZED -> "登录验证过期" + INTERNAL_SERVER_ERROR -> "服务器错误" + FORBIDDEN, NOT_FOUND -> "无效的请求" + REQUEST_TIMEOUT, GATEWAY_TIMEOUT, BAD_GATEWAY, SERVICE_UNAVAILABLE -> e.message() + else -> e.message() + } + } else if (e is ConnectException) { + return "网络连接异常,请检查您的网络状态" + } else if (e is SocketTimeoutException) { + return "网络连接超时,请检查您的网络状态,稍后重试" + } else if (e is UnknownHostException) { + return "网络异常,请检查您的网络状态" + } else if (e is JsonParseException || e is JSONException || e is ParseException) { + return "数据解析错误" + } else if (e is SSLHandshakeException) { + return "网络异常,请检查您的网络状态" + } else if (e is RuntimeException) { + return "运行时异常" + } + return e.message + } + + companion object { + const val TAG: String = "BaseLoader" + const val UNAUTHORIZED = 401 + const val FORBIDDEN = 403 + const val NOT_FOUND = 404 + const val REQUEST_TIMEOUT = 408 + const val INTERNAL_SERVER_ERROR = 500 + const val BAD_GATEWAY = 502 + const val SERVICE_UNAVAILABLE = 503 + const val GATEWAY_TIMEOUT = 504 + } +} \ No newline at end of file diff --git a/commonLib/src/main/java/com/yinuo/commonlib/net/RequestCallBack.kt b/commonLib/src/main/java/com/yinuo/commonlib/net/RequestCallBack.kt new file mode 100644 index 0000000..4f290af --- /dev/null +++ b/commonLib/src/main/java/com/yinuo/commonlib/net/RequestCallBack.kt @@ -0,0 +1,10 @@ +package com.common.commonlib.net + +/** + * 请求回调 + */ +interface RequestCallBack { + fun onResult(result: T) + fun onError(error: String?) + fun onComplete() +} \ No newline at end of file diff --git a/commonLib/src/main/java/com/yinuo/commonlib/net/RequestManager.kt b/commonLib/src/main/java/com/yinuo/commonlib/net/RequestManager.kt new file mode 100644 index 0000000..a4ea8a0 --- /dev/null +++ b/commonLib/src/main/java/com/yinuo/commonlib/net/RequestManager.kt @@ -0,0 +1,51 @@ +package com.common.commonlib.net + +import com.yinuo.commonlib.net.interceptor.BaseUrlInterceptor +import com.yinuo.commonlib.net.interceptor.HttpCommonInterceptor +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory +import retrofit2.converter.gson.GsonConverterFactory +import java.util.concurrent.TimeUnit + +/** + * 网络请求管理器 + */ +enum class RequestManager { + INSTANCE; + + /** + * 默认超时时间 + */ + private val defaultTimeOut: Long = 5 + + /** + * 默认读取超时时间 + */ + private val defaultReadTimeOut: Long = 10 + + private var mRetrofit: Retrofit? = null + + init { + val builder: OkHttpClient.Builder = OkHttpClient.Builder() + builder.connectTimeout(defaultTimeOut, TimeUnit.SECONDS) + builder.readTimeout(defaultReadTimeOut, TimeUnit.SECONDS) + val commonInterceptor: HttpCommonInterceptor = HttpCommonInterceptor.Builder().build() + builder.addInterceptor(commonInterceptor) + builder.addInterceptor(BaseUrlInterceptor()) + + mRetrofit = Retrofit.Builder() + .client(builder.build()) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .addConverterFactory(GsonConverterFactory.create()) + .baseUrl("https://www.wanandroid.com") + .build() + } + + /** + * 创建service + */ + fun create(service: Class): T { + return mRetrofit!!.create(service) + } +} \ No newline at end of file diff --git a/commonLib/src/main/java/com/yinuo/commonlib/net/bean/BaseResponse.kt b/commonLib/src/main/java/com/yinuo/commonlib/net/bean/BaseResponse.kt new file mode 100644 index 0000000..f5d894d --- /dev/null +++ b/commonLib/src/main/java/com/yinuo/commonlib/net/bean/BaseResponse.kt @@ -0,0 +1,21 @@ +package com.yinuo.commonlib.net.bean + +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize + +/** + * 基础响应类 + */ +@Parcelize +open class BaseResponse : Parcelable { + var errorCode: Int = 0 + var errorMessage: String = "" + + fun getMErrorCode(): Int { + return errorCode + } + + fun getMErrorMessage(): String { + return errorMessage + } +} diff --git a/commonLib/src/main/java/com/yinuo/commonlib/net/bean/RequestParam.kt b/commonLib/src/main/java/com/yinuo/commonlib/net/bean/RequestParam.kt new file mode 100644 index 0000000..b81cf00 --- /dev/null +++ b/commonLib/src/main/java/com/yinuo/commonlib/net/bean/RequestParam.kt @@ -0,0 +1,9 @@ +package com.yinuo.commonlib.net.bean + +/** + * 请求参数 + */ +data class RequestParam(var baseUrl: String) { + var params: Map? = null +} + diff --git a/commonLib/src/main/java/com/yinuo/commonlib/net/interceptor/BaseUrlInterceptor.kt b/commonLib/src/main/java/com/yinuo/commonlib/net/interceptor/BaseUrlInterceptor.kt new file mode 100644 index 0000000..95d1640 --- /dev/null +++ b/commonLib/src/main/java/com/yinuo/commonlib/net/interceptor/BaseUrlInterceptor.kt @@ -0,0 +1,83 @@ +package com.yinuo.commonlib.net.interceptor + +import android.text.TextUtils +import android.util.Log +import com.yinuo.commonlib.BuildConfig +import com.yinuo.commonlib.CommonApplication +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.Interceptor +import okhttp3.Request +import okhttp3.Response +import java.io.BufferedInputStream +import java.util.* + +/** + * BaseUrl链接拦截器 + */ +class BaseUrlInterceptor : Interceptor { + /** + * 拦截替换BaseUrl + */ + override fun intercept(chain: Interceptor.Chain): Response { + val oldRequest: Request = chain.request() + val requestBuilder: Request.Builder = oldRequest.newBuilder() + val headKey = BASE_URL_KEY + val headValue: String? = oldRequest.header(headKey) + // 判断是否需要替换BaseUrl,不需要则直接跳转 + if (TextUtils.isEmpty(headValue)) { + return chain.proceed(chain.request()) + } + // 该HEAD只用于替换BaseUrl,将对应的自定义HEAD去除 + requestBuilder.removeHeader(headKey) + // 尝试获取新BaseUrl + val newUrlValue = getValueFromProperties(headValue) + val newBaseUrl: HttpUrl? = newUrlValue.toHttpUrlOrNull() + // 获取到新BaseUrl进行替换 + return if (newBaseUrl != null) { + val oldHttpUrl: HttpUrl = oldRequest.url + val newUrl = oldHttpUrl.newBuilder() + .scheme(newBaseUrl.scheme) + .host(newBaseUrl.host) + .port(newBaseUrl.port) + .build() + chain.proceed(requestBuilder.url(newUrl).build()) + } else { + chain.proceed(chain.request()) + } + } + + /** + * 通过KEY从配置文件中获取值 + */ + private fun getValueFromProperties(key: String?): String { + val prop = Properties() + var value = "" + try { + val context = CommonApplication.getContext() + if (context == null) { + Log.e(TAG, "got context null, please init context!") + return value + } + val mAssets = context.assets + if (mAssets == null) { + Log.e(TAG, "got assets null, please check the assets folder") + } + // 通过输入缓冲流进行读取配置文件 + val inputStream = + BufferedInputStream(mAssets.open((BuildConfig.BASE_URL_CONFIG_PATH))) + // 加载输入流 + prop.load(inputStream) + // 根据关键字获取value值 + value = prop.getProperty(key); + } catch (e: Exception) { + Log.e(TAG, e.toString()) + } + return value + } + + companion object { + const val TAG = "BaseUrlInterceptor" + const val BASE_URL_KEY = "baseurl" + } +} \ No newline at end of file diff --git a/commonLib/src/main/java/com/yinuo/commonlib/net/interceptor/HttpCommonInterceptor.kt b/commonLib/src/main/java/com/yinuo/commonlib/net/interceptor/HttpCommonInterceptor.kt new file mode 100644 index 0000000..b4d40a0 --- /dev/null +++ b/commonLib/src/main/java/com/yinuo/commonlib/net/interceptor/HttpCommonInterceptor.kt @@ -0,0 +1,70 @@ +package com.yinuo.commonlib.net.interceptor + +import okhttp3.Interceptor +import okhttp3.Request +import okhttp3.Response + +/** + * 公共请求拦截器 + */ +class HttpCommonInterceptor : Interceptor { + /** + * 请求头部参数 + */ + var mHeaderHashMap: HashMap? = null + + /** + * 拦截请求并添加参数 + */ + override fun intercept(chain: Interceptor.Chain): Response { + val oldRequest: Request = chain.request() + val requestBuilder: Request.Builder = oldRequest.newBuilder() + requestBuilder.method(oldRequest.method, oldRequest.body) + if (mHeaderHashMap != null && mHeaderHashMap!!.size > 0) { + for (item: Map.Entry in mHeaderHashMap!!.entries) { + requestBuilder.addHeader(item.key, item.value) + } + } + return chain.proceed(requestBuilder.build()) + } + + /** + * builder构造函数 + */ + class Builder { + private var mHttpCommonInterceptor: HttpCommonInterceptor? = null + + init { + mHttpCommonInterceptor = HttpCommonInterceptor() + } + + fun addHeadParams(key: String, value: String): Builder { + mHttpCommonInterceptor?.mHeaderHashMap?.set(key, value) + return this + } + + fun addHeadParams(key: String, value: Int): Builder { + mHttpCommonInterceptor?.mHeaderHashMap?.set(key, value.toString()) + return this + } + + fun addHeadParams(key: String, value: Long): Builder { + mHttpCommonInterceptor?.mHeaderHashMap?.set(key, value.toString()) + return this + } + + fun addHeadParams(key: String, value: Float): Builder { + mHttpCommonInterceptor?.mHeaderHashMap?.set(key, value.toString()) + return this + } + + fun addHeadParams(key: String, value: Double): Builder { + mHttpCommonInterceptor?.mHeaderHashMap?.set(key, value.toString()) + return this + } + + fun build(): HttpCommonInterceptor { + return mHttpCommonInterceptor!! + } + } +} \ No newline at end of file diff --git a/yinuoapp.jks b/yinuoapp.jks new file mode 100644 index 0000000000000000000000000000000000000000..3ddd97943194e75a13a012a8f497bd563a0c26ef GIT binary patch literal 2469 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 Y1&dO9Ii>-_MWWp{#UczKU_!ya00+Zu(*OVf literal 0 HcmV?d00001