Skip to content

Commit

Permalink
feat(android): vfs support custom image loader (#3947)
Browse files Browse the repository at this point in the history
Co-authored-by: maxli <[email protected]>
Co-authored-by: OpenHippy <[email protected]>
  • Loading branch information
3 people authored Jul 9, 2024
1 parent a7dcbdb commit 5f40f8f
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 70 deletions.
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

0 comments on commit 5f40f8f

Please sign in to comment.