├── .gitignore ├── .idea ├── checkstyle-idea.xml ├── compiler.xml ├── encodings.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── jarRepositories.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── Multiple ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── lzx │ └── multiple │ ├── MultipleUpload.kt │ ├── ext │ └── whatIf.kt │ ├── impl │ ├── MultipleUploadManager.kt │ ├── SingleUploadManager.kt │ └── UploadBuilder.kt │ ├── intercept │ └── UploadIntercept.kt │ └── upload │ └── UploadInterface.kt ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── lzx │ │ └── imageupload │ │ └── MainActivity.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ └── activity_main.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.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 │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── art ├── 1.png └── 2.png ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.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 | -------------------------------------------------------------------------------- /.idea/checkstyle-idea.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Multiple/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /Multiple/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'kotlin-android' 4 | } 5 | 6 | android { 7 | compileSdkVersion 28 8 | 9 | defaultConfig { 10 | minSdkVersion 16 11 | targetSdkVersion 28 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | consumerProguardFiles "consumer-rules.pro" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | compileOptions { 25 | sourceCompatibility JavaVersion.VERSION_1_8 26 | targetCompatibility JavaVersion.VERSION_1_8 27 | } 28 | } 29 | 30 | dependencies { 31 | implementation 'androidx.appcompat:appcompat:1.3.0' 32 | implementation "org.jetbrains.kotlin:kotlin-stdlib:1.4.10" 33 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3" 34 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1" 35 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" 36 | implementation 'androidx.lifecycle:lifecycle-process:2.3.1' 37 | } -------------------------------------------------------------------------------- /Multiple/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EspoirX/ImageUploadManager/b6601e0632e4a45ba6af48b2c78a86eca55a79d2/Multiple/consumer-rules.pro -------------------------------------------------------------------------------- /Multiple/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /Multiple/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /Multiple/src/main/java/com/lzx/multiple/MultipleUpload.kt: -------------------------------------------------------------------------------- 1 | package com.lzx.multiple 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.os.Handler 6 | import androidx.annotation.MainThread 7 | import androidx.fragment.app.Fragment 8 | import androidx.fragment.app.FragmentActivity 9 | import androidx.lifecycle.LifecycleOwner 10 | import androidx.lifecycle.MutableLiveData 11 | import androidx.lifecycle.ProcessLifecycleOwner 12 | import com.lzx.multiple.impl.MultipleUploadManager 13 | import com.lzx.multiple.impl.SingleUploadManager 14 | import com.lzx.multiple.impl.UploadBuilder 15 | import java.io.File 16 | 17 | 18 | /** 19 | * 通用多线程并行上传框架 20 | */ 21 | class MultipleUpload private constructor(private val owner: LifecycleOwner) { 22 | 23 | companion object { 24 | 25 | const val TAG = "MultipleUpload" 26 | 27 | @JvmStatic 28 | fun with(context: Context): MultipleUpload { 29 | if (context !is FragmentActivity) { 30 | throw IllegalStateException("context is not to FragmentActivity") 31 | } 32 | return with(context) 33 | } 34 | 35 | @JvmStatic 36 | fun with(activity: Activity): MultipleUpload { 37 | if (activity !is FragmentActivity) { 38 | throw IllegalStateException("activity is not to FragmentActivity") 39 | } 40 | return with(activity) 41 | } 42 | 43 | @JvmStatic 44 | fun with(context: FragmentActivity) = with(context as LifecycleOwner) 45 | 46 | @JvmStatic 47 | fun with(fragment: Fragment) = with(fragment.viewLifecycleOwner) 48 | 49 | @JvmStatic 50 | fun with(owner: LifecycleOwner = ProcessLifecycleOwner.get()) = MultipleUpload(owner) 51 | } 52 | 53 | /** 54 | * 加载单个上传文件 55 | */ 56 | fun load(path: String?) = load(path, hashMapOf()) 57 | 58 | /** 59 | * 加载多个上传文件 60 | */ 61 | fun load(list: MutableList) = load(list, hashMapOf()) 62 | 63 | /** 64 | * 加载单个上传文件 65 | * params:具体上传可能需求一些其他参数,可以放到params里面,然后在实现具体上传接口时获取 66 | */ 67 | fun load(path: String?, params: HashMap): UploadBuilder { 68 | val list = mutableListOf() 69 | list.add(path) 70 | return load(list, params) 71 | } 72 | 73 | /** 74 | * 加载多个上传文件 75 | * params:具体上传可能需求一些其他参数,可以放到params里面,然后在实现具体上传接口时获取 76 | */ 77 | fun load(list: MutableList, params: HashMap): UploadBuilder { 78 | val realList = mutableListOf() 79 | list.filter { !it.isNullOrEmpty() && File(it).exists() }.forEach { realList.add(it!!) } 80 | return whatIfMap(realList.size == 1, whatIf = { 81 | SingleUploadManager(owner, realList[0], params) 82 | }, whatIfNot = { 83 | MultipleUploadManager(owner, realList, params) 84 | }) 85 | } 86 | } 87 | 88 | 89 | typealias UploadStateLiveData = MutableLiveData 90 | 91 | typealias MultipleUploadStateLiveData = MutableLiveData 92 | 93 | 94 | /***单个上传状态 */ 95 | class UploadState { 96 | companion object { 97 | const val IDEA = "IDEA" 98 | const val Start = "Start" 99 | const val Progress = "Progress" 100 | const val Success = "Success" 101 | const val Fail = "Fail" 102 | } 103 | 104 | var currState = IDEA //状态 105 | var progress: Int = 0 //进度 106 | var totalProgress: Int = 1 //进度 107 | var url: String? = null //文件链接 108 | var errorCode: Int = -1 //失败信息 109 | var errMsg: String? = null //失败信息 110 | var index: Int = 0 //当前上传第几个 111 | var otherParams: HashMap? = hashMapOf() //上传成功后可能有一些其他的业务信息,通过这个获取 112 | } 113 | 114 | /***多个上传状态 */ 115 | class MultipleUploadState { 116 | companion object { 117 | const val IDEA = "IDEA" 118 | const val Start = "Start" 119 | const val Fail = "Fail" 120 | const val Completion = "Completion" 121 | } 122 | 123 | var currState = IDEA //状态 124 | var catchIndex: Int = 0 //第几个上传失败了 125 | var errMsg: String? = null //失败信息 126 | var successNum = 0 //成功数量 127 | var failNum = 0 //失败数量 128 | var urls = mutableListOf() //成功上传的url集合 129 | } 130 | 131 | class SingleUploadObserver { 132 | internal var start: ((index: Int) -> Unit)? = null 133 | internal var progress: ((index: Int, progress: Int, totalProgress: Int) -> Unit)? = null 134 | internal var success: ((index: Int, url: String?, otherParams: HashMap?) -> Unit)? = null 135 | internal var fail: ((index: Int, errCode: Int, errMsg: String?) -> Unit)? = null 136 | 137 | fun onStart(start: (index: Int) -> Unit) { 138 | this.start = start 139 | } 140 | 141 | fun onProgress(progress: (index: Int, progress: Int, totalProgress: Int) -> Unit) { 142 | this.progress = progress 143 | } 144 | 145 | fun onSuccess(success: (index: Int, url: String?, otherParams: HashMap?) -> Unit) { 146 | this.success = success 147 | } 148 | 149 | fun onFailure(fail: ((index: Int, errCode: Int, errStr: String?) -> Unit)) { 150 | this.fail = fail 151 | } 152 | } 153 | 154 | class MultipleUploadObserver { 155 | internal var start: (() -> Unit)? = null 156 | internal var completion: ((successNum: Int, failNum: Int, urls: MutableList) -> Unit)? = null 157 | internal var fail: ((catchIndex: Int, errMsg: String?) -> Unit)? = null 158 | 159 | fun onStart(start: () -> Unit) { 160 | this.start = start 161 | } 162 | 163 | fun onCompletion(completion: (successNum: Int, failNum: Int, urls: MutableList) -> Unit) { 164 | this.completion = completion 165 | } 166 | 167 | fun onFailure(fail: ((catchIndex: Int, errStr: String?) -> Unit)) { 168 | this.fail = fail 169 | } 170 | } 171 | 172 | @MainThread 173 | fun UploadStateLiveData.singleUploadObserver(owner: LifecycleOwner?, observer: SingleUploadObserver.() -> Unit) { 174 | owner?.let { it -> 175 | val result = SingleUploadObserver();result.observer() 176 | observe(it, { 177 | when (it.currState) { 178 | UploadState.Start -> result.start?.invoke(it.index) 179 | UploadState.Progress -> result.progress?.invoke(it.index, it.progress, it.totalProgress) 180 | UploadState.Success -> result.success?.invoke(it.index, it.url, it.otherParams) 181 | UploadState.Fail -> result.fail?.invoke(it.index, it.errorCode, it.errMsg) 182 | } 183 | }) 184 | } 185 | } 186 | 187 | @MainThread 188 | fun MultipleUploadStateLiveData.multipleUploadObserver(owner: LifecycleOwner?, observer: MultipleUploadObserver.() -> Unit) { 189 | owner?.let { it -> 190 | val result = MultipleUploadObserver();result.observer() 191 | observe(it, { 192 | when (it.currState) { 193 | MultipleUploadState.Start -> result.start?.invoke() 194 | MultipleUploadState.Completion -> result.completion?.invoke(it.successNum, it.failNum, it.urls) 195 | MultipleUploadState.Fail -> result.fail?.invoke(it.catchIndex, it.errMsg) 196 | } 197 | }) 198 | } 199 | } 200 | 201 | /** 202 | * 解决数据丢失问题 203 | */ 204 | fun UploadStateLiveData.postValueFix(handler: Handler, state: UploadState) { 205 | handler.post { 206 | setValue(state) 207 | } 208 | } 209 | 210 | /***状态接口,兼容java */ 211 | interface OnSingleUploadState { 212 | fun uploadState(state: UploadState) 213 | } 214 | 215 | /***状态接口,兼容java */ 216 | interface OnMultipleUploadState { 217 | fun uploadState(state: MultipleUploadState) 218 | } -------------------------------------------------------------------------------- /Multiple/src/main/java/com/lzx/multiple/ext/whatIf.kt: -------------------------------------------------------------------------------- 1 | package com.lzx.multiple 2 | 3 | inline fun T.whatIf( 4 | given: Boolean?, 5 | whatIf: T.() -> Unit 6 | ): T { 7 | if (given == true) { 8 | this.apply { whatIf() } 9 | } 10 | return this 11 | } 12 | 13 | inline fun Boolean?.whatIf( 14 | whatIf: () -> Unit, 15 | whatIfNot: () -> Unit 16 | ): Boolean? { 17 | if (this == true) { 18 | whatIf() 19 | } else { 20 | whatIfNot() 21 | } 22 | return this 23 | } 24 | 25 | inline fun Boolean?.whatIf( 26 | whatIf: () -> Unit 27 | ): Boolean? { 28 | return this.whatIf( 29 | whatIf = whatIf, 30 | whatIfNot = { } 31 | ) 32 | } 33 | 34 | inline fun T.whatIf( 35 | given: (T) -> Boolean, 36 | whatIf: () -> Unit 37 | ): T { 38 | if (given(this)) { 39 | whatIf() 40 | } 41 | return this 42 | } 43 | 44 | inline fun T.whatIf( 45 | given: (T) -> Boolean, 46 | whatIf: () -> Unit, 47 | whatIfNot: () -> Unit 48 | ): T { 49 | if (given(this)) { 50 | whatIf() 51 | } else { 52 | whatIfNot() 53 | } 54 | return this 55 | } 56 | 57 | inline fun T.whatIfMap( 58 | given: Boolean?, 59 | default: R, 60 | whatIf: (T) -> R 61 | ): R { 62 | return this.whatIfMap( 63 | given = given, 64 | whatIf = whatIf, 65 | whatIfNot = { default } 66 | ) 67 | } 68 | 69 | inline fun T.whatIfMap( 70 | given: Boolean?, 71 | whatIf: (T) -> R, 72 | whatIfNot: (T) -> R 73 | ): R { 74 | if (given == true) { 75 | return whatIf(this) 76 | } 77 | return whatIfNot(this) 78 | } 79 | 80 | inline fun tryCatch( 81 | tryFun: () -> Unit, 82 | catchFun: (ex: Exception) -> Unit, 83 | finallyFun: () -> Unit 84 | ) { 85 | try { 86 | tryFun() 87 | } catch (ex: Exception) { 88 | catchFun(ex) 89 | } finally { 90 | finallyFun() 91 | } 92 | } 93 | 94 | inline fun tryCatch( 95 | tryFun: () -> Unit, 96 | catchFun: (ex: Exception) -> Unit 97 | ) { 98 | tryCatch(tryFun, catchFun, finallyFun = {}) 99 | } 100 | 101 | inline fun tryCatch( 102 | tryFun: () -> Unit 103 | ) { 104 | tryCatch(tryFun, catchFun = {}, finallyFun = {}) 105 | } -------------------------------------------------------------------------------- /Multiple/src/main/java/com/lzx/multiple/impl/MultipleUploadManager.kt: -------------------------------------------------------------------------------- 1 | package com.lzx.multiple.impl 2 | 3 | import android.os.Handler 4 | import android.os.Looper 5 | import androidx.lifecycle.LifecycleOwner 6 | import com.lzx.multiple.MultipleUploadState 7 | import com.lzx.multiple.MultipleUploadStateLiveData 8 | import com.lzx.multiple.UploadState 9 | import com.lzx.multiple.UploadStateLiveData 10 | import com.lzx.multiple.intercept.InterceptCallback 11 | import com.lzx.multiple.intercept.UploadIntercept 12 | import com.lzx.multiple.postValueFix 13 | import com.lzx.multiple.upload.UploadCallback 14 | import com.lzx.multiple.whatIfMap 15 | import kotlinx.coroutines.CoroutineScope 16 | import kotlinx.coroutines.Dispatchers 17 | import kotlinx.coroutines.async 18 | import kotlinx.coroutines.flow.asFlow 19 | import kotlinx.coroutines.flow.catch 20 | import kotlinx.coroutines.flow.collect 21 | import kotlinx.coroutines.flow.flowOn 22 | import kotlinx.coroutines.flow.map 23 | import kotlinx.coroutines.flow.onCompletion 24 | import kotlinx.coroutines.flow.onStart 25 | import kotlinx.coroutines.launch 26 | import kotlinx.coroutines.withContext 27 | import kotlin.coroutines.resume 28 | import kotlin.coroutines.suspendCoroutine 29 | 30 | /** 31 | * 多个上传逻辑 32 | */ 33 | class MultipleUploadManager(owner: LifecycleOwner?, 34 | private val list: MutableList, 35 | private val params: HashMap 36 | ) : UploadBuilder(owner) { 37 | 38 | private val handler = Handler(Looper.getMainLooper()) 39 | 40 | override fun asyncRun(scope: CoroutineScope?, 41 | singleLiveData: UploadStateLiveData, 42 | multipleLiveData: MultipleUploadStateLiveData) { 43 | if (uploadNut == null) return 44 | val uploadList = whatIfMap(filter != null, whatIf = { 45 | list.filter { filter?.apply(it) == true } 46 | }, whatIfNot = { list }) 47 | if (uploadList.isNullOrEmpty()) return 48 | 49 | scope?.launch { 50 | if (interceptors.isEmpty()) { 51 | multipleUpload(this, uploadList, singleLiveData, multipleLiveData) 52 | } else { 53 | doInterceptor(0, this, uploadList, singleLiveData, multipleLiveData) 54 | } 55 | } 56 | } 57 | 58 | /** 59 | * 拦截器逻辑 60 | */ 61 | private suspend fun doInterceptor(index: Int, 62 | scope: CoroutineScope, 63 | uploadList: List, 64 | singleLiveData: UploadStateLiveData, 65 | multipleLiveData: MultipleUploadStateLiveData) { 66 | if (index < interceptors.size) { 67 | val triple = interceptors[index] 68 | val interceptor = triple.first 69 | val interceptThread = triple.second 70 | if (interceptThread == UploadIntercept.UI) { 71 | withContext(Dispatchers.Main) { 72 | doInterceptImpl(interceptor, index, scope, uploadList, singleLiveData, multipleLiveData) 73 | } 74 | } else { 75 | withContext(supportDispatcher) { 76 | doInterceptImpl(interceptor, index, scope, uploadList, singleLiveData, multipleLiveData) 77 | } 78 | } 79 | } else { 80 | withContext(Dispatchers.Main) { 81 | multipleUpload(scope, uploadList, singleLiveData, multipleLiveData) 82 | } 83 | } 84 | } 85 | 86 | private fun doInterceptImpl(interceptor: UploadIntercept, 87 | index: Int, 88 | scope: CoroutineScope?, 89 | uploadList: List, 90 | singleLiveData: UploadStateLiveData, 91 | multipleLiveData: MultipleUploadStateLiveData) { 92 | interceptor.processMultiple(list, object : InterceptCallback { 93 | override fun onNext(path: String) { 94 | } 95 | 96 | override fun onNext(paths: MutableList) { 97 | scope?.launch { 98 | doInterceptor(index + 1, scope, uploadList, singleLiveData, multipleLiveData) //执行下一个 99 | } 100 | } 101 | 102 | override fun onInterrupt(code: Int, msg: String?) { 103 | msg?.let { 104 | val multipleInfo = MultipleUploadState() 105 | multipleInfo.currState = MultipleUploadState.Fail 106 | multipleInfo.errMsg = it 107 | multipleLiveData.postValue(multipleInfo) 108 | } 109 | } 110 | }) 111 | } 112 | 113 | /** 114 | * 多线程逻辑 115 | */ 116 | private suspend fun multipleUpload(scope: CoroutineScope, 117 | uploadList: List, 118 | singleLiveData: UploadStateLiveData, 119 | multipleLiveData: MultipleUploadStateLiveData) { 120 | val multipleInfo = MultipleUploadState() 121 | val resultUrls = mutableListOf() 122 | var successNum = 0 123 | var failNum = uploadList.size 124 | var currIndex = 0 125 | uploadList.mapIndexed { index, path -> 126 | scope.async(supportDispatcher) { 127 | uploadImpl(index, path, params, singleLiveData) 128 | } 129 | }.asFlow().map { 130 | it.await() 131 | }.flowOn(supportDispatcher) 132 | .onStart { 133 | multipleInfo.currState = MultipleUploadState.Start 134 | multipleLiveData.value = multipleInfo 135 | }.onCompletion { 136 | multipleInfo.currState = MultipleUploadState.Completion 137 | multipleInfo.successNum = successNum 138 | multipleInfo.failNum = whatIfMap(failNum == uploadList.size, whatIf = { 0 }, whatIfNot = { failNum }) 139 | multipleInfo.urls = resultUrls 140 | multipleLiveData.value = multipleInfo 141 | resultUrls.clear() 142 | successNum = 0 143 | failNum = uploadList.size 144 | currIndex = 0 145 | }.catch { 146 | multipleInfo.currState = MultipleUploadState.Fail 147 | multipleInfo.errMsg = it.message 148 | multipleInfo.catchIndex = currIndex 149 | multipleLiveData.value = multipleInfo 150 | }.collect { 151 | resultUrls.add(it.url) 152 | currIndex = it.index 153 | if (it.currState == UploadState.Success) { 154 | successNum++ 155 | } else if (it.currState == UploadState.Fail) { 156 | failNum-- 157 | } 158 | } 159 | } 160 | 161 | /** 162 | * 具体上传逻辑 163 | */ 164 | private suspend fun uploadImpl(index: Int, path: String, 165 | params: HashMap, 166 | singleLiveData: UploadStateLiveData): UploadState = 167 | suspendCoroutine { coroutine -> 168 | val uploadState = UploadState() 169 | uploadNut?.uploadFile(path, params, object : UploadCallback { 170 | override fun onUploadStart() { 171 | uploadState.currState = UploadState.Start 172 | uploadState.index = index 173 | singleLiveData.postValueFix(handler, uploadState) 174 | } 175 | 176 | override fun onUploadProgress(progress: Int, totalProgress: Int) { 177 | uploadState.currState = UploadState.Progress 178 | uploadState.progress = progress 179 | uploadState.totalProgress = totalProgress 180 | uploadState.index = index 181 | singleLiveData.postValueFix(handler, uploadState) 182 | } 183 | 184 | override fun onUploadSuccess(url: String, otherParams: HashMap?) { 185 | uploadState.currState = UploadState.Success 186 | uploadState.url = url 187 | uploadState.otherParams = otherParams 188 | uploadState.index = index 189 | singleLiveData.postValueFix(handler, uploadState) 190 | coroutine.resume(uploadState) 191 | } 192 | 193 | override fun onUploadFail(errCode: Int, errMsg: String?) { 194 | uploadState.currState = UploadState.Fail 195 | uploadState.errorCode = errCode 196 | uploadState.errMsg = errMsg 197 | uploadState.index = index 198 | singleLiveData.postValueFix(handler, uploadState) 199 | coroutine.resume(uploadState) 200 | } 201 | }) 202 | } 203 | } -------------------------------------------------------------------------------- /Multiple/src/main/java/com/lzx/multiple/impl/SingleUploadManager.kt: -------------------------------------------------------------------------------- 1 | package com.lzx.multiple.impl 2 | 3 | import androidx.lifecycle.LifecycleOwner 4 | import com.lzx.multiple.MultipleUploadStateLiveData 5 | import com.lzx.multiple.UploadState 6 | import com.lzx.multiple.UploadStateLiveData 7 | import com.lzx.multiple.intercept.InterceptCallback 8 | import com.lzx.multiple.intercept.UploadIntercept 9 | import com.lzx.multiple.upload.UploadCallback 10 | import kotlinx.coroutines.CoroutineScope 11 | import kotlinx.coroutines.Dispatchers 12 | import kotlinx.coroutines.launch 13 | import kotlinx.coroutines.withContext 14 | 15 | /** 16 | * 单个上传逻辑 17 | */ 18 | class SingleUploadManager(owner: LifecycleOwner?, 19 | private val path: String, 20 | private val params: HashMap 21 | ) : UploadBuilder(owner) { 22 | 23 | 24 | override fun asyncRun(scope: CoroutineScope?, 25 | singleLiveData: UploadStateLiveData, 26 | multipleLiveData: MultipleUploadStateLiveData) { 27 | if (filter?.apply(path) == false) return 28 | if (uploadNut == null) return 29 | 30 | if (interceptors.isEmpty()) { 31 | uploadImpl(singleLiveData) 32 | } else { 33 | scope?.launch { 34 | doInterceptor(0, scope, singleLiveData) 35 | } 36 | } 37 | } 38 | 39 | private suspend fun doInterceptor(index: Int, scope: CoroutineScope?, singleLiveData: UploadStateLiveData) { 40 | if (index < interceptors.size) { 41 | val pair = interceptors[index] 42 | val interceptor = pair.first 43 | val interceptThread = pair.second 44 | if (interceptThread == UploadIntercept.UI) { 45 | withContext(Dispatchers.Main) { 46 | doInterceptImpl(interceptor, index, scope, singleLiveData) 47 | } 48 | } else { 49 | withContext(supportDispatcher) { 50 | doInterceptImpl(interceptor, index, scope, singleLiveData) 51 | } 52 | } 53 | } else { 54 | withContext(Dispatchers.Main) { 55 | uploadImpl(singleLiveData) 56 | } 57 | } 58 | } 59 | 60 | private fun doInterceptImpl(interceptor: UploadIntercept, 61 | index: Int, 62 | scope: CoroutineScope?, 63 | singleLiveData: UploadStateLiveData) { 64 | interceptor.processSingle(path, object : InterceptCallback { 65 | override fun onNext(path: String) { 66 | scope?.launch { 67 | doInterceptor(index + 1, scope, singleLiveData) //执行下一个 68 | } 69 | } 70 | 71 | override fun onNext(paths: MutableList) { 72 | } 73 | 74 | override fun onInterrupt(code: Int, msg: String?) { 75 | msg?.let { 76 | val uploadState = UploadState() 77 | uploadState.currState = UploadState.Fail 78 | uploadState.errorCode = code 79 | uploadState.errMsg = msg 80 | singleLiveData.postValue(uploadState) 81 | } 82 | } 83 | }) 84 | } 85 | 86 | private fun uploadImpl(singleLiveData: UploadStateLiveData) { 87 | val uploadState = UploadState() 88 | uploadNut?.uploadFile(path, params, object : UploadCallback { 89 | override fun onUploadStart() { 90 | uploadState.currState = UploadState.Start 91 | singleLiveData.value = uploadState 92 | } 93 | 94 | override fun onUploadProgress(progress: Int, totalProgress: Int) { 95 | uploadState.currState = UploadState.Progress 96 | uploadState.progress = progress 97 | uploadState.totalProgress = totalProgress 98 | singleLiveData.value = uploadState 99 | } 100 | 101 | override fun onUploadSuccess(url: String, otherParams: HashMap?) { 102 | uploadState.currState = UploadState.Success 103 | uploadState.url = url 104 | uploadState.otherParams = otherParams 105 | singleLiveData.value = uploadState 106 | } 107 | 108 | override fun onUploadFail(errCode: Int, errMsg: String?) { 109 | uploadState.currState = UploadState.Fail 110 | uploadState.errorCode = errCode 111 | uploadState.errMsg = errMsg 112 | singleLiveData.value = uploadState 113 | } 114 | }) 115 | } 116 | } -------------------------------------------------------------------------------- /Multiple/src/main/java/com/lzx/multiple/impl/UploadBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.lzx.multiple.impl 2 | 3 | import android.os.Build 4 | import android.os.Process 5 | import androidx.lifecycle.LifecycleOwner 6 | import androidx.lifecycle.lifecycleScope 7 | import com.lzx.multiple.MultipleUploadObserver 8 | import com.lzx.multiple.MultipleUploadStateLiveData 9 | import com.lzx.multiple.OnMultipleUploadState 10 | import com.lzx.multiple.OnSingleUploadState 11 | import com.lzx.multiple.SingleUploadObserver 12 | import com.lzx.multiple.UploadStateLiveData 13 | import com.lzx.multiple.intercept.UploadIntercept 14 | import com.lzx.multiple.multipleUploadObserver 15 | import com.lzx.multiple.singleUploadObserver 16 | import com.lzx.multiple.upload.UploadInterface 17 | import kotlinx.coroutines.CoroutineScope 18 | import kotlinx.coroutines.ExecutorCoroutineDispatcher 19 | import kotlinx.coroutines.asCoroutineDispatcher 20 | import java.util.concurrent.LinkedBlockingQueue 21 | import java.util.concurrent.ThreadFactory 22 | import java.util.concurrent.ThreadPoolExecutor 23 | import java.util.concurrent.TimeUnit 24 | import java.util.concurrent.atomic.AtomicInteger 25 | 26 | abstract class UploadBuilder(private val owner: LifecycleOwner?) { 27 | 28 | internal val interceptors = mutableListOf>() 29 | internal var uploadNut: UploadInterface? = null 30 | internal var filter: UploadFilter? = null 31 | internal var filterDsl: ((String) -> Boolean) = { true } 32 | internal var supportDispatcher: ExecutorCoroutineDispatcher 33 | private var singleLiveData = UploadStateLiveData() 34 | private var multipleLiveData = MultipleUploadStateLiveData() 35 | 36 | internal abstract fun asyncRun(scope: CoroutineScope?, 37 | singleLiveData: UploadStateLiveData, 38 | multipleLiveData: MultipleUploadStateLiveData) 39 | 40 | init { 41 | val corePoolSize = when { 42 | Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> { 43 | (Runtime.getRuntime().availableProcessors() - 1).coerceAtLeast(1) 44 | } 45 | Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> 2 46 | else -> 1 47 | } 48 | val threadPoolExecutor = ThreadPoolExecutor(corePoolSize, corePoolSize, 49 | 5L, TimeUnit.SECONDS, LinkedBlockingQueue(), UploadThreadFactory()) 50 | threadPoolExecutor.allowCoreThreadTimeOut(true) 51 | supportDispatcher = threadPoolExecutor.asCoroutineDispatcher() 52 | } 53 | 54 | /** 55 | * 配置线程池 56 | */ 57 | fun setThreadPoolExecutor(executor: ThreadPoolExecutor) = apply { 58 | supportDispatcher = executor.asCoroutineDispatcher() 59 | } 60 | 61 | /** 62 | * 配置具体上传实现 63 | */ 64 | fun setUploadImpl(upload: UploadInterface) = apply { 65 | uploadNut = upload 66 | } 67 | 68 | /** 69 | * 配置url过滤器 70 | */ 71 | fun filter(filter: UploadFilter) = apply { 72 | this.filter = filter 73 | } 74 | 75 | /** 76 | * 配置url过滤器,dsl 77 | */ 78 | fun filter(filter: (String) -> Boolean) = apply { 79 | filterDsl = filter 80 | } 81 | 82 | /** 83 | * 添加拦截器 84 | */ 85 | fun addInterceptor( 86 | interceptor: UploadIntercept, 87 | interceptThread: String = UploadIntercept.UI 88 | ) = apply { interceptors.add(Pair(interceptor, interceptThread)) } 89 | 90 | /** 91 | * 单个上传监听,dsl形式 92 | */ 93 | fun singleUploadObserver(observer: SingleUploadObserver.() -> Unit) = apply { 94 | singleLiveData.singleUploadObserver(owner, observer) 95 | } 96 | 97 | /** 98 | * 单个上传监听,接口形式 99 | */ 100 | fun singleUploadObserver(state: OnSingleUploadState) = apply { 101 | owner?.let { it -> 102 | singleLiveData.observe(it, { state.uploadState(it) }) 103 | } 104 | } 105 | 106 | /** 107 | * 多个上传监听,dsl形式 108 | */ 109 | fun multipleUploadObserver(observer: MultipleUploadObserver.() -> Unit) = apply { 110 | multipleLiveData.multipleUploadObserver(owner, observer) 111 | } 112 | 113 | /** 114 | * 多个上传监听,接口形式 115 | */ 116 | fun multipleUploadObserver(state: OnMultipleUploadState) = apply { 117 | owner?.let { it -> 118 | multipleLiveData.observe(it, { state.uploadState(it) }) 119 | } 120 | } 121 | 122 | /** 123 | * 发起上传 124 | */ 125 | fun upload() { 126 | asyncRun(owner?.lifecycleScope, singleLiveData, multipleLiveData) 127 | } 128 | } 129 | 130 | /** 131 | * 上传前过滤 132 | */ 133 | interface UploadFilter { 134 | fun apply(path: String): Boolean 135 | } 136 | 137 | /** 138 | * 线程 139 | */ 140 | class UploadThreadFactory : ThreadFactory { 141 | private val group: ThreadGroup 142 | private val threadNumber = AtomicInteger(1) 143 | private val namePrefix: String 144 | 145 | companion object { 146 | private val poolNumber = AtomicInteger(1) 147 | private const val DEFAULT_PRIORITY = 148 | Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE 149 | } 150 | 151 | init { 152 | val s = System.getSecurityManager() 153 | group = s?.threadGroup ?: Thread.currentThread().threadGroup 154 | namePrefix = "Upload-${poolNumber.getAndIncrement()}-thread-" 155 | } 156 | 157 | override fun newThread(r: Runnable): Thread { 158 | val thread = object : Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0) { 159 | override fun run() { 160 | Process.setThreadPriority(DEFAULT_PRIORITY) 161 | super.run() 162 | } 163 | } 164 | if (thread.isDaemon) thread.isDaemon = false 165 | if (thread.priority != Thread.NORM_PRIORITY) thread.priority = Thread.NORM_PRIORITY 166 | return thread 167 | } 168 | } -------------------------------------------------------------------------------- /Multiple/src/main/java/com/lzx/multiple/intercept/UploadIntercept.kt: -------------------------------------------------------------------------------- 1 | package com.lzx.multiple.intercept 2 | 3 | abstract class UploadIntercept { 4 | companion object { 5 | const val UI = "UI" 6 | const val IO = "IO" 7 | } 8 | 9 | open fun processSingle(path: String, callback: InterceptCallback) {} 10 | 11 | open fun processMultiple(paths: MutableList, callback: InterceptCallback) {} 12 | } 13 | 14 | interface InterceptCallback { 15 | /** 16 | * 执行下一个,用于上传一个文件 17 | */ 18 | fun onNext(path: String) 19 | 20 | /** 21 | * 执行下一个,用于上传多个文件 22 | */ 23 | fun onNext(paths: MutableList) 24 | 25 | /** 26 | * 中断 27 | * code,msg:可以添加code 和 msg,如果 msg 不为空,则会回调失败回调 28 | */ 29 | fun onInterrupt(code: Int = -1, msg: String?) 30 | } 31 | 32 | -------------------------------------------------------------------------------- /Multiple/src/main/java/com/lzx/multiple/upload/UploadInterface.kt: -------------------------------------------------------------------------------- 1 | package com.lzx.multiple.upload 2 | 3 | interface UploadInterface { 4 | fun uploadFile(path: String, params: HashMap, callback: UploadCallback) 5 | } 6 | 7 | interface UploadCallback { 8 | fun onUploadStart() 9 | fun onUploadProgress(progress: Int, totalProgress: Int) 10 | 11 | //otherParams:上传成功后可能有一些其他的业务信息,通过这个传递出去 12 | fun onUploadSuccess(url: String, otherParams: HashMap? = null) 13 | fun onUploadFail(errCode: Int, errMsg: String?) 14 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImageUploadManager 2 | 3 | ## 基于 Flow 的多线程并行通用图片上传框架,结合 LiveData 监听回调 4 | 5 | 6 | ### 特点: 7 | 1. 对外开放上传接口,具体上传逻辑自己定义 8 | 2. 基于 Kotlin 的 Flow 实现多线程并行上传,并且上传结果按顺序返回哦 9 | 3. 结合 LiveData 监听上传回调,管理生命周期 10 | 4. 提供拦截器功能,拦截器可在主线程和子线程中使用,提供过滤器功能 11 | 12 | ### 使用 13 | ```gradle 14 | allprojects { 15 | repositories { 16 | ... 17 | maven { url 'https://jitpack.io' } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation 'com.github.EspoirX:ImageUploadManager:v1.0' 23 | } 24 | ``` 25 | [![](https://jitpack.io/v/EspoirX/ImageUploadManager.svg)](https://jitpack.io/#EspoirX/ImageUploadManager) 26 | 27 | ### API 预览: 28 | ```kotlin 29 | MultipleUpload 30 | .with( .. ) //传入当前的 Lifecycle 31 | .load( .. ) //传入上传的路径,可传一个,可传多个 32 | .setUploadImpl( .. ) //配置自己具体的上传逻辑 33 | .setThreadPoolExecutor( .. ) //配置自定义的线程池,否则用默认的 34 | .filter { .. } // 路径过滤器,dsl 形式 35 | .filter(object : UploadFilter { .. }) // 路径过滤器,接口形式 36 | .addInterceptor(object : UploadIntercept() { .. }, UploadIntercept.UI) //添加拦截器,第二个参数是拦截器运行的线程。不传默认是UI 37 | .addInterceptor(object : UploadIntercept() { .. }, UploadIntercept.IO) //添加拦截器,运行在子线程 38 | .singleUploadObserver { //单个上传的监听回调,dsl 形式 39 | onStart { index-> .. } //开始上传,index 是当前上传的第几个文件 40 | onProgress { index, progress, totalProgress -> .. } //上传进度 41 | onSuccess { index, url, otherParams -> .. } //上传成功 42 | onFailure { index, errCode, errStr -> .. } //上传失败 43 | } 44 | .singleUploadObserver(object : OnSingleUploadState{ .. }) //单个上传的监听回调,接口形式 45 | .multipleUploadObserver { //多个文件上传时总体监听回调,dsl 形式 46 | onStart { .. } //上传开始 47 | onCompletion { successNum, failNum, urls -> .. } //全部上传完成 48 | onFailure { catchIndex, errStr -> .. } //上传中发生 catch。 49 | } 50 | .multipleUploadObserver(object : OnMultipleUploadState{ .. }) //多个文件上传时总体监听回调,接口形式 51 | .upload() //发起上传 52 | ``` 53 | 54 | ### API详解 55 | 56 | #### 1. with 57 | ```kotlin 58 | fun with(context: Context) 59 | fun with(activity: Activity) 60 | fun with(context: FragmentActivity) 61 | fun with(fragment: Fragment) 62 | fun with(owner: LifecycleOwner = ProcessLifecycleOwner.get()) 63 | ``` 64 | with 方法有多个重载,可以传入 context,activity 等,但 context 和 activity 都必须是 FragmentActivity 的子类,因为最终需要的是 LifecycleOwner。 65 | 如果不传,则默认是 ProcessLifecycleOwner.get()。 66 | 67 | #### 2. load 68 | ```kotlin 69 | fun load(path: String?) 70 | fun load(list: MutableList) 71 | fun load(path: String?, params: HashMap) 72 | fun load(list: MutableList, params: HashMap) 73 | ``` 74 | load 方法有 4 个重载,可分成两类,一类是传入一个路径,即上传一个文件,一类是传入一个 List,即同时上传多个文件。 75 | 76 | params 参数的意思是,在我们实现上传逻辑的时候,有时候不只需要一个 path,可能还需要其他一些业务参数,这时候就可以用到 params 来传递了。 77 | 78 | #### 3. setUploadImpl 79 | ```kotlin 80 | fun setUploadImpl(upload: UploadInterface) 81 | 82 | interface UploadInterface { 83 | fun uploadFile(path: String, params: HashMap, callback: UploadCallback) 84 | } 85 | 86 | interface UploadCallback { 87 | fun onUploadStart() 88 | fun onUploadProgress(progress: Int, totalProgress: Int) 89 | fun onUploadSuccess(url: String, otherParams: HashMap? = null) 90 | fun onUploadFail(errCode: Int, errMsg: String?) 91 | } 92 | ``` 93 | 传入具体的上传逻辑,参数是 UploadInterface 接口,通过实现这个接口来实现自己具体的上传逻辑,然后通过 callback 回调给框架处理。 94 | 可以看到 uploadFile 方法的第二个参数就是上面 load 方法中说到的那个自定义参数了。 95 | 96 | 同时可以看到 onUploadSuccess 回调中第二个参数 otherParams,也是因为上传完成后可能需要传递一些其他业务内容出去,这时候也可以用到它去做。 97 | 98 | #### 4. setThreadPoolExecutor 99 | 配置自定义的线程池,如果你想上传时的线程池由自己去做,可以通过它来传进去,参数是 ThreadPoolExecutor,不传的话会使用默认的。 100 | 101 | #### 5. filter 102 | ```kotlin 103 | fun filter(filter: UploadFilter) 104 | fun filter(filter: (String) -> Boolean) 105 | 106 | interface UploadFilter { 107 | fun apply(path: String): Boolean 108 | } 109 | ``` 110 | filter 方法的作用是做一些路径过滤,需要实现 UploadFilter 接口,会过滤掉返回 false 的路径,它的调用时机是在上传前。 111 | 112 | 有两个重载,分别是接口形式和 dsl形式,可以理解为一个用于 kotlin,一个用于兼容 java。 113 | 114 | 随便一提,像判空,判断本地是否存在改文件这种过滤内部已经有了,就不需要自己去做了。 115 | 116 | 117 | #### 6. addInterceptor 118 | ```kotlin 119 | fun addInterceptor(interceptor: UploadIntercept,interceptThread: String = UploadIntercept.UI) 120 | 121 | abstract class UploadIntercept { 122 | companion object { 123 | const val UI = "UI" 124 | const val IO = "IO" 125 | } 126 | 127 | open fun processSingle(path: String, callback: InterceptCallback) {} //用于单个文件 128 | open fun processMultiple(paths: MutableList, callback: InterceptCallback) {} //用于多个文件 129 | } 130 | 131 | interface InterceptCallback { 132 | fun onNext(path: String) //执行下一个,用于上传一个文件 133 | fun onNext(paths: MutableList) //执行下一个,用于上传多个文件 134 | fun onInterrupt(code: Int = -1, msg: String?) //中断 code,msg:可以添加code 和 msg,如果 msg 不为空,则会回调失败回调 135 | } 136 | ``` 137 | 拦截器,作用大家应该很熟悉了,运行时机是在 filter 之后,上传之前。 138 | 139 | 实现拦截器需求实现 UploadIntercept,因为上传单个文件就一个路径,多个文件有多个路径,是一个 List,所以为了区分,就有了 processSingle 和 processMultiple 两个方法,可以根据需要实现。 140 | 141 | InterceptCallback 回调接口, onNext 方法代表执行下一个拦截逻辑,同样分两个,作用也跟刚刚说的一样。 142 | 143 | 如果要中断逻辑,则可以调用 onInterrupt 方法,有两个参数,如果 msg 有值的话,会回调上传失败。 144 | 145 | addInterceptor 添加拦截器的第二个参数 interceptThread,代表拦截器运行在 UI 还是 IO 线程,默认是 UI 线程。 146 | 147 | #### 7. singleUploadObserver 148 | ```kotlin 149 | fun singleUploadObserver(observer: SingleUploadObserver.() -> Unit) 150 | fun singleUploadObserver(state: OnSingleUploadState) 151 | ``` 152 | 单个文件上传监听,同样有两个重载,分别是接口形式和 dsl形式,可以理解为一个用于 kotlin,一个用于兼容 java。它是基于 LiveData 的。 153 | 154 | 有几个回调分别是 onStart,onProgress,onSuccess 和 onFailure。singleUploadObserver 无轮是上传多个还是一个文件的时候都会有回调。 155 | 156 | 它们都有一个参数 index 代表着目前正在上传第几个文件。onSuccess 中有一个参数是 otherParams,它的作用是上面第 3 点中 setUploadImpl 说到的。 157 | 158 | 159 | #### 8. multipleUploadObserver 160 | ```kotlin 161 | fun multipleUploadObserver(observer: MultipleUploadObserver.() -> Unit) 162 | fun multipleUploadObserver(state: OnMultipleUploadState) 163 | ``` 164 | 在多个文件上传时,除了需要了解每个文件上传的情况(即上面的 singleUploadObserver),还需要了解一些总体情况,比如上传开始,全部上传结束等, 165 | multipleUploadObserver 就是这个作用,同样两个重载,接口形式和 dsl形式,用于 kotlin,一个用于兼容 java。 166 | 167 | 有三个回调是 onStart,onCompletion,和 onFailure,代表上传开始,全部上传结束和上传发生错误。 168 | 169 | 在 onCompletion 中,你可以拿到 successNum(上传文件成功数),failNum(上传文件失败数),urls(上传完成后文件路径集合,**注意这个集合的顺序是跟你传入上传路径那个集合顺序是一样的** ) 170 | 171 | #### 9. upload 172 | 发起上传逻辑,调用这个方法后才会真正发起上传逻辑。 173 | 174 | ### Flow 多线程并行并且按顺序返回原理: 175 | ```kotlin 176 | scope?.launch { 177 | uploadList.mapIndexed { index, path -> 178 | scope.async(supportDispatcher) { 179 | uploadImpl() 180 | } 181 | }.asFlow().map { 182 | it.await() 183 | }.flowOn(supportDispatcher) 184 | } 185 | ``` 186 | 是的,就是这么几行代码。 187 | 188 | ### 运行效果 189 | 190 | 示例代码大家可以在 MainActivity 中找到,这里模拟上传,每个文件上传需要三秒。 191 | 192 | 1. 单个文件上传效果 193 | 194 | 195 | 196 | 197 | 2. 3个文件同时上传效果 198 | 199 | 200 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | compileSdkVersion 28 6 | defaultConfig { 7 | applicationId "com.lzx.imageupload" 8 | minSdkVersion 16 9 | targetSdkVersion 28 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | 20 | compileOptions { 21 | sourceCompatibility 1.8 22 | targetCompatibility 1.8 23 | } 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | implementation 'androidx.appcompat:appcompat:1.3.0' 29 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 30 | implementation "org.jetbrains.kotlin:kotlin-stdlib:1.4.10" 31 | implementation 'com.qw:soulpermission:1.3.0' 32 | implementation project(':Multiple') 33 | } 34 | 35 | repositories { 36 | mavenCentral() 37 | } 38 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/lzx/imageupload/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.lzx.imageupload 2 | 3 | import android.Manifest 4 | import android.annotation.SuppressLint 5 | import android.os.Bundle 6 | import android.os.Environment 7 | import android.os.Handler 8 | import android.os.Looper 9 | import android.os.Message 10 | import android.util.Log 11 | import android.view.View 12 | import android.widget.TextView 13 | import androidx.appcompat.app.AppCompatActivity 14 | import com.lzx.multiple.MultipleUpload 15 | import com.lzx.multiple.intercept.InterceptCallback 16 | import com.lzx.multiple.intercept.UploadIntercept 17 | import com.lzx.multiple.upload.UploadCallback 18 | import com.lzx.multiple.upload.UploadInterface 19 | import com.qw.soul.permission.SoulPermission 20 | import com.qw.soul.permission.bean.Permission 21 | import com.qw.soul.permission.bean.Permissions 22 | import com.qw.soul.permission.callbcak.CheckRequestPermissionsListener 23 | 24 | class MainActivity : AppCompatActivity() { 25 | 26 | private var resultText: TextView? = null 27 | 28 | @SuppressLint("SetTextI18n") 29 | override fun onCreate(savedInstanceState: Bundle?) { 30 | super.onCreate(savedInstanceState) 31 | setContentView(R.layout.activity_main) 32 | resultText = findViewById(R.id.resultText) 33 | 34 | SoulPermission.getInstance().checkAndRequestPermissions(Permissions.build( 35 | Manifest.permission.READ_EXTERNAL_STORAGE, 36 | Manifest.permission.WRITE_EXTERNAL_STORAGE), 37 | object : CheckRequestPermissionsListener { 38 | override fun onAllPermissionOk(allPermissions: Array) { 39 | } 40 | 41 | override fun onPermissionDenied(refusedPermissions: Array) { 42 | } 43 | }) 44 | 45 | findViewById(R.id.btn1).setOnClickListener { 46 | MultipleUpload 47 | .with(this@MainActivity) 48 | .load("0.jpg".toSdcardPath()) 49 | // .filter(object : UploadFilter { 50 | // override fun apply(path: String): Boolean { 51 | // return path.contains("IMG_20210621_145921") 52 | // } 53 | // }) 54 | .filter { it.contains("IMG_20210621_145921") } 55 | .addInterceptor(object : UploadIntercept() { 56 | override fun processSingle(path: String, callback: InterceptCallback) { 57 | super.processSingle(path, callback) 58 | Log.i("MainActivity", "执行拦截器 path = $path thread = " + Thread.currentThread().name) 59 | callback.onNext(path) 60 | } 61 | }, UploadIntercept.IO) 62 | .addInterceptor(object : UploadIntercept() { 63 | override fun processSingle(path: String, callback: InterceptCallback) { 64 | super.processSingle(path, callback) 65 | Log.i("MainActivity", "执行拦截器2 path = $path thread = " + Thread.currentThread().name) 66 | callback.onNext(path) 67 | } 68 | }, UploadIntercept.UI) 69 | .addInterceptor(object : UploadIntercept() { 70 | override fun processSingle(path: String, callback: InterceptCallback) { 71 | super.processSingle(path, callback) 72 | Log.i("MainActivity", "执行拦截器3 path = $path thread = " + Thread.currentThread().name) 73 | callback.onNext(path) 74 | } 75 | }, UploadIntercept.IO) 76 | .setUploadImpl(TestUpload()) 77 | .singleUploadObserver { 78 | onStart { 79 | Log.i("MainActivity", "上传开始...") 80 | resultText?.text = "上传开始..." 81 | } 82 | onProgress { _, progress, totalProgress -> 83 | Log.i("MainActivity", "上传中 progress = $progress totalProgress = $totalProgress") 84 | resultText?.text = "上传中 progress = $progress totalProgress = $totalProgress" 85 | } 86 | onSuccess { _, url, _ -> 87 | Log.i("MainActivity", "上传成功 url = $url") 88 | resultText?.text = "上传成功, url = $url" 89 | } 90 | onFailure { _, errCode, errStr -> 91 | Log.i("MainActivity", "上传失败 errCode = $errCode errStr = $errStr") 92 | resultText?.text = "上传失败 errCode = $errCode errStr = $errStr" 93 | } 94 | } 95 | .upload() 96 | } 97 | 98 | 99 | 100 | findViewById(R.id.btn2).setOnClickListener { 101 | var startTime: Long = 0 102 | val paths = mutableListOf() 103 | paths.add("0.jpg".toSdcardPath()) 104 | paths.add("1.jpg".toSdcardPath()) 105 | paths.add("2.jpg".toSdcardPath()) 106 | MultipleUpload 107 | .with(this@MainActivity) 108 | .load(paths) 109 | .setUploadImpl(TestUpload()) 110 | .addInterceptor(object : UploadIntercept() { 111 | override fun processMultiple(paths: MutableList, callback: InterceptCallback) { 112 | super.processMultiple(paths, callback) 113 | Log.i("MainActivity", "执行拦截器 path = $paths thread = " + Thread.currentThread().name) 114 | callback.onNext(paths) 115 | } 116 | }, UploadIntercept.IO) 117 | .addInterceptor(object : UploadIntercept() { 118 | override fun processMultiple(paths: MutableList, callback: InterceptCallback) { 119 | super.processMultiple(paths, callback) 120 | Log.i("MainActivity", "执行拦截器2 path = $paths thread = " + Thread.currentThread().name) 121 | callback.onNext(paths) 122 | } 123 | }, UploadIntercept.UI) 124 | .addInterceptor(object : UploadIntercept() { 125 | override fun processMultiple(paths: MutableList, callback: InterceptCallback) { 126 | super.processMultiple(paths, callback) 127 | Log.i("MainActivity", "执行拦截器3 path = $paths thread = " + Thread.currentThread().name) 128 | callback.onNext(paths) 129 | } 130 | }, UploadIntercept.IO) 131 | .singleUploadObserver { 132 | onStart { 133 | Log.i("MainActivity", "第 $it 个文件上传开始...") 134 | resultText?.text = "第 $it 个文件上传开始..." 135 | } 136 | onProgress { index, progress, totalProgress -> 137 | Log.i("MainActivity", "第 $index 个文件上传中 progress = $progress totalProgress = $totalProgress") 138 | resultText?.text = "第 $index 个文件上传中 progress = $progress totalProgress = $totalProgress" 139 | } 140 | onSuccess { index, url, _ -> 141 | Log.i("MainActivity", "第 $index 个文件上传成功 url = $url") 142 | resultText?.text = "第 $index 个文件上传成功 url = $url" 143 | } 144 | onFailure { index, errCode, errStr -> 145 | Log.i("MainActivity", "第 $index 个文件上传失败 errCode = $errCode errStr = $errStr") 146 | resultText?.text = "第 $index 个文件上传失败 errCode = $errCode errStr = $errStr" 147 | } 148 | } 149 | .multipleUploadObserver { 150 | onStart { 151 | startTime = System.currentTimeMillis() 152 | Log.i("MainActivity", "多文件上传开始...") 153 | resultText?.text = "多文件上传开始..." 154 | } 155 | onCompletion { successNum, failNum, urls -> 156 | Log.i("MainActivity", "耗时 = " + (System.currentTimeMillis() - startTime)) 157 | Log.i("MainActivity", "多文件上传结束 成功数 = $successNum 失败数 = $failNum 上传成功集合 = $urls") 158 | resultText?.text = "多文件上传结束 成功数 = $successNum 失败数 = $failNum 上传成功集合 = $urls" 159 | } 160 | onFailure { catchIndex, errStr -> 161 | Log.i("MainActivity", "多文件上传第 $catchIndex 个文件发生错误 ,errStr = $errStr") 162 | resultText?.text = "多文件上传第 $catchIndex 个文件发生错误 ,errStr = $errStr" 163 | } 164 | } 165 | .upload() 166 | } 167 | } 168 | } 169 | 170 | 171 | /** 172 | * 模拟上传 173 | */ 174 | class TestUpload : UploadInterface { 175 | 176 | 177 | override fun uploadFile(path: String, params: HashMap, callback: UploadCallback) { 178 | val xxUpload = XXUpload(path) 179 | xxUpload.callback = object : XXUpload.Callback { 180 | override fun onStart() { 181 | callback.onUploadStart() 182 | } 183 | 184 | override fun onPro(pro: Int, total: Int) { 185 | callback.onUploadProgress(pro, total) 186 | } 187 | 188 | override fun onSuccess(url: String) { 189 | callback.onUploadSuccess(url) 190 | } 191 | } 192 | xxUpload.startUpload() 193 | } 194 | } 195 | 196 | fun String.toSdcardPath(): String { 197 | return Environment.getExternalStorageDirectory().absolutePath.toString() + "/" + this 198 | } 199 | 200 | class XXUpload(private val path: String) { 201 | 202 | private var pro = 0 203 | private var totalPro = 1 204 | var callback: Callback? = null 205 | 206 | private val handler = object : Handler(Looper.getMainLooper()) { 207 | override fun handleMessage(msg: Message?) { 208 | super.handleMessage(msg) 209 | if (pro <= totalPro) { 210 | callback?.onPro(pro, totalPro) 211 | pro++ 212 | sendMsg() 213 | } else { 214 | val url = path.substring(path.length - 5, path.length) 215 | callback?.onSuccess("我是成功的url = " + url) 216 | } 217 | } 218 | } 219 | 220 | private fun sendMsg() { 221 | handler.sendEmptyMessageDelayed(0, 1000) 222 | } 223 | 224 | fun startUpload() { 225 | callback?.onStart() 226 | sendMsg() 227 | } 228 | 229 | interface Callback { 230 | fun onStart() 231 | fun onPro(pro: Int, total: Int) 232 | fun onSuccess(url: String) 233 | } 234 | } -------------------------------------------------------------------------------- /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/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 172 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |