├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── xml │ │ │ │ ├── style.xml │ │ │ │ ├── network_security_config.xml │ │ │ │ └── provider_paths.xml │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── drawable │ │ │ │ ├── android.png │ │ │ │ ├── image1.png │ │ │ │ ├── image2.png │ │ │ │ ├── image3.jpg │ │ │ │ ├── image4.jpg │ │ │ │ ├── image5.jpg │ │ │ │ ├── ic_dialog_error.png │ │ │ │ ├── ic_dialog_spin.png │ │ │ │ ├── ic_dialog_finish.png │ │ │ │ ├── ic_dialog_warning.png │ │ │ │ ├── btn_border.xml │ │ │ │ ├── btn_border_takephoto.xml │ │ │ │ ├── btn_border_nativephoto.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── anim │ │ │ │ ├── dialog_bottom_out.xml │ │ │ │ └── dialog_bottom_in.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── layout │ │ │ │ ├── dialog_toast.xml │ │ │ │ ├── dialog_camera_panel.xml │ │ │ │ └── activity_main.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── lvhttp │ │ │ │ └── test │ │ │ │ ├── LoginBean.kt │ │ │ │ ├── UpLoadBean.kt │ │ │ │ ├── Bean.kt │ │ │ │ ├── response │ │ │ │ └── ResponseData.kt │ │ │ │ ├── CustomInterceptor.java │ │ │ │ ├── ArticleBean.kt │ │ │ │ ├── BaseApplication.kt │ │ │ │ ├── Service.kt │ │ │ │ └── MainActivity.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── lvhttp │ │ │ └── test │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── lvhttp │ │ └── test │ │ └── ExampleInstrumentedTest.kt ├── build.gradle └── proguard-rules.pro ├── net ├── .gitignore ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── lvhttp │ │ └── net │ │ ├── error │ │ ├── ErrorDispose.kt │ │ ├── ErrorValue.kt │ │ ├── CodeException.kt │ │ └── ErrorKey.kt │ │ ├── converter │ │ ├── Chunked.java │ │ ├── LvDefaultConverterFactory.kt │ │ └── LvDefaultCallAdapterFactory.java │ │ ├── response │ │ ├── BaseResponse.kt │ │ └── ResultState.kt │ │ ├── interceptor │ │ ├── CacheInterceptor.kt │ │ └── LogInterceptor.kt │ │ ├── param │ │ ├── CreateBody.kt │ │ └── Part.kt │ │ ├── download │ │ ├── DownResponse.kt │ │ └── DownloadKt.kt │ │ ├── launch │ │ └── Launch.kt │ │ ├── LvController.kt │ │ ├── utils │ │ ├── NetWorkUtils.kt │ │ ├── HTTPSCerUtils.java │ │ ├── FileQUtils.java │ │ └── Utils.java │ │ └── LvHttp.kt ├── build.gradle └── proguard-rules.pro ├── .idea ├── sonarlint │ └── issuestore │ │ ├── 1 │ │ ├── 0 │ │ │ └── 1005d90025985822b7da15ba2898680ef959e0dc │ │ ├── 7 │ │ │ └── 17529f6699b8da6aeaeabc86bb7f16fdef578fca │ │ └── b │ │ │ └── 1b364e1270cbcdff3fea28545d339dc6339910b8 │ │ ├── 2 │ │ └── 2 │ │ │ └── 22f66b6c0d87856527afafd3c03282d55cea7a89 │ │ ├── 8 │ │ ├── c │ │ │ └── 8c55c3ccc257e5907959013f99656e4c8ec3903e │ │ └── e │ │ │ └── 8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d │ │ ├── a │ │ └── c │ │ │ ├── aca0b93f5725b4078b2a6a1d73c6ebf0dc8d02ba │ │ │ └── acfa96721f8c252d0e8c481efc590ea646cc561d │ │ ├── c │ │ └── 5 │ │ │ └── c5d0ef2c8da3717fa8007f2e0fb2c32315b2f34b │ │ ├── d │ │ ├── 4 │ │ │ └── d4b19da62ec6e2082dd46718b269adb34fe0cd0d │ │ └── a │ │ │ └── da62cce960093e3cf9870b834222fa1483bb156b │ │ ├── e │ │ └── 2 │ │ │ └── e2bbfd1a412389e952eefbeeb16ad9cf9e51670d │ │ └── index.pb ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── compiler.xml ├── kotlinc.xml ├── ktlint.xml ├── vcs.xml ├── migrations.xml ├── deploymentTargetSelector.xml ├── misc.xml ├── gradle.xml ├── runConfigurations.xml ├── jarRepositories.xml └── inspectionProfiles │ └── Project_Default.xml ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── gradle.properties ├── gradlew.bat ├── gradlew └── README.md /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /net/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/1/0/1005d90025985822b7da15ba2898680ef959e0dc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/1/7/17529f6699b8da6aeaeabc86bb7f16fdef578fca: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/1/b/1b364e1270cbcdff3fea28545d339dc6339910b8: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/2/2/22f66b6c0d87856527afafd3c03282d55cea7a89: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/8/c/8c55c3ccc257e5907959013f99656e4c8ec3903e: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/a/c/aca0b93f5725b4078b2a6a1d73c6ebf0dc8d02ba: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/c/5/c5d0ef2c8da3717fa8007f2e0fb2c32315b2f34b: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/d/a/da62cce960093e3cf9870b834222fa1483bb156b: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/e/2/e2bbfd1a412389e952eefbeeb16ad9cf9e51670d: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':net' 2 | rootProject.name='LvHttp' 3 | -------------------------------------------------------------------------------- /app/src/main/res/xml/style.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /net/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | net 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | LvHttp 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/app/src/main/res/drawable/android.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/app/src/main/res/drawable/image1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/app/src/main/res/drawable/image2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/image3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/app/src/main/res/drawable/image3.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/image4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/app/src/main/res/drawable/image4.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/image5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/app/src/main/res/drawable/image5.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dialog_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/app/src/main/res/drawable/ic_dialog_error.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dialog_spin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/app/src/main/res/drawable/ic_dialog_spin.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dialog_finish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/app/src/main/res/drawable/ic_dialog_finish.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dialog_warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/app/src/main/res/drawable/ic_dialog_warning.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/a/c/acfa96721f8c252d0e8c481efc590ea646cc561d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/.idea/sonarlint/issuestore/a/c/acfa96721f8c252d0e8c481efc590ea646cc561d -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/d/4/d4b19da62ec6e2082dd46718b269adb34fe0cd0d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kngLv/LvHttp/HEAD/.idea/sonarlint/issuestore/d/4/d4b19da62ec6e2082dd46718b269adb34fe0cd0d -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/ktlint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /net/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Feb 18 11:49:49 CST 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip 7 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /net/src/main/java/com/lvhttp/net/error/ErrorDispose.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.net.error 2 | 3 | /** 4 | * @name ErrorDispose 5 | * @package com.www.net.error 6 | * @author 345 QQ:1831712732 7 | * @time 2020/6/24 22:52 8 | * @description 全局的异常处理 9 | */ 10 | 11 | class ErrorDispose(errorName: ErrorKey, error: () -> Unit) -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /net/src/main/java/com/lvhttp/net/error/ErrorValue.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.net.error 2 | 3 | import java.lang.Exception 4 | 5 | /** 6 | * @name ErrorValue 7 | * @package com.www.net.error 8 | * @author 345 QQ:1831712732 9 | * @time 2020/6/27 17:55 10 | * @description 11 | */ 12 | class ErrorValue(val error: (Exception) -> Unit) -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/anim/dialog_bottom_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/anim/dialog_bottom_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | -------------------------------------------------------------------------------- /.idea/deploymentTargetSelector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /net/src/main/java/com/lvhttp/net/error/CodeException.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.net.error 2 | 3 | import java.lang.Exception 4 | 5 | /** 6 | * @name CodeException 7 | * @package com.www.net.error 8 | * @author 345 QQ:1831712732 9 | * @time 2020/6/27 18:25 10 | * @description 11 | */ 12 | class CodeException(val code: Int, private val mssage: String) : Exception(mssage) -------------------------------------------------------------------------------- /net/src/main/java/com/lvhttp/net/converter/Chunked.java: -------------------------------------------------------------------------------- 1 | package com.lvhttp.net.converter; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | 9 | @Target(ElementType.PARAMETER) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | public @interface Chunked { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_border.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_border_takephoto.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/lvhttp/test/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.test 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_border_nativephoto.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/xml/provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 12 | 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/lvhttp/test/LoginBean.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.test 2 | 3 | data class LoginBean( 4 | val admin: Boolean, 5 | val chapterTops: List, 6 | val coinCount: Int, 7 | val collectIds: List, 8 | val email: String, 9 | val icon: String, 10 | val id: Int, 11 | val nickname: String, 12 | val password: String, 13 | val publicName: String, 14 | val token: String, 15 | val type: Int, 16 | val username: String 17 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/lvhttp/test/UpLoadBean.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.test 2 | 3 | 4 | /** 5 | * @name UpLoadBean 6 | * @package com.www.lvhttp 7 | * @author 345 QQ:1831712732 8 | * @time 2020/6/28 23:43 9 | * @description 10 | */ 11 | 12 | //class UpLoadBean : ArrayList(){ 13 | data class UpLoadBean( 14 | val error: String, 15 | val info: String, // ok 16 | val path: String // updata_image/file/20200628161411JSeM0.png 17 | ) 18 | //} -------------------------------------------------------------------------------- /app/src/main/java/com/lvhttp/test/Bean.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.test 2 | 3 | /** 4 | * @name Bean 5 | * @package com.lvhttp.test 6 | * @author 345 QQ:1831712732 7 | * @time 2021/01/05 22:17 8 | * @description 9 | */ 10 | data class Bean( 11 | val `data`: List, 12 | val errorCode: Int, 13 | val errorMsg: String 14 | ) 15 | 16 | data class Data( 17 | val children: List, 18 | val courseId: Int, 19 | val id: Int, 20 | val name: String, 21 | val order: Int, 22 | val parentChapterId: Int, 23 | val userControlSetTop: Boolean, 24 | val visible: Int 25 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/lvhttp/test/response/ResponseData.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.test.response 2 | 3 | import android.util.Log 4 | import com.lvhttp.net.response.BaseResponse 5 | 6 | /** 7 | * @name ResponseData 8 | * @package com.lvhttp.test.response 9 | * @author 345 QQ:1831712732 10 | * @time 2021/06/30 22:28 11 | * @description 12 | */ 13 | 14 | data class ResponseData(val data: T, val errorCode: Int, val errorMsg: String) : 15 | BaseResponse() { 16 | override fun notifyData(): BaseResponse { 17 | _data = data 18 | _code = errorCode 19 | _message = errorMsg 20 | return super.notifyData() 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/lvhttp/test/CustomInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.lvhttp.test; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.io.IOException; 6 | 7 | import okhttp3.Interceptor; 8 | import okhttp3.Response; 9 | 10 | /** 11 | * @author 345 QQ:1831712732 12 | * @package : com.lvhttp.test 13 | * @time : 2020/10/19 20:43 14 | * @description : 15 | */ 16 | public class CustomInterceptor implements Interceptor { 17 | @NotNull 18 | @Override 19 | public Response intercept(@NotNull Chain chain) throws IOException { 20 | Response proceed = chain.proceed(chain.request()); 21 | 22 | 23 | throw new IOException("网络异常"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | -------------------------------------------------------------------------------- /net/src/main/java/com/lvhttp/net/response/BaseResponse.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.net.response 2 | 3 | import android.widget.Toast 4 | import com.lvhttp.net.LvHttp 5 | import com.lvhttp.net.error.CodeException 6 | import com.lvhttp.net.error.ErrorKey 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.withContext 9 | import java.lang.NullPointerException 10 | 11 | /** 12 | * @name Data 13 | * @package com.www.net 14 | * @author 345 QQ:1831712732 15 | * @time 2020/6/27 15:41 16 | * @description 对 Response 的包装,可直接继承,参考示例中的 ResponseData 17 | */ 18 | 19 | open class BaseResponse(var _data: T? = null, var _code: Int = 0, var _message: String? = null) { 20 | open fun notifyData(): BaseResponse = this 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/lvhttp/test/ArticleBean.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.test 2 | 3 | 4 | /** 5 | * @name Data 6 | * @package com.www.lvhttp 7 | * @author 345 QQ:1831712732 8 | * @time 2020/6/26 22:47 9 | * @description 10 | */ 11 | class ArticleBean : ArrayList() { 12 | data class BeanItem( 13 | val children: List, 14 | val courseId: Int, // 13 15 | val id: Int, // 408 16 | val name: String, // 鸿洋 17 | val order: Int, // 190000 18 | val parentChapterId: Int, // 407 19 | val userControlSetTop: Boolean, // false 20 | val visible: Int // 1 21 | ) 22 | 23 | override fun toString(): String { 24 | return joinToString() 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/lvhttp/test/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.test 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.www.lvhttp", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /net/src/main/java/com/lvhttp/net/response/ResultState.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.net.response 2 | 3 | import java.lang.Exception 4 | 5 | /** 6 | * @name ResultState 7 | * @package com.lvhttp.net.response 8 | * @author 345 QQ:1831712732 9 | * @time 2021/01/05 10:19 10 | * @description 11 | */ 12 | sealed class ResultState { 13 | 14 | class SuccessState(val t: T) : ResultState() 15 | class ErrorState(val error: Exception) : ResultState() 16 | 17 | /** 获取请求成功后的数据 */ 18 | fun toData(data: (T) -> Unit): ResultState { 19 | if (this is SuccessState) data.invoke(this.t) 20 | return this 21 | } 22 | 23 | /** 获取请求失败后的错误信息 */ 24 | fun toError(error: (Exception) -> Unit): ResultState { 25 | if (this is ErrorState) error.invoke(this.error) 26 | return this 27 | } 28 | } -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/lvhttp/test/BaseApplication.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.test 2 | 3 | import android.app.Application 4 | import android.util.Log 5 | import android.widget.Toast 6 | import com.lvhttp.net.LvHttp 7 | import com.lvhttp.net.error.ErrorKey 8 | import com.lvhttp.net.error.ErrorValue 9 | 10 | class BaseApplication : Application() { 11 | override fun onCreate() { 12 | super.onCreate() 13 | LvHttp.Builder() 14 | .setApplication(this) 15 | .setBaseUrl("https://www.wanandroid.com/") 16 | .isCache(false) 17 | .isLog(true) 18 | .setCode(0) 19 | .setErrorDispose(ErrorKey.ErrorCode, ErrorValue { 20 | Log.e("345:", "code 错误") 21 | Toast.makeText(this, "Code 错误", Toast.LENGTH_SHORT).show() 22 | }) 23 | .setErrorDispose(ErrorKey.AllEexeption, ErrorValue { 24 | it.printStackTrace() 25 | Log.e("345:", "网络错误") 26 | Toast.makeText(this, "网络错误", Toast.LENGTH_SHORT).show() 27 | }) 28 | .build() 29 | } 30 | } -------------------------------------------------------------------------------- /net/src/main/java/com/lvhttp/net/interceptor/CacheInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.net.interceptor 2 | 3 | import com.lvhttp.net.LvHttp 4 | import com.lvhttp.net.utils.isNetworkConnected 5 | import okhttp3.Interceptor 6 | import okhttp3.Response 7 | 8 | /** 9 | * @name CacheInterceptor 10 | * @package com.www.net.interceptor 11 | * @author 345 QQ:1831712732 12 | * @time 2020/6/22 23:32 13 | * @description 缓存 14 | */ 15 | class CacheInterceptor : Interceptor { 16 | override fun intercept(chain: Interceptor.Chain): Response { 17 | val request = chain.request() 18 | val response = chain.proceed(request) 19 | 20 | if (isNetworkConnected(LvHttp.getAppContext())) { 21 | //如果缓存已经存在:不超过maxAge---->不进行请求,直接返回缓存数据 22 | //超出了maxAge--->发起请求获取数据更新,请求失败返回旧的缓存数据 23 | val maxAge = 60 24 | return response.newBuilder() 25 | //清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效 26 | .removeHeader("Pragma") 27 | .header("Cache-Control", "max-age=$maxAge") 28 | .build() 29 | } 30 | return response 31 | } 32 | } -------------------------------------------------------------------------------- /.idea/sonarlint/issuestore/index.pb: -------------------------------------------------------------------------------- 1 | 2 | b 3 | 2net/src/main/java/com/lvhttp/net/error/ErrorKey.kt,c\5\c5d0ef2c8da3717fa8007f2e0fb2c32315b2f34b 4 | P 5 | app/src/main/AndroidManifest.xml,8\c\8c55c3ccc257e5907959013f99656e4c8ec3903e 6 | g 7 | 7net/src/main/java/com/lvhttp/net/error/CodeException.kt,d\a\da62cce960093e3cf9870b834222fa1483bb156b 8 | d 9 | 4app/src/main/java/com/lvhttp/test/BaseApplication.kt,2\2\22f66b6c0d87856527afafd3c03282d55cea7a89 10 | i 11 | 9net/src/main/java/com/lvhttp/net/response/BaseResponse.kt,1\7\17529f6699b8da6aeaeabc86bb7f16fdef578fca 12 | i 13 | 9net/src/main/java/com/lvhttp/net/download/DownResponse.kt,a\c\acfa96721f8c252d0e8c481efc590ea646cc561d 14 | g 15 | 7net/src/main/java/com/lvhttp/net/download/DownloadKt.kt,1\0\1005d90025985822b7da15ba2898680ef959e0dc 16 | \ 17 | ,app/src/main/java/com/lvhttp/test/Service.kt,a\c\aca0b93f5725b4078b2a6a1d73c6ebf0dc8d02ba 18 | 9 19 | README.md,8\e\8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d 20 | j 21 | :app/src/main/java/com/lvhttp/test/response/ResponseData.kt,1\b\1b364e1270cbcdff3fea28545d339dc6339910b8 22 | a 23 | 1net/src/main/java/com/lvhttp/net/launch/Launch.kt,e\2\e2bbfd1a412389e952eefbeeb16ad9cf9e51670d 24 | a 25 | 1app/src/main/java/com/lvhttp/test/MainActivity.kt,d\4\d4b19da62ec6e2082dd46718b269adb34fe0cd0d -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1024m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | android.nonTransitiveRClass=false 23 | android.nonFinalResIds=false 24 | -------------------------------------------------------------------------------- /net/src/main/java/com/lvhttp/net/param/CreateBody.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.net.param 2 | 3 | import okhttp3.MediaType.Companion.toMediaTypeOrNull 4 | import okhttp3.MultipartBody 5 | import okhttp3.RequestBody 6 | import okhttp3.RequestBody.Companion.asRequestBody 7 | import okhttp3.RequestBody.Companion.toRequestBody 8 | import java.io.File 9 | 10 | /** 11 | * @name CreateBody 12 | * @package com.www.net.param 13 | * @author 345 QQ:1831712732 14 | * @time 2020/6/29 19:46 15 | * @description 16 | */ 17 | 18 | /** 19 | * 返回一个新的请求体,该请求体传输此请求的内容。 20 | */ 21 | fun createFileRequestBody(file: File): RequestBody { 22 | return file.asRequestBody(MultipartBody.FORM) 23 | } 24 | 25 | /** 26 | * 返回传输此字符串的新请求主体 27 | */ 28 | fun createStrRequestBody(str: String): RequestBody { 29 | return str.toRequestBody(MultipartBody.FORM) 30 | } 31 | 32 | /** 33 | * 创建一个 RequestBody 34 | */ 35 | fun createRequestBody(values: String): RequestBody { 36 | return values.toRequestBody("application/json;charset=UTF-8".toMediaTypeOrNull()) 37 | } 38 | 39 | 40 | /** 41 | * 创建一个 map , value 为 RequestBody 42 | */ 43 | fun createRequestBodyMap(params: Map) 44 | : MutableMap { 45 | val par: MutableMap = mutableMapOf() 46 | params.forEach { 47 | par[it.key] = createStrRequestBody(it.value as String) 48 | } 49 | return par 50 | } -------------------------------------------------------------------------------- /net/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | android { 4 | compileSdk 34 5 | 6 | defaultConfig { 7 | minSdkVersion 21 8 | targetSdkVersion 34 9 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 10 | } 11 | 12 | buildTypes { 13 | release { 14 | consumerProguardFiles 'proguard-rule.pro' 15 | } 16 | debug { 17 | consumerProguardFiles 'proguard-rule.pro' 18 | } 19 | } 20 | kotlinOptions { 21 | jvmTarget = "1.8" 22 | } 23 | compileOptions { 24 | sourceCompatibility JavaVersion.VERSION_1_8 25 | targetCompatibility JavaVersion.VERSION_1_8 26 | } 27 | namespace 'com.lvhttp.net' 28 | } 29 | 30 | dependencies { 31 | implementation fileTree(dir: 'libs', include: ['*.jar']) 32 | implementation 'androidx.core:core-ktx:1.13.0' 33 | 34 | //网络请求依赖 35 | implementation "com.squareup.okhttp3:okhttp:4.12.0" 36 | implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0' 37 | implementation 'com.squareup.retrofit2:retrofit:2.9.0' 38 | implementation 'com.squareup.retrofit2:converter-gson:2.9.0' 39 | 40 | //协程基础库 41 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3" 42 | //协程 Android 库,提供 UI 调度器 43 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | 15 | 31 | 32 | 33 | 37 | 38 | -------------------------------------------------------------------------------- /net/src/main/java/com/lvhttp/net/param/Part.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.net.param 2 | 3 | import okhttp3.MultipartBody 4 | import java.io.File 5 | 6 | /** 7 | * @name Parts 8 | * @package com.www.net.param 9 | * @author 345 QQ:1831712732 10 | * @time 2020/6/29 19:50 11 | * @description 12 | */ 13 | 14 | 15 | /** 16 | * 创建一个 MultipartBody.part 17 | */ 18 | fun createPart(key: String, file: File): MultipartBody.Part { 19 | val fileBody = createFileRequestBody(file) 20 | return MultipartBody.Part.createFormData(key, file.name, fileBody) 21 | } 22 | 23 | 24 | /** 25 | * 创建一个 MultipartBody.part 类型的数组 26 | */ 27 | fun createParts(filesMap: Map) 28 | : Array { 29 | val list = arrayListOf() 30 | 31 | filesMap.forEach { 32 | val fileBody = createFileRequestBody(it.value) 33 | val part = MultipartBody.Part.createFormData(it.key, it.value.name, fileBody) 34 | list.add(part) 35 | } 36 | return list.toTypedArray() 37 | } 38 | 39 | /** 40 | * 创建一个 MultipartBody.part 类型的数组 41 | * 其中 key 是 唯一的 42 | */ 43 | fun createParts(key: String, lists: List) 44 | : Array { 45 | val parts = arrayListOf() 46 | lists.forEach { 47 | val fileBody = createFileRequestBody(it) 48 | val part = MultipartBody.Part.createFormData(key, it.name, fileBody) 49 | parts.add(part) 50 | } 51 | return parts.toTypedArray() 52 | } 53 | 54 | -------------------------------------------------------------------------------- /net/src/main/java/com/lvhttp/net/error/ErrorKey.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.net.error 2 | 3 | /** 4 | * @name ErrorKey 5 | * @package com.www.net.error 6 | * @author 345 QQ:1831712732 7 | * @time 2020/6/24 23:00 8 | * @description 9 | */ 10 | enum class ErrorKey { 11 | 12 | /** 13 | * 全局异常处理 14 | */ 15 | AllEexeption, 16 | 17 | /** 18 | * 自定义 Code 异常 19 | */ 20 | ErrorCode, 21 | 22 | /** 23 | * 域名解析失败 24 | * 原因 : 25 | * 1,网络断开 26 | * 2,DNS 服务器意外挂掉 27 | * 3,DNS 服务器故障 28 | */ 29 | UnknownHostException, 30 | 31 | /** 32 | * 数据解析错误 33 | */ 34 | JsonSyntaxException, 35 | 36 | /** 37 | * 连接超时 38 | * 原因: 39 | * 1,设备接入的网络本身带宽比较低 40 | * 2,设备接入的网络本身延迟比较高 41 | * 3,设备与服务器的网络路径中存在比较拥堵、负载比较重的节点 42 | * 4,网络中路由节点的临时性异常 43 | */ 44 | ConnectTimeoutException, 45 | 46 | /** 47 | * socket 超时 48 | */ 49 | SocketTimeoutException, 50 | 51 | /** 52 | * 客户端数据包可以到达目标主机,但由于各种原因,连接建立失败 53 | * 原因: 54 | * 1,连接的目标主机没有开对应的端口,可能服务器发生故障 55 | * 2,客户端设置了代理,而代理进程并没有跑起来 56 | */ 57 | HttpHostConnectException, 58 | 59 | /** 60 | * 无法连接远程地址与端口 61 | * 原因: 62 | * 1,防火墙的规则设置导致数据包无法被发送出去 63 | * 2,中间路由节点挂掉 64 | */ 65 | NoRouteToHostException, 66 | 67 | /** 68 | * SSL 失败 69 | */ 70 | SSLException, 71 | 72 | /** 73 | * IO 异常 74 | */ 75 | IOException, 76 | 77 | /** 78 | * 连接失败 79 | */ 80 | ConnectException 81 | } -------------------------------------------------------------------------------- /app/src/main/java/com/lvhttp/test/Service.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.test 2 | 3 | 4 | import com.lvhttp.net.converter.Chunked 5 | import com.lvhttp.test.response.ResponseData 6 | import okhttp3.MultipartBody 7 | import okhttp3.RequestBody 8 | import okhttp3.ResponseBody 9 | import retrofit2.http.* 10 | 11 | interface Service { 12 | /** 13 | * 普通请求 14 | */ 15 | @GET("wxarticle/chapters/json") 16 | suspend fun get(): ResponseData 17 | 18 | @GET("wxarticle/chapters/json") 19 | suspend fun get2(): Bean 20 | 21 | @FormUrlEncoded 22 | @POST("user/login") 23 | suspend fun login( 24 | @Field("username") userName: String, 25 | @Field("password") passWord: String 26 | ): ResponseData 27 | 28 | 29 | @Streaming 30 | @GET("https://files.pythonhosted.org/packages/6b/34/415834bfdafca3c5f451532e8a8d9ba89a21c9743a0c59fbd0205c7f9426/six-1.15.0.tar.gz") 31 | suspend fun download(): ResponseBody 32 | 33 | 34 | /** 35 | * post:文件 36 | */ 37 | @Multipart 38 | @POST("http://192.168.23.253:80/test/updata.php") 39 | suspend fun postFile(@Chunked @Part vararg file: MultipartBody.Part): UpLoadBean 40 | 41 | @Multipart 42 | @POST("http://192.168.23.253:80/test/updata.php") 43 | suspend fun file(@Body requestBody: RequestBody): ResponseBody 44 | 45 | /** 46 | * post:请求参数+文件 47 | */ 48 | @Multipart 49 | @POST 50 | suspend fun postFile( 51 | @Url url: String, 52 | @PartMap params: MutableMap, 53 | @Part vararg file: MultipartBody.Part 54 | ): UpLoadBean 55 | 56 | } -------------------------------------------------------------------------------- /net/src/main/java/com/lvhttp/net/download/DownResponse.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.net.download 2 | 3 | import android.content.ContentValues 4 | import android.net.Uri 5 | import android.os.Build 6 | import android.os.Environment 7 | import android.provider.MediaStore 8 | import androidx.annotation.RequiresApi 9 | import com.lvhttp.net.LvHttp 10 | import java.io.File 11 | import java.lang.Exception 12 | 13 | /** 14 | * 文件下载的辅助类 15 | * 在 Android Q 中:path 表示的路径为 根目录/dowload/path/name 16 | * 在 Android Q 以下,path 表示的是 根目录/path/name 17 | * 注意:name 后面需要加上后缀名 18 | */ 19 | abstract class DownResponse(val path: String, val name: String) { 20 | /** 21 | * 开始下载时调用 22 | * @param size 文件大小,MB 为单位(会有偏差) 23 | */ 24 | open fun create(size: Float) = Unit 25 | 26 | /** 27 | * 文件下载进度 28 | * @param process 进度百分比 29 | */ 30 | open fun process(process: Float) = Unit 31 | 32 | /** 33 | * 下载完成 34 | * @param file 文件 35 | */ 36 | open fun done(file: File) = Unit 37 | 38 | /** 39 | * 异常处理 40 | * @param e 异常 41 | */ 42 | open fun error(e: Exception) = Unit 43 | } 44 | 45 | 46 | /** 47 | * Q 获取下载文件 file 48 | */ 49 | @RequiresApi(Build.VERSION_CODES.Q) 50 | fun saveQ(path: String, name: String): Uri? { 51 | val values = ContentValues() 52 | values.put(MediaStore.MediaColumns.DISPLAY_NAME, name) 53 | values.put(MediaStore.MediaColumns.RELATIVE_PATH, "Download/") 54 | 55 | val external = MediaStore.Downloads.EXTERNAL_CONTENT_URI 56 | val resolver = LvHttp.getAppContext().contentResolver 57 | return resolver.insert(external, values) 58 | } 59 | 60 | /** 61 | * Q 以下获取文件 file 62 | */ 63 | fun saveFile(path: String, name: String): Uri? { 64 | val file = 65 | File(Environment.getExternalStorageDirectory().path + "/$path/") 66 | if (!file.exists()) { 67 | file.mkdirs() 68 | } 69 | return Uri.fromFile(File(file.parent!! + "/" + path + "/" + name)) 70 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_toast.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 21 | 22 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 32 | 35 | 36 | 37 | 38 | 42 | 43 | 47 | 48 | 49 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_camera_panel.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 21 | 22 | 28 | 29 | 30 | 40 | 41 | 47 | 48 | 58 | 59 | -------------------------------------------------------------------------------- /net/src/main/java/com/lvhttp/net/launch/Launch.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.net.launch 2 | 3 | import android.util.Log 4 | import androidx.lifecycle.LifecycleOwner 5 | import androidx.lifecycle.lifecycleScope 6 | import com.lvhttp.net.LvHttp 7 | import com.lvhttp.net.error.CodeException 8 | import com.lvhttp.net.error.ErrorKey 9 | import com.lvhttp.net.response.BaseResponse 10 | import com.lvhttp.net.response.ResultState 11 | import kotlinx.coroutines.* 12 | import java.lang.Exception 13 | 14 | /** 15 | * @name Launch 16 | * @package com.www.net 17 | * @author 345 QQ:1831712732 18 | * @time 2020/6/23 21:33 19 | * @description 20 | */ 21 | 22 | suspend fun launchHttp(block: suspend () -> T): ResultState = tryCatch(block) 23 | 24 | fun LifecycleOwner.zipLaunch( 25 | block: List T>, 26 | result: (List>) -> Unit 27 | ) { 28 | lifecycleScope.launch { 29 | val list = arrayListOf>>() 30 | block.forEach { 31 | list.add(async { tryCatch(it) }) 32 | } 33 | val data = list.awaitAll() 34 | launch(Dispatchers.Main) { 35 | result.invoke(data) 36 | } 37 | } 38 | } 39 | 40 | private suspend fun tryCatch(block: suspend () -> T): ResultState { 41 | var t: ResultState? = null 42 | try { 43 | val invoke = block.invoke() 44 | (invoke as? BaseResponse<*>)?.run { 45 | notifyData() 46 | if (_code != LvHttp.getCode()) { 47 | t = ResultState.ErrorState(error = CodeException(_code, "code 异常")) 48 | // Code 异常处理 49 | LvHttp.getErrorDispose(ErrorKey.ErrorCode)?.error?.let { 50 | it(CodeException(_code, _message ?: "code 异常")) 51 | } 52 | } else { 53 | t = ResultState.SuccessState(invoke) 54 | } 55 | } ?: run { 56 | t = ResultState.SuccessState(invoke) 57 | } 58 | } catch (e: Exception) { 59 | t = ResultState.ErrorState(error = e) 60 | // 自动匹配异常 61 | ErrorKey.values().forEach { 62 | if (it.name == e::class.java.simpleName) { 63 | LvHttp.getErrorDispose(it)?.error?.let { it(e) } 64 | } 65 | } 66 | // 如果全局异常启用 67 | LvHttp.getErrorDispose(ErrorKey.AllEexeption)?.error?.let { 68 | it(e) 69 | return t!! 70 | } 71 | } 72 | return t!! 73 | } -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | 6 | android { 7 | compileSdk 34 8 | defaultConfig { 9 | applicationId "com.www.lvhttp" 10 | minSdkVersion 21 11 | targetSdkVersion 34 12 | versionCode 1 13 | versionName "1.0" 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled true //开启混淆 20 | zipAlignEnabled true //压缩优化 21 | shrinkResources true //移出无用资源 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | debug { 25 | minifyEnabled false //开启混淆 26 | zipAlignEnabled false //压缩优化 27 | shrinkResources false //移出无用资源 28 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 29 | } 30 | } 31 | kotlinOptions { 32 | jvmTarget = "1.8" 33 | } 34 | compileOptions { 35 | sourceCompatibility JavaVersion.VERSION_1_8 36 | targetCompatibility JavaVersion.VERSION_1_8 37 | } 38 | namespace 'com.lvhttp.test' 39 | } 40 | 41 | dependencies { 42 | implementation fileTree(dir: 'libs', include: ['*.jar']) 43 | 44 | implementation 'androidx.appcompat:appcompat:1.4.2' 45 | implementation 'androidx.core:core-ktx:1.8.0' 46 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 47 | 48 | implementation project(path: ':net') 49 | 50 | 51 | // //网络请求依赖 52 | implementation 'com.squareup.okhttp3:okhttp:4.9.3' 53 | implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0' 54 | implementation 'com.squareup.retrofit2:retrofit:2.9.0' 55 | implementation 'com.squareup.retrofit2:converter-gson:2.9.0' 56 | //协程基础库 57 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1" 58 | //协程 Android 库,提供 UI 调度器 59 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1" 60 | 61 | api 'com.github.bumptech.glide:glide:4.12.0' 62 | api 'com.google.android.material:material:1.7.0-alpha02' 63 | 64 | implementation 'com.github.donkingliang:ImageSelector:2.2.0' 65 | 66 | // viewmodel / activity 的ktx扩展 67 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1" 68 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1" 69 | } 70 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 27 | 28 | 38 | 39 | 40 | 47 | 48 | 58 | 59 | 60 | 64 | 65 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 54 | -------------------------------------------------------------------------------- /net/src/main/java/com/lvhttp/net/converter/LvDefaultConverterFactory.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.net.converter 2 | 3 | import android.os.Build 4 | import android.util.Log 5 | import androidx.annotation.RequiresApi 6 | import com.google.gson.Gson 7 | import com.google.gson.TypeAdapter 8 | import com.google.gson.reflect.TypeToken 9 | import com.lvhttp.net.LvHttp 10 | import okhttp3.MediaType 11 | import okhttp3.MediaType.Companion.toMediaType 12 | import okhttp3.RequestBody 13 | import okhttp3.RequestBody.Companion.toRequestBody 14 | import okhttp3.ResponseBody 15 | import okio.Buffer 16 | import okio.BufferedSink 17 | import okio.Okio 18 | import retrofit2.Converter 19 | import retrofit2.Retrofit 20 | import retrofit2.http.Body 21 | import java.io.IOException 22 | import java.io.OutputStreamWriter 23 | import java.io.Writer 24 | import java.lang.reflect.Type 25 | import java.nio.charset.Charset 26 | 27 | 28 | /** 29 | * @name LvConverterFactory 30 | * @package com.www.net.converter 31 | * @author 345 QQ:1831712732 32 | * @time 2020/6/22 20:21 33 | * @description ConverterFactory 34 | */ 35 | @Suppress("UNCHECKED_CAST") 36 | class LvDefaultConverterFactory(private val gson: Gson) : Converter.Factory() { 37 | 38 | 39 | companion object { 40 | fun create(gson: Gson): LvDefaultConverterFactory { 41 | return LvDefaultConverterFactory(gson) 42 | } 43 | } 44 | 45 | override fun requestBodyConverter( 46 | type: Type?, 47 | parameterAnnotations: Array?, 48 | methodAnnotations: Array?, 49 | retrofit: Retrofit? 50 | ): Converter<*, RequestBody> { 51 | val adapter = gson.getAdapter(TypeToken.get(type)) 52 | return GsonRequestBodyConverter(gson, adapter) 53 | } 54 | 55 | override fun responseBodyConverter( 56 | type: Type, annotations: Array, retrofit: Retrofit 57 | ): Converter { 58 | return LvResponseBodyConverter(gson, type) 59 | } 60 | 61 | 62 | class LvResponseBodyConverter(private val gson: Gson, private val type: Type) : 63 | Converter { 64 | @RequiresApi(Build.VERSION_CODES.P) 65 | 66 | override fun convert(value: ResponseBody): T? { 67 | val string = value.string() 68 | if (type == String::class.java || type::class.java.isPrimitive) { 69 | return string as T 70 | } 71 | return gson.fromJson(string, type) 72 | } 73 | 74 | } 75 | 76 | } 77 | 78 | internal class GsonRequestBodyConverter( 79 | private val gson: Gson, 80 | private val adapter: TypeAdapter 81 | ) : Converter { 82 | @Throws(IOException::class) 83 | override fun convert(value: T): RequestBody { 84 | val buffer = Buffer() 85 | val writer: Writer = 86 | OutputStreamWriter(buffer.outputStream(), UTF_8) 87 | val jsonWriter = gson.newJsonWriter(writer) 88 | adapter.write(jsonWriter, value) 89 | jsonWriter.close() 90 | return buffer.readByteString().toRequestBody(MEDIA_TYPE) 91 | } 92 | 93 | companion object { 94 | private val MEDIA_TYPE: MediaType = "application/json; charset=UTF-8".toMediaType() 95 | private val UTF_8 = Charset.forName("UTF-8") 96 | } 97 | } -------------------------------------------------------------------------------- /net/src/main/java/com/lvhttp/net/download/DownloadKt.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.net.download 2 | 3 | import android.os.Build 4 | import com.lvhttp.net.LvHttp 5 | import com.lvhttp.net.utils.FileQUtils 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.withContext 8 | import okhttp3.ResponseBody 9 | import java.io.InputStream 10 | import java.io.OutputStream 11 | import java.lang.Exception 12 | import java.lang.NullPointerException 13 | import java.text.DecimalFormat 14 | 15 | /** 16 | * @name DownloadKt 17 | * @package com.lvhttp.net.download 18 | * @author 345 QQ:1831712732 19 | * @time 2020/7/12 22:33 20 | * @description 21 | */ 22 | 23 | suspend fun ResponseBody.start(downResponse: DownResponse) { 24 | tryCache(downResponse) { 25 | (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 26 | saveQ(downResponse.path, downResponse.name) 27 | } else { 28 | saveFile(downResponse.path, downResponse.name) 29 | })?.apply { 30 | val output = withMain { 31 | LvHttp.getAppContext().contentResolver.openOutputStream(this) 32 | } 33 | val file = FileQUtils.getFileByUri(this, LvHttp.getAppContext()) 34 | if (output != null) { 35 | download(byteStream(), output, downResponse, contentLength()) 36 | withMain { downResponse.done(file) } 37 | } else { 38 | file.delete() 39 | } 40 | } ?: withMain { downResponse.error(NullPointerException("LvHttp DownLoad :文件路径找不到")) } 41 | } 42 | 43 | } 44 | 45 | private suspend inline fun download( 46 | input: InputStream, 47 | output: OutputStream, 48 | downResponse: DownResponse, 49 | contentLength: Long 50 | ) { 51 | //上次下载位置 52 | var emittedProcess = 0f 53 | withMain { 54 | downResponse.create(format((contentLength).toFloat() / 1024 / 1024)) 55 | } 56 | input.use { 57 | it.copyTo(output) { process -> 58 | //计算百分比 59 | val progress = format((process).toFloat() * 100 / contentLength) 60 | //当前的值大于上一次的就进行通知 61 | if (progress - emittedProcess > 0) { 62 | withMain { downResponse.process(progress) } 63 | emittedProcess = progress 64 | } 65 | } 66 | withMain { downResponse.process(100f) } 67 | } 68 | } 69 | 70 | /** 71 | * 保留 2为小数 72 | */ 73 | private fun format(float: Float): Float { 74 | return DecimalFormat("#.00").format(float).toFloat() 75 | } 76 | 77 | private suspend fun tryCache(response: DownResponse, block: suspend () -> T) { 78 | try { 79 | block() 80 | } catch (e: Exception) { 81 | withMain { response.error(e) } 82 | } 83 | } 84 | 85 | private suspend inline fun withMain(crossinline block: () -> T) = 86 | withContext(Dispatchers.Main) { 87 | block() 88 | } 89 | 90 | private inline fun InputStream.copyTo( 91 | output: OutputStream, 92 | bufferSize: Int = DEFAULT_BUFFER_SIZE, 93 | progress: (Long) -> Unit 94 | ): Long { 95 | output.use { 96 | var bytesCopied: Long = 0 97 | val buffer = ByteArray(bufferSize) 98 | var bytes = read(buffer) 99 | while (bytes >= 0) { 100 | output.write(buffer, 0, bytes) 101 | bytesCopied += bytes 102 | bytes = read(buffer) 103 | progress(bytesCopied) 104 | } 105 | output.flush() 106 | return bytesCopied 107 | } 108 | } -------------------------------------------------------------------------------- /net/src/main/java/com/lvhttp/net/interceptor/LogInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.net.interceptor 2 | 3 | 4 | import android.util.Log 5 | import okhttp3.Headers 6 | import okhttp3.Interceptor 7 | import okhttp3.Response 8 | import okio.Buffer 9 | import java.io.EOFException 10 | import java.lang.Exception 11 | import java.nio.charset.Charset 12 | import java.nio.charset.StandardCharsets 13 | 14 | /** 15 | * @name LogInterceptor 16 | * @package com.tidycar.carzki.remote.interceptor 17 | * @author 345 QQ:1831712732 18 | * @time 2021/01/26 11:42 19 | * @description 日志拦截器 20 | */ 21 | class LogInterceptor : Interceptor { 22 | override fun intercept(chain: Interceptor.Chain): Response { 23 | 24 | val request = chain.request() 25 | val requestBody = request.body 26 | val requestBuffer = StringBuffer() 27 | 28 | 29 | val contentType = requestBody?.contentType() 30 | val charset: Charset = 31 | contentType?.charset(StandardCharsets.UTF_8) ?: StandardCharsets.UTF_8 32 | //请求日志 33 | requestBuffer.apply { 34 | append("{url:${request.url}} \n") 35 | append("{method:${request.method}} \n") 36 | append("{token:${request.headers["ACCESS-TOKEN"]}} \n") 37 | if (requestBody != null && !bodyHasUnknownEncoding(request.headers) && !requestBody.isDuplex() && !requestBody.isOneShot()) { 38 | val buffer = Buffer() 39 | requestBody.writeTo(buffer) 40 | if (buffer.isProbablyUtf8()) { 41 | append("{arguments:{${buffer.readString(charset)}}}\n") 42 | } 43 | } 44 | } 45 | 46 | val response = chain.proceed(request) 47 | try { 48 | val responseBody = response.body!! 49 | 50 | val source = responseBody.source() 51 | source.request(Long.MAX_VALUE) // Buffer the entire body. 52 | val bufferResponse = source.buffer 53 | 54 | requestBuffer.apply { 55 | append("\n{Code:${response.code}}\n") 56 | append("{URL:${response.request.url}}\n") 57 | if (!bufferResponse.isProbablyUtf8()) { 58 | append("<-- END HTTP (binary - byte body omitted)") 59 | } else { 60 | append("body:${bufferResponse.clone().readString(charset)}") 61 | } 62 | } 63 | Log.d("LvHttp ---- END HTTP>", requestBuffer.toString()) 64 | } catch (e: Exception) { 65 | e.printStackTrace() 66 | } 67 | return response 68 | } 69 | 70 | private fun bodyHasUnknownEncoding(headers: Headers): Boolean { 71 | val contentEncoding = headers["Content-Encoding"] ?: return false 72 | return !contentEncoding.equals("identity", ignoreCase = true) && 73 | !contentEncoding.equals("gzip", ignoreCase = true) 74 | } 75 | 76 | private fun Buffer.isProbablyUtf8(): Boolean { 77 | try { 78 | val prefix = Buffer() 79 | val byteCount = size.coerceAtMost(64) 80 | copyTo(prefix, 0, byteCount) 81 | for (i in 0 until 16) { 82 | if (prefix.exhausted()) { 83 | break 84 | } 85 | val codePoint = prefix.readUtf8CodePoint() 86 | if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) { 87 | return false 88 | } 89 | } 90 | return true 91 | } catch (_: EOFException) { 92 | return false // Truncated UTF-8 sequence. 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /net/src/main/java/com/lvhttp/net/LvController.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.net 2 | 3 | import android.app.Application 4 | import com.google.gson.Gson 5 | import com.lvhttp.net.converter.LvDefaultConverterFactory 6 | import com.lvhttp.net.error.ErrorKey 7 | import com.lvhttp.net.error.ErrorValue 8 | import com.lvhttp.net.interceptor.CacheInterceptor 9 | import com.lvhttp.net.interceptor.LogInterceptor 10 | import com.lvhttp.net.utils.HTTPSCerUtils 11 | import okhttp3.Cache 12 | import okhttp3.Interceptor 13 | import okhttp3.OkHttpClient 14 | import okhttp3.Protocol 15 | import okhttp3.logging.HttpLoggingInterceptor 16 | import retrofit2.Retrofit 17 | import java.util.* 18 | import java.util.concurrent.TimeUnit 19 | 20 | @Suppress("UNCHECKED_CAST") 21 | class LvController { 22 | 23 | lateinit var appContext: Application 24 | lateinit var retrofit: Retrofit 25 | private val mCache = mutableMapOf() 26 | val errorDisposes: MutableMap = mutableMapOf() 27 | var isLog = false 28 | var code: Int = -1 29 | 30 | fun newInstance(clazz: Class): T { 31 | if (mCache[clazz.name] == null) { 32 | val create = retrofit.create(clazz) as Any 33 | mCache[clazz.name] = create 34 | return create as T 35 | } 36 | return mCache[clazz.name] as T 37 | } 38 | 39 | 40 | class LvParams { 41 | lateinit var baseUrl: String 42 | lateinit var appContext: Application 43 | var connectTimeOut: Long = 10 44 | var readTimeOut: Long = 10 45 | var writeTimeOut: Long = 30 46 | var isLog = false 47 | var isCache = false 48 | var code = -1 49 | var cacheSize: Long = 1024 * 1024 * 20 50 | var interceptors = arrayListOf() 51 | val errorDisposes: MutableMap = mutableMapOf() 52 | var cerResId: Int = -1; 53 | 54 | fun apply(controller: LvController): Retrofit { 55 | val builder = OkHttpClient.Builder() 56 | .readTimeout(readTimeOut, TimeUnit.SECONDS) 57 | .writeTimeout(writeTimeOut, TimeUnit.SECONDS) 58 | .connectTimeout(connectTimeOut, TimeUnit.SECONDS) 59 | .protocols(Collections.singletonList(Protocol.HTTP_1_1)) 60 | .retryOnConnectionFailure(true) 61 | 62 | //验证证书 63 | if (cerResId != -1) { 64 | HTTPSCerUtils.setCertificate(appContext, builder, cerResId) 65 | } 66 | //设置缓存 67 | if (isCache) { 68 | builder.cache(Cache(appContext.cacheDir, cacheSize)) 69 | builder.addNetworkInterceptor(CacheInterceptor()) 70 | } 71 | //设置 Logging 72 | if (isLog) { 73 | builder.addInterceptor(LogInterceptor()) 74 | // builder.addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC)) 75 | } 76 | 77 | //设置拦截器 78 | for (interceptor in interceptors) { 79 | builder.addInterceptor(interceptor) 80 | } 81 | 82 | val client = builder.build() 83 | val retrofit: Retrofit = Retrofit.Builder() 84 | .baseUrl(baseUrl) 85 | .client(client) 86 | .addConverterFactory(LvDefaultConverterFactory.create(Gson())) 87 | .build() 88 | 89 | controller.retrofit = retrofit 90 | controller.appContext = appContext 91 | controller.errorDisposes.clear() 92 | controller.errorDisposes.putAll(errorDisposes) 93 | controller.isLog = isLog 94 | controller.code = code 95 | return retrofit 96 | } 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /net/src/main/java/com/lvhttp/net/utils/NetWorkUtils.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.net.utils 2 | 3 | import android.content.Context 4 | import android.net.ConnectivityManager 5 | import android.util.Log 6 | import java.io.BufferedReader 7 | import java.io.IOException 8 | import java.io.InputStreamReader 9 | 10 | /** 11 | * 判断wifi网络是否可用 12 | * 13 | * @param context 14 | * @return 15 | */ 16 | fun isWifiConnected(context: Context?): Boolean { 17 | if (context != null) { 18 | val mConnectivityManager = context 19 | .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 20 | val mWiFiNetworkInfo = mConnectivityManager 21 | .getNetworkInfo(ConnectivityManager.TYPE_WIFI) 22 | if (mWiFiNetworkInfo != null) { 23 | return mWiFiNetworkInfo.isAvailable 24 | } 25 | } 26 | return false 27 | } 28 | 29 | /** 30 | * 判断移动网络是否可用 31 | * 32 | * @param context 33 | * @return 34 | */ 35 | fun isMobileConnected(context: Context?): Boolean { 36 | if (context != null) { 37 | val mConnectivityManager = context 38 | .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 39 | val mMobileNetworkInfo = mConnectivityManager 40 | .getNetworkInfo(ConnectivityManager.TYPE_MOBILE) 41 | if (mMobileNetworkInfo != null) { 42 | return mMobileNetworkInfo.isAvailable 43 | } 44 | } 45 | return false 46 | } 47 | 48 | /** 49 | * 判断网络连接是否可用 50 | * 51 | * @param context 52 | * @return 53 | */ 54 | fun isNetworkConnected(context: Context?): Boolean { 55 | if (context != null) { 56 | val mConnectivityManager = (context 57 | .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager) 58 | val mNetworkInfo = mConnectivityManager.activeNetworkInfo 59 | if (mNetworkInfo != null) { 60 | return mNetworkInfo.isAvailable 61 | } 62 | } 63 | return false 64 | } 65 | 66 | 67 | /** 68 | * 获取当前网络连接的类型信息 69 | * 70 | * @param context 71 | * @return 72 | */ 73 | fun getConnectedType(context: Context?): Int { 74 | if (context != null) { 75 | val mConnectivityManager = context 76 | .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 77 | val mNetworkInfo = mConnectivityManager.activeNetworkInfo 78 | if (mNetworkInfo != null && mNetworkInfo.isAvailable) { 79 | return mNetworkInfo.type 80 | } 81 | } 82 | return -1 83 | } 84 | 85 | /** 86 | * 判断是否有外网连接(普通方法不能判断外网的网络是否连接,比如连接上局域网) 87 | * 88 | * @return 89 | */ 90 | fun ping(): Boolean { 91 | var result: String? = null 92 | try { 93 | val ip = "www.baidu.com" // ping 的地址,可以换成任何一种可靠的外网 94 | val p = 95 | Runtime.getRuntime().exec("ping -c 3 -w 100 $ip") // ping网址3次 96 | // 读取ping的内容,可以不加 97 | val input = p.inputStream 98 | val `in` = 99 | BufferedReader(InputStreamReader(input)) 100 | val stringBuffer = StringBuffer() 101 | var content: String? 102 | while (`in`.readLine().also { content = it } != null) { 103 | stringBuffer.append(content) 104 | } 105 | Log.d("------ping-----", "result content : $stringBuffer") 106 | // ping的状态 107 | val status = p.waitFor() 108 | if (status == 0) { 109 | result = "success" 110 | return true 111 | } else { 112 | result = "failed" 113 | } 114 | } catch (e: IOException) { 115 | result = "IOException" 116 | } catch (e: InterruptedException) { 117 | result = "InterruptedException" 118 | } finally { 119 | Log.d("----result---", "result = $result") 120 | } 121 | return false 122 | } 123 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | xmlns:android 17 | 18 | ^$ 19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 | xmlns:.* 28 | 29 | ^$ 30 | 31 | 32 | BY_NAME 33 | 34 |
35 |
36 | 37 | 38 | 39 | .*:id 40 | 41 | http://schemas.android.com/apk/res/android 42 | 43 | 44 | 45 |
46 |
47 | 48 | 49 | 50 | .*:name 51 | 52 | http://schemas.android.com/apk/res/android 53 | 54 | 55 | 56 |
57 |
58 | 59 | 60 | 61 | name 62 | 63 | ^$ 64 | 65 | 66 | 67 |
68 |
69 | 70 | 71 | 72 | style 73 | 74 | ^$ 75 | 76 | 77 | 78 |
79 |
80 | 81 | 82 | 83 | .* 84 | 85 | ^$ 86 | 87 | 88 | BY_NAME 89 | 90 |
91 |
92 | 93 | 94 | 95 | .* 96 | 97 | http://schemas.android.com/apk/res/android 98 | 99 | 100 | ANDROID_ATTRIBUTE_ORDER 101 | 102 |
103 |
104 | 105 | 106 | 107 | .* 108 | 109 | .* 110 | 111 | 112 | BY_NAME 113 | 114 |
115 |
116 |
117 |
118 | 119 | 121 |
122 |
-------------------------------------------------------------------------------- /net/src/main/java/com/lvhttp/net/LvHttp.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.net 2 | 3 | import android.app.Application 4 | import android.os.Build 5 | import androidx.annotation.RawRes 6 | import com.lvhttp.net.error.ErrorKey 7 | import com.lvhttp.net.error.ErrorValue 8 | import okhttp3.Interceptor 9 | import retrofit2.Retrofit 10 | 11 | 12 | object LvHttp { 13 | 14 | private val mController = LvController() 15 | 16 | /** 17 | * 获取 Retrofit 18 | */ 19 | @JvmStatic 20 | fun getRetrofit(): Retrofit { 21 | return mController.retrofit 22 | } 23 | 24 | /** 25 | * 创建 API 26 | */ 27 | @JvmStatic 28 | fun createApi(clazz: Class): T { 29 | return mController.newInstance(clazz) 30 | } 31 | 32 | /** 33 | * @return 获取异常处理 34 | */ 35 | @JvmStatic 36 | fun getErrorDispose(errorKey: ErrorKey): ErrorValue? { 37 | return mController.errorDisposes[errorKey] 38 | } 39 | 40 | /** 41 | * @return 是否打印日志 42 | */ 43 | fun getIsLogging(): Boolean { 44 | return mController.isLog 45 | } 46 | 47 | /** 48 | * 获取 Application 49 | */ 50 | fun getAppContext(): Application { 51 | return mController.appContext 52 | } 53 | 54 | /** 55 | * 获取 code 56 | */ 57 | fun getCode(): Int { 58 | return mController.code 59 | } 60 | 61 | /** 62 | * 设置异常处理 63 | * @param errorKey key 64 | * @param errorValue value 65 | */ 66 | @JvmStatic 67 | fun setErrorDispose(errorKey: ErrorKey, errorValue: ErrorValue) { 68 | mController.errorDisposes[errorKey] = errorValue 69 | } 70 | 71 | class Builder { 72 | private var p: LvController.LvParams = LvController.LvParams() 73 | 74 | fun setApplication(application: Application): Builder { 75 | p.appContext = application 76 | return this 77 | } 78 | 79 | /** 80 | * 设置 BaseUrl 81 | */ 82 | fun setBaseUrl(baseUrl: String): Builder { 83 | p.baseUrl = baseUrl 84 | return this 85 | } 86 | 87 | /** 88 | * 连接时间,秒为单位 89 | */ 90 | fun setConnectTimeOut(connecTime: Long): Builder { 91 | p.connectTimeOut = connecTime 92 | return this 93 | } 94 | 95 | /** 96 | * 下载响应的时候等待时间,秒为单位 97 | */ 98 | fun setReadTimeOut(readTime: Long): Builder { 99 | p.readTimeOut = readTime 100 | return this 101 | } 102 | 103 | /** 104 | * 写入请求的等待时间,秒为单位 105 | */ 106 | fun setWirteTimeOut(writeTimeOut: Long): Builder { 107 | p.writeTimeOut = writeTimeOut 108 | return this 109 | } 110 | 111 | /** 112 | * 是否打印 log 113 | * @param islog true 表示打印 114 | */ 115 | fun isLog(islog: Boolean): Builder { 116 | p.isLog = islog 117 | return this 118 | } 119 | 120 | /** 121 | * 是否开启缓存,默认关闭 122 | * @param iscache true 表示开启缓存 123 | */ 124 | fun isCache(iscache: Boolean): Builder { 125 | p.isCache = iscache 126 | return this 127 | } 128 | 129 | /** 130 | * 设置 code 值,如果 == code,则说明请求成功,否则进行异常处理 131 | */ 132 | fun setCode(code: Int): Builder { 133 | p.code = code 134 | return this 135 | } 136 | 137 | /** 138 | * 设置缓存大小,默认 20mb 139 | * @param cacheSize 缓存大小 140 | */ 141 | fun setCacheSize(cacheSize: Long): Builder { 142 | p.cacheSize = cacheSize 143 | return this 144 | } 145 | 146 | /** 147 | * 添加拦截器 148 | */ 149 | fun addInterceptor(vararg interceptor: Interceptor): Builder { 150 | p.interceptors.addAll(interceptor) 151 | return this 152 | } 153 | 154 | fun setCerResId(@RawRes cerRes: Int): Builder { 155 | p.cerResId = cerRes 156 | return this 157 | } 158 | 159 | /** 160 | * 设置异常处理 161 | */ 162 | fun setErrorDispose(errorKey: ErrorKey, errorValue: ErrorValue): Builder { 163 | p.errorDisposes[errorKey] = errorValue 164 | return this 165 | } 166 | 167 | fun build() { 168 | create() 169 | } 170 | 171 | private fun create(): Retrofit { 172 | return p.apply(mController) 173 | } 174 | } 175 | } -------------------------------------------------------------------------------- /net/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | 2 | ############################################# 3 | # 4 | # 基本指令区域(没什么别的需求不需要动) 5 | # 6 | ############################################# 7 | # 代码混淆压缩比,在0~7之间,默认为5,一般不做修改 8 | -optimizationpasses 5 9 | 10 | # 混合时不使用大小写混合,混合后的类名为小写 11 | -dontusemixedcaseclassnames 12 | 13 | # 指定不去忽略非公共库的类 14 | -dontskipnonpubliclibraryclasses 15 | 16 | # 这句话能够使我们的项目混淆后产生映射文件 17 | # 包含有类名->混淆后类名的映射关系 18 | -verbose 19 | 20 | # 指定不去忽略非公共库的类成员 21 | -dontskipnonpubliclibraryclassmembers 22 | 23 | # 不做预校验,preverify是proguard的四个步骤之一,Android不需要preverify,去掉这一步能够加快混淆速度。 24 | -dontpreverify 25 | 26 | # 保留Annotation不混淆 27 | -keepattributes *Annotation*,InnerClasses 28 | 29 | # 避免混淆泛型 30 | -keepattributes Signature 31 | 32 | # 抛出异常时保留代码行号 33 | -keepattributes SourceFile,LineNumberTable 34 | 35 | # 指定混淆是采用的算法,后面的参数是一个过滤器 36 | # 这个过滤器是谷歌推荐的算法,一般不做更改 37 | -optimizations !code/simplification/cast,!field/*,!class/merging/* 38 | 39 | 40 | ############################################# 41 | # 42 | # Android开发中一些需要保留的公共部分(没什么别的需求不需要动) 43 | # 44 | ############################################# 45 | 46 | # 保留我们使用的四大组件,自定义的Application等等这些类不被混淆 47 | # 因为这些子类都有可能被外部调用 48 | -keep public class * extends android.app.Activity 49 | -keep public class * extends android.app.Application 50 | -keep public class * extends android.app.Service 51 | -keep public class * extends android.content.BroadcastReceiver 52 | -keep public class * extends android.content.ContentProvider 53 | -keep public class * extends android.app.backup.BackupAgentHelper 54 | -keep public class * extends android.preference.Preference 55 | -keep public class * extends android.view.View 56 | 57 | 58 | # 保留support下的所有类及其内部类 59 | -keep class android.support.** {*;} 60 | 61 | # 保留继承的 62 | -keep public class * extends android.support.v4.** 63 | -keep public class * extends android.support.v7.** 64 | -keep public class * extends android.support.annotation.** 65 | 66 | # 保留R下面的资源 67 | -keep class **.R$* {*;} 68 | 69 | # 保留本地native方法不被混淆 70 | -keepclasseswithmembernames class * { 71 | native ; 72 | } 73 | 74 | # 保留在Activity中的方法参数是view的方法, 75 | # 这样以来我们在layout中写的onClick就不会被影响 76 | -keepclassmembers class * extends android.app.Activity{ 77 | public void *(android.view.View); 78 | } 79 | 80 | # 保留枚举类不被混淆 81 | -keepclassmembers enum * { 82 | public static **[] values(); 83 | public static ** valueOf(java.lang.String); 84 | } 85 | 86 | # 保留我们自定义控件(继承自View)不被混淆 87 | -keep public class * extends android.view.View{ 88 | *** get*(); 89 | void set*(***); 90 | public (android.content.Context); 91 | public (android.content.Context, android.util.AttributeSet); 92 | public (android.content.Context, android.util.AttributeSet, int); 93 | } 94 | 95 | # 保留Parcelable序列化类不被混淆 96 | -keep class * implements android.os.Parcelable { 97 | public static final android.os.Parcelable$Creator *; 98 | } 99 | 100 | # 保留Serializable序列化的类不被混淆 101 | -keepclassmembers class * implements java.io.Serializable { 102 | static final long serialVersionUID; 103 | private static final java.io.ObjectStreamField[] serialPersistentFields; 104 | !static !transient ; 105 | !private ; 106 | !private ; 107 | private void writeObject(java.io.ObjectOutputStream); 108 | private void readObject(java.io.ObjectInputStream); 109 | java.lang.Object writeReplace(); 110 | java.lang.Object readResolve(); 111 | } 112 | 113 | # 对于带有回调函数的onXXEvent、**On*Listener的,不能被混淆 114 | -keepclassmembers class * { 115 | void *(**On*Event); 116 | void *(**On*Listener); 117 | } 118 | 119 | # webView处理,项目中没有使用到webView忽略即可 120 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 121 | # public *; 122 | #} 123 | #-keepclassmembers class * extends android.webkit.webViewClient { 124 | # public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap); 125 | # public boolean *(android.webkit.WebView, java.lang.String); 126 | #} 127 | #-keepclassmembers class * extends android.webkit.webViewClient { 128 | # public void *(android.webkit.webView, jav.lang.String); 129 | #} 130 | 131 | # 移除Log类打印各个等级日志的代码,打正式包的时候可以做为禁log使用,这里可以作为禁止log打印的功能使用 132 | # 记得proguard-android.txt中一定不要加-dontoptimize才起作用 133 | # 另外的一种实现方案是通过BuildConfig.DEBUG的变量来控制 134 | #-assumenosideeffects class android.util.Log { 135 | # public static int v(...); 136 | # public static int i(...); 137 | # public static int w(...); 138 | # public static int d(...); 139 | # public static int e(...); 140 | #} 141 | 142 | 143 | ############################################# 144 | # 145 | # 项目中特殊处理部分 146 | # 147 | ############################################# 148 | 149 | #-----------处理反射类--------------- 150 | 151 | 152 | 153 | #-----------处理js交互--------------- 154 | 155 | 156 | 157 | #-----------处理实体类--------------- 158 | # 在开发的时候我们可以将所有的实体类放在一个包内,这样我们写一次混淆就行了。 159 | #-keep class com.ghs.ghspm.bean.** { *; } 160 | 161 | 162 | #-----------处理第三方依赖库--------- 163 | # 项目中libs下引用了哪些地方的jar包,还有就是gradle中添加了哪些第三方库的依赖。 164 | # 下面介绍一些常用的第三方混淆配置,仅供参考 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /net/src/main/java/com/lvhttp/net/utils/HTTPSCerUtils.java: -------------------------------------------------------------------------------- 1 | package com.lvhttp.net.utils; 2 | 3 | import android.content.Context; 4 | 5 | import java.io.InputStream; 6 | import java.security.KeyStore; 7 | import java.security.SecureRandom; 8 | import java.security.cert.Certificate; 9 | import java.security.cert.CertificateException; 10 | import java.security.cert.CertificateFactory; 11 | import java.security.cert.X509Certificate; 12 | 13 | import javax.net.ssl.HostnameVerifier; 14 | import javax.net.ssl.SSLContext; 15 | import javax.net.ssl.SSLSession; 16 | import javax.net.ssl.TrustManager; 17 | import javax.net.ssl.TrustManagerFactory; 18 | import javax.net.ssl.X509TrustManager; 19 | 20 | import okhttp3.OkHttpClient; 21 | 22 | public class HTTPSCerUtils { 23 | 24 | //信任所有证书 25 | public static void setTrustAllCertificate(OkHttpClient.Builder okHttpClientBuilder) { 26 | try { 27 | SSLContext sc = SSLContext.getInstance("TLS"); 28 | X509TrustManager trustAllManager = new X509TrustManager() { 29 | @Override 30 | public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { 31 | 32 | } 33 | 34 | @Override 35 | public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { 36 | 37 | } 38 | 39 | @Override 40 | public X509Certificate[] getAcceptedIssuers() { 41 | return new X509Certificate[0]; 42 | } 43 | }; 44 | sc.init(null, new TrustManager[]{trustAllManager}, new SecureRandom()); 45 | okHttpClientBuilder.sslSocketFactory(sc.getSocketFactory(), trustAllManager); 46 | //如果需要兼容安卓5.0以下,可以使用这句 47 | //okHttpClientBuilder.sslSocketFactory(new TLSSocketFactory(), trustAllManager); 48 | okHttpClientBuilder.hostnameVerifier(new HostnameVerifier() { 49 | @Override 50 | public boolean verify(String hostname, SSLSession session) { 51 | return true; 52 | } 53 | }); 54 | } catch (Exception e) { 55 | e.printStackTrace(); 56 | } 57 | } 58 | 59 | //只信任指定证书(传入raw资源ID) 60 | public static void setCertificate(Context context, OkHttpClient.Builder okHttpClientBuilder, int cerResID) { 61 | try { 62 | CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); 63 | InputStream inputStream = context.getResources().openRawResource(cerResID); 64 | Certificate ca = certificateFactory.generateCertificate(inputStream); 65 | 66 | KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); 67 | keyStore.load(null, null); 68 | keyStore.setCertificateEntry("ca", ca); 69 | 70 | inputStream.close(); 71 | 72 | TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 73 | tmf.init(keyStore); 74 | 75 | SSLContext sslContext = SSLContext.getInstance("TLS"); 76 | sslContext.init(null, tmf.getTrustManagers(), new SecureRandom()); 77 | okHttpClientBuilder.sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) tmf.getTrustManagers()[0]); 78 | okHttpClientBuilder.hostnameVerifier(new HostnameVerifier() { 79 | @Override 80 | public boolean verify(String hostname, SSLSession session) { 81 | return true; 82 | } 83 | }); 84 | } catch (Exception e) { 85 | e.printStackTrace(); 86 | } 87 | } 88 | 89 | //批量信任证书 90 | public static void setCertificates(Context context, OkHttpClient.Builder okHttpClientBuilder, int... cerResIDs) { 91 | try { 92 | CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); 93 | 94 | KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); 95 | keyStore.load(null, null); 96 | for (int i = 0; i < cerResIDs.length; i++) { 97 | Certificate ca = certificateFactory.generateCertificate(context.getResources().openRawResource(cerResIDs[i])); 98 | keyStore.setCertificateEntry("ca" + i, ca); 99 | } 100 | 101 | TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 102 | tmf.init(keyStore); 103 | 104 | SSLContext sslContext = SSLContext.getInstance("TLS"); 105 | sslContext.init(null, tmf.getTrustManagers(), new SecureRandom()); 106 | okHttpClientBuilder.sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) tmf.getTrustManagers()[0]); 107 | okHttpClientBuilder.hostnameVerifier(new HostnameVerifier() { 108 | @Override 109 | public boolean verify(String hostname, SSLSession session) { 110 | return true; 111 | } 112 | }); 113 | } catch (Exception e) { 114 | e.printStackTrace(); 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | 2 | ############################################# 3 | # 4 | # 基本指令区域(没什么别的需求不需要动) 5 | # 6 | ############################################# 7 | # 代码混淆压缩比,在0~7之间,默认为5,一般不做修改 8 | -optimizationpasses 5 9 | 10 | # 混合时不使用大小写混合,混合后的类名为小写 11 | -dontusemixedcaseclassnames 12 | 13 | # 指定不去忽略非公共库的类 14 | -dontskipnonpubliclibraryclasses 15 | 16 | # 这句话能够使我们的项目混淆后产生映射文件 17 | # 包含有类名->混淆后类名的映射关系 18 | -verbose 19 | 20 | # 指定不去忽略非公共库的类成员 21 | -dontskipnonpubliclibraryclassmembers 22 | 23 | # 不做预校验,preverify是proguard的四个步骤之一,Android不需要preverify,去掉这一步能够加快混淆速度。 24 | -dontpreverify 25 | 26 | # 保留Annotation不混淆 27 | -keepattributes *Annotation*,InnerClasses 28 | 29 | # 避免混淆泛型 30 | -keepattributes Signature 31 | 32 | # 抛出异常时保留代码行号 33 | -keepattributes SourceFile,LineNumberTable 34 | 35 | # 指定混淆是采用的算法,后面的参数是一个过滤器 36 | # 这个过滤器是谷歌推荐的算法,一般不做更改 37 | -optimizations !code/simplification/cast,!field/*,!class/merging/* 38 | 39 | 40 | ############################################# 41 | # 42 | # Android开发中一些需要保留的公共部分(没什么别的需求不需要动) 43 | # 44 | ############################################# 45 | 46 | # 保留我们使用的四大组件,自定义的Application等等这些类不被混淆 47 | # 因为这些子类都有可能被外部调用 48 | -keep public class * extends android.app.Activity 49 | -keep public class * extends android.app.Application 50 | -keep public class * extends android.app.Service 51 | -keep public class * extends android.content.BroadcastReceiver 52 | -keep public class * extends android.content.ContentProvider 53 | -keep public class * extends android.app.backup.BackupAgentHelper 54 | -keep public class * extends android.preference.Preference 55 | -keep public class * extends android.view.View 56 | 57 | 58 | # 保留support下的所有类及其内部类 59 | -keep class android.support.** {*;} 60 | 61 | # 保留继承的 62 | -keep public class * extends android.support.v4.** 63 | -keep public class * extends android.support.v7.** 64 | -keep public class * extends android.support.annotation.** 65 | 66 | # 保留R下面的资源 67 | -keep class **.R$* {*;} 68 | 69 | # 保留本地native方法不被混淆 70 | -keepclasseswithmembernames class * { 71 | native ; 72 | } 73 | 74 | # 保留在Activity中的方法参数是view的方法, 75 | # 这样以来我们在layout中写的onClick就不会被影响 76 | -keepclassmembers class * extends android.app.Activity{ 77 | public void *(android.view.View); 78 | } 79 | 80 | # 保留枚举类不被混淆 81 | -keepclassmembers enum * { 82 | public static **[] values(); 83 | public static ** valueOf(java.lang.String); 84 | } 85 | 86 | # 保留我们自定义控件(继承自View)不被混淆 87 | -keep public class * extends android.view.View{ 88 | *** get*(); 89 | void set*(***); 90 | public (android.content.Context); 91 | public (android.content.Context, android.util.AttributeSet); 92 | public (android.content.Context, android.util.AttributeSet, int); 93 | } 94 | 95 | # 保留Parcelable序列化类不被混淆 96 | -keep class * implements android.os.Parcelable { 97 | public static final android.os.Parcelable$Creator *; 98 | } 99 | 100 | # 保留Serializable序列化的类不被混淆 101 | -keepclassmembers class * implements java.io.Serializable { 102 | static final long serialVersionUID; 103 | private static final java.io.ObjectStreamField[] serialPersistentFields; 104 | !static !transient ; 105 | !private ; 106 | !private ; 107 | private void writeObject(java.io.ObjectOutputStream); 108 | private void readObject(java.io.ObjectInputStream); 109 | java.lang.Object writeReplace(); 110 | java.lang.Object readResolve(); 111 | } 112 | 113 | # 对于带有回调函数的onXXEvent、**On*Listener的,不能被混淆 114 | -keepclassmembers class * { 115 | void *(**On*Event); 116 | void *(**On*Listener); 117 | } 118 | 119 | # webView处理,项目中没有使用到webView忽略即可 120 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 121 | # public *; 122 | #} 123 | #-keepclassmembers class * extends android.webkit.webViewClient { 124 | # public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap); 125 | # public boolean *(android.webkit.WebView, java.lang.String); 126 | #} 127 | #-keepclassmembers class * extends android.webkit.webViewClient { 128 | # public void *(android.webkit.webView, jav.lang.String); 129 | #} 130 | 131 | # 移除Log类打印各个等级日志的代码,打正式包的时候可以做为禁log使用,这里可以作为禁止log打印的功能使用 132 | # 记得proguard-android.txt中一定不要加-dontoptimize才起作用 133 | # 另外的一种实现方案是通过BuildConfig.DEBUG的变量来控制 134 | #-assumenosideeffects class android.util.Log { 135 | # public static int v(...); 136 | # public static int i(...); 137 | # public static int w(...); 138 | # public static int d(...); 139 | # public static int e(...); 140 | #} 141 | 142 | 143 | ############################################# 144 | # 145 | # 项目中特殊处理部分 146 | # 147 | ############################################# 148 | 149 | #-----------处理反射类--------------- 150 | 151 | 152 | 153 | #-----------处理js交互--------------- 154 | 155 | 156 | 157 | #-----------处理实体类--------------- 158 | # 在开发的时候我们可以将所有的实体类放在一个包内,这样我们写一次混淆就行了。 159 | #-keep class com.ghs.ghspm.bean.** { *; } 160 | 161 | 162 | #-----------处理第三方依赖库--------- 163 | # 项目中libs下引用了哪些地方的jar包,还有就是gradle中添加了哪些第三方库的依赖。 164 | # 下面介绍一些常用的第三方混淆配置,仅供参考 165 | 166 | 167 | 168 | # Glide 169 | -keep public class * implements com.bumptech.glide.module.GlideModule 170 | -keep class * extends com.bumptech.glide.module.AppGlideModule { 171 | (...); 172 | } 173 | -keep public enum com.bumptech.glide.load.ImageHeaderParser$** { 174 | **[] $VALUES; 175 | public *; 176 | } 177 | 178 | 179 | 180 | 181 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /net/src/main/java/com/lvhttp/net/converter/LvDefaultCallAdapterFactory.java: -------------------------------------------------------------------------------- 1 | package com.lvhttp.net.converter; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.util.Log; 6 | 7 | import androidx.annotation.Nullable; 8 | 9 | import com.lvhttp.net.utils.Utils; 10 | 11 | import java.io.IOException; 12 | import java.lang.annotation.Annotation; 13 | import java.lang.reflect.ParameterizedType; 14 | import java.lang.reflect.Type; 15 | import java.util.Objects; 16 | import java.util.concurrent.Executor; 17 | 18 | import okhttp3.Request; 19 | import okio.Timeout; 20 | import retrofit2.Call; 21 | import retrofit2.CallAdapter; 22 | import retrofit2.Callback; 23 | import retrofit2.Response; 24 | import retrofit2.Retrofit; 25 | 26 | /** 27 | * @author 345 QQ:1831712732 28 | * @name LvDefaultCallAdapterFactory 29 | * @package com.www.net.converter 30 | * @time 2020/6/24 20:26 31 | * @description 32 | */ 33 | public class LvDefaultCallAdapterFactory extends CallAdapter.Factory { 34 | private final @Nullable 35 | Executor callbackExecutor; 36 | 37 | LvDefaultCallAdapterFactory(@Nullable Executor callbackExecutor) { 38 | this.callbackExecutor = callbackExecutor; 39 | } 40 | 41 | public static LvDefaultCallAdapterFactory create() { 42 | return new LvDefaultCallAdapterFactory(new MainThreadExecutor()); 43 | } 44 | 45 | 46 | static class MainThreadExecutor implements Executor { 47 | private final Handler handler = new Handler(Looper.getMainLooper()); 48 | 49 | @Override 50 | public void execute(Runnable r) { 51 | handler.post(r); 52 | } 53 | } 54 | 55 | 56 | @Override 57 | public @Nullable 58 | CallAdapter get( 59 | Type returnType, Annotation[] annotations, Retrofit retrofit) { 60 | if (getRawType(returnType) != Call.class) { 61 | return null; 62 | } 63 | if (!(returnType instanceof ParameterizedType)) { 64 | throw new IllegalArgumentException( 65 | "Call return type must be parameterized as Call or Call"); 66 | } 67 | final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType); 68 | 69 | return new CallAdapter>() { 70 | @Override 71 | public Type responseType() { 72 | return responseType; 73 | } 74 | 75 | @Override 76 | public Call adapt(Call call) { 77 | return new LvDefaultCallAdapterFactory.ExecutorCallbackCall<>(callbackExecutor, call); 78 | } 79 | }; 80 | } 81 | 82 | static final class ExecutorCallbackCall implements Call { 83 | final Executor callbackExecutor; 84 | final Call delegate; 85 | 86 | ExecutorCallbackCall(Executor callbackExecutor, Call delegate) { 87 | this.callbackExecutor = callbackExecutor; 88 | this.delegate = delegate; 89 | } 90 | 91 | @Override 92 | public void enqueue(final Callback callback) { 93 | Objects.requireNonNull(callback, "callback == null"); 94 | try { 95 | Response response = delegate.execute(); 96 | callbackExecutor.execute(() -> { 97 | if (delegate.isCanceled()) { 98 | Log.e("LvHttp ------> ", "请求取消"); 99 | } else { 100 | callback.onResponse(ExecutorCallbackCall.this, response); 101 | } 102 | }); 103 | } catch (Exception e) { 104 | e.printStackTrace(); 105 | Log.e("LvHttp ------> ", "全局异常处理:" + e.toString()); 106 | } 107 | 108 | /* delegate.enqueue(new Callback() { 109 | @Override 110 | public void onResponse(Call call, final Response response) { 111 | callbackExecutor.execute(() -> { 112 | try{ 113 | if (delegate.isCanceled()) { 114 | // Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation. 115 | callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled")); 116 | } else { 117 | callback.onResponse(ExecutorCallbackCall.this, response); 118 | } 119 | }catch (Exception e){ 120 | Log.e("LvHttp ------> ", "全局异常处理:1" + e.toString()); 121 | } 122 | }); 123 | } 124 | @Override 125 | public void onFailure(Call call, final Throwable t) { 126 | try { 127 | callbackExecutor.execute(() -> callback.onFailure(ExecutorCallbackCall.this, t)); 128 | } catch (Exception e) { 129 | Log.e("LvHttp ------> ", "全局异常处理:2" + e.toString()); 130 | } 131 | } 132 | });*/ 133 | } 134 | 135 | @Override 136 | public boolean isExecuted() { 137 | return delegate.isExecuted(); 138 | } 139 | 140 | @Override 141 | public Response execute() throws IOException { 142 | return delegate.execute(); 143 | } 144 | 145 | @Override 146 | public void cancel() { 147 | delegate.cancel(); 148 | } 149 | 150 | @Override 151 | public boolean isCanceled() { 152 | return delegate.isCanceled(); 153 | } 154 | 155 | @SuppressWarnings("CloneDoesntCallSuperClone") // Performing deep clone. 156 | @Override 157 | public Call clone() { 158 | return new LvDefaultCallAdapterFactory.ExecutorCallbackCall<>(callbackExecutor, delegate.clone()); 159 | } 160 | 161 | @Override 162 | public Request request() { 163 | return delegate.request(); 164 | } 165 | 166 | @Override 167 | public Timeout timeout() { 168 | return null; 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/java/com/lvhttp/test/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.lvhttp.test 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Activity 5 | import android.content.Intent 6 | import android.net.Uri 7 | import android.os.Bundle 8 | import android.os.Environment 9 | import android.util.Log 10 | import android.view.View 11 | import android.widget.Toast 12 | import androidx.annotation.Nullable 13 | import androidx.appcompat.app.AppCompatActivity 14 | import androidx.appcompat.widget.AppCompatButton 15 | import androidx.appcompat.widget.AppCompatTextView 16 | import androidx.lifecycle.lifecycleScope 17 | import com.bumptech.glide.Glide 18 | import com.donkingliang.imageselector.utils.ImageSelector 19 | import com.donkingliang.imageselector.utils.UriUtils 20 | import com.donkingliang.imageselector.utils.VersionUtils 21 | import com.lvhttp.net.LvHttp 22 | import com.lvhttp.net.download.DownResponse 23 | import com.lvhttp.net.download.start 24 | import com.lvhttp.net.launch.* 25 | import com.lvhttp.net.param.createFileRequestBody 26 | import com.lvhttp.net.param.createPart 27 | import com.lvhttp.net.param.createParts 28 | import com.lvhttp.net.param.createRequestBody 29 | import com.lvhttp.net.response.ResultState 30 | import com.lvhttp.test.response.ResponseData 31 | import kotlinx.coroutines.launch 32 | import java.io.* 33 | 34 | 35 | class MainActivity : AppCompatActivity() { 36 | 37 | 38 | override fun onCreate(savedInstanceState: Bundle?) { 39 | super.onCreate(savedInstanceState) 40 | setContentView(R.layout.activity_main) 41 | 42 | findViewById(R.id.test).setOnClickListener { 43 | 44 | lifecycleScope.launch { 45 | launchHttp { 46 | Toast.makeText(this@MainActivity, "加载中", Toast.LENGTH_SHORT).show() 47 | LvHttp.createApi(Service::class.java).get() 48 | }.toData { 49 | Toast.makeText(this@MainActivity, "${it.data}", Toast.LENGTH_SHORT).show() 50 | }.toError { 51 | //Error 52 | } 53 | 54 | // launchHttp { 55 | // LvHttp.createApi(Service::class.java).get2() 56 | // }.toData { 57 | // Toast.makeText(this@MainActivity, "${it.data}", Toast.LENGTH_SHORT).show() 58 | // } 59 | // 60 | // multipleRequest() 61 | } 62 | //下载 63 | val downloadButton = findViewById(R.id.downloadButton) 64 | downloadButton.setOnClickListener { 65 | lifecycleScope.launch { 66 | launchHttp { 67 | LvHttp.createApi(Service::class.java).download() 68 | .start(object : DownResponse("LvHttp", "chebangyang.apk") { 69 | override fun create(size: Float) { 70 | Log.e("-------->", "create:总大小 ${(size)} ") 71 | } 72 | 73 | @SuppressLint("SetTextI18n") 74 | override fun process(process: Float) { 75 | downloadButton.setText("$process %") 76 | } 77 | 78 | override fun error(e: Exception) { 79 | e.printStackTrace() 80 | downloadButton.setText("下载错误") 81 | } 82 | 83 | override fun done(file: File) { 84 | //完成 85 | Toast.makeText(this@MainActivity, "成功", Toast.LENGTH_SHORT) 86 | .show() 87 | } 88 | }) 89 | } 90 | } 91 | } 92 | 93 | 94 | } 95 | } 96 | 97 | private fun multipleRequest() { 98 | //并发 99 | val list = arrayListOf Bean>() 100 | (0..10).forEach { _ -> 101 | list.add { 102 | LvHttp.createApi(Service::class.java).get2() 103 | } 104 | } 105 | lifecycleScope.launch { 106 | zipLaunch(list) { list -> 107 | list.forEachIndexed { index, resultState -> 108 | resultState.toData { 109 | Log.e("---345--->$index", "${it}") 110 | }.toError { 111 | Log.e("---345--->", "${it.printStackTrace()}"); 112 | } 113 | } 114 | } 115 | } 116 | } 117 | 118 | private fun post() { 119 | lifecycleScope.launch { 120 | launchHttp { 121 | LvHttp.createApi(Service::class.java).login("15129379467", "147258369") 122 | }.toData { 123 | Toast.makeText(this@MainActivity, it.data.toString(), Toast.LENGTH_SHORT).show() 124 | }.toError { 125 | Log.e("---345--->", "${it.printStackTrace()}"); 126 | } 127 | } 128 | } 129 | 130 | /** 131 | * 文件上传 132 | */ 133 | private fun upload() { 134 | ImageSelector.builder() 135 | // .useCamera(true) // 设置是否使用拍照 136 | // .setSingle(true) //设置是否单选 137 | .setCrop(true) 138 | .onlyTakePhoto(true) 139 | .start(this, 0x0001) 140 | } 141 | 142 | 143 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 144 | super.onActivityResult(requestCode, resultCode, data) 145 | if (requestCode == 0x0001 && data != null) { 146 | val array = data.getStringArrayListExtra(ImageSelector.SELECT_RESULT) 147 | if (array != null && array.isNotEmpty()) { 148 | val file = File(array[0]) 149 | Log.e("-------", array[0]) 150 | 151 | Glide.with(this) 152 | .load(file) 153 | .into(findViewById(R.id.image)) 154 | 155 | 156 | // val requestBody = createFileRequestBody(file) 157 | 158 | lifecycleScope.launch { 159 | launchHttp { 160 | LvHttp.createApi(Service::class.java) 161 | .postFile(*createParts(mapOf("key" to file, "key2" to file))) 162 | }.toData { 163 | Toast.makeText(this@MainActivity, "成功", Toast.LENGTH_SHORT).show() 164 | }.toError { 165 | Toast.makeText(this@MainActivity, "失败", Toast.LENGTH_SHORT).show() 166 | } 167 | } 168 | } 169 | } 170 | 171 | } 172 | 173 | 174 | fun copyUriToExternalFilesDir(uri: Uri, fileName: String): File? { 175 | val inputStream = contentResolver.openInputStream(uri) 176 | val tempDir = getExternalFilesDir("temp") 177 | if (inputStream != null && tempDir != null) { 178 | val file = File("$tempDir/$fileName") 179 | val fos = FileOutputStream(file) 180 | val bis = BufferedInputStream(inputStream) 181 | val bos = BufferedOutputStream(fos) 182 | val byteArray = ByteArray(1024) 183 | var bytes = bis.read(byteArray) 184 | while (bytes > 0) { 185 | bos.write(byteArray, 0, bytes) 186 | bos.flush() 187 | bytes = bis.read(byteArray) 188 | } 189 | bos.close() 190 | fos.close() 191 | return file 192 | } 193 | return null 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### LiveHttp 2 | 3 | ​ 基于 Retrofit + 协程的网络请求组件,适用于以 `Kotlin` 开发的项目。 4 | 5 | ------ 6 | 7 | ### 特性 8 | 9 | - 适配在 Activity / Fragment / ViewMoel 的使用,防止内存泄露 10 | - 完善异常处理机制,可自定义处理请求过程中的异常,或者统一处理全局异常等。支持数据异常处理全局处理,如 code 异常。 11 | - 一键式设置缓存大小,是否开启缓存,打印日志等 12 | - 对文件**上传/下载**做了丰富的支持,一键式上传下载 13 | - 支持自定义 Response,格式参考 ResponseData 14 | - 支持在请求接口中直接返回 Bean 类。 15 | - 支持 Ktx 扩展 16 | - 支持证书验证 17 | - 完善的状态管理机制,使用非常简单 18 | 19 | ### 依赖: 20 | 21 | ```kotlin 22 | allprojects { 23 | repositories { 24 | ... 25 | maven { url 'https://jitpack.io' } 26 | } 27 | } 28 | ``` 29 | 30 | ```groovy 31 | dependencies { 32 | implementation 'com.github.LvKang-insist:LvHttp:1.1.9' 33 | } 34 | ``` 35 | 36 | ### 请自行导入以下组件 37 | 38 | ```groovy 39 | //网络请求依赖 40 | //网络请求依赖 41 | implementation 'com.squareup.okhttp3:okhttp:4.9.3' 42 | implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0' 43 | implementation 'com.squareup.retrofit2:retrofit:2.9.0' 44 | implementation 'com.squareup.retrofit2:converter-gson:2.9.0' 45 | 46 | //协程基础库 47 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1" 48 | //协程 Android 库,提供 UI 调度器 49 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1" 50 | // viewmodel / activity 的ktx扩展 51 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1" 52 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1" 53 | ``` 54 | 55 | ### 关于混淆 56 | ``` 57 | -keep class com.lvhttp.net.response.** { *; } 58 | ``` 59 | 如果你自定义了 Response ,那么自定义的那个类也需要混淆,切记 60 | 61 | ### 初始化 62 | 63 | ```kotlin 64 | LvHttp.Builder() 65 | .setApplication(this) 66 | .setBaseUrl("https://api.github.com/") 67 | //是否开启缓存 68 | .isCache(false) 69 | //是否打印 log 70 | .isLog(true) 71 | //验证证书 72 | .setCerResId(if (BuildConfig.DEBUG) R.raw.ca_debug else R.raw.ca_release) 73 | //对 Code 异常的处理,可自定义,参考 ResponseData 类 74 | .setErrorDispose(ErrorKey.ErrorCode, ErrorValue { 75 | Toast.makeText(this, "Code 错误", Toast.LENGTH_SHORT).show() 76 | }) 77 | //全局异常处理,参考 Launch.kt ,可自定义异常处理,参考 ErrorKey 即可 78 | .setErrorDispose(ErrorKey.AllEexeption, ErrorValue { 79 | it.printStackTrace() 80 | Toast.makeText(this, "网络错误", Toast.LENGTH_SHORT).show() 81 | }) 82 | .build() 83 | ``` 84 | 85 | ------ 86 | 87 | ### 使用方式 88 | 89 | 如果你是在 `Activity` 中或者 `ViewModel` 等地方使用,请在请求的外层加上他们的协程作用域,这样可以有效的解决很多副作用,例如 `lifecycleScope.launch{}` 、`viewModelScope.launch` 等。 90 | 91 | ```kotlin 92 | /** 93 | * 普通请求 94 | */ 95 | @GET("wxarticle/chapters/json") 96 | suspend fun get(): ResponseData 97 | ``` 98 | 99 | ```kotlin 100 | lifecycleScope.launch { 101 | launchHttp { 102 | Toast.makeText(this@MainActivity, "加载中", Toast.LENGTH_SHORT).show() 103 | LvHttp.createApi(Service::class.java).get() 104 | }.toData { 105 | Toast.makeText(this@MainActivity, "${it.data}", Toast.LENGTH_SHORT).show() 106 | }.toError { 107 | Log.e("---345--->", "${it.printStackTrace()}"); 108 | } 109 | //Stop Loading 110 | Toast.makeText(this@MainActivity, "完成", Toast.LENGTH_SHORT).show() 111 | } 112 | ``` 113 | 114 | 请求结果分为两个,分别是 `toData` 表示成功,`tpError`表示失败 115 | 116 | > 需要注意的是,如果请求过程中失败,如果启用了全局异常,那么全局异常和 toError 都会被调用,全局异常可用于吐司提示用户或者异常上报等,toError 可以用于修改 UI 状态等 117 | 118 | 上面的请求的时候对数据进行了包装,也就是 `ResponseData` ,继承自 `BaseResponse`。 119 | 120 | 你可以参照自己项目中的统一的数据格式来定制包装类,定义的包装类都需要继承 `BaseResponse` 。这样可以有效的进行 Code 验证等其他的操作。 121 | 122 | 如果有些接口返回的数据违背了项目规定的数据格式,例如项目中调用了第三方的接口,返回的格式和项目规定的格式不同,则可以采取不添加包装类的写法。在定义请求接口方法的时候直接使用对象即可。如下所示: 123 | 124 | ```kotlin 125 | @GET("wxarticle/chapters/json") 126 | suspend fun get2(): Bean 127 | ``` 128 | 如上,支持不添加数据包装类。 129 | ```kotlin 130 | lifecycleScope.launch { 131 | launchHttp { 132 | LvHttp.createApi(Service::class.java).get2() 133 | }.toData { 134 | Toast.makeText(this@MainActivity, "${it.data}", Toast.LENGTH_SHORT).show() 135 | } 136 | //Stop Loading 137 | Toast.makeText(this@MainActivity, "完成", Toast.LENGTH_SHORT).show() 138 | } 139 | ``` 140 | 141 | 如上,即可完成 Get 请求 142 | 143 | > 需要注意的是上面使用的是 `launchHttpPack` ,这种方式无包装类,所以无法自动验证 code, 144 | 145 | ### 全局异常处理 146 | 在 application 中初始化的时候调用 setErrorDispose 拦截异常,可拦截的异常见 [ErrorKey](https://github.com/LvKang-insist/LvHttp/blob/master/net/src/main/java/com/lvhttp/net/error/ErrorKey.kt) 这个类, 147 | 148 | 拦截方式如下: 149 | 150 | ```kotlin 151 | ....... 152 | .setCode(1) 153 | .setErrorDispose(ErrorKey.ErrorCode, ErrorValue { 154 | Log.e("345:", "Code 错误") 155 | Toast.makeText(this, "Code 错误", Toast.LENGTH_SHORT).show() 156 | }) 157 | .setErrorDispose(ErrorKey.AllEexeption, ErrorValue { 158 | it.printStackTrace() 159 | Log.e("345:", "网络错误") 160 | Toast.makeText(this, "网络错误", Toast.LENGTH_SHORT).show() 161 | }) 162 | 163 | ``` 164 | setCode 是设置正确的 code,如果不等于 setCode 值,就会进行 ErrorCode 异常处理 165 | AllExeption 是所有异常都会调用到这里(不包括code异常) 166 | 更多异常详见 ErrorKey 167 | 168 | ### 文件下载 169 | 170 | 直接返回 `ResponseBody` 即可。库中对 `ResponseBody` 进行了扩展,具体使用方式如下: 171 | 172 | ```kotlin 173 | @Streaming 174 | @GET("https://files.pythonhosted.org/packages/6b/34/415834bfdafca3c5f451532e8a8d9ba89a21c9743a0c59fbd0205c7f9426/six-1.15.0.tar.gz") 175 | suspend fun download(): ResponseBody 176 | ``` 177 | 178 | ```kotlin 179 | lifecycleScope.launch { 180 | launchHttp { 181 | LvHttp.createApi(Service::class.java).download() 182 | .start(object : DownResponse("LvHttp", "chebangyang.apk") { 183 | override fun create(size: Float) { 184 | Log.e("-------->", "create:总大小 ${(size)} ") 185 | } 186 | 187 | @SuppressLint("SetTextI18n") 188 | override fun process(process: Float) { 189 | downloadButton.setText("$process %") 190 | } 191 | 192 | override fun error(e: Exception) { 193 | e.printStackTrace() 194 | downloadButton.setText("下载错误") 195 | } 196 | 197 | override fun done(file: File) { 198 | //完成 199 | Toast.makeText(this@MainActivity, "成功", Toast.LENGTH_SHORT) 200 | .show() 201 | } 202 | }) 203 | } 204 | } 205 | ``` 206 | 207 | 其中四个方法可根据需要进行重写 208 | 在 Android Q 中:path 表示的路径为 根目录/dowload/path/name 209 | 在 Android Q 以下,path 表示的是 根目录/path/name 210 | 注意:name 后面需要加上后缀名 211 | 212 | ### 文件上传 213 | 214 | ```kotlin 215 | /** 216 | * post:文件,支持一个或者多个文件 217 | */ 218 | @Multipart 219 | @POST("http://192.168.43.253:80/test/updata.php") 220 | suspend fun postFile(@Part vararg file: MultipartBody.Part): UpLoadBean 221 | ``` 222 | 223 | ```kotlin 224 | lifecycleScope.launch { 225 | launchHttp { 226 | LvHttp.createApi(Service::class.java).postFile(createPart("key", file)) 227 | }.toData { 228 | Toast.makeText(this@MainActivity, "成功", Toast.LENGTH_SHORT).show() 229 | }.toError { 230 | Toast.makeText(this@MainActivity, "失败", Toast.LENGTH_SHORT).show() 231 | } 232 | } 233 | ``` 234 | 235 | 上传多个文件 236 | 237 | ```kotlin 238 | val file1 = File(Environment.getExternalStorageDirectory().path, "/image1.png") 239 | val file2 = File(Environment.getExternalStorageDirectory().path, "/image2.png") 240 | 241 | lifecycleScope.launch { 242 | launchHttp { 243 | LvHttp.createApi(Service::class.java) 244 | .postFile(*createParts(mapOf("key" to file, "key2" to file))) 245 | }.toData { 246 | Toast.makeText(this@MainActivity, "成功", Toast.LENGTH_SHORT).show() 247 | }.toError { 248 | Toast.makeText(this@MainActivity, "失败", Toast.LENGTH_SHORT).show() 249 | } 250 | } 251 | ``` 252 | 253 | ### 自定义 Response 254 | 255 | 自定义 Response 需要继承 `BaseResponse`,并且实现 notifyData 方法,如下所示: 256 | 257 | ```kotlin 258 | data class ResponseData(val data: T, val code: Int, val errorMsg: String) : 259 | BaseResponse() { 260 | override fun notifyData(): BaseResponse { 261 | _data = data 262 | _code = code 263 | _message = errorMsg 264 | return super.notifyData() 265 | } 266 | } 267 | ``` 268 | 269 | > 如果您有任何建议或者问题请联系,可直接联系我 270 | -------------------------------------------------------------------------------- /net/src/main/java/com/lvhttp/net/utils/FileQUtils.java: -------------------------------------------------------------------------------- 1 | package com.lvhttp.net.utils; 2 | 3 | import android.content.ContentResolver; 4 | import android.content.ContentValues; 5 | import android.content.Context; 6 | import android.content.res.AssetFileDescriptor; 7 | import android.database.Cursor; 8 | import android.graphics.Bitmap; 9 | import android.graphics.BitmapFactory; 10 | import android.net.Uri; 11 | import android.os.Build; 12 | import android.os.Bundle; 13 | import android.os.Environment; 14 | import android.provider.MediaStore; 15 | import android.text.TextUtils; 16 | import android.util.Log; 17 | 18 | import androidx.annotation.RequiresApi; 19 | 20 | import java.io.BufferedInputStream; 21 | import java.io.File; 22 | import java.io.FileInputStream; 23 | import java.io.FileNotFoundException; 24 | import java.io.IOException; 25 | import java.io.InputStream; 26 | import java.io.OutputStream; 27 | import java.util.Objects; 28 | 29 | /** 30 | * Created by Petterp 31 | * on 2019-12-27 32 | *

33 | * 以下方法适用于Android10 34 | *

35 | * Android10 文件存储机制为沙盒存储,每个App都只能访问自身目录下的文件(私有文件)和公共存储文件 36 | *

37 | * 什么是私有文件? 38 | * 包名底下的文件,如 com.business.toos/xxx,读写私有文件无需任何权限 39 | *

40 | * 什么是公有文件? 41 | * 如DCIM,PICTURES,MUSIC,Movies...,访问共有文件还是需要申请权限 42 | *

43 | * 在AndroidQ中,文件的操作使用 ContentResolver 进行操作 44 | * 获取及创建私有文件使用 context.getExternalFilesDir( xxx ) 45 | * 需要注意,沙盒文件会在卸载App之后被删除 46 | *

47 | * 公共目录下文件操作 48 | * 通过MediaStore 49 | *

50 | * 官网链接 https://developer.android.google.cn/training/data-storage/files/media 51 | */ 52 | public class FileQUtils { 53 | 54 | private static final String TAG = "FileQUtils"; 55 | 56 | /** 57 | * 用于Android10保存image 58 | * 59 | * @param context 60 | * @param saveFileName 文件名 61 | * @param saveDirName 子文件路径,如果不存在则新建 62 | * @return 63 | */ 64 | @RequiresApi(api = Build.VERSION_CODES.Q) 65 | public static Uri saveImageWithAndroidQ(Context context, 66 | String saveFileName, 67 | String saveDirName) { 68 | Uri uri = null; 69 | ContentValues values = new ContentValues(); 70 | values.put(MediaStore.Images.Media.DISPLAY_NAME, saveFileName); 71 | values.put(MediaStore.Images.Media.MIME_TYPE, "image/png"); 72 | values.put(MediaStore.Images.Media.RELATIVE_PATH, "DCIM/" + saveDirName); 73 | Uri external = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 74 | ContentResolver resolver = context.getContentResolver(); 75 | 76 | //这里为了避免用户如果删除了DCIM文件夹,此时让系统帮我们去寻找合适的存储路径 77 | //适合Android10 78 | try { 79 | uri = resolver.insert(external, values); 80 | } catch (IllegalArgumentException e) { 81 | values.remove(MediaStore.Images.Media.RELATIVE_PATH); 82 | uri = resolver.insert(external, values); 83 | } finally { 84 | return uri; 85 | } 86 | } 87 | 88 | /** 89 | * 用户保存任何文件时传入 90 | * 91 | * @param context 92 | * @param saveFileName 文件名 93 | * @param mineType 文件类型,参考FileIntentUtils类中的getMap 94 | * @param saveDirName 文件路径 95 | * @return 96 | */ 97 | @RequiresApi(api = Build.VERSION_CODES.Q) 98 | public static Uri saveFileWithAndroidQ(Context context, 99 | String saveFileName, 100 | String mineType, 101 | String saveDirName) { 102 | Uri uri = null; 103 | ContentValues values = new ContentValues(); 104 | values.put(MediaStore.Images.Media.DISPLAY_NAME, saveFileName); 105 | values.put(MediaStore.Images.Media.MIME_TYPE, mineType); 106 | values.put(MediaStore.Images.Media.RELATIVE_PATH, "Download/" + saveDirName); 107 | Uri external = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 108 | ContentResolver resolver = context.getContentResolver(); 109 | 110 | //这里为了避免用户如果删除了Download文件夹,此时让系统帮我们去寻找合适的存储路径 111 | //适合Android10 112 | try { 113 | uri = resolver.insert(external, values); 114 | } catch (IllegalArgumentException e) { 115 | values.remove(MediaStore.Images.Media.RELATIVE_PATH); 116 | uri = resolver.insert(external, values); 117 | } finally { 118 | return uri; 119 | } 120 | } 121 | 122 | /** 123 | * 用于将Uri转为File 124 | * 125 | * @param uri 126 | * @param context 127 | * @return 128 | */ 129 | @RequiresApi(api = Build.VERSION_CODES.KITKAT) 130 | public static File getFileByUri(Uri uri, Context context) { 131 | String path = null; 132 | if ("file".equals(uri.getScheme())) { 133 | path = uri.getEncodedPath(); 134 | if (path != null) { 135 | path = Uri.decode(path); 136 | ContentResolver cr = context.getContentResolver(); 137 | Cursor cur = cr.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[]{MediaStore.Images.ImageColumns._ID, MediaStore.Images.ImageColumns.DATA}, "(" + MediaStore.Images.ImageColumns.DATA + "=" + "'" + path + "'" + ")", null, null); 138 | int index = 0; 139 | int dataIdx = 0; 140 | for (Objects.requireNonNull(cur).moveToFirst(); !cur.isAfterLast(); cur.moveToNext()) { 141 | index = cur.getColumnIndex(MediaStore.Images.ImageColumns._ID); 142 | index = cur.getInt(index); 143 | dataIdx = cur.getColumnIndex(MediaStore.Images.ImageColumns.DATA); 144 | path = cur.getString(dataIdx); 145 | } 146 | cur.close(); 147 | if (index != 0) { 148 | Uri u = Uri.parse("content://media/external/images/media/" + index); 149 | System.out.println("temp uri is :" + u); 150 | } 151 | } 152 | if (path != null) { 153 | return new File(path); 154 | } 155 | } else if ("content".equals(uri.getScheme())) { 156 | // 4.2.2以后 157 | String[] proj = {MediaStore.Images.Media.DATA}; 158 | Cursor cursor = context.getContentResolver().query(uri, proj, null, null, null); 159 | if (Objects.requireNonNull(cursor).moveToFirst()) { 160 | int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); 161 | path = cursor.getString(columnIndex); 162 | } 163 | cursor.close(); 164 | return new File(Objects.requireNonNull(path)); 165 | } 166 | return null; 167 | } 168 | 169 | 170 | /** 171 | * 判断共有文件是否存在 172 | * 173 | * @param context 174 | * @param path 175 | * @return 176 | */ 177 | public static boolean isAndroidQFileExists(Context context, String path) { 178 | AssetFileDescriptor afd; 179 | ContentResolver cr = context.getContentResolver(); 180 | try { 181 | Uri uri = Uri.parse(path); 182 | //r表示读文件 w表示写 183 | afd = cr.openAssetFileDescriptor(uri, "r"); 184 | if (afd == null) { 185 | return false; 186 | } 187 | } catch (FileNotFoundException e) { 188 | return false; 189 | } 190 | return true; 191 | } 192 | 193 | /** 194 | * 图片保存 195 | * 196 | * @param context context 197 | * @param sourceFile 源文件 198 | * @param saveFileName 保存的文件名 199 | * @param saveDirName picture子目录 200 | * @return 成功或者失败 201 | */ 202 | @RequiresApi(api = Build.VERSION_CODES.Q) 203 | public static boolean saveImageWithAndroidQ(Context context, 204 | File sourceFile, 205 | String saveFileName, 206 | String saveDirName) { 207 | if (!isExternalStorageReadable()) { 208 | throw new RuntimeException("External storage cannot be written!"); 209 | } 210 | ContentValues values = new ContentValues(); 211 | values.put(MediaStore.Images.Media.DISPLAY_NAME, saveFileName); 212 | values.put(MediaStore.Images.Media.MIME_TYPE, "image/png"); 213 | values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/" + saveDirName); 214 | 215 | Uri external = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 216 | ContentResolver resolver = context.getContentResolver(); 217 | 218 | Uri insertUri = resolver.insert(external, values); 219 | BufferedInputStream inputStream; 220 | OutputStream os = null; 221 | boolean result; 222 | try { 223 | inputStream = new BufferedInputStream(new FileInputStream(sourceFile)); 224 | if (insertUri != null) { 225 | os = resolver.openOutputStream(insertUri); 226 | } 227 | if (os != null) { 228 | byte[] buffer = new byte[1024 * 4]; 229 | int len; 230 | while ((len = inputStream.read(buffer)) != -1) { 231 | os.write(buffer, 0, len); 232 | } 233 | os.flush(); 234 | } 235 | result = true; 236 | } catch (IOException e) { 237 | result = false; 238 | } 239 | return result; 240 | } 241 | 242 | 243 | /** 244 | * 复制或下载文件到公有目录 245 | * 246 | * @param context 247 | * @param sourcePath 248 | * @param mimeType 249 | * @param fileName 250 | * @param saveDirName 251 | * @return 252 | */ 253 | @RequiresApi(api = Build.VERSION_CODES.Q) 254 | public static Uri copyToDownloadAndroidQ(Context context, String sourcePath, String mimeType, String fileName, String saveDirName) { 255 | if (!isExternalStorageReadable()) { 256 | throw new RuntimeException("External storage cannot be written!"); 257 | } 258 | ContentValues values = new ContentValues(); 259 | //显示名称 260 | values.put(MediaStore.Downloads.DISPLAY_NAME, fileName); 261 | //存储文件的类型 262 | values.put(MediaStore.Downloads.MIME_TYPE, mimeType); 263 | //公有文件路径 264 | values.put(MediaStore.Downloads.RELATIVE_PATH, "Download/" + saveDirName.replaceAll("/", "") + "/"); 265 | 266 | //生成一个Uri 267 | Uri external = MediaStore.Downloads.EXTERNAL_CONTENT_URI; 268 | ContentResolver resolver = context.getContentResolver(); 269 | 270 | //写入 271 | Uri insertUri = resolver.insert(external, values); 272 | if (insertUri == null) { 273 | return null; 274 | } 275 | 276 | String mFilePath = insertUri.toString(); 277 | 278 | InputStream is = null; 279 | OutputStream os = null; 280 | try { 281 | //输出流 282 | os = resolver.openOutputStream(insertUri); 283 | if (os == null) { 284 | return null; 285 | } 286 | int read; 287 | File sourceFile = new File(sourcePath); 288 | if (sourceFile.exists()) { // 文件存在时 289 | is = new FileInputStream(sourceFile); // 读入原文件 290 | byte[] buffer = new byte[1444]; 291 | while ((read = is.read(buffer)) != -1) { 292 | //写入uri中 293 | os.write(buffer, 0, read); 294 | } 295 | } 296 | } catch (Exception e) { 297 | e.printStackTrace(); 298 | } 299 | return insertUri; 300 | 301 | } 302 | 303 | 304 | /** 305 | * 判断外部存储是否可写入读取 306 | * 307 | * @return 308 | */ 309 | public boolean isExternalStorageWritable() { 310 | String state = Environment.getExternalStorageState(); 311 | if (Environment.MEDIA_MOUNTED.equals(state)) { 312 | return true; 313 | } 314 | return false; 315 | } 316 | 317 | 318 | /** 319 | * 判断外部存储是否可写入 320 | * 321 | * @return 322 | */ 323 | public static boolean isExternalStorageReadable() { 324 | String state = Environment.getExternalStorageState(); 325 | if (Environment.MEDIA_MOUNTED.equals(state) || 326 | Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { 327 | return true; 328 | } 329 | return false; 330 | } 331 | 332 | 333 | /** 334 | * 获取私有相册目录 335 | * 私有目录会被删除 336 | * 337 | * @param context 338 | * @param albumName 339 | * @return 340 | */ 341 | public File getPrivateAlbumStorageDir(Context context, String albumName) { 342 | // Get the directory for the app's private pictures directory. 343 | File file = new File(context.getExternalFilesDir( 344 | Environment.DIRECTORY_PICTURES), albumName); 345 | if (!file.mkdirs()) { 346 | Log.e(TAG, "Directory not created"); 347 | } 348 | return file; 349 | } 350 | 351 | 352 | /** 353 | * 查询指定图片 354 | * 355 | * @param context 356 | * @param path 路径 357 | * @param environmentType 沙盒类型 358 | * @param fileName 文件名 359 | * @return bitmap 360 | */ 361 | public static Bitmap querySignImageBox(Context context, String path, String environmentType, String fileName) { 362 | if (TextUtils.isEmpty(fileName)) { 363 | return null; 364 | } 365 | Bitmap bitmap = null; 366 | //指定沙盒文件夹 367 | String builder = environmentType.replaceAll("/", "") + "/" + 368 | path.replaceAll("/", "") + "/" + 369 | fileName.replaceAll("/", ""); 370 | File picturesFile = context.getExternalFilesDir(builder); 371 | assert picturesFile != null; 372 | if (picturesFile.exists()) { 373 | return BitmapFactory.decodeFile(picturesFile.toString()); 374 | } 375 | return bitmap; 376 | } 377 | 378 | /** 379 | * 删除文件 380 | * 381 | * @param context 382 | * @param fileUri 383 | */ 384 | public void delete(Context context, Uri fileUri) { 385 | context.getContentResolver().delete(fileUri, null, null); 386 | Bundle bundle; 387 | } 388 | 389 | 390 | } 391 | -------------------------------------------------------------------------------- /net/src/main/java/com/lvhttp/net/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package com.lvhttp.net.utils; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | import java.io.IOException; 6 | import java.lang.annotation.Annotation; 7 | import java.lang.reflect.Array; 8 | import java.lang.reflect.GenericArrayType; 9 | import java.lang.reflect.GenericDeclaration; 10 | import java.lang.reflect.Method; 11 | import java.lang.reflect.ParameterizedType; 12 | import java.lang.reflect.Type; 13 | import java.lang.reflect.TypeVariable; 14 | import java.lang.reflect.WildcardType; 15 | import java.util.Arrays; 16 | import java.util.NoSuchElementException; 17 | import java.util.Objects; 18 | 19 | import okhttp3.ResponseBody; 20 | import okio.Buffer; 21 | 22 | 23 | public final class Utils { 24 | static final Type[] EMPTY_TYPE_ARRAY = new Type[0]; 25 | 26 | private Utils() { 27 | // No instances. 28 | } 29 | 30 | 31 | static RuntimeException methodError(Method method, String message, Object... args) { 32 | return methodError(method, null, message, args); 33 | } 34 | 35 | static RuntimeException methodError(Method method, @Nullable Throwable cause, String message, 36 | Object... args) { 37 | message = String.format(message, args); 38 | return new IllegalArgumentException(message 39 | + "\n for method " 40 | + method.getDeclaringClass().getSimpleName() 41 | + "." 42 | + method.getName(), cause); 43 | } 44 | 45 | static RuntimeException parameterError(Method method, 46 | Throwable cause, int p, String message, Object... args) { 47 | return methodError(method, cause, message + " (parameter #" + (p + 1) + ")", args); 48 | } 49 | 50 | static RuntimeException parameterError(Method method, int p, String message, Object... args) { 51 | return methodError(method, message + " (parameter #" + (p + 1) + ")", args); 52 | } 53 | 54 | static Class getRawType(Type type) { 55 | Objects.requireNonNull(type, "type == null"); 56 | 57 | if (type instanceof Class) { 58 | // Type is a normal class. 59 | return (Class) type; 60 | } 61 | if (type instanceof ParameterizedType) { 62 | ParameterizedType parameterizedType = (ParameterizedType) type; 63 | 64 | // I'm not exactly sure why getRawType() returns Type instead of Class. Neal isn't either but 65 | // suspects some pathological case related to nested classes exists. 66 | Type rawType; 67 | rawType = parameterizedType.getRawType(); 68 | if (!(rawType instanceof Class)) { 69 | throw new IllegalArgumentException(); 70 | } 71 | return (Class) rawType; 72 | } 73 | if (type instanceof GenericArrayType) { 74 | Type componentType = ((GenericArrayType) type).getGenericComponentType(); 75 | return Array.newInstance(getRawType(componentType), 0).getClass(); 76 | } 77 | if (type instanceof TypeVariable) { 78 | // We could use the variable's bounds, but that won't work if there are multiple. Having a raw 79 | // type that's more general than necessary is okay. 80 | return Object.class; 81 | } 82 | if (type instanceof WildcardType) { 83 | return getRawType(((WildcardType) type).getUpperBounds()[0]); 84 | } 85 | 86 | throw new IllegalArgumentException("Expected a Class, ParameterizedType, or " 87 | + "GenericArrayType, but <" + type + "> is of type " + type.getClass().getName()); 88 | } 89 | 90 | /** 91 | * Returns true if {@code a} and {@code b} are equal. 92 | */ 93 | static boolean equals(Type a, Type b) { 94 | if (a == b) { 95 | return true; // Also handles (a == null && b == null). 96 | 97 | } else if (a instanceof Class) { 98 | return a.equals(b); // Class already specifies equals(). 99 | 100 | } else if (a instanceof ParameterizedType) { 101 | if (!(b instanceof ParameterizedType)) { 102 | return false; 103 | } 104 | ParameterizedType pa = (ParameterizedType) a; 105 | ParameterizedType pb = (ParameterizedType) b; 106 | Object ownerA = pa.getOwnerType(); 107 | Object ownerB = pb.getOwnerType(); 108 | return (ownerA == ownerB || (ownerA != null && ownerA.equals(ownerB))) 109 | && pa.getRawType().equals(pb.getRawType()) 110 | && Arrays.equals(pa.getActualTypeArguments(), pb.getActualTypeArguments()); 111 | 112 | } else if (a instanceof GenericArrayType) { 113 | if (!(b instanceof GenericArrayType)) { 114 | return false; 115 | } 116 | GenericArrayType ga = (GenericArrayType) a; 117 | GenericArrayType gb = (GenericArrayType) b; 118 | return equals(ga.getGenericComponentType(), gb.getGenericComponentType()); 119 | 120 | } else if (a instanceof WildcardType) { 121 | if (!(b instanceof WildcardType)) { 122 | return false; 123 | } 124 | WildcardType wa = (WildcardType) a; 125 | WildcardType wb = (WildcardType) b; 126 | return Arrays.equals(wa.getUpperBounds(), wb.getUpperBounds()) 127 | && Arrays.equals(wa.getLowerBounds(), wb.getLowerBounds()); 128 | 129 | } else if (a instanceof TypeVariable) { 130 | if (!(b instanceof TypeVariable)) { 131 | return false; 132 | } 133 | TypeVariable va = (TypeVariable) a; 134 | TypeVariable vb = (TypeVariable) b; 135 | return va.getGenericDeclaration() == vb.getGenericDeclaration() 136 | && va.getName().equals(vb.getName()); 137 | 138 | } else { 139 | return false; // This isn't a type we support! 140 | } 141 | } 142 | 143 | /** 144 | * Returns the generic supertype for {@code supertype}. For example, given a class {@code 145 | * IntegerSet}, the result for when supertype is {@code Set.class} is {@code Set} and the 146 | * result when the supertype is {@code Collection.class} is {@code Collection}. 147 | */ 148 | static Type getGenericSupertype(Type context, Class rawType, Class toResolve) { 149 | if (toResolve == rawType) { 150 | return context; 151 | } 152 | 153 | // We skip searching through interfaces if unknown is an interface. 154 | if (toResolve.isInterface()) { 155 | Class[] interfaces = rawType.getInterfaces(); 156 | for (int i = 0, length = interfaces.length; i < length; i++) { 157 | if (interfaces[i] == toResolve) { 158 | return rawType.getGenericInterfaces()[i]; 159 | } else if (toResolve.isAssignableFrom(interfaces[i])) { 160 | return getGenericSupertype(rawType.getGenericInterfaces()[i], interfaces[i], toResolve); 161 | } 162 | } 163 | } 164 | 165 | // Check our supertypes. 166 | if (!rawType.isInterface()) { 167 | while (rawType != Object.class) { 168 | Class rawSupertype = rawType.getSuperclass(); 169 | if (rawSupertype == toResolve) { 170 | return rawType.getGenericSuperclass(); 171 | } else if (toResolve.isAssignableFrom(rawSupertype)) { 172 | return getGenericSupertype(rawType.getGenericSuperclass(), rawSupertype, toResolve); 173 | } 174 | rawType = rawSupertype; 175 | } 176 | } 177 | 178 | // We can't resolve this further. 179 | return toResolve; 180 | } 181 | 182 | private static int indexOf(Object[] array, Object toFind) { 183 | for (int i = 0; i < array.length; i++) { 184 | if (toFind.equals(array[i])) { 185 | return i; 186 | } 187 | } 188 | throw new NoSuchElementException(); 189 | } 190 | 191 | static String typeToString(Type type) { 192 | return type instanceof Class ? ((Class) type).getName() : type.toString(); 193 | } 194 | 195 | /** 196 | * Returns the generic form of {@code supertype}. For example, if this is {@code 197 | * ArrayList}, this returns {@code Iterable} given the input {@code 198 | * Iterable.class}. 199 | * 200 | * @param supertype a superclass of, or interface implemented by, this. 201 | */ 202 | static Type getSupertype(Type context, Class contextRawType, Class supertype) { 203 | if (!supertype.isAssignableFrom(contextRawType)) throw new IllegalArgumentException(); 204 | return resolve(context, contextRawType, 205 | getGenericSupertype(context, contextRawType, supertype)); 206 | } 207 | 208 | static Type resolve(Type context, Class contextRawType, Type toResolve) { 209 | // This implementation is made a little more complicated in an attempt to avoid object-creation. 210 | while (true) { 211 | if (toResolve instanceof TypeVariable) { 212 | TypeVariable typeVariable = (TypeVariable) toResolve; 213 | toResolve = resolveTypeVariable(context, contextRawType, typeVariable); 214 | if (toResolve == typeVariable) { 215 | return toResolve; 216 | } 217 | 218 | } else if (toResolve instanceof Class && ((Class) toResolve).isArray()) { 219 | Class original = (Class) toResolve; 220 | Type componentType = original.getComponentType(); 221 | Type newComponentType = resolve(context, contextRawType, componentType); 222 | return componentType == newComponentType ? original : new Utils.GenericArrayTypeImpl( 223 | newComponentType); 224 | 225 | } else if (toResolve instanceof GenericArrayType) { 226 | GenericArrayType original = (GenericArrayType) toResolve; 227 | Type componentType = original.getGenericComponentType(); 228 | Type newComponentType = resolve(context, contextRawType, componentType); 229 | return componentType == newComponentType ? original : new Utils.GenericArrayTypeImpl( 230 | newComponentType); 231 | 232 | } else if (toResolve instanceof ParameterizedType) { 233 | ParameterizedType original = (ParameterizedType) toResolve; 234 | Type ownerType = original.getOwnerType(); 235 | Type newOwnerType = resolve(context, contextRawType, ownerType); 236 | boolean changed = newOwnerType != ownerType; 237 | 238 | Type[] args = original.getActualTypeArguments(); 239 | for (int t = 0, length = args.length; t < length; t++) { 240 | Type resolvedTypeArgument = resolve(context, contextRawType, args[t]); 241 | if (resolvedTypeArgument != args[t]) { 242 | if (!changed) { 243 | args = args.clone(); 244 | changed = true; 245 | } 246 | args[t] = resolvedTypeArgument; 247 | } 248 | } 249 | 250 | return changed 251 | ? new Utils.ParameterizedTypeImpl(newOwnerType, original.getRawType(), args) 252 | : original; 253 | 254 | } else if (toResolve instanceof WildcardType) { 255 | WildcardType original = (WildcardType) toResolve; 256 | Type[] originalLowerBound = original.getLowerBounds(); 257 | Type[] originalUpperBound = original.getUpperBounds(); 258 | 259 | if (originalLowerBound.length == 1) { 260 | Type lowerBound = resolve(context, contextRawType, originalLowerBound[0]); 261 | if (lowerBound != originalLowerBound[0]) { 262 | return new Utils.WildcardTypeImpl(new Type[]{Object.class}, new Type[]{lowerBound}); 263 | } 264 | } else if (originalUpperBound.length == 1) { 265 | Type upperBound = resolve(context, contextRawType, originalUpperBound[0]); 266 | if (upperBound != originalUpperBound[0]) { 267 | return new Utils.WildcardTypeImpl(new Type[]{upperBound}, EMPTY_TYPE_ARRAY); 268 | } 269 | } 270 | return original; 271 | 272 | } else { 273 | return toResolve; 274 | } 275 | } 276 | } 277 | 278 | private static Type resolveTypeVariable( 279 | Type context, Class contextRawType, TypeVariable unknown) { 280 | Class declaredByRaw = declaringClassOf(unknown); 281 | 282 | // We can't reduce this further. 283 | if (declaredByRaw == null) { 284 | return unknown; 285 | } 286 | 287 | Type declaredBy = getGenericSupertype(context, contextRawType, declaredByRaw); 288 | if (declaredBy instanceof ParameterizedType) { 289 | int index = indexOf(declaredByRaw.getTypeParameters(), unknown); 290 | return ((ParameterizedType) declaredBy).getActualTypeArguments()[index]; 291 | } 292 | 293 | return unknown; 294 | } 295 | 296 | /** 297 | * Returns the declaring class of {@code typeVariable}, or {@code null} if it was not declared by 298 | * a class. 299 | */ 300 | private static @Nullable 301 | Class declaringClassOf(TypeVariable typeVariable) { 302 | GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration(); 303 | return genericDeclaration instanceof Class ? (Class) genericDeclaration : null; 304 | } 305 | 306 | static void checkNotPrimitive(Type type) { 307 | if (type instanceof Class && ((Class) type).isPrimitive()) { 308 | throw new IllegalArgumentException(); 309 | } 310 | } 311 | 312 | /** 313 | * Returns true if {@code annotations} contains an instance of {@code cls}. 314 | */ 315 | public static boolean isAnnotationPresent(Annotation[] annotations, 316 | Class cls) { 317 | for (Annotation annotation : annotations) { 318 | if (cls.isInstance(annotation)) { 319 | return true; 320 | } 321 | } 322 | return false; 323 | } 324 | 325 | static ResponseBody buffer(final ResponseBody body) throws IOException { 326 | Buffer buffer = new Buffer(); 327 | body.source().readAll(buffer); 328 | return ResponseBody.create(body.contentType(), body.contentLength(), buffer); 329 | } 330 | 331 | public static Type getParameterUpperBound(int index, ParameterizedType type) { 332 | Type[] types = type.getActualTypeArguments(); 333 | if (index < 0 || index >= types.length) { 334 | throw new IllegalArgumentException( 335 | "Index " + index + " not in range [0," + types.length + ") for " + type); 336 | } 337 | Type paramType = types[index]; 338 | if (paramType instanceof WildcardType) { 339 | return ((WildcardType) paramType).getUpperBounds()[0]; 340 | } 341 | return paramType; 342 | } 343 | 344 | static Type getParameterLowerBound(int index, ParameterizedType type) { 345 | Type paramType = type.getActualTypeArguments()[index]; 346 | if (paramType instanceof WildcardType) { 347 | return ((WildcardType) paramType).getLowerBounds()[0]; 348 | } 349 | return paramType; 350 | } 351 | 352 | static boolean hasUnresolvableType(@Nullable Type type) { 353 | if (type instanceof Class) { 354 | return false; 355 | } 356 | if (type instanceof ParameterizedType) { 357 | ParameterizedType parameterizedType = (ParameterizedType) type; 358 | for (Type typeArgument : parameterizedType.getActualTypeArguments()) { 359 | if (hasUnresolvableType(typeArgument)) { 360 | return true; 361 | } 362 | } 363 | return false; 364 | } 365 | if (type instanceof GenericArrayType) { 366 | return hasUnresolvableType(((GenericArrayType) type).getGenericComponentType()); 367 | } 368 | if (type instanceof TypeVariable) { 369 | return true; 370 | } 371 | if (type instanceof WildcardType) { 372 | return true; 373 | } 374 | String className = type == null ? "null" : type.getClass().getName(); 375 | throw new IllegalArgumentException("Expected a Class, ParameterizedType, or " 376 | + "GenericArrayType, but <" + type + "> is of type " + className); 377 | } 378 | 379 | static final class ParameterizedTypeImpl implements ParameterizedType { 380 | private final @Nullable 381 | Type ownerType; 382 | private final Type rawType; 383 | private final Type[] typeArguments; 384 | 385 | ParameterizedTypeImpl(@Nullable Type ownerType, Type rawType, Type... typeArguments) { 386 | // Require an owner type if the raw type needs it. 387 | if (rawType instanceof Class 388 | && (ownerType == null) != (((Class) rawType).getEnclosingClass() == null)) { 389 | throw new IllegalArgumentException(); 390 | } 391 | 392 | for (Type typeArgument : typeArguments) { 393 | Objects.requireNonNull(typeArgument, "typeArgument == null"); 394 | checkNotPrimitive(typeArgument); 395 | } 396 | 397 | this.ownerType = ownerType; 398 | this.rawType = rawType; 399 | this.typeArguments = typeArguments.clone(); 400 | } 401 | 402 | @Override 403 | public Type[] getActualTypeArguments() { 404 | return typeArguments.clone(); 405 | } 406 | 407 | @Override 408 | public Type getRawType() { 409 | return rawType; 410 | } 411 | 412 | @Override 413 | public @Nullable 414 | Type getOwnerType() { 415 | return ownerType; 416 | } 417 | 418 | @Override 419 | public boolean equals(Object other) { 420 | return other instanceof ParameterizedType && Utils.equals(this, (ParameterizedType) other); 421 | } 422 | 423 | @Override 424 | public int hashCode() { 425 | return Arrays.hashCode(typeArguments) 426 | ^ rawType.hashCode() 427 | ^ (ownerType != null ? ownerType.hashCode() : 0); 428 | } 429 | 430 | @Override 431 | public String toString() { 432 | if (typeArguments.length == 0) { 433 | return typeToString(rawType); 434 | } 435 | StringBuilder result = new StringBuilder(30 * (typeArguments.length + 1)); 436 | result.append(typeToString(rawType)); 437 | result.append("<").append(typeToString(typeArguments[0])); 438 | for (int i = 1; i < typeArguments.length; i++) { 439 | result.append(", ").append(typeToString(typeArguments[i])); 440 | } 441 | return result.append(">").toString(); 442 | } 443 | } 444 | 445 | private static final class GenericArrayTypeImpl implements GenericArrayType { 446 | private final Type componentType; 447 | 448 | GenericArrayTypeImpl(Type componentType) { 449 | this.componentType = componentType; 450 | } 451 | 452 | @Override 453 | public Type getGenericComponentType() { 454 | return componentType; 455 | } 456 | 457 | @Override 458 | public boolean equals(Object o) { 459 | return o instanceof GenericArrayType 460 | && Utils.equals(this, (GenericArrayType) o); 461 | } 462 | 463 | @Override 464 | public int hashCode() { 465 | return componentType.hashCode(); 466 | } 467 | 468 | @Override 469 | public String toString() { 470 | return typeToString(componentType) + "[]"; 471 | } 472 | } 473 | 474 | /** 475 | * The WildcardType interface supports multiple upper bounds and multiple 476 | * lower bounds. We only support what the Java 6 language needs - at most one 477 | * bound. If a lower bound is set, the upper bound must be Object.class. 478 | */ 479 | private static final class WildcardTypeImpl implements WildcardType { 480 | private final Type upperBound; 481 | private final @Nullable 482 | Type lowerBound; 483 | 484 | WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) { 485 | if (lowerBounds.length > 1) { 486 | throw new IllegalArgumentException(); 487 | } 488 | if (upperBounds.length != 1) { 489 | throw new IllegalArgumentException(); 490 | } 491 | 492 | if (lowerBounds.length == 1) { 493 | if (lowerBounds[0] == null) { 494 | throw new NullPointerException(); 495 | } 496 | checkNotPrimitive(lowerBounds[0]); 497 | if (upperBounds[0] != Object.class) { 498 | throw new IllegalArgumentException(); 499 | } 500 | this.lowerBound = lowerBounds[0]; 501 | this.upperBound = Object.class; 502 | } else { 503 | if (upperBounds[0] == null) { 504 | throw new NullPointerException(); 505 | } 506 | checkNotPrimitive(upperBounds[0]); 507 | this.lowerBound = null; 508 | this.upperBound = upperBounds[0]; 509 | } 510 | } 511 | 512 | @Override 513 | public Type[] getUpperBounds() { 514 | return new Type[]{upperBound}; 515 | } 516 | 517 | @Override 518 | public Type[] getLowerBounds() { 519 | return lowerBound != null ? new Type[]{lowerBound} : EMPTY_TYPE_ARRAY; 520 | } 521 | 522 | @Override 523 | public boolean equals(Object other) { 524 | return other instanceof WildcardType && Utils.equals(this, (WildcardType) other); 525 | } 526 | 527 | @Override 528 | public int hashCode() { 529 | // This equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds()). 530 | return (lowerBound != null ? 31 + lowerBound.hashCode() : 1) ^ (31 + upperBound.hashCode()); 531 | } 532 | 533 | @Override 534 | public String toString() { 535 | if (lowerBound != null) { 536 | return "? super " + typeToString(lowerBound); 537 | } 538 | if (upperBound == Object.class) { 539 | return "?"; 540 | } 541 | return "? extends " + typeToString(upperBound); 542 | } 543 | } 544 | 545 | // https://github.com/ReactiveX/RxJava/blob/6a44e5d0543a48f1c378dc833a155f3f71333bc2/ 546 | // src/main/java/io/reactivex/exceptions/Exceptions.java#L66 547 | static void throwIfFatal(Throwable t) { 548 | if (t instanceof VirtualMachineError) { 549 | throw (VirtualMachineError) t; 550 | } else if (t instanceof ThreadDeath) { 551 | throw (ThreadDeath) t; 552 | } else if (t instanceof LinkageError) { 553 | throw (LinkageError) t; 554 | } 555 | } 556 | } --------------------------------------------------------------------------------