From 669153089a97b74db7d78aece7b5c7e049c1e88b Mon Sep 17 00:00:00 2001 From: Conny Duck Date: Fri, 7 Sep 2018 19:57:25 +0200 Subject: [PATCH] dont hold whole file content in memory when uploading media --- .../keylesspalace/tusky/ComposeActivity.java | 46 +++++++------------ .../tusky/network/ProgressRequestBody.java | 28 +++++------ .../tusky/util/DownsizeImageTask.java | 29 ++++++------ 3 files changed, 45 insertions(+), 58 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java index 9bfe6686..8cbfb729 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java @@ -101,7 +101,6 @@ import com.keylesspalace.tusky.network.ProgressRequestBody; import com.keylesspalace.tusky.service.SendTootService; import com.keylesspalace.tusky.util.CountUpDownLatch; import com.keylesspalace.tusky.util.DownsizeImageTask; -import com.keylesspalace.tusky.util.IOUtils; import com.keylesspalace.tusky.util.ListUtils; import com.keylesspalace.tusky.util.MediaUtils; import com.keylesspalace.tusky.util.MentionTokenizer; @@ -124,7 +123,6 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -996,8 +994,8 @@ public final class ComposeActivity @NonNull private File createNewImageFile() throws IOException { // Create an image file name - String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date()); - String imageFileName = "Tusky_" + timeStamp + "_"; + String randomId = StringUtils.randomAlphanumericString(12); + String imageFileName = "Tusky_" + randomId + "_"; File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); return File.createTempFile( imageFileName, /* prefix */ @@ -1094,7 +1092,7 @@ public final class ComposeActivity } else { uploadMedia(item); } - } catch (FileNotFoundException e) { + } catch (IOException e) { onUploadFailure(item, false); } } @@ -1224,14 +1222,17 @@ public final class ComposeActivity } } - private void downsizeMedia(final QueuedMedia item) { + private void downsizeMedia(final QueuedMedia item) throws IOException { item.readyStage = QueuedMedia.ReadyStage.DOWNSIZING; - new DownsizeImageTask(STATUS_IMAGE_SIZE_LIMIT, getContentResolver(), + new DownsizeImageTask(STATUS_IMAGE_SIZE_LIMIT, getContentResolver(), createNewImageFile(), new DownsizeImageTask.Listener() { @Override - public void onSuccess(List contentList) { - item.content = contentList.get(0); + public void onSuccess(File tempFile) { + item.uri = FileProvider.getUriForFile( + ComposeActivity.this, + BuildConfig.APPLICATION_ID+".fileprovider", + tempFile); uploadMedia(item); } @@ -1259,32 +1260,20 @@ public final class ComposeActivity StringUtils.randomAlphanumericString(10), fileExtension); - byte[] content = item.content; + InputStream stream; - if (content == null) { - InputStream stream; - - try { - stream = getContentResolver().openInputStream(item.uri); - } catch (FileNotFoundException e) { - Log.d(TAG, Log.getStackTraceString(e)); - return; - } - - content = MediaUtils.inputStreamGetBytes(stream); - IOUtils.closeQuietly(stream); - - if (content == null) { - return; - } + try { + stream = getContentResolver().openInputStream(item.uri); + } catch (FileNotFoundException e) { + Log.w(TAG, e); + return; } if (mimeType == null) mimeType = "multipart/form-data"; item.preview.setProgress(0); - ProgressRequestBody fileBody = new ProgressRequestBody(content, MediaType.parse(mimeType), - false, // If request body logging is enabled, pass true + ProgressRequestBody fileBody = new ProgressRequestBody(stream, MediaUtils.getMediaSize(getContentResolver(), item.uri), MediaType.parse(mimeType), new ProgressRequestBody.UploadCallback() { // may reference activity longer than I would like to int lastProgress = -1; @@ -1544,7 +1533,6 @@ public final class ComposeActivity String id; Call uploadRequest; ReadyStage readyStage; - byte[] content; long mediaSize; String description; diff --git a/app/src/main/java/com/keylesspalace/tusky/network/ProgressRequestBody.java b/app/src/main/java/com/keylesspalace/tusky/network/ProgressRequestBody.java index 2851d47e..69b31428 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/ProgressRequestBody.java +++ b/app/src/main/java/com/keylesspalace/tusky/network/ProgressRequestBody.java @@ -17,18 +17,18 @@ package com.keylesspalace.tusky.network; import android.support.annotation.NonNull; -import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import okhttp3.MediaType; import okhttp3.RequestBody; import okio.BufferedSink; public final class ProgressRequestBody extends RequestBody { - private final byte[] content; - private final UploadCallback mListener; + private final InputStream content; + private final long contentLength; + private final UploadCallback uploadListener; private final MediaType mediaType; - private boolean shouldIgnoreThisPass; private static final int DEFAULT_BUFFER_SIZE = 2048; @@ -36,11 +36,11 @@ public final class ProgressRequestBody extends RequestBody { void onProgressUpdate(int percentage); } - public ProgressRequestBody(final byte[] content, final MediaType mediaType, boolean shouldIgnoreFirst, final UploadCallback listener) { + public ProgressRequestBody(final InputStream content, long contentLength, final MediaType mediaType, final UploadCallback listener) { this.content = content; + this.contentLength = contentLength; this.mediaType = mediaType; - mListener = listener; - shouldIgnoreThisPass = shouldIgnoreFirst; + this.uploadListener = listener; } @Override @@ -50,29 +50,25 @@ public final class ProgressRequestBody extends RequestBody { @Override public long contentLength() { - return content.length; + return contentLength; } @Override public void writeTo(@NonNull BufferedSink sink) throws IOException { - long length = content.length; byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; - ByteArrayInputStream in = new ByteArrayInputStream(content); long uploaded = 0; try { int read; - while ((read = in.read(buffer)) != -1) { - if (!shouldIgnoreThisPass) { - mListener.onProgressUpdate((int)(100 * uploaded / length)); - } + while ((read = content.read(buffer)) != -1) { + uploadListener.onProgressUpdate((int)(100 * uploaded / contentLength)); + uploaded += read; sink.write(buffer, 0, read); } } finally { - in.close(); + content.close(); } - shouldIgnoreThisPass = false; } } \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/util/DownsizeImageTask.java b/app/src/main/java/com/keylesspalace/tusky/util/DownsizeImageTask.java index 3b9f04d0..9a48d599 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/DownsizeImageTask.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/DownsizeImageTask.java @@ -21,11 +21,11 @@ import android.graphics.BitmapFactory; import android.net.Uri; import android.os.AsyncTask; -import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; +import java.io.OutputStream; /** * Reduces the file size of images to fit under a given limit by resizing them, maintaining both @@ -35,22 +35,23 @@ public class DownsizeImageTask extends AsyncTask { private int sizeLimit; private ContentResolver contentResolver; private Listener listener; - private List resultList; + private File tempFile; /** * @param sizeLimit the maximum number of bytes each image can take * @param contentResolver to resolve the specified images' URIs + * @param tempFile the file where the result will be stored * @param listener to whom the results are given */ - public DownsizeImageTask(int sizeLimit, ContentResolver contentResolver, Listener listener) { + public DownsizeImageTask(int sizeLimit, ContentResolver contentResolver, File tempFile, Listener listener) { this.sizeLimit = sizeLimit; this.contentResolver = contentResolver; + this.tempFile = tempFile; this.listener = listener; } @Override protected Boolean doInBackground(Uri... uris) { - resultList = new ArrayList<>(); for (Uri uri : uris) { InputStream inputStream; try { @@ -65,8 +66,6 @@ public class DownsizeImageTask extends AsyncTask { IOUtils.closeQuietly(inputStream); // Get EXIF data, for orientation info. int orientation = MediaUtils.getImageOrientation(uri, contentResolver); - // Then use that information to determine how much to compress. - ByteArrayOutputStream stream = new ByteArrayOutputStream(); /* Unfortunately, there isn't a determined worst case compression ratio for image * formats. So, the only way to tell if they're too big is to compress them and * test, and keep trying at smaller sizes. The initial estimate should be good for @@ -74,7 +73,12 @@ public class DownsizeImageTask extends AsyncTask { * sure it gets downsized to below the limit. */ int scaledImageSize = 1024; do { - stream.reset(); + OutputStream stream; + try { + stream = new FileOutputStream(tempFile); + } catch (FileNotFoundException e) { + return false; + } try { inputStream = contentResolver.openInputStream(uri); } catch (FileNotFoundException e) { @@ -109,9 +113,8 @@ public class DownsizeImageTask extends AsyncTask { reorientedBitmap.compress(format, 85, stream); reorientedBitmap.recycle(); scaledImageSize /= 2; - } while (stream.size() > sizeLimit); + } while (tempFile.length() > sizeLimit); - resultList.add(stream.toByteArray()); if (isCancelled()) { return false; } @@ -122,7 +125,7 @@ public class DownsizeImageTask extends AsyncTask { @Override protected void onPostExecute(Boolean successful) { if (successful) { - listener.onSuccess(resultList); + listener.onSuccess(tempFile); } else { listener.onFailure(); } @@ -131,7 +134,7 @@ public class DownsizeImageTask extends AsyncTask { /** Used to communicate the results of the task. */ public interface Listener { - void onSuccess(List contentList); + void onSuccess(File file); void onFailure(); } }