diff --git a/app/build.gradle b/app/build.gradle index 42ff3f26e..89aa0eb16 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -60,7 +60,7 @@ dependencies { compile 'com.android.support:design:23.3.0' compile 'com.github.amlcurran.showcaseview:library:5.4.3' compile 'com.github.anrwatchdog:anrwatchdog:1.2.0' - compile 'com.github.seven332:beerbelly:0.1.1' + compile 'com.github.seven332:beerbelly:0.1.2' compile 'com.github.seven332:conaco:0.1.2-eh' compile 'com.github.seven332:easyrecyclerview:0.1.0' compile 'com.github.seven332:drawerLayout:0.1.0' diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/DirGalleryProvider.java b/app/src/main/java/com/hippo/ehviewer/gallery/DirGalleryProvider.java index 10a89f492..11ee83068 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/DirGalleryProvider.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/DirGalleryProvider.java @@ -84,7 +84,7 @@ public class DirGalleryProvider extends GalleryProvider implements Runnable { } @Override - public void request(int index) { + protected void onRequest(int index) { synchronized (mRequests) { if (!mRequests.contains(index) && index != mDecodingIndex.get()) { mRequests.add(index); @@ -95,7 +95,12 @@ public class DirGalleryProvider extends GalleryProvider implements Runnable { } @Override - public void cancelRequest(int index) { + protected void onForceRequest(int index) { + onRequest(index); + } + + @Override + public void onCancelRequest(int index) { synchronized (mRequests) { mRequests.remove(Integer.valueOf(index)); } diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/EhGalleryProvider.java b/app/src/main/java/com/hippo/ehviewer/gallery/EhGalleryProvider.java index 8fdcbed79..49cf20cce 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/EhGalleryProvider.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/EhGalleryProvider.java @@ -111,7 +111,7 @@ public class EhGalleryProvider extends GalleryProvider implements SpiderQueen.On } @Override - public void request(int index) { + protected void onRequest(int index) { if (mSpiderQueen != null) { Object object = mSpiderQueen.request(index); if (object instanceof Float) { @@ -125,7 +125,7 @@ public class EhGalleryProvider extends GalleryProvider implements SpiderQueen.On } @Override - public void forceRequest(int index) { + protected void onForceRequest(int index) { if (mSpiderQueen != null) { Object object = mSpiderQueen.forceRequest(index); if (object instanceof Float) { @@ -139,7 +139,7 @@ public class EhGalleryProvider extends GalleryProvider implements SpiderQueen.On } @Override - public void cancelRequest(int index) { + protected void onCancelRequest(int index) { if (mSpiderQueen != null) { mSpiderQueen.cancelRequest(index); } diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/GalleryProvider.java b/app/src/main/java/com/hippo/ehviewer/gallery/GalleryProvider.java index a4bd336cc..ecb4f02e6 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/GalleryProvider.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/GalleryProvider.java @@ -21,15 +21,25 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; +import com.hippo.beerbelly.LruCache; import com.hippo.gl.glrenderer.GLCanvas; import com.hippo.gl.view.GLRoot; import com.hippo.image.Image; +import com.hippo.image.ImageWrapper; import com.hippo.unifile.UniFile; import com.hippo.yorozuya.ConcurrentPool; +import com.hippo.yorozuya.IOUtils; +import com.hippo.yorozuya.MathUtils; +import com.hippo.yorozuya.NumberUtils; import com.hippo.yorozuya.OSUtils; +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public abstract class GalleryProvider { @@ -48,6 +58,8 @@ public abstract class GalleryProvider { private volatile GalleryProviderListener mGalleryProviderListener; private volatile GLRoot mGLRoot; + private final ImageCache mImageCache = new ImageCache(); + private boolean mStarted = false; @UiThread @@ -63,25 +75,40 @@ public abstract class GalleryProvider { @UiThread public void stop() { OSUtils.checkMainLoop(); + mImageCache.close(); } public void setGLRoot(GLRoot glRoot) { mGLRoot = glRoot; } - /** * @return {@link #STATE_WAIT} for wait, 0 for empty */ public abstract int size(); - public abstract void request(int index); - - public void forceRequest(int index) { - request(index); + public final void request(int index) { + ImageWrapper imageWrapper = mImageCache.get(index); + if (imageWrapper != null) { + notifyPageSucceed(index, imageWrapper); + } else { + onRequest(index); + } } - public abstract void cancelRequest(int index); + public final void forceRequest(int index) { + onForceRequest(index); + } + + protected abstract void onRequest(int index); + + protected abstract void onForceRequest(int index); + + public final void cancelRequest(int index) { + onCancelRequest(index); + } + + protected abstract void onCancelRequest(int index); public abstract String getError(); @@ -126,6 +153,12 @@ public abstract class GalleryProvider { } public void notifyPageSucceed(int index, Image image) { + ImageWrapper imageWrapper = new ImageWrapper(image); + mImageCache.add(index, imageWrapper); + notifyPageSucceed(index, imageWrapper); + } + + public void notifyPageSucceed(int index, ImageWrapper image) { notify(NotifyTask.TYPE_SUCCEED, index, 0.0f, image, null); } @@ -133,7 +166,7 @@ public abstract class GalleryProvider { notify(NotifyTask.TYPE_FAILED, index, 0.0f, null, error); } - private void notify(@NotifyTask.Type int type, int index, float percent, Image image, String error) { + private void notify(@NotifyTask.Type int type, int index, float percent, ImageWrapper image, String error) { GalleryProviderListener listener = mGalleryProviderListener; if (listener == null) { return; @@ -171,7 +204,7 @@ public abstract class GalleryProvider { private int mType; private int mIndex; private float mPercent; - private Image mImage; + private ImageWrapper mImage; private String mError; public NotifyTask(GalleryProviderListener galleryProviderListener, ConcurrentPool pool) { @@ -179,7 +212,7 @@ public abstract class GalleryProvider { mPool = pool; } - public void setData(@Type int type, int index, float percent, Image image, String error) { + public void setData(@Type int type, int index, float percent, ImageWrapper image, String error) { mType = type; mIndex = index; mPercent = percent; @@ -220,4 +253,67 @@ public abstract class GalleryProvider { return false; } } + + private static class ImageCache extends LruCache { + + private static final long MAX_CACHE_SIZE = 128 * 1024 * 1024; + private static final long MIN_CACHE_SIZE = 32 * 1024 * 1024; + + private static final String PROCFS_MEMFILE = "/proc/meminfo"; + private static final Pattern PROCFS_MEMFILE_FORMAT = + Pattern.compile("^([a-zA-Z]*):[ \t]*([0-9]*)[ \t]kB"); + private static final String MEMTOTAL_STRING = "MemTotal"; + private static long sMemTotal = -2L; + + private static long getTotalMemory() { + if (sMemTotal != -2L) { + return sMemTotal; + } + + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(PROCFS_MEMFILE), 64); + String line; + while ((line = reader.readLine()) != null) { + Matcher matcher = PROCFS_MEMFILE_FORMAT.matcher(line); + if (matcher.find() && MEMTOTAL_STRING.equals(matcher.group(1))) { + long mem = NumberUtils.parseLongSafely(matcher.group(2), -1L); + if (mem != -1L) { + mem *= 1024; + } + sMemTotal = mem; + return mem; + } + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + IOUtils.closeQuietly(reader); + } + sMemTotal = -1L; + return -1L; + } + + public ImageCache() { + super((int) MathUtils.clamp(getTotalMemory() / 16, MIN_CACHE_SIZE, MAX_CACHE_SIZE)); + } + + public void add(Integer key, ImageWrapper value) { + if (value.obtain()) { + put(key, value); + } + } + + @Override + protected int sizeOf(Integer key, ImageWrapper value) { + return value.getWidth() * value.getHeight() * 4; + } + + @Override + protected void entryRemoved(boolean evicted, Integer key, ImageWrapper oldValue, ImageWrapper newValue) { + if (oldValue != null) { + oldValue.release(); + } + } + } } diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/GalleryProviderListener.java b/app/src/main/java/com/hippo/ehviewer/gallery/GalleryProviderListener.java index 05a8f3d76..0e628aad4 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/GalleryProviderListener.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/GalleryProviderListener.java @@ -16,7 +16,7 @@ package com.hippo.ehviewer.gallery; -import com.hippo.image.Image; +import com.hippo.image.ImageWrapper; public interface GalleryProviderListener { @@ -26,7 +26,7 @@ public interface GalleryProviderListener { void onPagePercent(int index, float percent); - void onPageSucceed(int index, Image image); + void onPageSucceed(int index, ImageWrapper image); void onPageFailed(int index, String error); diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ZipGalleryProvider.java b/app/src/main/java/com/hippo/ehviewer/gallery/ZipGalleryProvider.java index a15b0fd74..1b462d610 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/ZipGalleryProvider.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ZipGalleryProvider.java @@ -85,7 +85,7 @@ public class ZipGalleryProvider extends GalleryProvider implements Runnable { } @Override - public void request(int index) { + protected void onRequest(int index) { synchronized (mRequests) { if (!mRequests.contains(index) && index != mDecodingIndex.get()) { mRequests.add(index); @@ -96,7 +96,12 @@ public class ZipGalleryProvider extends GalleryProvider implements Runnable { } @Override - public void cancelRequest(int index) { + protected void onForceRequest(int index) { + onRequest(index); + } + + @Override + protected void onCancelRequest(int index) { synchronized (mRequests) { mRequests.remove(Integer.valueOf(index)); } diff --git a/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java index 729f3f102..80e91271a 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java @@ -62,7 +62,7 @@ import com.hippo.ehviewer.widget.GalleryGuideView; import com.hippo.ehviewer.widget.ReversibleSeekBar; import com.hippo.gl.glrenderer.ImageTexture; import com.hippo.gl.view.GLRootView; -import com.hippo.image.Image; +import com.hippo.image.ImageWrapper; import com.hippo.unifile.UniFile; import com.hippo.util.SystemUiHelper; import com.hippo.widget.ColorView; @@ -549,20 +549,26 @@ public class GalleryActivity extends EhActivity } @Override - public void onPageSucceed(int index, Image image) { + public void onPageSucceed(int index, ImageWrapper image) { GalleryPageView page = findPageByIndex(index); if (page != null) { - ImageTexture imageTexture = new ImageTexture(image); - if (mUploader != null) { - mUploader.addTexture(imageTexture); + if (image.obtain()) { + ImageTexture imageTexture = new ImageTexture(image); + if (mUploader != null) { + mUploader.addTexture(imageTexture); + } + page.showImage(); + page.setImage(imageTexture); + page.setPage(index + 1); + page.setProgress(GalleryPageView.PROGRESS_GONE); + page.setError(null, null); + } else { + // The image is recycled, request again. + // TODO request loop ? + if (mGalleryProvider != null) { + mGalleryProvider.request(index); + } } - page.showImage(); - page.setImage(imageTexture); - page.setPage(index + 1); - page.setProgress(GalleryPageView.PROGRESS_GONE); - page.setError(null, null); - } else { - image.recycle(); } } diff --git a/app/src/main/java/com/hippo/gl/glrenderer/ImageTexture.java b/app/src/main/java/com/hippo/gl/glrenderer/ImageTexture.java index 6d2b4e7a3..b0ef3673a 100644 --- a/app/src/main/java/com/hippo/gl/glrenderer/ImageTexture.java +++ b/app/src/main/java/com/hippo/gl/glrenderer/ImageTexture.java @@ -24,7 +24,7 @@ import android.support.annotation.NonNull; import com.hippo.gl.annotation.RenderThread; import com.hippo.gl.view.GLRoot; -import com.hippo.image.Image; +import com.hippo.image.ImageWrapper; import com.hippo.yorozuya.thread.PVLock; import com.hippo.yorozuya.thread.PriorityThreadFactory; @@ -66,10 +66,10 @@ public class ImageTexture implements Texture, Animatable { private static Tile sLargeFreeTileHead = null; private static final Object sFreeTileLock = new Object(); - private final Image mImage; + private final ImageWrapper mImage; private int mUploadIndex = 0; private final Tile[] mTiles; // Can be modified in different threads. - // Should be protected by "synchronized." + // Should be protected by "synchronized." private final int mWidth; private final int mHeight; @@ -79,7 +79,8 @@ public class ImageTexture implements Texture, Animatable { private final AtomicBoolean mFrameDirty = new AtomicBoolean(); private boolean mImageBusy = false; - private final AtomicBoolean mNeedRecycle = new AtomicBoolean(); + private final AtomicBoolean mNeedRelease = new AtomicBoolean(); + private final AtomicBoolean mReleased = new AtomicBoolean(); private final AtomicReference mAnimateThread = new AtomicReference<>(); @@ -142,7 +143,7 @@ public class ImageTexture implements Texture, Animatable { private int mTileType; public int offsetX; public int offsetY; - public Image image; + public ImageWrapper image; public Tile nextFreeTile; public int contentWidth; public int contentHeight; @@ -244,8 +245,8 @@ public class ImageTexture implements Texture, Animatable { long lastDelay = -1L; synchronized (mImage) { - // Check recycled - if (mImage.isRecycled()) { + // Check released + if (mReleased.get()) { return; } // Check image busy @@ -253,8 +254,8 @@ public class ImageTexture implements Texture, Animatable { // Image is busy, means it is recycling return; } - if (mNeedRecycle.get()) { - // Need recycle + if (mNeedRelease.get()) { + // Need release return; } // Obtain image @@ -270,7 +271,7 @@ public class ImageTexture implements Texture, Animatable { } catch (InterruptedException e) { interrupted = true; } - if (!interrupted && !mNeedRecycle.get()) { + if (!interrupted && !mNeedRelease.get()) { mImage.complete(); } sPVLock.v(); @@ -284,15 +285,15 @@ public class ImageTexture implements Texture, Animatable { mImageBusy = false; } - if (interrupted || mNeedRecycle.get() || frameCount <= 1) { + if (interrupted || mNeedRelease.get() || frameCount <= 1) { return; } while (!Thread.currentThread().isInterrupted()) { // Obtain synchronized (mImage) { - // Check recycled - if (mImage.isRecycled()) { + // Check released + if (mReleased.get()) { return; } // Check image busy @@ -300,8 +301,8 @@ public class ImageTexture implements Texture, Animatable { // Image is busy, means it is recycling return; } - if (mNeedRecycle.get()) { - // Need recycle + if (mNeedRelease.get()) { + // Need release return; } // Obtain image @@ -338,11 +339,11 @@ public class ImageTexture implements Texture, Animatable { public void run() { doRun(); - while (mNeedRecycle.get()) { + while (mNeedRelease.get()) { // Obtain synchronized (mImage) { - // Check recycled - if (mImage.isRecycled()) { + // Check released + if (mReleased.get()) { break; } // Check image busy @@ -354,7 +355,8 @@ public class ImageTexture implements Texture, Animatable { mImageBusy = true; } - mImage.recycle(); + mImage.release(); + mReleased.lazySet(true); synchronized (mImage) { // Release image @@ -366,7 +368,7 @@ public class ImageTexture implements Texture, Animatable { } } - public ImageTexture(@NonNull Image image) { + public ImageTexture(@NonNull ImageWrapper image) { mImage = image; int width = mWidth = image.getWidth(); int height = mHeight = image.getHeight(); @@ -457,7 +459,7 @@ public class ImageTexture implements Texture, Animatable { @Override public void start() { - if (mImage.isRecycled() || (mImage.isCompleted() && mImage.getFrameCount() <= 1)) { + if (mReleased.get() || (mImage.isCompleted() && mImage.getFrameCount() <= 1)) { return; } @@ -471,7 +473,7 @@ public class ImageTexture implements Texture, Animatable { @Override public void stop() { - if (!mImage.isRecycled() && (mImage.isCompleted() && mImage.getFrameCount() > 1)) { + if (!mReleased.get() && (mImage.isCompleted() && mImage.getFrameCount() > 1)) { Thread thread = mAnimateThread.getAndSet(null); if (null != thread) { thread.interrupt(); @@ -481,7 +483,7 @@ public class ImageTexture implements Texture, Animatable { @Override public boolean isRunning() { - return false; + return mAnimateThread.get() != null; } @@ -654,24 +656,25 @@ public class ImageTexture implements Texture, Animatable { mTile.free(); } - boolean recycleNow; + boolean releaseNow; synchronized (mImage) { if (!mImageBusy) { - recycleNow = true; + releaseNow = true; mImageBusy = true; } else { - recycleNow = false; + releaseNow = false; } } - if (recycleNow) { - mImage.recycle(); + if (releaseNow) { + mImage.release(); + mReleased.set(true); synchronized (mImage) { mImageBusy = false; } } else { - mNeedRecycle.set(true); + mNeedRelease.set(true); } Thread thread = mAnimateThread.getAndSet(null);