Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adapt Android10/Q #606

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions sample/build.gradle
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
apply plugin: 'com.android.application'

android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
compileSdkVersion 29

defaultConfig {
applicationId "com.yalantis.ucrop.sample"
minSdkVersion 14
targetSdkVersion 28
targetSdkVersion 29
versionCode 13
versionName "1.2.4"
}
Expand Down
5 changes: 2 additions & 3 deletions ucrop/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ apply plugin: 'com.android.library'
apply from: '../mavenpush.gradle'

android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
compileSdkVersion 29

defaultConfig {
minSdkVersion 14
targetSdkVersion 28
targetSdkVersion 29
versionCode 25
versionName "2.2.4-native"

Expand Down
102 changes: 65 additions & 37 deletions ucrop/src/main/java/com/yalantis/ucrop/task/BitmapLoadTask.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
package com.yalantis.ucrop.task;

import android.Manifest.permission;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageDecoder;
import android.graphics.Matrix;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import android.util.Log;
import android.util.Size;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;

import com.yalantis.ucrop.callback.BitmapLoadCallback;
import com.yalantis.ucrop.model.ExifInfo;
Expand All @@ -26,9 +34,6 @@
import java.io.InputStream;
import java.io.OutputStream;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
Expand All @@ -45,6 +50,7 @@ public class BitmapLoadTask extends AsyncTask<Void, Void, BitmapLoadTask.BitmapW

private static final String TAG = "BitmapWorkerTask";

private final boolean mBeforeAndroidQ;
private final Context mContext;
private Uri mInputUri;
private Uri mOutputUri;
Expand Down Expand Up @@ -74,6 +80,7 @@ public BitmapLoadTask(@NonNull Context context,
@NonNull Uri inputUri, @Nullable Uri outputUri,
int requiredWidth, int requiredHeight,
BitmapLoadCallback loadCallback) {
mBeforeAndroidQ = android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q;
mContext = context;
mInputUri = inputUri;
mOutputUri = outputUri;
Expand All @@ -95,51 +102,72 @@ protected BitmapWorkerResult doInBackground(Void... params) {
return new BitmapWorkerResult(e);
}

final ParcelFileDescriptor parcelFileDescriptor;
try {
parcelFileDescriptor = mContext.getContentResolver().openFileDescriptor(mInputUri, "r");
} catch (FileNotFoundException e) {
return new BitmapWorkerResult(e);
}
Bitmap decodeSampledBitmap = null;
ContentResolver resolver = mContext.getContentResolver();
if (mBeforeAndroidQ) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unnecessary check, it's not really needed to use file descriptor. We fixed this in our fork, you can take a look

bandlab@6ffe481

Copy link
Author

@joker-fu joker-fu Jan 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I just considered Android10 compatibility. I looked at your fork and did a simple test, and it worked fine. Why is pr closed?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We just didn't have time to prepare proper PR, but we plan to do that. Also those changes changed behavior, image is not saved anymore to Downloads, it should be documented

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more thing, to make a proper fix, you should send PR not to master, but to develop AND to feature/non_native branches, because non-native and native versions of the lib are essentially forks

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I found UCrop's jni in my project. I haven’t had time to modify it. I will close this pr and use your solution in my project.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After several hours of incomplete testing, I came to the conclusion that your plan is more time-consuming. There are two main points: 1. CopyFile is executed regardless of the version, which increases the time 2. decodeStream is more time-consuming than decodeFileDescriptor

image

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only correct way anyway. copyFile is required because it's used by other parts of the app. Using file descriptor is just incorrect

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, are you sure that your measurement is correct and you don't forget to include copyFile function, because it's required and for old and for new code

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite agree with FileDescriptor's incorrect statement (I want to know why), because UCrop was at least available before Android10. Then there is the issue of time. Even if you add the copy application time to the version that does not use copyFile, it still saves a little time. Of course, I only tested 2 phones.

Copy link

@gildor gildor Jan 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UCrop was at least available before Android10

I don't say that it will not work, it's just too hacky, and broken work with Android 10 proofs this. ContentResolve is the only correct and intended by Android framework way to do that


final FileDescriptor fileDescriptor;
if (parcelFileDescriptor != null) {
fileDescriptor = parcelFileDescriptor.getFileDescriptor();
} else {
return new BitmapWorkerResult(new NullPointerException("ParcelFileDescriptor was null for given Uri: [" + mInputUri + "]"));
}
final ParcelFileDescriptor parcelFileDescriptor;
try {
parcelFileDescriptor = resolver.openFileDescriptor(mInputUri, "r");
} catch (FileNotFoundException e) {
return new BitmapWorkerResult(e);
}

final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
if (options.outWidth == -1 || options.outHeight == -1) {
return new BitmapWorkerResult(new IllegalArgumentException("Bounds for bitmap could not be retrieved from the Uri: [" + mInputUri + "]"));
}
final FileDescriptor fileDescriptor;
if (parcelFileDescriptor != null) {
fileDescriptor = parcelFileDescriptor.getFileDescriptor();
} else {
return new BitmapWorkerResult(new NullPointerException("ParcelFileDescriptor was null for given Uri: [" + mInputUri + "]"));
}

options.inSampleSize = BitmapLoadUtils.calculateInSampleSize(options, mRequiredWidth, mRequiredHeight);
options.inJustDecodeBounds = false;
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
if (options.outWidth == -1 || options.outHeight == -1) {
return new BitmapWorkerResult(new IllegalArgumentException("Bounds for bitmap could not be retrieved from the Uri: [" + mInputUri + "]"));
}

Bitmap decodeSampledBitmap = null;
options.inSampleSize = BitmapLoadUtils.calculateInSampleSize(options, mRequiredWidth, mRequiredHeight);
options.inJustDecodeBounds = false;

boolean decodeAttemptSuccess = false;
while (!decodeAttemptSuccess) {
try {
decodeSampledBitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
decodeAttemptSuccess = true;
} catch (OutOfMemoryError error) {
Log.e(TAG, "doInBackground: BitmapFactory.decodeFileDescriptor: ", error);
options.inSampleSize *= 2;
}
}

boolean decodeAttemptSuccess = false;
while (!decodeAttemptSuccess) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
BitmapLoadUtils.close(parcelFileDescriptor);
}
} else {
try {
decodeSampledBitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
decodeAttemptSuccess = true;
} catch (OutOfMemoryError error) {
Log.e(TAG, "doInBackground: BitmapFactory.decodeFileDescriptor: ", error);
options.inSampleSize *= 2;
ImageDecoder.Source source = ImageDecoder.createSource(resolver, mInputUri);
decodeSampledBitmap = ImageDecoder.decodeBitmap(source, new ImageDecoder.OnHeaderDecodedListener() {
@SuppressLint("NewApi")
@Override
public void onHeaderDecoded(@NonNull ImageDecoder decoder, @NonNull ImageDecoder.ImageInfo info, @NonNull ImageDecoder.Source source) {
Size size = info.getSize();
if (size.getWidth() == -1 || size.getWidth() == -1) {
throw new IllegalArgumentException("Bounds for bitmap could not be retrieved from the Uri: [" + mInputUri + "]");
}
}
});
} catch (IllegalArgumentException e) {
return new BitmapWorkerResult(e);
} catch (IOException e) {
Log.e(TAG, "doInBackground: ImageDecoder.decodeBitmap: ", e);
}
}

if (decodeSampledBitmap == null) {
return new BitmapWorkerResult(new IllegalArgumentException("Bitmap could not be decoded from the Uri: [" + mInputUri + "]"));
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
BitmapLoadUtils.close(parcelFileDescriptor);
}

int exifOrientation = BitmapLoadUtils.getExifOrientation(mContext, mInputUri);
int exifDegrees = BitmapLoadUtils.exifToDegrees(exifOrientation);
int exifTranslation = BitmapLoadUtils.exifToTranslation(exifOrientation);
Expand Down Expand Up @@ -172,7 +200,7 @@ private void processInputUri() throws NullPointerException, IOException {
}
} else if ("content".equals(inputUriScheme)) {
String path = getFilePath();
if (!TextUtils.isEmpty(path) && new File(path).exists()) {
if (!TextUtils.isEmpty(path) && new File(path).exists() && mBeforeAndroidQ) {
mInputUri = Uri.fromFile(new File(path));
} else {
try {
Expand Down Expand Up @@ -213,7 +241,7 @@ private void copyFile(@NonNull Uri inputUri, @Nullable Uri outputUri) throws Nul
throw new NullPointerException("InputStream for given input Uri is null");
}

byte buffer[] = new byte[1024];
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
Expand Down