Browse Source

Merge pull request #1759 from ag2s20150909/epublazy

优化
kunfei 4 năm trước cách đây
mục cha
commit
3799062b8f

+ 2 - 1
app/src/main/java/io/legado/app/help/http/cronet/AbsCallBack.kt

@@ -2,6 +2,7 @@ package io.legado.app.help.http.cronet
 
 
 import io.legado.app.help.http.okHttpClient
 import io.legado.app.help.http.okHttpClient
 import io.legado.app.utils.DebugLog
 import io.legado.app.utils.DebugLog
+import io.legado.app.utils.rethrowAsIOException
 import okhttp3.*
 import okhttp3.*
 import okhttp3.EventListener
 import okhttp3.EventListener
 import okhttp3.MediaType.Companion.toMediaTypeOrNull
 import okhttp3.MediaType.Companion.toMediaTypeOrNull
@@ -123,7 +124,7 @@ abstract class AbsCallBack(
     //UrlResponseInfo可能为null
     //UrlResponseInfo可能为null
     override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) {
     override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) {
         DebugLog.i(javaClass.name, error.message.toString())
         DebugLog.i(javaClass.name, error.message.toString())
-        mException = IOException(error.message, error)
+        mException = error.rethrowAsIOException()
         this.eventListener?.callFailed(mCall, error)
         this.eventListener?.callFailed(mCall, error)
         responseCallback?.onFailure(mCall, error)
         responseCallback?.onFailure(mCall, error)
     }
     }

+ 5 - 2
app/src/main/java/io/legado/app/help/http/cronet/CronetLoader.kt

