mirror of https://github.com/NekoX-Dev/NekoX.git
703 lines
24 KiB
Java
703 lines
24 KiB
Java
package org.telegram.messenger.utils;
|
|
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.BitmapFactory;
|
|
import android.os.Build;
|
|
|
|
import org.telegram.messenger.AndroidUtilities;
|
|
import org.telegram.messenger.BuildVars;
|
|
import org.telegram.messenger.DispatchQueuePoolBackground;
|
|
import org.telegram.messenger.FileLoader;
|
|
import org.telegram.messenger.FileLog;
|
|
import org.telegram.messenger.SharedConfig;
|
|
import org.telegram.messenger.Utilities;
|
|
import org.telegram.ui.Components.RLottieDrawable;
|
|
|
|
import java.io.File;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.io.OutputStream;
|
|
import java.io.RandomAccessFile;
|
|
import java.nio.ByteBuffer;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.Comparator;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.concurrent.CountDownLatch;
|
|
import java.util.concurrent.LinkedBlockingQueue;
|
|
import java.util.concurrent.ThreadPoolExecutor;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
public class BitmapsCache {
|
|
|
|
public final static int FRAME_RESULT_OK = 0;
|
|
public final static int FRAME_RESULT_NO_FRAME = -1;
|
|
public static final int COMPRESS_QUALITY_DEFAULT = 60;
|
|
private final Cacheable source;
|
|
String fileName;
|
|
int w;
|
|
int h;
|
|
|
|
ArrayList<FrameOffset> frameOffsets = new ArrayList<>();
|
|
|
|
|
|
final boolean useSharedBuffers;
|
|
static ConcurrentHashMap<Thread, byte[]> sharedBuffers = new ConcurrentHashMap();
|
|
static volatile boolean cleanupScheduled;
|
|
byte[] bufferTmp;
|
|
|
|
private final static int N = Utilities.clamp(Runtime.getRuntime().availableProcessors() - 2, 8, 1);
|
|
private static ThreadPoolExecutor bitmapCompressExecutor;
|
|
private final Object mutex = new Object();
|
|
private int frameIndex;
|
|
boolean error;
|
|
volatile boolean fileExist;
|
|
int compressQuality;
|
|
|
|
final File file;
|
|
|
|
private int tryCount;
|
|
|
|
public AtomicBoolean cancelled = new AtomicBoolean(false);
|
|
private Runnable cleanupSharedBuffers = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
for (Thread thread : sharedBuffers.keySet()) {
|
|
if (!thread.isAlive()) {
|
|
sharedBuffers.remove(thread);
|
|
}
|
|
}
|
|
if (!sharedBuffers.isEmpty()) {
|
|
AndroidUtilities.runOnUIThread(cleanupSharedBuffers, 5000);
|
|
} else {
|
|
cleanupScheduled = false;
|
|
}
|
|
}
|
|
};
|
|
|
|
public BitmapsCache(File sourceFile, Cacheable source, CacheOptions options, int w, int h, boolean noLimit) {
|
|
this.source = source;
|
|
this.w = w;
|
|
this.h = h;
|
|
compressQuality = options.compressQuality;
|
|
fileName = sourceFile.getName();
|
|
if (bitmapCompressExecutor == null) {
|
|
bitmapCompressExecutor = new ThreadPoolExecutor(N, N, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
|
|
}
|
|
|
|
File fileTmo = new File(FileLoader.checkDirectory(FileLoader.MEDIA_DIR_CACHE), "acache");
|
|
file = new File(fileTmo, fileName + "_" + w + "_" + h + (noLimit ? "_nolimit" : " ") + ".pcache2");
|
|
useSharedBuffers = w < AndroidUtilities.dp(60) && h < AndroidUtilities.dp(60);
|
|
|
|
// check cache created in file load queue only for high devices
|
|
if (SharedConfig.getDevicePerformanceClass() >= SharedConfig.PERFORMANCE_CLASS_HIGH) {
|
|
fileExist = file.exists();
|
|
if (fileExist) {
|
|
RandomAccessFile randomAccessFile = null;
|
|
try {
|
|
randomAccessFile = new RandomAccessFile(file, "r");
|
|
cacheCreated = randomAccessFile.readBoolean();
|
|
if (cacheCreated && frameOffsets.isEmpty()) {
|
|
randomAccessFile.seek(randomAccessFile.readInt());
|
|
int count = randomAccessFile.readInt();
|
|
if (count > 10_000) {
|
|
count = 0;
|
|
}
|
|
fillFrames(randomAccessFile, count);
|
|
if (frameOffsets.size() == 0) {
|
|
cacheCreated = false;
|
|
fileExist = false;
|
|
file.delete();
|
|
} else {
|
|
cachedFile = randomAccessFile;
|
|
}
|
|
}
|
|
} catch (Throwable e) {
|
|
e.printStackTrace();
|
|
file.delete();
|
|
fileExist = false;
|
|
} finally {
|
|
try {
|
|
if (cachedFile != randomAccessFile && randomAccessFile != null) {
|
|
randomAccessFile.close();
|
|
}
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
fileExist = false;
|
|
cacheCreated = false;
|
|
}
|
|
}
|
|
|
|
volatile boolean checkCache;
|
|
volatile boolean cacheCreated;
|
|
volatile boolean recycled;
|
|
|
|
RandomAccessFile cachedFile;
|
|
BitmapFactory.Options options;
|
|
|
|
private static int taskCounter;
|
|
private static CacheGeneratorSharedTools sharedTools;
|
|
|
|
public static void incrementTaskCounter() {
|
|
taskCounter++;
|
|
}
|
|
|
|
public static void decrementTaskCounter() {
|
|
taskCounter--;
|
|
if (taskCounter <= 0) {
|
|
taskCounter = 0;
|
|
RLottieDrawable.lottieCacheGenerateQueue.postRunnable(() -> {
|
|
if (sharedTools != null) {
|
|
sharedTools.release();
|
|
sharedTools = null;
|
|
}
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
public void createCache() {
|
|
try {
|
|
if (file.exists()) {
|
|
RandomAccessFile randomAccessFile = null;
|
|
try {
|
|
randomAccessFile = new RandomAccessFile(file, "r");
|
|
cacheCreated = randomAccessFile.readBoolean();
|
|
if (cacheCreated) {
|
|
frameOffsets.clear();
|
|
randomAccessFile.seek(randomAccessFile.readInt());
|
|
int count = randomAccessFile.readInt();
|
|
if (count > 10_000) {
|
|
count = 0;
|
|
}
|
|
if (count > 0) {
|
|
fillFrames(randomAccessFile, count);
|
|
randomAccessFile.seek(0);
|
|
cachedFile = randomAccessFile;
|
|
fileExist = true;
|
|
return;
|
|
} else {
|
|
fileExist = false;
|
|
cacheCreated = false;
|
|
}
|
|
}
|
|
if (!cacheCreated) {
|
|
file.delete();
|
|
}
|
|
} catch (Throwable e) {
|
|
try {
|
|
file.delete();
|
|
} catch (Throwable e2) {
|
|
|
|
}
|
|
} finally {
|
|
if (cachedFile != randomAccessFile && randomAccessFile != null) {
|
|
try {
|
|
randomAccessFile.close();
|
|
} catch (Throwable e2) {
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
|
|
|
|
if (sharedTools == null) {
|
|
sharedTools = new CacheGeneratorSharedTools();
|
|
}
|
|
sharedTools.allocate(h, w);
|
|
Bitmap[] bitmap = sharedTools.bitmap;
|
|
ByteArrayOutputStream[] byteArrayOutputStream = sharedTools.byteArrayOutputStream;
|
|
CountDownLatch[] countDownLatch = new CountDownLatch[N];
|
|
|
|
ArrayList<FrameOffset> frameOffsets = new ArrayList<>();
|
|
RandomAccessFile finalRandomAccessFile = randomAccessFile;
|
|
|
|
finalRandomAccessFile.writeBoolean(false);
|
|
finalRandomAccessFile.writeInt(0);
|
|
|
|
int index = 0;
|
|
int framePosition = 0;
|
|
|
|
AtomicBoolean closed = new AtomicBoolean(false);
|
|
source.prepareForGenerateCache();
|
|
|
|
while (true) {
|
|
if (countDownLatch[index] != null) {
|
|
try {
|
|
countDownLatch[index].await();
|
|
} catch (InterruptedException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
if (cancelled.get() || closed.get()) {
|
|
if (BuildVars.DEBUG_VERSION) {
|
|
FileLog.d("cancelled cache generation");
|
|
}
|
|
closed.set(true);
|
|
for (int i = 0; i < N; i++) {
|
|
if (countDownLatch[i] != null) {
|
|
try {
|
|
countDownLatch[i].await();
|
|
} catch (InterruptedException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
if (bitmap[i] != null) {
|
|
try {
|
|
bitmap[i].recycle();
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
}
|
|
}
|
|
randomAccessFile.close();
|
|
source.releaseForGenerateCache();
|
|
return;
|
|
}
|
|
|
|
if (source.getNextFrame(bitmap[index]) != 1) {
|
|
break;
|
|
}
|
|
countDownLatch[index] = new CountDownLatch(1);
|
|
|
|
|
|
int finalIndex = index;
|
|
int finalFramePosition = framePosition;
|
|
RandomAccessFile finalRandomAccessFile1 = randomAccessFile;
|
|
bitmapCompressExecutor.execute(() -> {
|
|
if (cancelled.get() || closed.get()) {
|
|
return;
|
|
}
|
|
|
|
Bitmap.CompressFormat format = Bitmap.CompressFormat.WEBP;
|
|
if (Build.VERSION.SDK_INT <= 26) {
|
|
format = Bitmap.CompressFormat.PNG;
|
|
}
|
|
bitmap[finalIndex].compress(format, compressQuality, byteArrayOutputStream[finalIndex]);
|
|
int size = byteArrayOutputStream[finalIndex].count;
|
|
|
|
try {
|
|
synchronized (mutex) {
|
|
FrameOffset frameOffset = new FrameOffset(finalFramePosition);
|
|
frameOffset.frameOffset = (int) finalRandomAccessFile1.length();
|
|
|
|
frameOffsets.add(frameOffset);
|
|
|
|
finalRandomAccessFile1.write(byteArrayOutputStream[finalIndex].buf, 0, size);
|
|
frameOffset.frameSize = size;
|
|
byteArrayOutputStream[finalIndex].reset();
|
|
}
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
try {
|
|
finalRandomAccessFile1.close();
|
|
} catch (Exception e2) {
|
|
} finally {
|
|
closed.set(true);
|
|
}
|
|
}
|
|
|
|
countDownLatch[finalIndex].countDown();
|
|
});
|
|
|
|
index++;
|
|
framePosition++;
|
|
if (index >= N) {
|
|
index = 0;
|
|
}
|
|
}
|
|
for (int i = 0; i < N; i++) {
|
|
if (countDownLatch[i] != null) {
|
|
try {
|
|
countDownLatch[i].await();
|
|
} catch (InterruptedException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
|
|
int arrayOffset = (int) randomAccessFile.length();
|
|
|
|
Collections.sort(frameOffsets, Comparator.comparingInt(o -> o.index));
|
|
byteArrayOutputStream[0].reset();
|
|
int count = frameOffsets.size();
|
|
byteArrayOutputStream[0].writeInt(count);
|
|
for (int i = 0; i < frameOffsets.size(); i++) {
|
|
byteArrayOutputStream[0].writeInt(frameOffsets.get(i).frameOffset);
|
|
byteArrayOutputStream[0].writeInt(frameOffsets.get(i).frameSize);
|
|
}
|
|
randomAccessFile.write(byteArrayOutputStream[0].buf, 0, 4 + 4 * 2 * count);
|
|
byteArrayOutputStream[0].reset();
|
|
randomAccessFile.seek(0);
|
|
randomAccessFile.writeBoolean(true);
|
|
randomAccessFile.writeInt(arrayOffset);
|
|
closed.set(true);
|
|
randomAccessFile.close();
|
|
|
|
this.frameOffsets.clear();
|
|
this.frameOffsets.addAll(frameOffsets);
|
|
cachedFile = new RandomAccessFile(file, "r");
|
|
cacheCreated = true;
|
|
fileExist = true;
|
|
} catch (FileNotFoundException e) {
|
|
e.printStackTrace();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
} finally {
|
|
source.releaseForGenerateCache();
|
|
}
|
|
}
|
|
|
|
private void fillFrames(RandomAccessFile randomAccessFile, int count) throws Throwable {
|
|
if (count == 0) {
|
|
return;
|
|
}
|
|
byte[] bytes = new byte[4 * 2 * count];
|
|
randomAccessFile.read(bytes);
|
|
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
|
|
for (int i = 0; i < count; i++) {
|
|
FrameOffset frameOffset = new FrameOffset(i);
|
|
frameOffset.frameOffset = byteBuffer.getInt();
|
|
frameOffset.frameSize = byteBuffer.getInt();
|
|
frameOffsets.add(frameOffset);
|
|
}
|
|
}
|
|
|
|
public void cancelCreate() {
|
|
// cancelled.set(true);
|
|
}
|
|
|
|
public int getFrame(Bitmap bitmap, Metadata metadata) {
|
|
int res = getFrame(frameIndex, bitmap);
|
|
metadata.frame = frameIndex;
|
|
if (cacheCreated && !frameOffsets.isEmpty()) {
|
|
frameIndex++;
|
|
if (frameIndex >= frameOffsets.size()) {
|
|
frameIndex = 0;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
public boolean cacheExist() {
|
|
if (checkCache) {
|
|
return cacheCreated;
|
|
}
|
|
RandomAccessFile randomAccessFile = null;
|
|
int framesCount;
|
|
try {
|
|
synchronized (mutex) {
|
|
randomAccessFile = new RandomAccessFile(file, "r");
|
|
cacheCreated = randomAccessFile.readBoolean();
|
|
randomAccessFile.seek(randomAccessFile.readInt());
|
|
framesCount = randomAccessFile.readInt();
|
|
if (framesCount <= 0) {
|
|
cacheCreated = false;
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
|
|
} finally {
|
|
if (randomAccessFile != null) {
|
|
try {
|
|
randomAccessFile.close();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
checkCache = true;
|
|
return cacheCreated;
|
|
}
|
|
|
|
public int getFrame(int index, Bitmap bitmap) {
|
|
if (error) {
|
|
return FRAME_RESULT_NO_FRAME;
|
|
}
|
|
RandomAccessFile randomAccessFile = null;
|
|
try {
|
|
FrameOffset selectedFrame;
|
|
if (!cacheCreated && !fileExist) {
|
|
return FRAME_RESULT_NO_FRAME;
|
|
}
|
|
byte[] bufferTmp;
|
|
if (!cacheCreated || cachedFile == null) {
|
|
randomAccessFile = new RandomAccessFile(file, "r");
|
|
cacheCreated = randomAccessFile.readBoolean();
|
|
if (cacheCreated && frameOffsets.isEmpty()) {
|
|
randomAccessFile.seek(randomAccessFile.readInt());
|
|
int count = randomAccessFile.readInt();
|
|
fillFrames(randomAccessFile, count);
|
|
}
|
|
if (frameOffsets.size() == 0) {
|
|
cacheCreated = false;
|
|
}
|
|
|
|
if (!cacheCreated) {
|
|
randomAccessFile.close();
|
|
randomAccessFile = null;
|
|
return FRAME_RESULT_NO_FRAME;
|
|
}
|
|
} else {
|
|
randomAccessFile = cachedFile;
|
|
}
|
|
if (frameOffsets.size() == 0) {
|
|
return FRAME_RESULT_NO_FRAME;
|
|
}
|
|
index = Utilities.clamp(index, frameOffsets.size() - 1, 0);
|
|
selectedFrame = frameOffsets.get(index);
|
|
randomAccessFile.seek(selectedFrame.frameOffset);
|
|
|
|
bufferTmp = getBuffer(selectedFrame);
|
|
randomAccessFile.readFully(bufferTmp, 0, selectedFrame.frameSize);
|
|
if (!recycled) {
|
|
cachedFile = randomAccessFile;
|
|
} else {
|
|
cachedFile = null;
|
|
randomAccessFile.close();
|
|
}
|
|
|
|
if (options == null) {
|
|
options = new BitmapFactory.Options();
|
|
}
|
|
options.inBitmap = bitmap;
|
|
BitmapFactory.decodeByteArray(bufferTmp, 0, selectedFrame.frameSize, options);
|
|
return FRAME_RESULT_OK;
|
|
} catch (FileNotFoundException e) {
|
|
|
|
} catch (Throwable e) {
|
|
FileLog.e(e, false);
|
|
tryCount++;
|
|
if (tryCount > 10) {
|
|
error = true;
|
|
}
|
|
}
|
|
|
|
if (error && randomAccessFile != null) {
|
|
try {
|
|
randomAccessFile.close();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
// source.getFirstFrame(bitmap);
|
|
return FRAME_RESULT_NO_FRAME;
|
|
}
|
|
|
|
private byte[] getBuffer(FrameOffset selectedFrame) {
|
|
boolean useSharedBuffers = this.useSharedBuffers && Thread.currentThread().getName().startsWith(DispatchQueuePoolBackground.THREAD_PREFIX);
|
|
byte[] bufferTmp;
|
|
if (useSharedBuffers) {
|
|
bufferTmp = sharedBuffers.get(Thread.currentThread());
|
|
} else {
|
|
bufferTmp = this.bufferTmp;
|
|
}
|
|
|
|
if (bufferTmp == null || bufferTmp.length < selectedFrame.frameSize) {
|
|
bufferTmp = new byte[(int) (selectedFrame.frameSize * 1.3f)];
|
|
if (useSharedBuffers) {
|
|
sharedBuffers.put(Thread.currentThread(), bufferTmp);
|
|
if (!cleanupScheduled) {
|
|
cleanupScheduled = true;
|
|
AndroidUtilities.runOnUIThread(cleanupSharedBuffers, 5000);
|
|
}
|
|
} else {
|
|
this.bufferTmp = bufferTmp;
|
|
}
|
|
}
|
|
return bufferTmp;
|
|
}
|
|
|
|
public boolean needGenCache() {
|
|
return !cacheCreated || !fileExist;
|
|
}
|
|
|
|
public void recycle() {
|
|
if (cachedFile != null) {
|
|
try {
|
|
cachedFile.close();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
cachedFile = null;
|
|
}
|
|
recycled = true;
|
|
}
|
|
|
|
public int getFrameCount() {
|
|
return frameOffsets.size();
|
|
}
|
|
|
|
private class FrameOffset {
|
|
final int index;
|
|
int frameSize;
|
|
int frameOffset;
|
|
|
|
private FrameOffset(int index) {
|
|
this.index = index;
|
|
}
|
|
}
|
|
|
|
public interface Cacheable {
|
|
void prepareForGenerateCache();
|
|
|
|
int getNextFrame(Bitmap bitmap);
|
|
|
|
void releaseForGenerateCache();
|
|
|
|
Bitmap getFirstFrame(Bitmap bitmap);
|
|
}
|
|
|
|
public static class ByteArrayOutputStream extends OutputStream {
|
|
|
|
protected byte buf[];
|
|
|
|
protected int count;
|
|
|
|
public ByteArrayOutputStream() {
|
|
this(32);
|
|
}
|
|
|
|
public ByteArrayOutputStream(int size) {
|
|
buf = new byte[size];
|
|
}
|
|
|
|
private void ensureCapacity(int minCapacity) {
|
|
if (minCapacity - buf.length > 0) {
|
|
grow(minCapacity);
|
|
}
|
|
}
|
|
|
|
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
|
|
|
|
private void grow(int minCapacity) {
|
|
int oldCapacity = buf.length;
|
|
int newCapacity = oldCapacity << 1;
|
|
if (newCapacity - minCapacity < 0)
|
|
newCapacity = minCapacity;
|
|
if (newCapacity - MAX_ARRAY_SIZE > 0)
|
|
newCapacity = hugeCapacity(minCapacity);
|
|
buf = Arrays.copyOf(buf, newCapacity);
|
|
}
|
|
|
|
private static int hugeCapacity(int minCapacity) {
|
|
if (minCapacity < 0) // overflow
|
|
throw new OutOfMemoryError();
|
|
return (minCapacity > MAX_ARRAY_SIZE) ?
|
|
Integer.MAX_VALUE :
|
|
MAX_ARRAY_SIZE;
|
|
}
|
|
|
|
public synchronized void write(int b) {
|
|
ensureCapacity(count + 1);
|
|
buf[count] = (byte) b;
|
|
count += 1;
|
|
}
|
|
|
|
public void writeInt(int value) {
|
|
ensureCapacity(count + 4);
|
|
buf[count] = (byte) (value >>> 24);
|
|
buf[count + 1] = (byte) (value >>> 16);
|
|
buf[count + 2] = (byte) (value >>> 8);
|
|
buf[count + 3] = (byte) (value);
|
|
count += 4;
|
|
}
|
|
|
|
public synchronized void write(byte b[], int off, int len) {
|
|
if ((off < 0) || (off > b.length) || (len < 0) ||
|
|
((off + len) - b.length > 0)) {
|
|
throw new IndexOutOfBoundsException();
|
|
}
|
|
ensureCapacity(count + len);
|
|
System.arraycopy(b, off, buf, count, len);
|
|
count += len;
|
|
}
|
|
|
|
public synchronized void writeTo(OutputStream out) throws IOException {
|
|
out.write(buf, 0, count);
|
|
}
|
|
|
|
public synchronized void reset() {
|
|
count = 0;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
public static class Metadata {
|
|
public int frame;
|
|
}
|
|
|
|
public static class CacheOptions {
|
|
public int compressQuality = 100;
|
|
public boolean fallback = false;
|
|
}
|
|
|
|
private static class CacheGeneratorSharedTools {
|
|
ByteArrayOutputStream[] byteArrayOutputStream = new ByteArrayOutputStream[N];
|
|
private Bitmap[] bitmap = new Bitmap[N];
|
|
|
|
private int lastSize;
|
|
|
|
void allocate(int h, int w) {
|
|
int size = (w << 16) + h;
|
|
boolean recreateBitmaps = false;
|
|
if (lastSize != size) {
|
|
recreateBitmaps = true;
|
|
}
|
|
lastSize = size;
|
|
for (int i = 0; i < N; i++) {
|
|
if (recreateBitmaps || bitmap[i] == null) {
|
|
if (bitmap[i] != null) {
|
|
Bitmap bitmapToRecycle = bitmap[i];
|
|
Utilities.globalQueue.postRunnable(() -> {
|
|
try {
|
|
bitmapToRecycle.recycle();
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
});
|
|
}
|
|
bitmap[i] = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
|
|
}
|
|
if (byteArrayOutputStream[i] == null) {
|
|
byteArrayOutputStream[i] = new ByteArrayOutputStream(w * h * 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
void release() {
|
|
ArrayList<Bitmap> bitmapsToRecycle = null;
|
|
for (int i = 0; i < N; i++) {
|
|
if (bitmap[i] != null) {
|
|
if (bitmapsToRecycle == null) {
|
|
bitmapsToRecycle = new ArrayList<>();
|
|
}
|
|
bitmapsToRecycle.add(bitmap[i]);
|
|
}
|
|
bitmap[i] = null;
|
|
byteArrayOutputStream[i] = null;
|
|
}
|
|
if (!bitmapsToRecycle.isEmpty()) {
|
|
ArrayList<Bitmap> finalBitmapsToRecycle = bitmapsToRecycle;
|
|
Utilities.globalQueue.postRunnable(() -> {
|
|
for (Bitmap bitmap : finalBitmapsToRecycle) {
|
|
bitmap.recycle();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|