├── .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 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
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/#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 |
16 |
17 |
24 |
25 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/ImageUploadManager/b6601e0632e4a45ba6af48b2c78a86eca55a79d2/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/ImageUploadManager/b6601e0632e4a45ba6af48b2c78a86eca55a79d2/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/ImageUploadManager/b6601e0632e4a45ba6af48b2c78a86eca55a79d2/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/ImageUploadManager/b6601e0632e4a45ba6af48b2c78a86eca55a79d2/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/ImageUploadManager/b6601e0632e4a45ba6af48b2c78a86eca55a79d2/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/ImageUploadManager/b6601e0632e4a45ba6af48b2c78a86eca55a79d2/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/ImageUploadManager/b6601e0632e4a45ba6af48b2c78a86eca55a79d2/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/ImageUploadManager/b6601e0632e4a45ba6af48b2c78a86eca55a79d2/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/ImageUploadManager/b6601e0632e4a45ba6af48b2c78a86eca55a79d2/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/ImageUploadManager/b6601e0632e4a45ba6af48b2c78a86eca55a79d2/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ImageUploadManager
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/art/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/ImageUploadManager/b6601e0632e4a45ba6af48b2c78a86eca55a79d2/art/1.png
--------------------------------------------------------------------------------
/art/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/ImageUploadManager/b6601e0632e4a45ba6af48b2c78a86eca55a79d2/art/2.png
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 |
5 | repositories {
6 | google()
7 | jcenter()
8 |
9 | }
10 | dependencies {
11 | classpath 'com.android.tools.build:gradle:3.5.0'
12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10"
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | jcenter()
22 |
23 | }
24 | }
25 |
26 | task clean(type: Delete) {
27 | delete rootProject.buildDir
28 | }
29 |
--------------------------------------------------------------------------------
/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 | android.enableJetifier=true
10 | android.useAndroidX=true
11 | org.gradle.jvmargs=-Xmx1536m
12 | # When configured, Gradle will run in incubating parallel mode.
13 | # This option should only be used with decoupled projects. More details, visit
14 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
15 | # org.gradle.parallel=true
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/ImageUploadManager/b6601e0632e4a45ba6af48b2c78a86eca55a79d2/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Apr 19 09:21:40 CST 2019
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-5.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | include ':Multiple'
3 |
4 |
--------------------------------------------------------------------------------