@@ -55,9 +55,12 @@ object CronetLoader : CronetEngine.Builder.LibraryLoader() {
      * 判断Cronet是否安装完成
      * 判断Cronet是否安装完成
      */
      */
     fun install(): Boolean {
     fun install(): Boolean {
-        if (cacheInstall) {
-            return true
+        synchronized(this) {
+            if (cacheInstall) {
+                return true
+            }
         }
         }
+
         if (AppConfig.isGooglePlay) {
         if (AppConfig.isGooglePlay) {
             return false
             return false
         }
         }

+ 9 - 1
app/src/main/java/io/legado/app/utils/ThrowableExtensions.kt

@@ -1,5 +1,7 @@
 package io.legado.app.utils
 package io.legado.app.utils
 
 
+import java.io.IOException
+
 val Throwable.msg: String
 val Throwable.msg: String
     get() {
     get() {
         val stackTrace = stackTraceToString()
         val stackTrace = stackTraceToString()
@@ -8,4 +10,10 @@ val Throwable.msg: String
             stackTrace.isNotEmpty() -> stackTrace
             stackTrace.isNotEmpty() -> stackTrace
             else -> lMsg
             else -> lMsg
         }
         }
-    }
+    }
+
+fun Throwable.rethrowAsIOException(): IOException {
+    val newException = IOException(this.message)
+    newException.initCause(this)
+    throw newException
+}

+ 529 - 166
epublib/src/main/java/me/ag2s/epublib/zip/AndroidRandomReadableFile.java

@@ -1,5 +1,7 @@
 package me.ag2s.epublib.zip;
 package me.ag2s.epublib.zip;
 
 
+import static me.ag2s.utils.ThrowableUtils.rethrowAsIOException;
+
 import android.content.Context;
 import android.content.Context;
 import android.net.Uri;
 import android.net.Uri;
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelFileDescriptor;
@@ -16,78 +18,156 @@ import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.IOException;
+import java.io.UTFDataFormatException;
 import java.nio.ByteBuffer;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.ByteOrder;
 import java.nio.channels.FileChannel;
 import java.nio.channels.FileChannel;
 
 
+import me.ag2s.utils.AndroidCloseGuard;
+
+@SuppressWarnings("unused")
 public class AndroidRandomReadableFile implements DataInput, Closeable {
 public class AndroidRandomReadableFile implements DataInput, Closeable {
-    private ParcelFileDescriptor pfd;
+    private final ParcelFileDescriptor pfd;
     private FileInputStream fis;
     private FileInputStream fis;
-    private DataInputStream dis;
-    public long pos = 0;
+    private final Object lock = new Object();
+
+    /**
+     * 读取基本类型的buffer
+     */
+    private final byte[] readBuffer = new byte[8];
+    private long pos = 0;
+
+    private final AndroidCloseGuard guard = AndroidCloseGuard.getInstance();
 
 
     public AndroidRandomReadableFile(@NonNull Context context, @NonNull Uri treeUri) throws FileNotFoundException {
     public AndroidRandomReadableFile(@NonNull Context context, @NonNull Uri treeUri) throws FileNotFoundException {
-        try {
-            pfd = context.getContentResolver().openFileDescriptor(treeUri, "r");
-            fis = new FileInputStream(pfd.getFileDescriptor());
-            dis = new DataInputStream(fis);
-        } catch (FileNotFoundException e) {
-            throw e;
-        }
+        pfd = context.getContentResolver().openFileDescriptor(treeUri, "r");
+        fis = new FileInputStream(pfd.getFileDescriptor());
+        guard.open("close");
+
     }
     }
 
 
+
     public final FileDescriptor getFD() {
     public final FileDescriptor getFD() {
         return pfd.getFileDescriptor();
         return pfd.getFileDescriptor();
     }
     }
 
 
+
+    /**
+     * Returns the unique {@link java.nio.channels.FileChannel FileChannel}
+     * object associated with this file.
+     *
+     * <p> The {@link java.nio.channels.FileChannel#position()
+     * position} of the returned channel will always be equal to
+     * this object's file-pointer offset as returned by the {@link
+     * #getFilePointer getFilePointer} method.  Changing this object's
+     * file-pointer offset, whether explicitly or by reading or writing bytes,
+     * will change the position of the channel, and vice versa.  Changing the
+     * file's length via this object will change the length seen via the file
+     * channel, and vice versa.
+     *
+     * @return the file channel associated with this file
+     * @spec JSR-51
+     * @since 1.4
+     */
     public final FileChannel getChannel() {
     public final FileChannel getChannel() {
-        if (fis == null || pos != getPos()) {
-            fis = new FileInputStream(pfd.getFileDescriptor());
+        synchronized (lock) {
+            if (fis == null || pos != getPos()) {
+                fis = new FileInputStream(pfd.getFileDescriptor());
+            }
         }
         }
+
         return fis.getChannel();
         return fis.getChannel();
     }
     }
 
 
     public final FileInputStream getFileInputStream() {
     public final FileInputStream getFileInputStream() {
-        if (fis == null || pos != getPos()) {
-            fis = new FileInputStream(pfd.getFileDescriptor());
+        synchronized (lock) {
+            if (fis == null || this.pos != getPos()) {
+                this.pos = getPos();
+                fis = new FileInputStream(pfd.getFileDescriptor());
+            }
         }
         }
+
         return fis;
         return fis;
     }
     }
 
 
+    /**
+     * Reads a byte of data from this input stream. This method blocks
+     * if no input is yet available.
+     *
+     * @return the next byte of data, or <code>-1</code> if the end of the
+     * file is reached.
+     * @throws IOException if an I/O error occurs.
+     */
     public int read() throws IOException {
     public int read() throws IOException {
-        byte[] b = new byte[1];
-        return (read(b, 0, 1) != -1) ? b[0] & 0xff : -1;
+        return (read(readBuffer, 0, 1) != -1) ? readBuffer[0] & 0xff : -1;
     }
     }
 
 
-    public int read(byte[] b) {
+    /**
+     * Reads a sub array as a sequence of bytes.
+     *
+     * @param b   the buffer into which the data is read.
+     * @param off the start offset of the data.
+     * @param len the number of bytes to read.
+     * @throws IOException If an I/O error has occurred.
+     */
+    private int readBytes(byte[] b, int off, int len) throws IOException {
         try {
         try {
-            return read(b, 0, b.length);
-        } catch (IOException e) {
-            e.printStackTrace();
-            return -1;
+            return android.system.Os.read(pfd.getFileDescriptor(), b, off, len);
+        } catch (ErrnoException e) {
+            throw rethrowAsIOException(e);
         }
         }
     }
     }
 
 
+
+    /**
+     * Reads up to <code>b.length</code> bytes of data from this input
+     * stream into an array of bytes. This method blocks until some input
+     * is available.
+     *
+     * @param b the buffer into which the data is read.
+     * @return the total number of bytes read into the buffer, or
+     * <code>-1</code> if there is no more data because the end of
+     * the file has been reached.
+     * @throws IOException if an I/O error occurs.
+     */
+    public int read(byte[] b) throws IOException {
+        return readBytes(b, 0, b.length);
+    }
+
+    /**
+     * Reads up to <code>len</code> bytes of data from this input stream
+     * into an array of bytes. If <code>len</code> is not zero, the method
+     * blocks until some input is available; otherwise, no
+     * bytes are read and <code>0</code> is returned.
+     *
+     * @param b   the buffer into which the data is read.
+     * @param off the start offset in the destination array <code>b</code>
+     * @param len the maximum number of bytes read.
+     * @return the total number of bytes read into the buffer, or
+     * <code>-1</code> if there is no more data because the end of
+     * the file has been reached.
+     * @throws NullPointerException      If <code>b</code> is <code>null</code>.
+     * @throws IndexOutOfBoundsException If <code>off</code> is negative,
+     *                                   <code>len</code> is negative, or <code>len</code> is greater than
+     *                                   <code>b.length - off</code>
+     * @throws IOException               if an I/O error occurs.
+     */
     public int read(byte[] b, int off, int len) throws IOException {
     public int read(byte[] b, int off, int len) throws IOException {
-        try {
-            return android.system.Os.read(pfd.getFileDescriptor(), b, off, len);
-        } catch (Exception e) {
-            return -1;
-        }
+        return readBytes(b, off, len);
     }
     }
 
 
 
 
     private void syncInputStream() {
     private void syncInputStream() {
+        this.pos = getPos();
         fis = new FileInputStream(pfd.getFileDescriptor());
         fis = new FileInputStream(pfd.getFileDescriptor());
-        dis = new DataInputStream(fis);
     }
     }
 
 
 
 
     public void seek(long pos) throws IOException {
     public void seek(long pos) throws IOException {
         try {
         try {
             android.system.Os.lseek(pfd.getFileDescriptor(), pos, OsConstants.SEEK_SET);
             android.system.Os.lseek(pfd.getFileDescriptor(), pos, OsConstants.SEEK_SET);
-        } catch (Exception e) {
-            throw new IOException(e);
+        } catch (ErrnoException e) {
+            throw rethrowAsIOException(e);
         }
         }
 
 
     }
     }
@@ -95,53 +175,199 @@ public class AndroidRandomReadableFile implements DataInput, Closeable {
     public long length() throws IOException {
     public long length() throws IOException {
         try {
         try {
             return android.system.Os.lseek(pfd.getFileDescriptor(), 0, OsConstants.SEEK_END);
             return android.system.Os.lseek(pfd.getFileDescriptor(), 0, OsConstants.SEEK_END);
-        } catch (Exception e) {
-            throw new IOException(e);
+        } catch (ErrnoException e) {
+            throw rethrowAsIOException(e);
         }
         }
 
 
     }
     }
 
 
-    public long getPos() {
+    /**
+     * Returns the current offset in this file.
+     *
+     * @return the offset from the beginning of the file, in bytes,
+     * at which the next read or write occurs.
+     * @throws IOException if an I/O error occurs.
+     */
+    public long getFilePointer() throws IOException {
         try {
         try {
             return android.system.Os.lseek(pfd.getFileDescriptor(), 0, OsConstants.SEEK_CUR);
             return android.system.Os.lseek(pfd.getFileDescriptor(), 0, OsConstants.SEEK_CUR);
-        } catch (Exception e) {
-            return -1;
+        } catch (ErrnoException e) {
+            throw rethrowAsIOException(e);
         }
         }
-
     }
     }
 
 
-    @Override
-    public void readFully(byte[] b) throws IOException {
+    public long getPos() {
         try {
         try {
-            android.system.Os.read(pfd.getFileDescriptor(), b, 0, b.length);
+            return android.system.Os.lseek(pfd.getFileDescriptor(), 0, OsConstants.SEEK_CUR);
         } catch (ErrnoException e) {
         } catch (ErrnoException e) {
-            e.printStackTrace();
+            return -1;
         }
         }
 
 
+    }
 
 
+
+    /**
+     * Reads some bytes from an input
+     * stream and stores them into the buffer
+     * array {@code b}. The number of bytes
+     * read is equal
+     * to the length of {@code b}.
+     * <p>
+     * This method blocks until one of the
+     * following conditions occurs:
+     * <ul>
+     * <li>{@code b.length}
+     * bytes of input data are available, in which
+     * case a normal return is made.
+     *
+     * <li>End of
+     * file is detected, in which case an {@code EOFException}
+     * is thrown.
+     *
+     * <li>An I/O error occurs, in
+     * which case an {@code IOException} other
+     * than {@code EOFException} is thrown.
+     * </ul>
+     * <p>
+     * If {@code b} is {@code null},
+     * a {@code NullPointerException} is thrown.
+     * If {@code b.length} is zero, then
+     * no bytes are read. Otherwise, the first
+     * byte read is stored into element {@code b[0]},
+     * the next one into {@code b[1]}, and
+     * so on.
+     * If an exception is thrown from
+     * this method, then it may be that some but
+     * not all bytes of {@code b} have been
+     * updated with data from the input stream.
+     *
+     * @param b the buffer into which the data is read.
+     * @throws EOFException if this stream reaches the end before reading
+     *                      all the bytes.
+     * @throws IOException  if an I/O error occurs.
+     */
+    @Override
+    public void readFully(byte[] b) throws IOException {
+        readFully(b, 0, b.length);
     }
     }
 
 
+    /**
+     * Reads {@code len}
+     * bytes from
+     * an input stream.
+     * <p>
+     * This method
+     * blocks until one of the following conditions
+     * occurs:
+     * <ul>
+     * <li>{@code len} bytes
+     * of input data are available, in which case
+     * a normal return is made.
+     *
+     * <li>End of file
+     * is detected, in which case an {@code EOFException}
+     * is thrown.
+     *
+     * <li>An I/O error occurs, in
+     * which case an {@code IOException} other
+     * than {@code EOFException} is thrown.
+     * </ul>
+     * <p>
+     * If {@code b} is {@code null},
+     * a {@code NullPointerException} is thrown.
+     * If {@code off} is negative, or {@code len}
+     * is negative, or {@code off+len} is
+     * greater than the length of the array {@code b},
+     * then an {@code IndexOutOfBoundsException}
+     * is thrown.
+     * If {@code len} is zero,
+     * then no bytes are read. Otherwise, the first
+     * byte read is stored into element {@code b[off]},
+     * the next one into {@code b[off+1]},
+     * and so on. The number of bytes read is,
+     * at most, equal to {@code len}.
+     *
+     * @param b   the buffer into which the data is read.
+     * @param off an int specifying the offset into the data.
+     * @param len an int specifying the number of bytes to read.
+     * @throws EOFException if this stream reaches the end before reading
+     *                      all the bytes.
+     * @throws IOException  if an I/O error occurs.
+     */
     @Override
     @Override
     public void readFully(byte[] b, int off, int len) throws IOException {
     public void readFully(byte[] b, int off, int len) throws IOException {
-        try {
-            android.system.Os.read(pfd.getFileDescriptor(), b, off, len);
-            //syncInputStream();
-        } catch (ErrnoException e) {
-            throw new IOException(e);
-        }
+        int n = 0;
+        do {
+            int count = this.read(b, off + n, len - n);
+            if (count < 0)
+                throw new EOFException();
+            n += count;
+        } while (n < len);
     }
     }
 
 
+    /**
+     * Makes an attempt to skip over
+     * {@code n} bytes
+     * of data from the input
+     * stream, discarding the skipped bytes. However,
+     * it may skip
+     * over some smaller number of
+     * bytes, possibly zero. This may result from
+     * any of a
+     * number of conditions; reaching
+     * end of file before {@code n} bytes
+     * have been skipped is
+     * only one possibility.
+     * This method never throws an {@code EOFException}.
+     * The actual
+     * number of bytes skipped is returned.
+     *
+     * @param n the number of bytes to be skipped.
+     * @return the number of bytes actually skipped.
+     * @throws IOException if an I/O error occurs.
+     */
     @Override
     @Override
     public int skipBytes(int n) throws IOException {
     public int skipBytes(int n) throws IOException {
-        try {
-            byte[] b = new byte[n];
-            return android.system.Os.read(pfd.getFileDescriptor(), b, 0, b.length);
-        } catch (Exception e) {
-            return -1;
+        long pos;
+        long len;
+        long newpos;
+
+        if (n <= 0) {
+            return 0;
+        }
+        pos = getFilePointer();
+        len = length();
+        newpos = pos + n;
+        if (newpos > len) {
+            newpos = len;
         }
         }
+        seek(newpos);
+
+        /* return the actual number of bytes skipped */
+        return (int) (newpos - pos);
+//        try {
+//            byte[] b = new byte[n];
+//            return android.system.Os.read(pfd.getFileDescriptor(), b, 0, b.length);
+//        } catch (Exception e) {
+//            return -1;
+//        }
 
 
     }
     }
 
 
+
+    /**
+     * Reads one input byte and returns
+     * {@code true} if that byte is nonzero,
+     * {@code false} if that byte is zero.
+     * This method is suitable for reading
+     * the byte written by the {@code writeBoolean}
+     * method of interface {@code DataOutput}.
+     *
+     * @return the {@code boolean} value read.
+     * @throws EOFException if this stream reaches the end before reading
+     *                      all the bytes.
+     * @throws IOException  if an I/O error occurs.
+     */
     @Override
     @Override
     public boolean readBoolean() throws IOException {
     public boolean readBoolean() throws IOException {
         int ch = this.read();
         int ch = this.read();
@@ -151,6 +377,20 @@ public class AndroidRandomReadableFile implements DataInput, Closeable {
 
 
     }
     }
 
 
+    /**
+     * Reads and returns one input byte.
+     * The byte is treated as a signed value in
+     * the range {@code -128} through {@code 127},
+     * inclusive.
+     * This method is suitable for
+     * reading the byte written by the {@code writeByte}
+     * method of interface {@code DataOutput}.
+     *
+     * @return the 8-bit value read.
+     * @throws EOFException if this stream reaches the end before reading
+     *                      all the bytes.
+     * @throws IOException  if an I/O error occurs.
+     */
     @Override
     @Override
     public byte readByte() throws IOException {
     public byte readByte() throws IOException {
         int ch = this.read();
         int ch = this.read();
@@ -160,6 +400,24 @@ public class AndroidRandomReadableFile implements DataInput, Closeable {
     }
     }
 
 
 
 
+    /**
+     * Reads one input byte, zero-extends
+     * it to type {@code int}, and returns
+     * the result, which is therefore in the range
+     * {@code 0}
+     * through {@code 255}.
+     * This method is suitable for reading
+     * the byte written by the {@code writeByte}
+     * method of interface {@code DataOutput}
+     * if the argument to {@code writeByte}
+     * was intended to be a value in the range
+     * {@code 0} through {@code 255}.
+     *
+     * @return the unsigned 8-bit value read.
+     * @throws EOFException if this stream reaches the end before reading
+     *                      all the bytes.
+     * @throws IOException  if an I/O error occurs.
+     */
     @Override
     @Override
     public int readUnsignedByte() throws IOException {
     public int readUnsignedByte() throws IOException {
         int ch = this.read();
         int ch = this.read();
@@ -168,32 +426,132 @@ public class AndroidRandomReadableFile implements DataInput, Closeable {
         return ch;
         return ch;
     }
     }
 
 
-    private final byte[] readBuffer = new byte[8];
 
 
+    /**
+     * Reads two input bytes and returns
+     * a {@code short} value. Let {@code a}
+     * be the first byte read and {@code b}
+     * be the second byte. The value
+     * returned
+     * is:
+     * <pre>{@code (short)((a << 8) | (b & 0xff))
+     * }</pre>
+     * This method
+     * is suitable for reading the bytes written
+     * by the {@code writeShort} method of
+     * interface {@code DataOutput}.
+     *
+     * @return the 16-bit value read.
+     * @throws EOFException if this stream reaches the end before reading
+     *                      all the bytes.
+     * @throws IOException  if an I/O error occurs.
+     */
     @Override
     @Override
     public short readShort() throws IOException {
     public short readShort() throws IOException {
         readFully(readBuffer, 0, 2);
         readFully(readBuffer, 0, 2);
         return ByteBuffer.wrap(readBuffer).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get();
         return ByteBuffer.wrap(readBuffer).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get();
     }
     }
 
 
+    /**
+     * Reads two input bytes and returns
+     * an {@code int} value in the range {@code 0}
+     * through {@code 65535}. Let {@code a}
+     * be the first byte read and
+     * {@code b}
+     * be the second byte. The value returned is:
+     * <pre>{@code (((a & 0xff) << 8) | (b & 0xff))
+     * }</pre>
+     * This method is suitable for reading the bytes
+     * written by the {@code writeShort} method
+     * of interface {@code DataOutput}  if
+     * the argument to {@code writeShort}
+     * was intended to be a value in the range
+     * {@code 0} through {@code 65535}.
+     *
+     * @return the unsigned 16-bit value read.
+     * @throws EOFException if this stream reaches the end before reading
+     *                      all the bytes.
+     * @throws IOException  if an I/O error occurs.
+     */
     @Override
     @Override
     public int readUnsignedShort() throws IOException {
     public int readUnsignedShort() throws IOException {
         readFully(readBuffer, 0, 2);
         readFully(readBuffer, 0, 2);
         return ByteBuffer.wrap(readBuffer).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get() & 0xffff;
         return ByteBuffer.wrap(readBuffer).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get() & 0xffff;
     }
     }
 
 
+    /**
+     * Reads two input bytes and returns a {@code char} value.
+     * Let {@code a}
+     * be the first byte read and {@code b}
+     * be the second byte. The value
+     * returned is:
+     * <pre>{@code (char)((a << 8) | (b & 0xff))
+     * }</pre>
+     * This method
+     * is suitable for reading bytes written by
+     * the {@code writeChar} method of interface
+     * {@code DataOutput}.
+     *
+     * @return the {@code char} value read.
+     * @throws EOFException if this stream reaches the end before reading
+     *                      all the bytes.
+     * @throws IOException  if an I/O error occurs.
+     */
     @Override
     @Override
     public char readChar() throws IOException {
     public char readChar() throws IOException {
         readFully(readBuffer, 0, 2);
         readFully(readBuffer, 0, 2);
-        return (char) ByteBuffer.wrap(readBuffer).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get();
+        return ByteBuffer.wrap(readBuffer).order(ByteOrder.BIG_ENDIAN).asCharBuffer().get();
     }
     }
 
 
+    /**
+     * Reads four input bytes and returns an
+     * {@code int} value. Let {@code a-d}
+     * be the first through fourth bytes read. The value returned is:
+     * <pre>{@code
+     * (((a & 0xff) << 24) | ((b & 0xff) << 16) |
+     *  ((c & 0xff) <<  8) | (d & 0xff))
+     * }</pre>
+     * This method is suitable
+     * for reading bytes written by the {@code writeInt}
+     * method of interface {@code DataOutput}.
+     *
+     * @return the {@code int} value read.
+     * @throws EOFException if this stream reaches the end before reading
+     *                      all the bytes.
+     * @throws IOException  if an I/O error occurs.
+     */
     @Override
     @Override
     public int readInt() throws IOException {
     public int readInt() throws IOException {
         readFully(readBuffer, 0, 4);
         readFully(readBuffer, 0, 4);
         return ByteBuffer.wrap(readBuffer).order(ByteOrder.BIG_ENDIAN).asIntBuffer().get();
         return ByteBuffer.wrap(readBuffer).order(ByteOrder.BIG_ENDIAN).asIntBuffer().get();
     }
     }
 
 
+
+    /**
+     * Reads eight input bytes and returns
+     * a {@code long} value. Let {@code a-h}
+     * be the first through eighth bytes read.
+     * The value returned is:
+     * <pre>{@code
+     * (((long)(a & 0xff) << 56) |
+     *  ((long)(b & 0xff) << 48) |
+     *  ((long)(c & 0xff) << 40) |
+     *  ((long)(d & 0xff) << 32) |
+     *  ((long)(e & 0xff) << 24) |
+     *  ((long)(f & 0xff) << 16) |
+     *  ((long)(g & 0xff) <<  8) |
+     *  ((long)(h & 0xff)))
+     * }</pre>
+     * <p>
+     * This method is suitable
+     * for reading bytes written by the {@code writeLong}
+     * method of interface {@code DataOutput}.
+     *
+     * @return the {@code long} value read.
+     * @throws EOFException if this stream reaches the end before reading
+     *                      all the bytes.
+     * @throws IOException  if an I/O error occurs.
+     */
     @Override
     @Override
     public long readLong() throws IOException {
     public long readLong() throws IOException {
         readFully(readBuffer, 0, 8);
         readFully(readBuffer, 0, 8);
@@ -207,162 +565,167 @@ public class AndroidRandomReadableFile implements DataInput, Closeable {
                 ((readBuffer[7] & 255) << 0));
                 ((readBuffer[7] & 255) << 0));
     }
     }
 
 
+    /**
+     * Reads four input bytes and returns
+     * a {@code float} value. It does this
+     * by first constructing an {@code int}
+     * value in exactly the manner
+     * of the {@code readInt}
+     * method, then converting this {@code int}
+     * value to a {@code float} in
+     * exactly the manner of the method {@code Float.intBitsToFloat}.
+     * This method is suitable for reading
+     * bytes written by the {@code writeFloat}
+     * method of interface {@code DataOutput}.
+     *
+     * @return the {@code float} value read.
+     * @throws EOFException if this stream reaches the end before reading
+     *                      all the bytes.
+     * @throws IOException  if an I/O error occurs.
+     */
     @Override
     @Override
     public float readFloat() throws IOException {
     public float readFloat() throws IOException {
         return Float.intBitsToFloat(readInt());
         return Float.intBitsToFloat(readInt());
     }
     }
 
 
+    /**
+     * Reads eight input bytes and returns
+     * a {@code double} value. It does this
+     * by first constructing a {@code long}
+     * value in exactly the manner
+     * of the {@code readLong}
+     * method, then converting this {@code long}
+     * value to a {@code double} in exactly
+     * the manner of the method {@code Double.longBitsToDouble}.
+     * This method is suitable for reading
+     * bytes written by the {@code writeDouble}
+     * method of interface {@code DataOutput}.
+     *
+     * @return the {@code double} value read.
+     * @throws EOFException if this stream reaches the end before reading
+     *                      all the bytes.
+     * @throws IOException  if an I/O error occurs.
+     */
     @Override
     @Override
     public double readDouble() throws IOException {
     public double readDouble() throws IOException {
         return Double.longBitsToDouble(readLong());
         return Double.longBitsToDouble(readLong());
     }
     }
 
 
-    private char[] lineBuffer;
 
 
-    @Deprecated
+    /**
+     * Reads the next line of text from this file.  This method successively
+     * reads bytes from the file, starting at the current file pointer,
+     * until it reaches a line terminator or the end
+     * of the file.  Each byte is converted into a character by taking the
+     * byte's value for the lower eight bits of the character and setting the
+     * high eight bits of the character to zero.  This method does not,
+     * therefore, support the full Unicode character set.
+     *
+     * <p> A line of text is terminated by a carriage-return character
+     * ({@code '\u005Cr'}), a newline character ({@code '\u005Cn'}), a
+     * carriage-return character immediately followed by a newline character,
+     * or the end of the file.  Line-terminating characters are discarded and
+     * are not included as part of the string returned.
+     *
+     * <p> This method blocks until a newline character is read, a carriage
+     * return and the byte following it are read (to see if it is a newline),
+     * the end of the file is reached, or an exception is thrown.
+     *
+     * @return the next line of text from this file, or null if end
+     * of file is encountered before even one byte is read.
+     * @throws IOException if an I/O error occurs.
+     */
+
     @Override
     @Override
     public String readLine() throws IOException {
     public String readLine() throws IOException {
-        char[] buf = lineBuffer;
-
-        if (buf == null) {
-            buf = lineBuffer = new char[128];
-        }
-
-        int room = buf.length;
-        int offset = 0;
-        int c;
+        StringBuilder input = new StringBuilder();
+        int c = -1;
+        boolean eol = false;
 
 
-        loop:
-        while (true) {
-            switch (c = this.read()) {
+        while (!eol) {
+            switch (c = read()) {
                 case -1:
                 case -1:
                 case '\n':
                 case '\n':
-                    break loop;
-
+                    eol = true;
+                    break;
                 case '\r':
                 case '\r':
-                    int c2 = this.read();
-                    break loop;
-
-                default:
-                    if (--room < 0) {
-                        buf = new char[offset + 128];
-                        room = buf.length - offset - 1;
-                        System.arraycopy(lineBuffer, 0, buf, 0, offset);
-                        lineBuffer = buf;
+                    eol = true;
+                    long cur = getFilePointer();
+                    if ((read()) != '\n') {
+                        seek(cur);
                     }
                     }
-                    buf[offset++] = (char) c;
+                    break;
+                default:
+                    input.append((char) c);
                     break;
                     break;
             }
             }
         }
         }
-        if ((c == -1) && (offset == 0)) {
+
+        if ((c == -1) && (input.length() == 0)) {
             return null;
             return null;
         }
         }
-        return String.copyValueOf(buf, 0, offset);
+        return input.toString();
     }
     }
 
 
+    /**
+     * See the general contract of the <code>readUTF</code>
+     * method of <code>DataInput</code>.
+     * <p>
+     * Bytes
+     * for this operation are read from the contained
+     * input stream.
+     *
+     * @return a Unicode string.
+     * @throws EOFException           if this input stream reaches the end before
+     *                                reading all the bytes.
+     * @throws IOException            the stream has been closed and the contained
+     *                                input stream does not support reading after close, or
+     *                                another I/O error occurs.
+     * @throws UTFDataFormatException if the bytes do not represent a valid
+     *                                modified UTF-8 encoding of a string.
+     * @see java.io.DataInputStream#readUTF(java.io.DataInput)
+     */
     @Override
     @Override
     public String readUTF() throws IOException {
     public String readUTF() throws IOException {
-        if (pos != getPos()) {
-            syncInputStream();
-        }
-        return dis.readUTF();
+        return DataInputStream.readUTF(this);
     }
     }
 
 
-//    public final static String readUTF(DataInput in) throws IOException {
-//        int utflen = in.readUnsignedShort();
-//        byte[] bytearr = null;
-//        char[] chararr = null;
-//        if (in instanceof DataInputStream) {
-//            DataInputStream dis = (DataInputStream)in;
-//            if (dis.bytearr.length < utflen){
-//                dis.bytearr = new byte[utflen*2];
-//                dis.chararr = new char[utflen*2];
-//            }
-//            chararr = dis.chararr;
-//            bytearr = dis.bytearr;
-//        } else {
-//            bytearr = new byte[utflen];
-//            chararr = new char[utflen];
-//        }
-//
-//        int c, char2, char3;
-//        int count = 0;
-//        int chararr_count=0;
-//
-//        in.readFully(bytearr, 0, utflen);
-//
-//        while (count < utflen) {
-//            c = (int) bytearr[count] & 0xff;
-//            if (c > 127) break;
-//            count++;
-//            chararr[chararr_count++]=(char)c;
-//        }
-//
-//        while (count < utflen) {
-//            c = (int) bytearr[count] & 0xff;
-//            switch (c >> 4) {
-//                case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
-//                    /* 0xxxxxxx*/
-//                    count++;
-//                    chararr[chararr_count++]=(char)c;
-//                    break;
-//                case 12: case 13:
-//                    /* 110x xxxx   10xx xxxx*/
-//                    count += 2;
-//                    if (count > utflen)
-//                        throw new UTFDataFormatException(
-//                                "malformed input: partial character at end");
-//                    char2 = (int) bytearr[count-1];
-//                    if ((char2 & 0xC0) != 0x80)
-//                        throw new UTFDataFormatException(
-//                                "malformed input around byte " + count);
-//                    chararr[chararr_count++]=(char)(((c & 0x1F) << 6) |
-//                            (char2 & 0x3F));
-//                    break;
-//                case 14:
-//                    /* 1110 xxxx  10xx xxxx  10xx xxxx */
-//                    count += 3;
-//                    if (count > utflen)
-//                        throw new UTFDataFormatException(
-//                                "malformed input: partial character at end");
-//                    char2 = (int) bytearr[count-2];
-//                    char3 = (int) bytearr[count-1];
-//                    if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80))
-//                        throw new UTFDataFormatException(
-//                                "malformed input around byte " + (count-1));
-//                    chararr[chararr_count++]=(char)(((c     & 0x0F) << 12) |
-//                            ((char2 & 0x3F) << 6)  |
-//                            ((char3 & 0x3F) << 0));
-//                    break;
-//                default:
-//                    /* 10xx xxxx,  1111 xxxx */
-//                    throw new UTFDataFormatException(
-//                            "malformed input around byte " + count);
-//            }
-//        }
-//        // The number of chars produced may be less than utflen
-//        return new String(chararr, 0, chararr_count);
-//    }
 
 
     @Override
     @Override
     public void close() throws IOException {
     public void close() throws IOException {
-        try {
-            dis.close();
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
+
+        guard.close();
 
 
         try {
         try {
-            fis.close();
+            if (fis != null) {
+                fis.close();
+            }
         } catch (Exception e) {
         } catch (Exception e) {
             e.printStackTrace();
             e.printStackTrace();
         }
         }
         try {
         try {
-            pfd.close();
+            if (pfd != null) {
+                pfd.close();
+            }
+
         } catch (Exception e) {
         } catch (Exception e) {
             e.printStackTrace();
             e.printStackTrace();
         }
         }
 
 
 
 
     }
     }
+
+    protected void finalize() throws Throwable {
+        try {
+            // Note that guard could be null if the constructor threw.
+
+            guard.warnIfOpen();
+
+
+            close();
+
+        } finally {
+            super.finalize();
+        }
+    }
 }
 }

+ 33 - 0
epublib/src/main/java/me/ag2s/utils/Android11CloseGuard.java

@@ -0,0 +1,33 @@
+package me.ag2s.utils;
+
+import android.os.Build;
+import android.util.CloseGuard;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+@RequiresApi(api = Build.VERSION_CODES.R)
+final class Android11CloseGuard implements AndroidCloseGuard {
+    @NonNull
+    private final CloseGuard mImpl;
+
+
+    public Android11CloseGuard() {
+        mImpl = new CloseGuard();
+    }
+
+    @Override
+    public void open(@NonNull String closeMethodName) {
+        mImpl.open(closeMethodName);
+    }
+
+    @Override
+    public void close() {
+        mImpl.close();
+    }
+
+    @Override
+    public void warnIfOpen() {
+        mImpl.warnIfOpen();
+    }
+}

+ 38 - 0
epublib/src/main/java/me/ag2s/utils/AndroidCloseGuard.java

@@ -0,0 +1,38 @@
+package me.ag2s.utils;
+
+
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+
+public interface AndroidCloseGuard {
+
+
+    static AndroidCloseGuard getInstance() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+            return new Android11CloseGuard();
+        } else {
+            return new AndroidRefCloseGuard();
+        }
+    }
+
+    /**
+     * Initializes the instance with a warning that the caller should have explicitly called the
+     * {@code closeMethodName} method instead of relying on finalization.
+     *
+     * @param closeMethodName non-null name of explicit termination method. Printed by warnIfOpen.
+     * @throws NullPointerException if closeMethodName is null.
+     */
+    void open(@NonNull String closeMethodName);
+
+    /**
+     * Marks this CloseGuard instance as closed to avoid warnings on finalization.
+     */
+    void close();
+
+    /**
+     * Logs a warning if the caller did not properly cleanup by calling an explicit close method
+     * before finalization.
+     */
+    void warnIfOpen();
+}

+ 83 - 0
epublib/src/main/java/me/ag2s/utils/AndroidRefCloseGuard.java

@@ -0,0 +1,83 @@
+package me.ag2s.utils;
+
+import androidx.annotation.NonNull;
+
+import java.lang.reflect.Method;
+
+final class AndroidRefCloseGuard implements AndroidCloseGuard {
+    private static Object closeGuardInstance;
+    private static Method getMethod;
+    private static Method closeMethod;
+    private static Method openMethod;
+    private static Method warnIfOpenMethod;
+
+
+    public AndroidRefCloseGuard() {
+
+        if (getMethod == null || closeMethod == null || openMethod == null || warnIfOpenMethod == null) {
+            try {
+                Class<?> closeGuardClass = Class.forName("dalvik.system.CloseGuard");
+                getMethod = closeGuardClass.getMethod("get");
+                closeMethod = closeGuardClass.getMethod("close");
+                openMethod = closeGuardClass.getMethod("open", String.class);
+                warnIfOpenMethod = closeGuardClass.getMethod("warnIfOpen");
+            } catch (Exception ignored) {
+                getMethod = null;
+                openMethod = null;
+                warnIfOpenMethod = null;
+            }
+        }
+
+
+    }
+
+    Object createAndOpen(String closer) {
+        if (getMethod != null) {
+            try {
+                if (closeGuardInstance == null) {
+                    closeGuardInstance = getMethod.invoke(null);
+                }
+
+                openMethod.invoke(closeGuardInstance, closer);
+                return closeGuardInstance;
+            } catch (Exception ignored) {
+            }
+        }
+        return null;
+    }
+
+
+    boolean warnIfOpen(Object closeGuardInstance) {
+        boolean reported = false;
+        if (closeGuardInstance != null) {
+            try {
+                warnIfOpenMethod.invoke(closeGuardInstance);
+                reported = true;
+            } catch (Exception ignored) {
+            }
+        }
+        return reported;
+    }
+
+
+    @Override
+    public void open(@NonNull String closeMethodName) {
+        closeGuardInstance = createAndOpen(closeMethodName);
+    }
+
+    @Override
+    public void close() {
+        if (closeGuardInstance != null) {
+            try {
+                closeMethod.invoke(closeMethod);
+            } catch (Exception ignored) {
+            }
+        }
+
+    }
+
+    @Override
+    public void warnIfOpen() {
+        warnIfOpen(closeGuardInstance);
+    }
+}

+ 16 - 0
epublib/src/main/java/me/ag2s/utils/ThrowableUtils.java

@@ -0,0 +1,16 @@
+package me.ag2s.utils;
+
+import androidx.annotation.NonNull;
+
+import java.io.IOException;
+
+public class ThrowableUtils {
+
+
+    public static @NonNull
+    IOException rethrowAsIOException(Throwable throwable) throws IOException {
+        IOException newException = new IOException(throwable.getMessage());
+        newException.initCause(throwable);
+        throw newException;
+    }
+}