增加上传进度(目前只对接了:答案图片上传,问题图片上传)
This commit is contained in:
@ -21,6 +21,8 @@ public class WaitingDialogFragment extends BaseDialogFragment {
|
||||
|
||||
private OnDialogBackListener mBackListener;
|
||||
|
||||
private TextView message;
|
||||
|
||||
public static WaitingDialogFragment newInstance(String message) {
|
||||
Bundle args = new Bundle();
|
||||
args.putString(KEY_MSG, message);
|
||||
@ -43,7 +45,7 @@ public class WaitingDialogFragment extends BaseDialogFragment {
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
final View view = inflater.inflate(R.layout.set_wait_dialog, null);
|
||||
final TextView message = (TextView) view.findViewById(R.id.set_wait_message);
|
||||
message = (TextView) view.findViewById(R.id.set_wait_message);
|
||||
message.setText(getArguments().getString(KEY_MSG));
|
||||
return view;
|
||||
}
|
||||
@ -62,6 +64,10 @@ public class WaitingDialogFragment extends BaseDialogFragment {
|
||||
this.mBackListener = backListener;
|
||||
}
|
||||
|
||||
public void uploadWaitingHint(String hint) {
|
||||
if (message != null) message.setText(hint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBack() {
|
||||
if (mBackListener != null) {
|
||||
|
||||
132
app/src/main/java/com/gh/common/util/CompressImageUtils.kt
Normal file
132
app/src/main/java/com/gh/common/util/CompressImageUtils.kt
Normal file
@ -0,0 +1,132 @@
|
||||
package com.gh.common.util
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Matrix
|
||||
import com.halo.assistant.HaloApp
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
||||
/**
|
||||
* Created by khy on 02/08/18.
|
||||
* 图片压缩工具类
|
||||
* 资料参考:https://github.com/zetbaitsu/Compressor
|
||||
* 压缩算法:http://gitlab.ghzhushou.com/pm/halo-app-issues/issues/298
|
||||
*/
|
||||
object CompressImageUtils {
|
||||
|
||||
private const val compressLimit = 1280
|
||||
private const val defaultQuality = 90
|
||||
|
||||
/**
|
||||
* 压缩图片并保存到目标文件
|
||||
* 该压缩方法是同步执行 请勿在主线程执行
|
||||
* 返回源文件的三种情况:小于特定值,图片类型为GIF,压缩失败
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
fun compressImageAndSaveToFile(imageFile: File, compressGif: Boolean): File {
|
||||
// 小于300K直接返回原图
|
||||
if (imageFile.length() < 50 * 1024) {
|
||||
return imageFile
|
||||
}
|
||||
|
||||
val cacheDir = getImageCacheDir()
|
||||
val parentFile = cacheDir.parentFile
|
||||
if (!parentFile.exists()) parentFile.mkdirs()
|
||||
var fileOutputStream: FileOutputStream? = null
|
||||
|
||||
try {
|
||||
// 确定图片类型
|
||||
val options = BitmapFactory.Options()
|
||||
options.inJustDecodeBounds = true
|
||||
BitmapFactory.decodeFile(imageFile.absolutePath, options)
|
||||
val formatType = if (options.outMimeType.contains("png")) {
|
||||
Bitmap.CompressFormat.PNG
|
||||
} else if (options.outMimeType.contains("gif") && !compressGif) { // gif直接返回原图
|
||||
return imageFile
|
||||
} else {
|
||||
Bitmap.CompressFormat.WEBP
|
||||
}
|
||||
|
||||
fileOutputStream = FileOutputStream(cacheDir)
|
||||
// write the compressed bitmap at the destination specified by destinationPath.
|
||||
decodeSampledBitmapFromFile(imageFile).compress(formatType, defaultQuality, fileOutputStream)
|
||||
return cacheDir
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
if (cacheDir.exists()) {
|
||||
cacheDir.delete()
|
||||
}
|
||||
} finally {
|
||||
if (fileOutputStream != null) {
|
||||
fileOutputStream.flush()
|
||||
fileOutputStream.close()
|
||||
}
|
||||
}
|
||||
return imageFile
|
||||
}
|
||||
|
||||
private fun getImageCacheDir(): File {
|
||||
// return File(Environment.getExternalStorageDirectory().absolutePath + "/Pictures/test/" + System.currentTimeMillis() + ".jpg")
|
||||
// 统一用jpg保存应该没有影响吧
|
||||
return File(HaloApp.getInstance().application.cacheDir.absolutePath + File.separator + System.currentTimeMillis() + ".jpg")
|
||||
}
|
||||
|
||||
// 根据图片获取压缩后的位图
|
||||
@Throws(Exception::class)
|
||||
private fun decodeSampledBitmapFromFile(imageFile: File): Bitmap {
|
||||
// First decode with inJustDecodeBounds=true to check dimensions
|
||||
val options = BitmapFactory.Options()
|
||||
options.inJustDecodeBounds = true
|
||||
BitmapFactory.decodeFile(imageFile.absolutePath, options)
|
||||
|
||||
// Raw height and width of image
|
||||
val height = options.outHeight
|
||||
val width = options.outWidth
|
||||
var inSampleSize = 1
|
||||
|
||||
/**
|
||||
* 压缩类型:用于矩阵缩放
|
||||
* 0: 短边等比压缩至1280 H:长边 W:短边
|
||||
* 1: 取长边等比压缩至1280 H:短边 W: 长边
|
||||
*/
|
||||
var compressType = -1
|
||||
|
||||
val longSide = Math.max(height, width) //最长边
|
||||
val shortSide = Math.min(height, width) //最短边
|
||||
val scale = longSide.toFloat() / shortSide // 长短边比例
|
||||
|
||||
if (longSide > compressLimit && shortSide > compressLimit) {
|
||||
if (scale > 2) {
|
||||
inSampleSize = if (shortSide / compressLimit == 0) 1 else shortSide / compressLimit
|
||||
compressType = 0
|
||||
} else {
|
||||
inSampleSize = if (longSide / compressLimit == 0) 1 else longSide / compressLimit
|
||||
compressType = 1
|
||||
|
||||
}
|
||||
} else if (longSide > compressLimit && shortSide < compressLimit) {
|
||||
if (scale <= 2) {
|
||||
inSampleSize = if (longSide / compressLimit == 0) 1 else longSide / compressLimit
|
||||
compressType = 1
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate inSampleSize
|
||||
options.inSampleSize = inSampleSize
|
||||
|
||||
// Decode bitmap with inSampleSize set
|
||||
options.inJustDecodeBounds = false
|
||||
|
||||
var scaledBitmap = BitmapFactory.decodeFile(imageFile.absolutePath, options)
|
||||
|
||||
val matrix = Matrix() // 精确缩放
|
||||
if (compressType != -1) {
|
||||
val targetMatrixScale = if (compressType == 0) compressLimit.toFloat() / scaledBitmap.width
|
||||
else compressLimit.toFloat() / scaledBitmap.height
|
||||
matrix.setScale(targetMatrixScale, targetMatrixScale)
|
||||
scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.width, scaledBitmap.height, matrix, true)
|
||||
}
|
||||
return scaledBitmap
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,8 @@
|
||||
package com.gh.common.util
|
||||
|
||||
import android.text.TextUtils
|
||||
import com.gh.gamecenter.retrofit.BiResponse
|
||||
import com.gh.gamecenter.retrofit.FileRequestBody
|
||||
import com.gh.gamecenter.retrofit.RetrofitCallback
|
||||
import com.gh.gamecenter.retrofit.RetrofitManager
|
||||
import com.halo.assistant.HaloApp
|
||||
import io.reactivex.Observable
|
||||
@ -16,9 +17,9 @@ import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.ResponseBody
|
||||
import org.json.JSONObject
|
||||
import top.zibin.luban.Luban
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
object UploadImageUtils {
|
||||
@ -28,7 +29,7 @@ object UploadImageUtils {
|
||||
suggestion
|
||||
}
|
||||
|
||||
private val boundary = UUID.randomUUID().toString().replace("-".toRegex(), "").substring(0, 16)
|
||||
val boundary = UUID.randomUUID().toString().replace("-".toRegex(), "").substring(0, 16)
|
||||
|
||||
// 社区图片上传 后续统一其他
|
||||
fun compressAndUploadImage(type: UploadType, imgPath: String, compressGif: Boolean, listener: OnUploadImageListener): Disposable {
|
||||
@ -36,11 +37,14 @@ object UploadImageUtils {
|
||||
return Single.just(imgPath)
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.flatMap {
|
||||
var compressPath = compressImage(it, compressGif) // compress
|
||||
if (compressPath.isNullOrEmpty()) compressPath = it
|
||||
val file = File(compressPath)
|
||||
val requestFile = RequestBody.create(MediaType.parse("multipart/form-data;boundary=$boundary"), file)
|
||||
val part = MultipartBody.Part.createFormData("Filedata", file.name, requestFile)
|
||||
val compressFile = compressImage(it, compressGif) // compress
|
||||
val requestBody = FileRequestBody<ResponseBody>(compressFile, object : RetrofitCallback<ResponseBody>() {
|
||||
override fun onProgress(total: Long, progress: Long) {
|
||||
listener.onProgress(total, progress)
|
||||
}
|
||||
})
|
||||
val part = MultipartBody.Part.createFormData("Filedata", compressFile.name, requestBody)
|
||||
|
||||
return@flatMap RetrofitManager.getInstance(HaloApp.getInstance().application).uploadApi.uploadImage(part, type.name)
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
@ -65,40 +69,34 @@ object UploadImageUtils {
|
||||
}
|
||||
|
||||
fun compressAndUploadImageList(type: UploadType, imgs: List<String>, compressGif: Boolean, listener: OnUploadImageListListener) {
|
||||
|
||||
val postImageList = HashMap<String, String>()
|
||||
|
||||
Observable.create(ObservableOnSubscribe<Map<String, String>> {
|
||||
val compressList = compressImageList(imgs, compressGif)
|
||||
|
||||
if (compressList != null) {
|
||||
for (img in compressList) {
|
||||
val requestFile = RequestBody.create(MediaType.parse("multipart/form-data;boundary=$boundary"), img)
|
||||
val part = MultipartBody.Part.createFormData("Filedata", img.name, requestFile)
|
||||
RetrofitManager.getInstance(HaloApp.getInstance().application)
|
||||
.uploadApi.uploadImage(part, type.name)
|
||||
.subscribe(object : BiResponse<ResponseBody>() {
|
||||
override fun onSuccess(data: ResponseBody) {
|
||||
val string = data.string()
|
||||
if (!string.isNullOrEmpty()) {
|
||||
val url = JSONObject(string).getString("url")
|
||||
if (!url.isNullOrEmpty()) {
|
||||
val map = HashMap<String, String>()
|
||||
map[img.path] = url
|
||||
it.onNext(map)
|
||||
return
|
||||
}
|
||||
for (img in compressList) {
|
||||
val requestFile = RequestBody.create(MediaType.parse("multipart/form-data;boundary=$boundary"), img)
|
||||
val part = MultipartBody.Part.createFormData("Filedata", img.name, requestFile)
|
||||
RetrofitManager.getInstance(HaloApp.getInstance().application)
|
||||
.uploadApi.uploadImage(part, type.name)
|
||||
.subscribe(object : BiResponse<ResponseBody>() {
|
||||
override fun onSuccess(data: ResponseBody) {
|
||||
val string = data.string()
|
||||
if (!string.isNullOrEmpty()) {
|
||||
val url = JSONObject(string).getString("url")
|
||||
if (!url.isNullOrEmpty()) {
|
||||
val map = HashMap<String, String>()
|
||||
map[img.path] = url
|
||||
it.onNext(map)
|
||||
return
|
||||
}
|
||||
onFailure(IllegalAccessException("HeHe"))
|
||||
}
|
||||
onFailure(IllegalAccessException("HeHe"))
|
||||
}
|
||||
|
||||
override fun onFailure(exception: Exception) {
|
||||
it.onError(exception)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
override fun onFailure(exception: Exception) {
|
||||
it.onError(exception)
|
||||
}
|
||||
})
|
||||
}
|
||||
it.onComplete()
|
||||
})
|
||||
@ -126,21 +124,23 @@ object UploadImageUtils {
|
||||
}
|
||||
|
||||
// 同步调用->避免在主线程调用以免阻塞主线程
|
||||
private fun compressImageList(imgs: List<String>, compressGif: Boolean): List<File>? {
|
||||
return Luban.with(HaloApp.getInstance().application).load<Any>(imgs).filter { path ->
|
||||
!(TextUtils.isEmpty(path) || (path.toLowerCase().endsWith(".gif") && !compressGif))
|
||||
}.ignoreBy(200).get()
|
||||
private fun compressImageList(imgs: List<String>, compressGif: Boolean): List<File> {
|
||||
val compressList: MutableList<File> = ArrayList()
|
||||
for (img in imgs) {
|
||||
compressList.add(CompressImageUtils.compressImageAndSaveToFile(File(img), compressGif))
|
||||
}
|
||||
return compressList
|
||||
}
|
||||
|
||||
// 同步调用->避免在主线程调用以免阻塞主线程
|
||||
private fun compressImage(imgPath: String, compressGif: Boolean): String? {
|
||||
if (TextUtils.isEmpty(imgPath) || (imgPath.toLowerCase().endsWith(".gif") && !compressGif)) return imgPath
|
||||
return Luban.with(HaloApp.getInstance().application).ignoreBy(200).get(imgPath).path
|
||||
private fun compressImage(imgPath: String, compressGif: Boolean): File {
|
||||
return CompressImageUtils.compressImageAndSaveToFile(File(imgPath), compressGif)
|
||||
}
|
||||
|
||||
interface OnUploadImageListener {
|
||||
fun onSuccess(imageUrl: String)
|
||||
fun onError(e: Throwable?)
|
||||
fun onProgress(total: Long, progress: Long)
|
||||
}
|
||||
|
||||
interface OnUploadImageListListener {
|
||||
|
||||
@ -43,6 +43,7 @@ import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import butterknife.BindView;
|
||||
@ -221,6 +222,16 @@ public class AnswerEditFragment extends NormalFragment {
|
||||
mUploadImageDisposable = UploadImageUtils.INSTANCE.compressAndUploadImage(UploadImageUtils.UploadType.community
|
||||
, picturePath, false, new UploadImageUtils.OnUploadImageListener() {
|
||||
|
||||
@Override
|
||||
public void onProgress(long total, long progress) {
|
||||
float percent = 100 * (progress / (float) total);
|
||||
if (percent >= 100) percent = 99.9F;
|
||||
String format = String.format(Locale.CHINA, "%.1f", percent);
|
||||
if (postDialog != null) {
|
||||
postDialog.uploadWaitingHint("图片上传中" + format + "%");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@NotNull String imageUrl) {
|
||||
if (postDialog != null) postDialog.dismissAllowingStateLoss();
|
||||
|
||||
@ -128,18 +128,22 @@ class QuestionEditActivity : BaseActivity() {
|
||||
// Process dialog
|
||||
mViewModel?.processDialog?.observe(this, Observer { it ->
|
||||
if (it?.isShow!!) {
|
||||
mProcessingDialog = WaitingDialogFragment.newInstance(it.msg, false)
|
||||
mProcessingDialog?.show(supportFragmentManager, null, {
|
||||
if (mViewModel?.uploadImageSubscription != null && !mViewModel?.uploadImageSubscription!!.isDisposed) {
|
||||
mUpdateImageCancelDialog = DialogUtils.showAlertDialog(this, "提示"
|
||||
, "图片正在上传中,确定取消吗?"
|
||||
, "确定", "取消", {
|
||||
mViewModel?.uploadImageSubscription!!.dispose()
|
||||
mUpdateImageCancelDialog?.dismiss()
|
||||
mProcessingDialog?.dismiss()
|
||||
}, null)
|
||||
}
|
||||
})
|
||||
if (mProcessingDialog != null && mProcessingDialog?.isVisible!!) {
|
||||
mProcessingDialog?.uploadWaitingHint(it.msg)
|
||||
} else {
|
||||
mProcessingDialog = WaitingDialogFragment.newInstance(it.msg, false)
|
||||
mProcessingDialog?.show(supportFragmentManager, null, {
|
||||
if (mViewModel?.uploadImageSubscription != null && !mViewModel?.uploadImageSubscription!!.isDisposed) {
|
||||
mUpdateImageCancelDialog = DialogUtils.showAlertDialog(this, "提示"
|
||||
, "图片正在上传中,确定取消吗?"
|
||||
, "确定", "取消", {
|
||||
mViewModel?.uploadImageSubscription!!.dispose()
|
||||
mUpdateImageCancelDialog?.dismiss()
|
||||
mProcessingDialog?.dismiss()
|
||||
}, null)
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
mUpdateImageCancelDialog?.dismiss()
|
||||
mProcessingDialog?.dismiss()
|
||||
|
||||
@ -186,6 +186,13 @@ class QuestionEditViewModel(application: Application) : AndroidViewModel(applica
|
||||
processDialog.postValue(WaitingDialogFragment.WaitingDialogData("上传中...", true))
|
||||
uploadImageSubscription = UploadImageUtils.compressAndUploadImage(UploadImageUtils.UploadType.community
|
||||
, picPath, true, object : UploadImageUtils.OnUploadImageListener {
|
||||
override fun onProgress(total: Long, progress: Long) {
|
||||
var percent = 100 * (progress / total.toFloat())
|
||||
if (percent >= 100) percent = 99.9F
|
||||
val format = String.format("%.1f", percent)
|
||||
processDialog.postValue(WaitingDialogFragment.WaitingDialogData("图片上传中$format%", true))
|
||||
}
|
||||
|
||||
override fun onSuccess(imageUrl: String) {
|
||||
processDialog.postValue(WaitingDialogFragment.WaitingDialogData("上传中...", false))
|
||||
val list = picList.value ?: ArrayList()
|
||||
|
||||
@ -1,65 +1,79 @@
|
||||
package com.gh.gamecenter.retrofit;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import com.gh.common.util.UploadImageUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.RequestBody;
|
||||
import okio.Buffer;
|
||||
import okio.BufferedSink;
|
||||
import okio.ForwardingSink;
|
||||
import okio.Okio;
|
||||
import okio.Sink;
|
||||
|
||||
public class FileRequestBody<T> extends RequestBody {
|
||||
|
||||
private RequestBody requestBody;
|
||||
|
||||
private RetrofitCallback<T> callback;
|
||||
|
||||
private BufferedSink bufferedSink;
|
||||
private int DEFAULT_BUFFER_SIZE = 1024 * 8;
|
||||
|
||||
private File mFile;
|
||||
|
||||
|
||||
public FileRequestBody(RequestBody requestBody, RetrofitCallback<T> callback) {
|
||||
public FileRequestBody(File file, RetrofitCallback<T> callback) {
|
||||
super();
|
||||
this.requestBody = requestBody;
|
||||
this.mFile = file;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long contentLength() throws IOException {
|
||||
return mFile.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaType contentType() {
|
||||
return requestBody.contentType();
|
||||
return MediaType.parse("multipart/form-data;boundary=" + UploadImageUtils.INSTANCE.getBoundary());
|
||||
}
|
||||
|
||||
// 注意 addNetworkInterceptor ->HttpLoggingInterceptor 会调用两遍
|
||||
@Override
|
||||
public void writeTo(BufferedSink sink) throws IOException {
|
||||
bufferedSink = Okio.buffer(sink(sink));
|
||||
long fileLength = mFile.length();
|
||||
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
|
||||
FileInputStream in = new FileInputStream(mFile);
|
||||
long uploaded = 0;
|
||||
|
||||
//写入
|
||||
requestBody.writeTo(bufferedSink);
|
||||
//必须调用flush,否则最后一部分数据可能不会被写入
|
||||
bufferedSink.flush();
|
||||
try {
|
||||
int read;
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
while ((read = in.read(buffer)) != -1) {
|
||||
uploaded += read;
|
||||
sink.write(buffer, 0, read);
|
||||
// update progress on UI thread
|
||||
handler.post(new ProgressUpdater(uploaded, fileLength));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
|
||||
private Sink sink(Sink sink) {
|
||||
return new ForwardingSink(sink) {
|
||||
//当前写入字节数
|
||||
long bytesWritten = 0L;
|
||||
//总字节长度,避免多次调用contentLength()方法
|
||||
long contentLength = 0L;
|
||||
private class ProgressUpdater implements Runnable {
|
||||
private long mUploaded;
|
||||
private long mTotal;
|
||||
|
||||
@Override
|
||||
public void write(Buffer source, long byteCount) throws IOException {
|
||||
super.write(source, byteCount);
|
||||
if (contentLength == 0) {
|
||||
//获得contentLength的值,后续不再调用
|
||||
contentLength = contentLength();
|
||||
}
|
||||
//增加当前写入的字节数
|
||||
bytesWritten += byteCount;
|
||||
//回调
|
||||
callback.onLoading(contentLength, bytesWritten);
|
||||
}
|
||||
};
|
||||
public ProgressUpdater(long uploaded, long total) {
|
||||
mUploaded = uploaded;
|
||||
mTotal = total;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
callback.onProgress(mTotal, mUploaded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,8 +15,15 @@ public abstract class RetrofitCallback<T> implements Callback<T> {
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void onSuccess(Call<T> call, Response<T> response);
|
||||
public void onSuccess(Call<T> call, Response<T> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<T> call, Throwable t) {
|
||||
|
||||
}
|
||||
|
||||
//用于进度的回调
|
||||
public abstract void onLoading(long total, long progress);
|
||||
public abstract void onProgress(long total, long progress);
|
||||
}
|
||||
Reference in New Issue
Block a user