diff --git a/app/build.gradle b/app/build.gradle index 104132d4e..80bdd1c00 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -63,7 +63,7 @@ dependencies { compile 'com.github.seven332:conaco:0.1.3-eh' compile 'com.github.seven332:easyrecyclerview:0.1.0' compile 'com.github.seven332:drawerLayout:0.1.0' - compile 'com.github.seven332:image:0.1.6-jpeg-png-gif' + compile 'com.github.seven332:image:0.1.7-jpeg-png-gif' compile 'com.github.seven332:okhttp:3.2.0-eh' compile 'com.github.seven332:refreshlayout:0.1.0' compile 'com.github.seven332:ripple:0.1.1' diff --git a/app/src/main/java/com/hippo/ehviewer/ImageBitmapHelper.java b/app/src/main/java/com/hippo/ehviewer/ImageBitmapHelper.java index aa8a10128..519433741 100644 --- a/app/src/main/java/com/hippo/ehviewer/ImageBitmapHelper.java +++ b/app/src/main/java/com/hippo/ehviewer/ImageBitmapHelper.java @@ -20,7 +20,6 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.hippo.conaco.ValueHelper; -import com.hippo.image.Image; import com.hippo.image.ImageBitmap; import com.hippo.streampipe.InputStreamPipe; @@ -35,12 +34,7 @@ public class ImageBitmapHelper implements ValueHelper { public ImageBitmap decode(@NonNull InputStreamPipe isPipe) { try { isPipe.obtain(); - Image image = Image.decode(isPipe.open(), false); - if (image != null) { - return new ImageBitmap(image); - } else { - return null; - } + return ImageBitmap.decode(isPipe.open()); } catch (OutOfMemoryError e) { return null; } catch (IOException e) { 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 ecb4f02e6..d2db96ba4 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/GalleryProvider.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/GalleryProvider.java @@ -23,9 +23,9 @@ import android.support.annotation.UiThread; import com.hippo.beerbelly.LruCache; import com.hippo.gl.glrenderer.GLCanvas; +import com.hippo.gl.glrenderer.ImageWrapper; 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; 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 0e628aad4..d6b3a1244 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.ImageWrapper; +import com.hippo.gl.glrenderer.ImageWrapper; public interface GalleryProviderListener { diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/gl/ImageView.java b/app/src/main/java/com/hippo/ehviewer/gallery/gl/ImageView.java index 3a4f8572a..f22a6dd08 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/gl/ImageView.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/gl/ImageView.java @@ -206,6 +206,7 @@ public class ImageView extends GLView implements ImageTexture.Callback { // Remove callback if (mImageTexture != null) { mImageTexture.setCallback(null); + mImageTexture.stop(); } int oldTextureWidth = mTextureWidth; @@ -229,6 +230,7 @@ public class ImageView extends GLView implements ImageTexture.Callback { getValidRect(mValidRect); if (!mValidRect.isEmpty()) { startAnimation(mAlphaAnimation, true); + mImageTexture.start(); } } else { mTextureWidth = 1; diff --git a/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java b/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java index d001e3804..6569a4cc0 100644 --- a/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java +++ b/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java @@ -1347,27 +1347,34 @@ public final class SpiderQueen implements Runnable { Image image = null; String error = null; + InputStream is; + + pipe.obtain(); try { - pipe.obtain(); - // TODO how to keep stream open - image = Image.decode(pipe.open(), false); + is = new AutoCloseInputStream(pipe, pipe.open()); + } catch (IOException e) { + // Can't open pipe + error = GetText.getString(R.string.error_reading_failed); + is = null; + pipe.close(); + pipe.release(); + } + + if (is != null) { + image = Image.decode(is, true); if (image == null) { error = GetText.getString(R.string.error_decoding_failed); } - resetDecodeIndex(); - } catch (IOException e) { - resetDecodeIndex(); - error = GetText.getString(R.string.error_reading_failed); - } finally { - pipe.close(); - pipe.release(); - // Notify - if (image != null) { - notifyGetImageSuccess(index, image); - } else { - notifyGetImageFailure(index, error); - } } + + // Notify + if (image != null) { + notifyGetImageSuccess(index, image); + } else { + notifyGetImageFailure(index, error); + } + + resetDecodeIndex(); } if (DEBUG_LOG) { @@ -1376,6 +1383,33 @@ public final class SpiderQueen implements Runnable { } } + private class AutoCloseInputStream extends InputStream { + + private final InputStreamPipe mPipe; + private final InputStream mIs; + + public AutoCloseInputStream(InputStreamPipe pipe, InputStream is) { + mPipe = pipe; + mIs = is; + } + + @Override + public int read() throws IOException { + return mIs.read(); + } + + @Override + public int read(@NonNull byte[] buffer, int byteOffset, int byteCount) throws IOException { + return mIs.read(buffer, byteOffset, byteCount); + } + + @Override + public void close() throws IOException { + mPipe.close(); + mPipe.release(); + } + } + public interface OnSpiderListener { void onGetPages(int pages); 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 40cded973..a502f823e 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java @@ -61,8 +61,8 @@ import com.hippo.ehviewer.gallery.gl.ImageView; import com.hippo.ehviewer.widget.GalleryGuideView; import com.hippo.ehviewer.widget.ReversibleSeekBar; import com.hippo.gl.glrenderer.ImageTexture; +import com.hippo.gl.glrenderer.ImageWrapper; import com.hippo.gl.view.GLRootView; -import com.hippo.image.ImageWrapper; import com.hippo.unifile.UniFile; import com.hippo.util.SystemUiHelper; import com.hippo.widget.ColorView; diff --git a/app/src/main/java/com/hippo/gl/glrenderer/ImageMovableTextTexture.java b/app/src/main/java/com/hippo/gl/glrenderer/ImageMovableTextTexture.java index c169cf102..4aa928e4d 100644 --- a/app/src/main/java/com/hippo/gl/glrenderer/ImageMovableTextTexture.java +++ b/app/src/main/java/com/hippo/gl/glrenderer/ImageMovableTextTexture.java @@ -24,7 +24,6 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.hippo.image.Image; -import com.hippo.image.ImageWrapper; import java.util.Arrays; diff --git a/app/src/main/java/com/hippo/gl/glrenderer/ImageSpriteTexture.java b/app/src/main/java/com/hippo/gl/glrenderer/ImageSpriteTexture.java index d7a2e237f..bdc90fc1d 100644 --- a/app/src/main/java/com/hippo/gl/glrenderer/ImageSpriteTexture.java +++ b/app/src/main/java/com/hippo/gl/glrenderer/ImageSpriteTexture.java @@ -19,7 +19,6 @@ package com.hippo.gl.glrenderer; import android.graphics.RectF; import android.support.annotation.NonNull; -import com.hippo.image.ImageWrapper; import com.hippo.yorozuya.AssertUtils; public class ImageSpriteTexture extends ImageTexture { 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 224f60e7c..663b71c86 100644 --- a/app/src/main/java/com/hippo/gl/glrenderer/ImageTexture.java +++ b/app/src/main/java/com/hippo/gl/glrenderer/ImageTexture.java @@ -18,13 +18,14 @@ package com.hippo.gl.glrenderer; import android.graphics.RectF; import android.graphics.drawable.Animatable; +import android.os.Process; import android.os.SystemClock; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import com.hippo.gl.annotation.RenderThread; import com.hippo.gl.view.GLRoot; -import com.hippo.image.ImageWrapper; +import com.hippo.yorozuya.thread.InfiniteThreadExecutor; import com.hippo.yorozuya.thread.PVLock; import com.hippo.yorozuya.thread.PriorityThreadFactory; @@ -33,8 +34,9 @@ import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.LinkedList; +import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; public class ImageTexture implements Texture, Animatable { @@ -59,7 +61,7 @@ public class ImageTexture implements Texture, Animatable { // In this 16ms, we use about 4~8 ms to upload tiles. private static final long UPLOAD_TILE_LIMIT = 4; // ms - private static final PriorityThreadFactory sThreadFactory; + private static final Executor sThreadExecutor; private static final PVLock sPVLock; private static Tile sSmallFreeTileHead = null; @@ -77,17 +79,19 @@ public class ImageTexture implements Texture, Animatable { private final RectF mSrcRect = new RectF(); private final RectF mDestRect = new RectF(); - private final AtomicBoolean mFrameDirty = new AtomicBoolean(); private boolean mImageBusy = false; + private final AtomicBoolean mRunning = new AtomicBoolean(); + private final AtomicBoolean mFrameDirty = new AtomicBoolean(); private final AtomicBoolean mNeedRelease = new AtomicBoolean(); private final AtomicBoolean mReleased = new AtomicBoolean(); - private final AtomicReference mAnimateThread = new AtomicReference<>(); + private Runnable mAnimateRunnable = null; private WeakReference mCallback; static { - sThreadFactory = new PriorityThreadFactory("ImageTexture$AnimateTask", android.os.Process.THREAD_PRIORITY_BACKGROUND); + sThreadExecutor = new InfiniteThreadExecutor(10 * 1000, new LinkedList<>(), + new PriorityThreadFactory("ImageTexture$AnimateTask", Process.THREAD_PRIORITY_BACKGROUND)); sPVLock = new PVLock(3); } @@ -253,37 +257,25 @@ public class ImageTexture implements Texture, Animatable { long lastDelay = -1L; synchronized (mImage) { - // Check released - if (mReleased.get()) { - return; - } - // Check image busy - if (mImageBusy) { - // Image is busy, means it is recycling - return; - } - if (mNeedRelease.get()) { - // Need release + // Check released, image busy, Need release + if (mReleased.get() || mImageBusy || mNeedRelease.get()) { + mAnimateRunnable = null; return; } // Obtain image mImageBusy = true; } - boolean interrupted = Thread.currentThread().isInterrupted(); - if (!mImage.isCompleted()) { - if (!interrupted) { - try { - sPVLock.p(); - } catch (InterruptedException e) { - interrupted = true; - } - if (!interrupted && !mNeedRelease.get()) { - mImage.complete(); - } - sPVLock.v(); + try { + sPVLock.p(); + } catch (InterruptedException e) { + // Ignore } + if (!mNeedRelease.get()) { + mImage.complete(); + } + sPVLock.v(); } int frameCount = mImage.getFrameCount(); @@ -291,26 +283,19 @@ public class ImageTexture implements Texture, Animatable { synchronized (mImage) { // Release image mImageBusy = false; + // Check need release, frameCount <= 1 + if (mNeedRelease.get() || frameCount <= 1) { + mAnimateRunnable = null; + return; + } } - if (interrupted || mNeedRelease.get() || frameCount <= 1) { - return; - } - - while (!Thread.currentThread().isInterrupted()) { + for (;;) { // Obtain synchronized (mImage) { - // Check released - if (mReleased.get()) { - return; - } - // Check image busy - if (mImageBusy) { - // Image is busy, means it is recycling - return; - } - if (mNeedRelease.get()) { - // Need release + // Check released, image busy, Need release, not running + if (mReleased.get() || mImageBusy || mNeedRelease.get() || !mRunning.get()) { + mAnimateRunnable = null; return; } // Obtain image @@ -337,7 +322,7 @@ public class ImageTexture implements Texture, Animatable { try { Thread.sleep(delay); } catch (InterruptedException e) { - break; + // Ignore } } } @@ -371,8 +356,6 @@ public class ImageTexture implements Texture, Animatable { mImageBusy = false; } } - - mAnimateThread.lazySet(null); } } @@ -443,10 +426,10 @@ public class ImageTexture implements Texture, Animatable { mTiles = list.toArray(new Tile[list.size()]); - if (!mImage.isCompleted() || mImage.getFrameCount() > 1) { - Thread thread = sThreadFactory.newThread(new AnimateRunnable()); - mAnimateThread.lazySet(thread); - thread.start(); + if (!mImage.isCompleted()) { + Runnable runnable = new AnimateRunnable(); + mAnimateRunnable = runnable; + sThreadExecutor.execute(runnable); } } @@ -470,33 +453,31 @@ public class ImageTexture implements Texture, Animatable { @Override public void start() { - if (mReleased.get() || (mImage.isCompleted() && mImage.getFrameCount() <= 1)) { + if (mReleased.get() || mNeedRelease.get() || (mImage.isCompleted() && mImage.getFrameCount() <= 1) || + mRunning.get()) { return; } - Thread thread = mAnimateThread.get(); - if (thread == null) { - thread = sThreadFactory.newThread(new AnimateRunnable()); - mAnimateThread.set(thread); - thread.start(); - } - } + mRunning.lazySet(true); - @Override - public void stop() { - if (!mReleased.get() && (mImage.isCompleted() && mImage.getFrameCount() > 1)) { - Thread thread = mAnimateThread.getAndSet(null); - if (null != thread) { - thread.interrupt(); + synchronized (mImage) { + if (mAnimateRunnable == null) { + Runnable runnable = new AnimateRunnable(); + mAnimateRunnable = runnable; + sThreadExecutor.execute(runnable); } } } @Override - public boolean isRunning() { - return mAnimateThread.get() != null; + public void stop() { + mRunning.lazySet(false); } + @Override + public boolean isRunning() { + return mRunning.get(); + } private boolean uploadNextTile(GLCanvas canvas) { if (mUploadIndex == mTiles.length) return true; @@ -663,6 +644,8 @@ public class ImageTexture implements Texture, Animatable { } public void recycle() { + mRunning.lazySet(false); + for (Tile mTile : mTiles) { mTile.free(); } @@ -675,6 +658,7 @@ public class ImageTexture implements Texture, Animatable { mImageBusy = true; } else { releaseNow = false; + mNeedRelease.set(true); } } @@ -684,13 +668,6 @@ public class ImageTexture implements Texture, Animatable { synchronized (mImage) { mImageBusy = false; } - } else { - mNeedRelease.set(true); - } - - Thread thread = mAnimateThread.getAndSet(null); - if (null != thread) { - thread.interrupt(); } } diff --git a/app/src/main/java/com/hippo/gl/glrenderer/ImageWrapper.java b/app/src/main/java/com/hippo/gl/glrenderer/ImageWrapper.java new file mode 100644 index 000000000..4641f4d56 --- /dev/null +++ b/app/src/main/java/com/hippo/gl/glrenderer/ImageWrapper.java @@ -0,0 +1,153 @@ +/* + * Copyright 2016 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.gl.glrenderer; + +import android.graphics.Bitmap; +import android.support.annotation.NonNull; + +import com.hippo.image.Image; + +/** + * A wrapper for {@link Image}. It is useful for multi-usage. + * It handles image recycle automatically. + */ +public class ImageWrapper { + + private final Image mImage; + private int mReferences; + + /** + * Create ImageWrapper + * + * @param image the image should not be obtained or recycled. + */ + public ImageWrapper(@NonNull Image image) { + mImage = image; + } + + /** + * Obtain the image + * + * @return false for the image is recycled and obtain failed + */ + public synchronized boolean obtain() { + if (mImage.isRecycled()) { + return false; + } else { + ++mReferences; + return true; + } + } + + /** + * Release the image + */ + public synchronized void release() { + --mReferences; + if (mReferences <= 0 && !mImage.isRecycled()) { + mImage.recycle(); + } + } + + /** + * @see Image#getFormat() + */ + public int getFormat() { + return mImage.getFormat(); + } + + /** + * @see Image#getFormat() + */ + public int getWidth() { + return mImage.getWidth(); + } + + /** + * @see Image#getHeight() + */ + public int getHeight() { + return mImage.getHeight(); + } + + /** + * @see Image#complete() + */ + public boolean complete() { + synchronized (mImage) { + return mImage.complete(); + } + } + + /** + * @see Image#isCompleted() + */ + public boolean isCompleted() { + return mImage.isCompleted(); + } + + /** + * @see Image#render(int, int, Bitmap, int, int, int, int, boolean, int) + */ + public void render(int srcX, int srcY, Bitmap dst, int dstX, int dstY, + int width, int height, boolean fillBlank, int defaultColor) { + mImage.render(srcX, srcY, dst, dstX, dstY, + width, height, fillBlank, defaultColor); + } + + /** + * @see Image#texImage(boolean, int, int, int, int) + */ + public void texImage(boolean init, int offsetX, int offsetY, int width, int height) { + mImage.texImage(init, offsetX, offsetY, width, height); + } + + /** + * @see Image#advance() + */ + public void advance() { + mImage.advance(); + } + + /** + * @see Image#getDelay() + */ + public int getDelay() { + return mImage.getDelay(); + } + + /** + * @see Image#getFrameCount() + */ + public int getFrameCount() { + return mImage.getFrameCount(); + } + + /** + * @see Image#isOpaque() + */ + public boolean isOpaque() { + return mImage.isOpaque(); + } + + /** + * @see Image#isRecycled() + */ + public boolean isRecycled() { + return mImage.isRecycled(); + } +}