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

feat(android): vfs support custom image loader #3947

Merged
merged 2 commits into from
Jul 9, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

public class ImageDataPool extends BasePool<ImageDataKey, ImageRecycleObject> {

private static final int DEFAULT_IMAGE_POOL_SIZE = 24;
private static final int DEFAULT_IMAGE_POOL_SIZE = 16;
private LruCache<ImageDataKey, ImageRecycleObject> mPools;

public ImageDataPool() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.tencent.vfs;

import android.graphics.Bitmap;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

Expand Down Expand Up @@ -48,6 +49,8 @@ public enum TransferType {
@Nullable
public byte[] bytes;
@Nullable
public Bitmap bitmap;
@Nullable
public HashMap<String, String> requestHeaders;
@Nullable
public HashMap<String, String> requestParams;
Expand Down Expand Up @@ -104,6 +107,7 @@ public static ResourceDataHolder obtain() {
public void recycle() {
buffer = null;
bytes = null;
bitmap = null;
callback = null;
errorMessage = null;
processorTag = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ private boolean checkOverDrag(MotionEvent event) {
setOverPullState(OVER_PULL_UP_ING);
}
Number deltaY = (event.getRawY() - lastRawY) / 3.0f;
LogUtils.e("maxli", "checkOverDrag: deltaY " + deltaY + ", offset " + offset);
recyclerView.offsetChildrenVertical(deltaY.intValue());
if (overPullListener != null) {
overPullListener.onOverPullStateChanged(overPullState, overPullState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import static com.tencent.renderer.NativeRenderException.ExceptionCode.IMAGE_DATA_DECODE_ERR;

import android.graphics.ImageDecoder;
import android.graphics.drawable.Animatable;
import android.os.Build.VERSION_CODES;

import androidx.annotation.RequiresApi;
Expand All @@ -45,7 +44,6 @@

import java.nio.ByteBuffer;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

public class ImageDataHolder extends ImageRecycleObject implements ImageDataSupplier {

Expand All @@ -61,6 +59,11 @@ public class ImageDataHolder extends ImageRecycleObject implements ImageDataSupp
* Mark that image data is decoded internally and needs to recycle.
*/
private static final int FLAG_RECYCLABLE = 0x00000004;
/**
* Mark that image bitmap is provided by host, do not need cache.
*/
private static final int FLAG_CACHEABLE = 0x00000008;

private int mStateFlags = 0;
private int mWidth;
private int mHeight;
Expand All @@ -77,23 +80,34 @@ public class ImageDataHolder extends ImageRecycleObject implements ImageDataSupp
private BitmapFactory.Options mOptions;

public ImageDataHolder(@NonNull String source) {
init(source, null, 0, 0);
init(source, null, null, 0, 0);
}

public ImageDataHolder(@NonNull String source, int width, int height) {
init(source, null, width, height);
init(source, null, null, width, height);
}

public ImageDataHolder(@NonNull String source, @Nullable Bitmap bitmap, int width, int height) {
init(source, null, bitmap, width, height);
}

public ImageDataHolder(@NonNull String source, @NonNull ImageDataKey key, int width,
int height) {
init(source, key, width, height);
init(source, key, null, width, height);
}

public ImageDataHolder(@NonNull String source, @NonNull ImageDataKey key, @Nullable Bitmap bitmap, int width,
int height) {
init(source, key, bitmap, width, height);
}

public void init(@NonNull String source, @Nullable ImageDataKey key, int width, int height) {
public void init(@NonNull String source, @Nullable ImageDataKey key, @Nullable Bitmap bitmap, int width,
int height) {
mSource = source;
mWidth = width;
mHeight = height;
mKey = (key == null) ? new ImageDataKey(source) : key;
mBitmap = bitmap;
}

@Nullable
Expand Down Expand Up @@ -172,6 +186,9 @@ public Movie getGifMovie() {

@Override
public boolean isScraped() {
if (!checkStateFlag(FLAG_CACHEABLE)) {
return mBitmap == null || mBitmap.isRecycled();
}
if (mOptions == null) {
return true;
}
Expand All @@ -194,14 +211,33 @@ public boolean isRecyclable() {
return checkStateFlag(FLAG_RECYCLABLE);
}

@Override
public boolean isCacheable() {
return checkStateFlag(FLAG_CACHEABLE);
}

@Override
public int getImageWidth() {
return (mOptions != null) ? mOptions.outWidth : 0;
if (mOptions != null) {
return mOptions.outWidth;
} else if (mBitmap != null) {
return mBitmap.getWidth();
} else if (mGifMovie != null) {
return mGifMovie.width();
}
return 0;
}

@Override
public int getImageHeight() {
return (mOptions != null) ? mOptions.outHeight : 0;
if (mOptions != null) {
return mOptions.outHeight;
} else if (mBitmap != null) {
return mBitmap.getHeight();
} else if (mGifMovie != null) {
return mGifMovie.height();
}
return 0;
}

@Override
Expand All @@ -216,7 +252,7 @@ public int getLayoutHeight() {

@Override
public boolean isAnimated() {
return mOptions != null && ImageDataUtils.isGif(mOptions);
return ImageDataUtils.isGif(mOptions);
}

@Nullable
Expand Down Expand Up @@ -256,6 +292,7 @@ public void decodeImageData(@NonNull byte[] data, @Nullable Map<String, Object>
if (imageDecoderAdapter != null) {
imageDecoderAdapter.afterDecode(initProps, this, mOptions);
}
setStateFlag(FLAG_CACHEABLE);
} catch (OutOfMemoryError | Exception e) {
throw new NativeRenderException(IMAGE_DATA_DECODE_ERR, e.getMessage());
}
Expand All @@ -275,9 +312,8 @@ public void setBitmap(Bitmap bitmap) {
* Decode image data with ImageDecoder.
*
* <p>
* Warning! AnimatedImageDrawable start will cause crash in some android platform when use
* ImageDecoder createSource API with ByteBuffer. Therefore, AnimatedImageDrawable is not
* supported at present.
* Warning! AnimatedImageDrawable start will cause crash in some android platform when use ImageDecoder createSource
* API with ByteBuffer. Therefore, AnimatedImageDrawable is not supported at present.
* <p/>
*/
@RequiresApi(api = VERSION_CODES.P)
Expand All @@ -290,7 +326,8 @@ private Drawable decodeGifForTarget28(@NonNull byte[] data) throws IOException {
// will work around the issue
ImageDecoder.Source source = ImageDecoder.createSource(ByteBuffer.wrap(data));
return ImageDecoder.decodeDrawable(source,
(decoder, info, source1) -> {});
(decoder, info, source1) -> {
});
}

@RequiresApi(api = VERSION_CODES.P)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public interface ImageDataSupplier {

boolean isRecyclable();

boolean isCacheable();

boolean isAnimated();

@NonNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ public ImageDataSupplier getImageFromCache(@NonNull String url) {

@Override
public void saveImageToCache(@NonNull ImageDataSupplier data) {
mImagePool.release((ImageDataHolder) data);
if (data.isCacheable()) {
mImagePool.release((ImageDataHolder) data);
}
}

private void doListenerCallback(@NonNull final ImageRequestListener listener,
Expand All @@ -81,16 +83,13 @@ private Runnable generateCallbackRunnable(final ImageDataKey urlKey,
@Nullable final ImageDataHolder imageHolder,
@Nullable String errorMessage) {
final String error = (errorMessage != null) ? errorMessage : "";
return new Runnable() {
@Override
public void run() {
ArrayList<ImageRequestListener> listeners = mListenersMap.get(urlKey);
mListenersMap.remove(urlKey);
if (listeners != null && listeners.size() > 0) {
for (ImageRequestListener listener : listeners) {
if (listener != null) {
doListenerCallback(listener, imageHolder, error);
}
return () -> {
ArrayList<ImageRequestListener> listeners = mListenersMap.get(urlKey);
mListenersMap.remove(urlKey);
if (listeners != null && listeners.size() > 0) {
for (ImageRequestListener listener : listeners) {
if (listener != null) {
doListenerCallback(listener, imageHolder, error);
}
}
}
Expand All @@ -104,20 +103,26 @@ private void handleResourceData(@NonNull String url, @NonNull final ImageDataKey
String errorMessage = null;
byte[] bytes = dataHolder.getBytes();
if (dataHolder.resultCode
== ResourceDataHolder.RESOURCE_LOAD_SUCCESS_CODE && bytes != null) {
imageHolder = new ImageDataHolder(url, urlKey, width, height);
try {
imageHolder.decodeImageData(bytes, initProps, mImageDecoderAdapter);
// Should check the request data returned from the host, if the data is
// invalid, the request is considered to have failed
if (!imageHolder.checkImageData()) {
== ResourceDataHolder.RESOURCE_LOAD_SUCCESS_CODE) {
if (dataHolder.bitmap != null) {
imageHolder = new ImageDataHolder(url, urlKey, dataHolder.bitmap, width, height);
} else if (bytes != null) {
imageHolder = new ImageDataHolder(url, urlKey, width, height);
try {
imageHolder.decodeImageData(bytes, initProps, mImageDecoderAdapter);
// Should check the request data returned from the host, if the data is
// invalid, the request is considered to have failed
if (!imageHolder.checkImageData()) {
imageHolder = null;
errorMessage = "Image data decoding failed!";
}
} catch (NativeRenderException e) {
e.printStackTrace();
imageHolder = null;
errorMessage = "Image data decoding failed!";
errorMessage = e.getMessage();
}
} catch (NativeRenderException e) {
e.printStackTrace();
imageHolder = null;
errorMessage = e.getMessage();
} else {
errorMessage = dataHolder.errorMessage;
}
} else {
errorMessage = dataHolder.errorMessage;
Expand All @@ -133,15 +138,12 @@ private void handleResourceData(@NonNull String url, @NonNull final ImageDataKey

private void handleRequestProgress(final long total, final long loaded,
@NonNull final ImageDataKey urlKey) {
Runnable progressRunnable = new Runnable() {
@Override
public void run() {
ArrayList<ImageRequestListener> listeners = mListenersMap.get(urlKey);
if (listeners != null) {
for (ImageRequestListener listener : listeners) {
if (listener != null) {
listener.onRequestProgress(total, loaded);
}
Runnable progressRunnable = () -> {
ArrayList<ImageRequestListener> listeners = mListenersMap.get(urlKey);
if (listeners != null) {
for (ImageRequestListener listener : listeners) {
if (listener != null) {
listener.onRequestProgress(total, loaded);
}
}
}
Expand Down Expand Up @@ -169,24 +171,23 @@ public ImageDataSupplier fetchImageSync(@NonNull String url,
ResourceDataHolder dataHolder = mVfsManager.fetchResourceSync(url, null, requestParams);
byte[] bytes = dataHolder.getBytes();
if (dataHolder.resultCode
!= ResourceDataHolder.RESOURCE_LOAD_SUCCESS_CODE || bytes == null) {
!= ResourceDataHolder.RESOURCE_LOAD_SUCCESS_CODE) {
return null;
}
ImageDataHolder imageHolder = ImageDataHolder.obtain();
if (imageHolder != null) {
imageHolder.init(url, null, width, height);
} else {
imageHolder = new ImageDataHolder(url, width, height);
}
try {
imageHolder.decodeImageData(bytes, initProps, mImageDecoderAdapter);
if (imageHolder.checkImageData()) {
return imageHolder;
if (dataHolder.bitmap != null) {
return new ImageDataHolder(url, dataHolder.bitmap, width, height);
} else if (bytes != null) {
ImageDataHolder imageHolder = new ImageDataHolder(url, width, height);
try {
imageHolder.decodeImageData(bytes, initProps, mImageDecoderAdapter);
if (imageHolder.checkImageData()) {
return imageHolder;
}
} catch (NativeRenderException e) {
e.printStackTrace();
} finally {
dataHolder.recycle();
}
} catch (NativeRenderException e) {
e.printStackTrace();
} finally {
dataHolder.recycle();
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import android.graphics.BitmapFactory;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

public class ImageDataUtils {

Expand All @@ -36,23 +37,23 @@ public static BitmapFactory.Options generateBitmapOptions(@NonNull byte[] data)
return options;
}

public static boolean isWebp(@NonNull BitmapFactory.Options options) {
return !TextUtils.isEmpty(options.outMimeType) && options.outMimeType.equalsIgnoreCase(
public static boolean isWebp(@Nullable BitmapFactory.Options options) {
return options != null && !TextUtils.isEmpty(options.outMimeType) && options.outMimeType.equalsIgnoreCase(
IMAGE_TYPE_WEBP);
}

public static boolean isJpeg(@NonNull BitmapFactory.Options options) {
return !TextUtils.isEmpty(options.outMimeType) && options.outMimeType.equalsIgnoreCase(
public static boolean isJpeg(@Nullable BitmapFactory.Options options) {
return options != null && !TextUtils.isEmpty(options.outMimeType) && options.outMimeType.equalsIgnoreCase(
IMAGE_TYPE_JPEG);
}

public static boolean isPng(@NonNull BitmapFactory.Options options) {
return !TextUtils.isEmpty(options.outMimeType) && options.outMimeType.equalsIgnoreCase(
public static boolean isPng(@Nullable BitmapFactory.Options options) {
return options != null && !TextUtils.isEmpty(options.outMimeType) && options.outMimeType.equalsIgnoreCase(
IMAGE_TYPE_PNG);
}

public static boolean isGif(@NonNull BitmapFactory.Options options) {
return !TextUtils.isEmpty(options.outMimeType) && options.outMimeType.equalsIgnoreCase(
public static boolean isGif(@Nullable BitmapFactory.Options options) {
return options != null && !TextUtils.isEmpty(options.outMimeType) && options.outMimeType.equalsIgnoreCase(
IMAGE_TYPE_GIF);
}

Expand Down
Loading