From 7aaf9bdf5e5f0d6e46a7fdc3e4427cd5fe415ff1 Mon Sep 17 00:00:00 2001 From: ag2s20150909 Date: Thu, 15 Jul 2021 20:59:09 +0800 Subject: [PATCH 01/10] =?UTF-8?q?=E6=B7=BB=E5=8A=A0Cronet=20Flavor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 18 +- .../main/java/io/legado/app/help/AppConfig.kt | 1 + .../java/io/legado/app/help/JsExtensions.kt | 10 +- .../io/legado/app/help/http/HttpHelper.kt | 6 + .../app/help/http/cronet/CronetHelper.kt | 65 ++++++ .../app/help/http/cronet/CronetInterceptor.kt | 20 ++ .../http/cronet/CronetUrlRequestCallback.kt | 207 ++++++++++++++++++ build.gradle | 2 +- 8 files changed, 319 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/io/legado/app/help/http/cronet/CronetHelper.kt create mode 100644 app/src/main/java/io/legado/app/help/http/cronet/CronetInterceptor.kt create mode 100644 app/src/main/java/io/legado/app/help/http/cronet/CronetUrlRequestCallback.kt diff --git a/app/build.gradle b/app/build.gradle index 4cedca019..57e5e5210 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -84,6 +84,11 @@ android { applicationId "io.legado.play" manifestPlaceholders = [APP_CHANNEL_VALUE: "google"] } + cronet { + dimension "mode" + applicationId "io.legado.cronet" + manifestPlaceholders = [APP_CHANNEL_VALUE: "cronet"] + } } compileOptions { // Flag to enable support for the new language APIs @@ -117,8 +122,8 @@ dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' implementation fileTree(dir: 'libs', include: ['*.jar','*.aar']) testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test:runner:1.3.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + androidTestImplementation 'androidx.test:runner:1.4.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' implementation 'androidx.multidex:multidex:2.0.1' //kotlin implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" @@ -129,7 +134,7 @@ dependencies { //androidX implementation 'androidx.appcompat:appcompat:1.3.0' - implementation 'androidx.core:core-ktx:1.5.0' + implementation 'androidx.core:core-ktx:1.6.0' implementation "androidx.activity:activity-ktx:1.2.3" implementation "androidx.fragment:fragment-ktx:1.3.5" implementation 'androidx.preference:preference-ktx:1.1.1' @@ -170,7 +175,7 @@ dependencies { //规则相关 implementation 'org.jsoup:jsoup:1.14.1' //noinspection GradleDependency - implementation 'cn.wanghaomiao:JsoupXpath:2.3.2' + implementation 'cn.wanghaomiao:JsoupXpath:2.4.3' implementation 'com.jayway.jsonpath:json-path:2.6.0' //JS rhino @@ -178,6 +183,11 @@ dependencies { //网络 implementation 'com.squareup.okhttp3:okhttp:4.9.1' + def cronet_version='92.0.4509.1' + compileOnly "org.microg:cronet-api:$cronet_version" + cronetImplementation "org.microg:cronet-native:$cronet_version" + cronetImplementation "org.microg:cronet-api:$cronet_version" + cronetImplementation "org.microg:cronet-common:$cronet_version" //Glide implementation 'com.github.bumptech.glide:glide:4.12.0' diff --git a/app/src/main/java/io/legado/app/help/AppConfig.kt b/app/src/main/java/io/legado/app/help/AppConfig.kt index 4dc5d3877..2a6c469b3 100644 --- a/app/src/main/java/io/legado/app/help/AppConfig.kt +++ b/app/src/main/java/io/legado/app/help/AppConfig.kt @@ -10,6 +10,7 @@ import splitties.init.appCtx @Suppress("MemberVisibilityCanBePrivate") object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener { val isGooglePlay = appCtx.channel == "google" + val isCronet= appCtx.channel=="cronet" var userAgent: String = getPrefUserAgent() var isEInkMode = appCtx.getPrefString(PreferKey.themeMode) == "3" var clickActionTL = appCtx.getPrefInt(PreferKey.clickActionTL, 2) diff --git a/app/src/main/java/io/legado/app/help/JsExtensions.kt b/app/src/main/java/io/legado/app/help/JsExtensions.kt index cab0b6d96..1f464db47 100644 --- a/app/src/main/java/io/legado/app/help/JsExtensions.kt +++ b/app/src/main/java/io/legado/app/help/JsExtensions.kt @@ -292,7 +292,7 @@ interface JsExtensions { val bos = ByteArrayOutputStream() val zis = ZipInputStream(ByteArrayInputStream(bytes)) - var entry: ZipEntry = zis.nextEntry + var entry: ZipEntry? = zis.nextEntry while (entry != null) { if (entry.name.equals(path)) { @@ -303,7 +303,7 @@ interface JsExtensions { } Debug.log("getZipContent 未发现内容") - return ""; + return "" } /** @@ -319,17 +319,17 @@ interface JsExtensions { val bos = ByteArrayOutputStream() val zis = ZipInputStream(ByteArrayInputStream(bytes)) - var entry: ZipEntry = zis.nextEntry + var entry: ZipEntry? = zis.nextEntry while (entry != null) { if (entry.name.equals(path)) { zis.use { it.copyTo(bos) } return bos.toByteArray() } - entry = zis.nextEntry; + entry = zis.nextEntry } Debug.log("getZipContent 未发现内容") - return null; + return null } /** diff --git a/app/src/main/java/io/legado/app/help/http/HttpHelper.kt b/app/src/main/java/io/legado/app/help/http/HttpHelper.kt index cdf6a03bc..26ce5ee7c 100644 --- a/app/src/main/java/io/legado/app/help/http/HttpHelper.kt +++ b/app/src/main/java/io/legado/app/help/http/HttpHelper.kt @@ -1,5 +1,7 @@ package io.legado.app.help.http +import io.legado.app.help.AppConfig +import io.legado.app.help.http.cronet.CronetInterceptor import kotlinx.coroutines.suspendCancellableCoroutine import okhttp3.ConnectionSpec import okhttp3.Credentials @@ -41,6 +43,10 @@ val okHttpClient: OkHttpClient by lazy { .build() chain.proceed(request) }) + if (AppConfig.isCronet){ + builder.addInterceptor(CronetInterceptor()) + } + builder.build() } diff --git a/app/src/main/java/io/legado/app/help/http/cronet/CronetHelper.kt b/app/src/main/java/io/legado/app/help/http/cronet/CronetHelper.kt new file mode 100644 index 000000000..4dac61698 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/http/cronet/CronetHelper.kt @@ -0,0 +1,65 @@ +package io.legado.app.help.http.cronet + +import io.legado.app.help.http.CookieStore +import okhttp3.Headers +import okhttp3.MediaType +import okhttp3.Request +import okio.Buffer +import org.chromium.net.CronetEngine.Builder.HTTP_CACHE_DISK +import org.chromium.net.ExperimentalCronetEngine +import org.chromium.net.UploadDataProviders +import org.chromium.net.UrlRequest +import splitties.init.appCtx +import java.util.concurrent.Executor +import java.util.concurrent.Executors + + +val executor: Executor by lazy { Executors.newSingleThreadExecutor() } + +val cronetEngine: ExperimentalCronetEngine by lazy { + + val builder = ExperimentalCronetEngine.Builder(appCtx) + .setStoragePath(appCtx.externalCacheDir?.absolutePath) + .enableHttpCache(HTTP_CACHE_DISK, (1024 * 1024 * 50)) + .enableQuic(true) + .enablePublicKeyPinningBypassForLocalTrustAnchors(true) + .enableHttp2(true) + //Brotli压缩 + builder.enableBrotli(true) + return@lazy builder.build() + +} + + +fun buildRequest(request: Request, callback: UrlRequest.Callback): UrlRequest { + val url = request.url.toString() + val requestBuilder = cronetEngine.newUrlRequestBuilder(url, callback, executor) + requestBuilder.setHttpMethod(request.method) + val cookie = CookieStore.getCookie(url) + if (cookie.length > 1) { + requestBuilder.addHeader("Cookie", cookie) + } + val headers: Headers = request.headers + headers.forEachIndexed { index, pair -> + requestBuilder.addHeader(headers.name(index), headers.value(index)) + } + + val requestBody = request.body + if (requestBody != null) { + val contentType: MediaType? = requestBody.contentType() + if (contentType != null) { + requestBuilder.addHeader("Content-Type", contentType.toString()) + } + val buffer = Buffer() + requestBody.writeTo(buffer) + requestBuilder.setUploadDataProvider( + UploadDataProviders.create(buffer.readByteArray()), + executor + ); + + } + + return requestBuilder.build() + +} + diff --git a/app/src/main/java/io/legado/app/help/http/cronet/CronetInterceptor.kt b/app/src/main/java/io/legado/app/help/http/cronet/CronetInterceptor.kt new file mode 100644 index 000000000..023fb97f3 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/http/cronet/CronetInterceptor.kt @@ -0,0 +1,20 @@ +package io.legado.app.help.http.cronet + +import okhttp3.* +import java.io.IOException + +class CronetInterceptor : Interceptor { + @Throws(IOException::class) + override fun intercept(chain: Interceptor.Chain): Response { + return proceedWithCronet(chain.request(), chain.call()) + } + + @Throws(IOException::class) + private fun proceedWithCronet(request: Request, call: Call): Response { + val callback = CronetUrlRequestCallback(request, call) + val urlRequest = buildRequest(request, callback) + urlRequest.start() + return callback.waitForDone() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/http/cronet/CronetUrlRequestCallback.kt b/app/src/main/java/io/legado/app/help/http/cronet/CronetUrlRequestCallback.kt new file mode 100644 index 000000000..e7e3686c0 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/http/cronet/CronetUrlRequestCallback.kt @@ -0,0 +1,207 @@ +package io.legado.app.help.http.cronet + +import android.os.ConditionVariable +import android.util.Log +import io.legado.app.help.http.okHttpClient +import okhttp3.* +import okhttp3.EventListener +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.ResponseBody.Companion.toResponseBody +import org.chromium.net.CronetException +import org.chromium.net.UrlRequest +import org.chromium.net.UrlResponseInfo +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.nio.ByteBuffer +import java.nio.channels.Channels +import java.util.* + +class CronetUrlRequestCallback @JvmOverloads internal constructor( + private val originalRequest: Request, + private val mCall: Call, + eventListener: EventListener? = null, + responseCallback: Callback? = null +) : UrlRequest.Callback() { + private val eventListener: EventListener? + private val responseCallback: Callback? + private var followCount = 0 + private var mResponse: Response + private var mException: IOException? = null + private val mResponseCondition = ConditionVariable() + private val mBytesReceived = ByteArrayOutputStream() + private val mReceiveChannel = Channels.newChannel(mBytesReceived) + + @Throws(IOException::class) + fun waitForDone(): Response { + mResponseCondition.block() + if (mException != null) { + throw mException as IOException + } + return mResponse + } + + override fun onRedirectReceived( + request: UrlRequest, + info: UrlResponseInfo, + newLocationUrl: String + ) { + if (followCount > MAX_FOLLOW_COUNT) { + request.cancel() + } + followCount += 1 + val client = okHttpClient + if (originalRequest.url.isHttps && newLocationUrl.startsWith("http://") && client.followSslRedirects) { + request.followRedirect() + } else if (!originalRequest.url.isHttps && newLocationUrl.startsWith("https://") && client.followSslRedirects) { + request.followRedirect() + } else if (client.followRedirects) { + request.followRedirect() + } else { + request.cancel() + } + } + + override fun onResponseStarted(request: UrlRequest, info: UrlResponseInfo) { + mResponse = responseFromResponse(mResponse, info) +// val sb: StringBuilder = StringBuilder(info.url).append("\r\n") +// sb.append("[Cached:").append(info.wasCached()).append("][StatusCode:") +// .append(info.httpStatusCode).append("][StatusText:").append(info.httpStatusText) +// .append("][Protocol:").append(info.negotiatedProtocol).append("][ByteCount:") +// .append(info.receivedByteCount).append("]\r\n"); +// val httpHeaders=info.allHeadersAsList +// httpHeaders.forEach { h -> +// sb.append("[").append(h.key).append("]").append(h.value).append("\r\n"); +// } +// Log.e("Cronet", sb.toString()) + if (eventListener != null) { + eventListener.responseHeadersEnd(mCall, mResponse) + eventListener.responseBodyStart(mCall) + } + request.read(ByteBuffer.allocateDirect(32 * 1024)) + } + + @Throws(Exception::class) + override fun onReadCompleted( + request: UrlRequest, + info: UrlResponseInfo, + byteBuffer: ByteBuffer + ) { + byteBuffer.flip() + try { + mReceiveChannel.write(byteBuffer) + } catch (e: IOException) { + Log.i(TAG, "IOException during ByteBuffer read. Details: ", e) + throw e + } + byteBuffer.clear() + request.read(byteBuffer) + } + + override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) { + eventListener?.responseBodyEnd(mCall, info.receivedByteCount) + val contentType: MediaType? = (mResponse.header("content-type") + ?: "text/plain; charset=\"utf-8\"").toMediaTypeOrNull() + val responseBody: ResponseBody = + mBytesReceived.toByteArray().toResponseBody(contentType) + val newRequest = originalRequest.newBuilder().url(info.url).build() + mResponse = mResponse.newBuilder().body(responseBody).request(newRequest).build() + mResponseCondition.open() + eventListener?.callEnd(mCall) + if (responseCallback != null) { + try { + responseCallback.onResponse(mCall, mResponse) + } catch (e: IOException) { + // Pass? + } + } + } + + override fun onFailed(request: UrlRequest, info: UrlResponseInfo, error: CronetException) { + val e = IOException("Cronet Exception Occurred", error) + mException = e + mResponseCondition.open() + eventListener?.callFailed(mCall, e) + responseCallback?.onFailure(mCall, e) + } + + override fun onCanceled(request: UrlRequest, info: UrlResponseInfo) { + mResponseCondition.open() + eventListener?.callEnd(mCall) + } + + companion object { + private const val TAG = "Callback" + private const val MAX_FOLLOW_COUNT = 20 + private fun protocolFromNegotiatedProtocol(responseInfo: UrlResponseInfo): Protocol { + val negotiatedProtocol = responseInfo.negotiatedProtocol.lowercase(Locale.getDefault()) +// Log.e("Cronet", responseInfo.url) +// Log.e("Cronet", negotiatedProtocol) + + return when { + negotiatedProtocol.contains("h3") -> { + return Protocol.QUIC + } + negotiatedProtocol.contains("quic") -> { + Protocol.QUIC + } + negotiatedProtocol.contains("spdy") -> { + Protocol.SPDY_3 + } + negotiatedProtocol.contains("h2") -> { + Protocol.HTTP_2 + } + negotiatedProtocol.contains("1.1") -> { + Protocol.HTTP_1_1 + } + else -> { + Protocol.HTTP_1_0 + } + } + } + + private fun headersFromResponse(responseInfo: UrlResponseInfo): Headers { + val headers = responseInfo.allHeadersAsList + val headerBuilder = Headers.Builder() + for ((key, value) in headers) { + try { + if (key.equals("content-encoding", ignoreCase = true)) { + // Strip all content encoding headers as decoding is done handled by cronet + continue + } + headerBuilder.add(key, value) + } catch (e: Exception) { + Log.w(TAG, "Invalid HTTP header/value: $key$value") + // Ignore that header + } + } + return headerBuilder.build() + } + + private fun responseFromResponse( + response: Response, + responseInfo: UrlResponseInfo + ): Response { + val protocol = protocolFromNegotiatedProtocol(responseInfo) + val headers = headersFromResponse(responseInfo) + return response.newBuilder() + .receivedResponseAtMillis(System.currentTimeMillis()) + .protocol(protocol) + .code(responseInfo.httpStatusCode) + .message(responseInfo.httpStatusText) + .headers(headers) + .build() + } + } + + init { + mResponse = Response.Builder() + .sentRequestAtMillis(System.currentTimeMillis()) + .request(originalRequest) + .protocol(Protocol.HTTP_1_0) + .code(0) + .message("") + .build() + this.responseCallback = responseCallback + this.eventListener = eventListener + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 4e78684d5..2e71dab2c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.5.20' + ext.kotlin_version = '1.5.21' repositories { google() mavenCentral() From db31ee89f2ab4b6ca801ebdf6f261a992a8d8069 Mon Sep 17 00:00:00 2001 From: ag2s20150909 Date: Thu, 15 Jul 2021 21:07:32 +0800 Subject: [PATCH 02/10] =?UTF-8?q?=E6=B7=BB=E5=8A=A0Cronet=20=E6=B7=B7?= =?UTF-8?q?=E6=B7=86=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/proguard-rules.pro | 135 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 3733c9bc4..aedd4a7c4 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -222,3 +222,138 @@ public static **[] values(); public static ** valueOf(java.lang.String); } + + +# Copyright 2016 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Contains flags that can be safely shared with Cronet, and thus would be +# appropriate for third-party apps to include. + +# Keep all annotation related attributes that can affect runtime +-keepattributes RuntimeVisible*Annotations +-keepattributes AnnotationDefault + +# Keep the annotations, because if we don't, the ProGuard rules that use them +# will not be respected. These classes then show up in our final dex, which we +# do not want - see crbug.com/628226. +-keep @interface org.chromium.base.annotations.AccessedByNative +-keep @interface org.chromium.base.annotations.CalledByNative +-keep @interface org.chromium.base.annotations.CalledByNativeUnchecked +-keep @interface org.chromium.base.annotations.DoNotInline +-keep @interface org.chromium.base.annotations.RemovableInRelease +-keep @interface org.chromium.base.annotations.UsedByReflection + +# Android support library annotations will get converted to androidx ones +# which we want to keep. +-keep @interface androidx.annotation.Keep +-keep @androidx.annotation.Keep class * +-keepclasseswithmembers class * { + @androidx.annotation.Keep ; +} +-keepclasseswithmembers class * { + @androidx.annotation.Keep ; +} + +# Keeps for class level annotations. +-keep @org.chromium.base.annotations.UsedByReflection class ** {} + +# Keeps for method level annotations. +-keepclasseswithmembers class ** { + @org.chromium.base.annotations.AccessedByNative ; +} +-keepclasseswithmembers,includedescriptorclasses class ** { + @org.chromium.base.annotations.CalledByNative ; +} +-keepclasseswithmembers,includedescriptorclasses class ** { + @org.chromium.base.annotations.CalledByNativeUnchecked ; +} +-keepclasseswithmembers class ** { + @org.chromium.base.annotations.UsedByReflection ; +} +-keepclasseswithmembers class ** { + @org.chromium.base.annotations.UsedByReflection ; +} +# Even unused methods kept due to explicit jni registration: +# https://crbug.com/688465. +-keepclasseswithmembers,includedescriptorclasses class !org.chromium.base.library_loader.**,** { + native ; +} +-keepclasseswithmembernames,includedescriptorclasses class org.chromium.base.library_loader.** { + native ; +} + +-assumenosideeffects class ** { + # Remove @RemovableInRelease methods so long as return values are unused. + @org.chromium.base.annotations.RemovableInRelease ; +} + +-assumevalues class ** { + # Remove object @RemovableInRelease methods even when return value is used. + # Note: ** in return type does not match primitives. + @org.chromium.base.annotations.RemovableInRelease ** *(...) return null; + # Remove boolean @RemovableInRelease methods even when return value is used. + @org.chromium.base.annotations.RemovableInRelease boolean *(...) return false; +} + +# Never inline classes or methods with this annotation, but allow shrinking and +# obfuscation. +-if @org.chromium.base.annotations.DoNotInline class * { + *** *(...); +} +-keep,allowobfuscation class <1> { + *** <2>(...); +} +-keepclassmembers,allowobfuscation class * { + @org.chromium.base.annotations.DoNotInline ; +} + +# Keep all CREATOR fields within Parcelable that are kept. +-keepclassmembers class org.chromium.** implements android.os.Parcelable { + public static *** CREATOR; +} + +# Don't obfuscate Parcelables as they might be marshalled outside Chrome. +# If we annotated all Parcelables that get put into Bundles other than +# for saveInstanceState (e.g. PendingIntents), then we could actually keep the +# names of just those ones. For now, we'll just keep them all. +-keepnames class org.chromium.** implements android.os.Parcelable {} + +# Keep all enum values and valueOf methods. See +# http://proguard.sourceforge.net/index.html#manual/examples.html +# for the reason for this. Also, see http://crbug.com/248037. +-keepclassmembers enum org.chromium.** { + public static **[] values(); +} +# Proguard config for apps that depend on cronet_impl_native_java.jar. + +# This constructor is called using the reflection from Cronet API (cronet_api.jar). +-keep class org.chromium.net.impl.NativeCronetProvider { + public (android.content.Context); +} + +# Suppress unnecessary warnings. +-dontnote org.chromium.net.ProxyChangeListener$ProxyReceiver +-dontnote org.chromium.net.AndroidKeyStore +# Needs 'void setTextAppearance(int)' (API level 23). +-dontwarn org.chromium.base.ApiCompatibilityUtils +# Needs 'boolean onSearchRequested(android.view.SearchEvent)' (API level 23). +-dontwarn org.chromium.base.WindowCallbackWrapper + +# Generated for chrome apk and not included into cronet. +-dontwarn org.chromium.base.library_loader.NativeLibraries +-dontwarn org.chromium.base.multidex.ChromiumMultiDexInstaller +-dontwarn org.chromium.base.library_loader.LibraryLoader +-dontwarn org.chromium.base.SysUtils + +# Objects of this type are passed around by native code, but the class +# is never used directly by native code. Since the class is not loaded, it does +# not need to be preserved as an entry point. +-dontnote org.chromium.net.UrlRequest$ResponseHeadersMap +# https://android.googlesource.com/platform/sdk/+/marshmallow-mr1-release/files/proguard-android.txt#54 +-dontwarn android.support.** + +# This class should be explicitly kept to avoid failure if +# class/merging/horizontal proguard optimization is enabled. +-keep class org.chromium.base.CollectionUtil \ No newline at end of file From 0757b44a3f68f658e14782c6dc7c1f5c74f36f95 Mon Sep 17 00:00:00 2001 From: ag2s20150909 Date: Thu, 15 Jul 2021 21:48:58 +0800 Subject: [PATCH 03/10] =?UTF-8?q?=E6=B7=BB=E5=8A=A0Cronet=20=E6=B7=B7?= =?UTF-8?q?=E6=B7=86=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/proguard-rules.pro | 138 ++--------------------------------------- 1 file changed, 5 insertions(+), 133 deletions(-) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index aedd4a7c4..a2949e6bf 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -224,136 +224,8 @@ } -# Copyright 2016 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -# Contains flags that can be safely shared with Cronet, and thus would be -# appropriate for third-party apps to include. - -# Keep all annotation related attributes that can affect runtime --keepattributes RuntimeVisible*Annotations --keepattributes AnnotationDefault - -# Keep the annotations, because if we don't, the ProGuard rules that use them -# will not be respected. These classes then show up in our final dex, which we -# do not want - see crbug.com/628226. --keep @interface org.chromium.base.annotations.AccessedByNative --keep @interface org.chromium.base.annotations.CalledByNative --keep @interface org.chromium.base.annotations.CalledByNativeUnchecked --keep @interface org.chromium.base.annotations.DoNotInline --keep @interface org.chromium.base.annotations.RemovableInRelease --keep @interface org.chromium.base.annotations.UsedByReflection - -# Android support library annotations will get converted to androidx ones -# which we want to keep. --keep @interface androidx.annotation.Keep --keep @androidx.annotation.Keep class * --keepclasseswithmembers class * { - @androidx.annotation.Keep ; -} --keepclasseswithmembers class * { - @androidx.annotation.Keep ; -} - -# Keeps for class level annotations. --keep @org.chromium.base.annotations.UsedByReflection class ** {} - -# Keeps for method level annotations. --keepclasseswithmembers class ** { - @org.chromium.base.annotations.AccessedByNative ; -} --keepclasseswithmembers,includedescriptorclasses class ** { - @org.chromium.base.annotations.CalledByNative ; -} --keepclasseswithmembers,includedescriptorclasses class ** { - @org.chromium.base.annotations.CalledByNativeUnchecked ; -} --keepclasseswithmembers class ** { - @org.chromium.base.annotations.UsedByReflection ; -} --keepclasseswithmembers class ** { - @org.chromium.base.annotations.UsedByReflection ; -} -# Even unused methods kept due to explicit jni registration: -# https://crbug.com/688465. --keepclasseswithmembers,includedescriptorclasses class !org.chromium.base.library_loader.**,** { - native ; -} --keepclasseswithmembernames,includedescriptorclasses class org.chromium.base.library_loader.** { - native ; -} - --assumenosideeffects class ** { - # Remove @RemovableInRelease methods so long as return values are unused. - @org.chromium.base.annotations.RemovableInRelease ; -} - --assumevalues class ** { - # Remove object @RemovableInRelease methods even when return value is used. - # Note: ** in return type does not match primitives. - @org.chromium.base.annotations.RemovableInRelease ** *(...) return null; - # Remove boolean @RemovableInRelease methods even when return value is used. - @org.chromium.base.annotations.RemovableInRelease boolean *(...) return false; -} - -# Never inline classes or methods with this annotation, but allow shrinking and -# obfuscation. --if @org.chromium.base.annotations.DoNotInline class * { - *** *(...); -} --keep,allowobfuscation class <1> { - *** <2>(...); -} --keepclassmembers,allowobfuscation class * { - @org.chromium.base.annotations.DoNotInline ; -} - -# Keep all CREATOR fields within Parcelable that are kept. --keepclassmembers class org.chromium.** implements android.os.Parcelable { - public static *** CREATOR; -} - -# Don't obfuscate Parcelables as they might be marshalled outside Chrome. -# If we annotated all Parcelables that get put into Bundles other than -# for saveInstanceState (e.g. PendingIntents), then we could actually keep the -# names of just those ones. For now, we'll just keep them all. --keepnames class org.chromium.** implements android.os.Parcelable {} - -# Keep all enum values and valueOf methods. See -# http://proguard.sourceforge.net/index.html#manual/examples.html -# for the reason for this. Also, see http://crbug.com/248037. --keepclassmembers enum org.chromium.** { - public static **[] values(); -} -# Proguard config for apps that depend on cronet_impl_native_java.jar. - -# This constructor is called using the reflection from Cronet API (cronet_api.jar). --keep class org.chromium.net.impl.NativeCronetProvider { - public (android.content.Context); -} - -# Suppress unnecessary warnings. --dontnote org.chromium.net.ProxyChangeListener$ProxyReceiver --dontnote org.chromium.net.AndroidKeyStore -# Needs 'void setTextAppearance(int)' (API level 23). --dontwarn org.chromium.base.ApiCompatibilityUtils -# Needs 'boolean onSearchRequested(android.view.SearchEvent)' (API level 23). --dontwarn org.chromium.base.WindowCallbackWrapper - -# Generated for chrome apk and not included into cronet. --dontwarn org.chromium.base.library_loader.NativeLibraries --dontwarn org.chromium.base.multidex.ChromiumMultiDexInstaller --dontwarn org.chromium.base.library_loader.LibraryLoader --dontwarn org.chromium.base.SysUtils - -# Objects of this type are passed around by native code, but the class -# is never used directly by native code. Since the class is not loaded, it does -# not need to be preserved as an entry point. --dontnote org.chromium.net.UrlRequest$ResponseHeadersMap -# https://android.googlesource.com/platform/sdk/+/marshmallow-mr1-release/files/proguard-android.txt#54 --dontwarn android.support.** - -# This class should be explicitly kept to avoid failure if -# class/merging/horizontal proguard optimization is enabled. --keep class org.chromium.base.CollectionUtil \ No newline at end of file +# Keep all of Cronet API as it's used by the Cronet module. +-keep public class org.chromium.net.* { + !private *; + *; +} \ No newline at end of file From 940f9cf42ae1347290a9d15baaa13ab788185d6c Mon Sep 17 00:00:00 2001 From: ag2s20150909 Date: Thu, 15 Jul 2021 21:54:58 +0800 Subject: [PATCH 04/10] =?UTF-8?q?=E6=B7=BB=E5=8A=A0Cronet=20=E6=B7=B7?= =?UTF-8?q?=E6=B7=86=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/build.gradle b/app/build.gradle index 57e5e5210..add8ff815 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -35,6 +35,9 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" project.ext.set("archivesBaseName", name + "_" + version) multiDexEnabled true + ndk { + abiFilters 'arm64-v8a' + } javaCompileOptions { annotationProcessorOptions { arguments += [ From 6e73c904528d9c4fe2836aee79142413e1198b03 Mon Sep 17 00:00:00 2001 From: ag2s20150909 Date: Thu, 15 Jul 2021 22:38:15 +0800 Subject: [PATCH 05/10] =?UTF-8?q?=E6=B7=BB=E5=8A=A0Cronet=20=E6=B7=B7?= =?UTF-8?q?=E6=B7=86=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index add8ff815..6d8529cd8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -123,7 +123,7 @@ kapt { dependencies { implementation project(path: ':epublib') coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - implementation fileTree(dir: 'libs', include: ['*.jar','*.aar']) + implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' @@ -186,7 +186,7 @@ dependencies { //网络 implementation 'com.squareup.okhttp3:okhttp:4.9.1' - def cronet_version='92.0.4509.1' + def cronet_version = '92.0.4509.1' compileOnly "org.microg:cronet-api:$cronet_version" cronetImplementation "org.microg:cronet-native:$cronet_version" cronetImplementation "org.microg:cronet-api:$cronet_version" From 2131984f6c69537fe38998b6eddecc070946af8b Mon Sep 17 00:00:00 2001 From: gedoor Date: Thu, 15 Jul 2021 23:02:54 +0800 Subject: [PATCH 06/10] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/assets/updateLog.md | 4 ++ .../java/io/legado/app/help/JsExtensions.kt | 51 ++++++++++++++++--- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md index 00a06a045..ac1946a60 100644 --- a/app/src/main/assets/updateLog.md +++ b/app/src/main/assets/updateLog.md @@ -8,6 +8,10 @@ * 正文出现缺字漏字、内容缺失、排版错乱等情况,有可能是净化规则出现问题。先关闭替换净化并刷新,再观察是否正常。如果正常说明净化规则存在误杀,如果关闭后仍然出现相关问题,请点击源链接查看原文与正文是否相同,如果不同,再进行反馈。 * 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源! +**2021/07/16** +1. js扩展函数添加删除本地文件方法 +2. js扩展函数对于文件的读写删操作都是相对路径,只能操作阅读缓存内的文件,/android/data/{package}/cache/... + **2021/07/15** 1. 添加js函数来修复开启js沙箱后某些书源失效。by ag2s20150909 ```kotlin diff --git a/app/src/main/java/io/legado/app/help/JsExtensions.kt b/app/src/main/java/io/legado/app/help/JsExtensions.kt index 1f464db47..084058044 100644 --- a/app/src/main/java/io/legado/app/help/JsExtensions.kt +++ b/app/src/main/java/io/legado/app/help/JsExtensions.kt @@ -23,6 +23,11 @@ import java.util.* import java.util.zip.ZipEntry import java.util.zip.ZipInputStream +/** + * js扩展类, 在js中通过java变量调用 + * 所有对于文件的读写删操作都是相对路径,只能操作阅读缓存内的文件 + * /android/data/{package}/cache/... + */ @Keep @Suppress("unused") interface JsExtensions { @@ -253,20 +258,51 @@ interface JsExtensions { } /** - * 读取本地文件 + * 获取本地文件 + * @param path 相对路径 + * @return File */ - fun readFile(path: String): ByteArray { - return File(path).readBytes() + fun getFile(path: String): File { + val cachePath = appCtx.eCacheDir.path + val aPath = if (path.startsWith(File.separator)) { + cachePath + path + } else { + cachePath + File.separator + path + } + return File(aPath) + } + + fun readFile(path: String): ByteArray? { + val file = getFile(path) + if (file.exists()) { + return file.readBytes() + } + return null } fun readTxtFile(path: String): String { - val f = File(path) - val charsetName = EncodingDetect.getEncode(f) - return String(f.readBytes(), charset(charsetName)) + val file = getFile(path) + if (file.exists()) { + val charsetName = EncodingDetect.getEncode(file) + return String(file.readBytes(), charset(charsetName)) + } + return "" } fun readTxtFile(path: String, charsetName: String): String { - return String(File(path).readBytes(), charset(charsetName)) + val file = getFile(path) + if (file.exists()) { + return String(file.readBytes(), charset(charsetName)) + } + return "" + } + + /** + * 删除本地文件 + */ + fun deleteFile(path: String) { + val file = getFile(path) + FileUtils.delete(file) } /** @@ -405,7 +441,6 @@ interface JsExtensions { transformation: String, iv: String ): ByteArray? { - return EncoderUtils.decryptAES( data = str.encodeToByteArray(), key = key.encodeToByteArray(), From cddd809503a92119246b779fe464d3c778945a44 Mon Sep 17 00:00:00 2001 From: gedoor Date: Fri, 16 Jul 2021 09:29:55 +0800 Subject: [PATCH 07/10] =?UTF-8?q?js=E5=8F=AA=E8=83=BD=E8=AE=BF=E9=97=AEcac?= =?UTF-8?q?he=E6=96=87=E4=BB=B6=E5=A4=B9=E4=B8=8B=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/io/legado/app/help/BookHelp.kt | 2 +- .../java/io/legado/app/help/JsExtensions.kt | 93 ++++++++++--------- .../io/legado/app/model/localBook/EpubFile.kt | 4 +- .../legado/app/model/localBook/LocalBook.kt | 2 +- .../io/legado/app/model/localBook/UmdFile.kt | 7 +- .../ui/book/info/edit/BookInfoEditActivity.kt | 4 +- .../ui/book/read/config/BgTextConfigDialog.kt | 18 ++-- .../app/ui/config/OtherConfigFragment.kt | 4 +- .../app/ui/config/ThemeConfigFragment.kt | 4 +- .../app/ui/widget/font/FontSelectDialog.kt | 2 +- .../io/legado/app/utils/ContextExtensions.kt | 4 +- .../java/io/legado/app/utils/FileUtils.kt | 3 +- 12 files changed, 74 insertions(+), 73 deletions(-) diff --git a/app/src/main/java/io/legado/app/help/BookHelp.kt b/app/src/main/java/io/legado/app/help/BookHelp.kt index 41dc7376e..1cf95b2f2 100644 --- a/app/src/main/java/io/legado/app/help/BookHelp.kt +++ b/app/src/main/java/io/legado/app/help/BookHelp.kt @@ -23,7 +23,7 @@ import kotlin.math.min object BookHelp { private const val cacheFolderName = "book_cache" private const val cacheImageFolderName = "images" - private val downloadDir: File = appCtx.externalFilesDir + private val downloadDir: File = appCtx.externalFiles private val downloadImages = CopyOnWriteArraySet() fun clearCache() { diff --git a/app/src/main/java/io/legado/app/help/JsExtensions.kt b/app/src/main/java/io/legado/app/help/JsExtensions.kt index 084058044..04915cf77 100644 --- a/app/src/main/java/io/legado/app/help/JsExtensions.kt +++ b/app/src/main/java/io/legado/app/help/JsExtensions.kt @@ -85,6 +85,7 @@ interface JsExtensions { /** * 实现16进制字符串转文件 + * @return 相对路径 */ fun downloadFile(content: String, url: String): String { val type = AnalyzeUrl(url).type ?: return "" @@ -99,45 +100,7 @@ interface JsExtensions { zipFile.writeBytes(it) } } - return zipPath - } - - /** - * js实现压缩文件解压 - */ - fun unzipFile(zipPath: String): String { - if (zipPath.isEmpty()) return "" - val unzipPath = FileUtils.getPath( - FileUtils.createFolderIfNotExist(FileUtils.getCachePath()), - FileUtils.getNameExcludeExtension(zipPath) - ) - FileUtils.deleteFile(unzipPath) - val zipFile = FileUtils.createFileIfNotExist(zipPath) - val unzipFolder = FileUtils.createFolderIfNotExist(unzipPath) - ZipUtils.unzipFile(zipFile, unzipFolder) - FileUtils.deleteFile(zipPath) - return unzipPath - } - - /** - * js实现文件夹内所有文件读取 - */ - fun getTxtInFolder(unzipPath: String): String { - if (unzipPath.isEmpty()) return "" - val unzipFolder = FileUtils.createFolderIfNotExist(unzipPath) - val contents = StringBuilder() - unzipFolder.listFiles().let { - if (it != null) { - for (f in it) { - val charsetName = EncodingDetect.getEncode(f) - contents.append(String(f.readBytes(), charset(charsetName))) - .append("\n") - } - contents.deleteCharAt(contents.length - 1) - } - } - FileUtils.deleteFile(unzipPath) - return contents.toString() + return zipPath.substring(FileUtils.getCachePath().length) } /** @@ -263,7 +226,7 @@ interface JsExtensions { * @return File */ fun getFile(path: String): File { - val cachePath = appCtx.eCacheDir.path + val cachePath = appCtx.externalCache.absolutePath val aPath = if (path.startsWith(File.separator)) { cachePath + path } else { @@ -306,13 +269,43 @@ interface JsExtensions { } /** - * 解析字体,返回字体解析类 + * js实现压缩文件解压 + * @param zipPath 相对路径 + * @return 相对路径 */ - fun queryBase64TTF(base64: String?): QueryTTF? { - base64DecodeToByteArray(base64)?.let { - return QueryTTF(it) + fun unzipFile(zipPath: String): String { + if (zipPath.isEmpty()) return "" + val unzipPath = FileUtils.getPath( + FileUtils.createFolderIfNotExist(FileUtils.getCachePath()), + FileUtils.getNameExcludeExtension(zipPath) + ) + FileUtils.deleteFile(unzipPath) + val zipFile = getFile(zipPath) + val unzipFolder = FileUtils.createFolderIfNotExist(unzipPath) + ZipUtils.unzipFile(zipFile, unzipFolder) + FileUtils.deleteFile(zipPath) + return unzipPath.substring(FileUtils.getCachePath().length) + } + + /** + * js实现文件夹内所有文件读取 + */ + fun getTxtInFolder(unzipPath: String): String { + if (unzipPath.isEmpty()) return "" + val unzipFolder = getFile(unzipPath) + val contents = StringBuilder() + unzipFolder.listFiles().let { + if (it != null) { + for (f in it) { + val charsetName = EncodingDetect.getEncode(f) + contents.append(String(f.readBytes(), charset(charsetName))) + .append("\n") + } + contents.deleteCharAt(contents.length - 1) + } } - return null + FileUtils.deleteFile(unzipPath) + return contents.toString() } /** @@ -368,6 +361,16 @@ interface JsExtensions { return null } + /** + * 解析字体,返回字体解析类 + */ + fun queryBase64TTF(base64: String?): QueryTTF? { + base64DecodeToByteArray(base64)?.let { + return QueryTTF(it) + } + return null + } + /** * 返回字体解析类 * @param str 支持url,本地文件,base64,自动判断,自动缓存 diff --git a/app/src/main/java/io/legado/app/model/localBook/EpubFile.kt b/app/src/main/java/io/legado/app/model/localBook/EpubFile.kt index 5365be900..93f96132f 100644 --- a/app/src/main/java/io/legado/app/model/localBook/EpubFile.kt +++ b/app/src/main/java/io/legado/app/model/localBook/EpubFile.kt @@ -9,7 +9,7 @@ import io.legado.app.help.BookHelp import io.legado.app.utils.FileUtils import io.legado.app.utils.HtmlFormatter import io.legado.app.utils.MD5Utils -import io.legado.app.utils.externalFilesDir +import io.legado.app.utils.externalFiles import me.ag2s.epublib.domain.EpubBook import me.ag2s.epublib.epub.EpubReader import org.jsoup.Jsoup @@ -80,7 +80,7 @@ class EpubFile(var book: Book) { epubBook?.let { if (book.coverUrl.isNullOrEmpty()) { book.coverUrl = FileUtils.getPath( - appCtx.externalFilesDir, + appCtx.externalFiles, "covers", "${MD5Utils.md5Encode16(book.bookUrl)}.jpg" ) diff --git a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt index 30c9f65db..46d7ba187 100644 --- a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt +++ b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt @@ -119,7 +119,7 @@ object LocalBook { author = author, originName = fileName, coverUrl = FileUtils.getPath( - appCtx.externalFilesDir, + appCtx.externalFiles, "covers", "${MD5Utils.md5Encode16(path)}.jpg" ) diff --git a/app/src/main/java/io/legado/app/model/localBook/UmdFile.kt b/app/src/main/java/io/legado/app/model/localBook/UmdFile.kt index de6c1586f..0451b44ca 100644 --- a/app/src/main/java/io/legado/app/model/localBook/UmdFile.kt +++ b/app/src/main/java/io/legado/app/model/localBook/UmdFile.kt @@ -4,17 +4,16 @@ import android.net.Uri import android.util.Log import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookChapter - import io.legado.app.utils.FileUtils import io.legado.app.utils.MD5Utils -import io.legado.app.utils.externalFilesDir +import io.legado.app.utils.externalFiles import io.legado.app.utils.isContentScheme import me.ag2s.umdlib.domain.UmdBook import me.ag2s.umdlib.umd.UmdReader import splitties.init.appCtx import java.io.File import java.io.InputStream -import java.util.ArrayList +import java.util.* class UmdFile(var book: Book) { companion object { @@ -74,7 +73,7 @@ class UmdFile(var book: Book) { umdBook?.let { if (book.coverUrl.isNullOrEmpty()) { book.coverUrl = FileUtils.getPath( - appCtx.externalFilesDir, + appCtx.externalFiles, "covers", "${MD5Utils.md5Encode16(book.bookUrl)}.jpg" ) diff --git a/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt b/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt index c8e645b11..805edfe5f 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt @@ -108,7 +108,7 @@ class BookInfoEditActivity : if (uri.isContentScheme()) { val doc = DocumentFile.fromSingleUri(this, uri) doc?.name?.let { - var file = this.externalFilesDir + var file = this.externalFiles file = FileUtils.createFileIfNotExist(file, "covers", it) kotlin.runCatching { DocumentUtils.readBytes(this, doc.uri) @@ -128,7 +128,7 @@ class BookInfoEditActivity : RealPathUtil.getPath(this, uri)?.let { path -> val imgFile = File(path) if (imgFile.exists()) { - var file = this.externalFilesDir + var file = this.externalFiles file = FileUtils.createFileIfNotExist(file, "covers", imgFile.name) file.writeBytes(imgFile.readBytes()) coverChangeTo(file.absolutePath) diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt index 224d4b93d..3837b6c22 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt @@ -224,7 +224,7 @@ class BgTextConfigDialog : BaseDialogFragment() { } execute { val exportFiles = arrayListOf() - val configDirPath = FileUtils.getPath(requireContext().eCacheDir, "readConfig") + val configDirPath = FileUtils.getPath(requireContext().externalCache, "readConfig") FileUtils.deleteFile(configDirPath) val configDir = FileUtils.createFolderIfNotExist(configDirPath) val configExportPath = FileUtils.getPath(configDir, "readConfig.json") @@ -269,7 +269,7 @@ class BgTextConfigDialog : BaseDialogFragment() { exportFiles.add(bgExportFile) } } - val configZipPath = FileUtils.getPath(requireContext().eCacheDir, configFileName) + val configZipPath = FileUtils.getPath(requireContext().externalCache, configFileName) if (ZipUtils.zipFiles(exportFiles, File(configZipPath))) { if (uri.isContentScheme()) { DocumentFile.fromTreeUri(requireContext(), uri)?.let { treeDoc -> @@ -331,11 +331,11 @@ class BgTextConfigDialog : BaseDialogFragment() { @Suppress("BlockingMethodInNonBlockingContext") private fun importConfig(byteArray: ByteArray) { execute { - val configZipPath = FileUtils.getPath(requireContext().eCacheDir, configFileName) + val configZipPath = FileUtils.getPath(requireContext().externalCache, configFileName) FileUtils.deleteFile(configZipPath) val zipFile = FileUtils.createFileIfNotExist(configZipPath) zipFile.writeBytes(byteArray) - val configDirPath = FileUtils.getPath(requireContext().eCacheDir, "readConfig") + val configDirPath = FileUtils.getPath(requireContext().externalCache, "readConfig") FileUtils.deleteFile(configDirPath) ZipUtils.unzipFile(zipFile, FileUtils.createFolderIfNotExist(configDirPath)) val configDir = FileUtils.createFolderIfNotExist(configDirPath) @@ -344,7 +344,7 @@ class BgTextConfigDialog : BaseDialogFragment() { if (config.textFont.isNotEmpty()) { val fontName = FileUtils.getName(config.textFont) val fontPath = - FileUtils.getPath(requireContext().externalFilesDir, "font", fontName) + FileUtils.getPath(requireContext().externalFiles, "font", fontName) if (!FileUtils.exist(fontPath)) { FileUtils.getFile(configDir, fontName).copyTo(File(fontPath)) } @@ -352,7 +352,7 @@ class BgTextConfigDialog : BaseDialogFragment() { } if (config.bgType == 2) { val bgName = FileUtils.getName(config.bgStr) - val bgPath = FileUtils.getPath(requireContext().externalFilesDir, "bg", bgName) + val bgPath = FileUtils.getPath(requireContext().externalFiles, "bg", bgName) if (!FileUtils.exist(bgPath)) { val bgFile = FileUtils.getFile(configDir, bgName) if (bgFile.exists()) { @@ -362,7 +362,7 @@ class BgTextConfigDialog : BaseDialogFragment() { } if (config.bgTypeNight == 2) { val bgName = FileUtils.getName(config.bgStrNight) - val bgPath = FileUtils.getPath(requireContext().externalFilesDir, "bg", bgName) + val bgPath = FileUtils.getPath(requireContext().externalFiles, "bg", bgName) if (!FileUtils.exist(bgPath)) { val bgFile = FileUtils.getFile(configDir, bgName) if (bgFile.exists()) { @@ -372,7 +372,7 @@ class BgTextConfigDialog : BaseDialogFragment() { } if (config.bgTypeEInk == 2) { val bgName = FileUtils.getName(config.bgStrEInk) - val bgPath = FileUtils.getPath(requireContext().externalFilesDir, "bg", bgName) + val bgPath = FileUtils.getPath(requireContext().externalFiles, "bg", bgName) if (!FileUtils.exist(bgPath)) { val bgFile = FileUtils.getFile(configDir, bgName) if (bgFile.exists()) { @@ -395,7 +395,7 @@ class BgTextConfigDialog : BaseDialogFragment() { val doc = DocumentFile.fromSingleUri(requireContext(), uri) doc?.name?.let { val file = - FileUtils.createFileIfNotExist(requireContext().externalFilesDir, "bg", it) + FileUtils.createFileIfNotExist(requireContext().externalFiles, "bg", it) kotlin.runCatching { DocumentUtils.readBytes(requireContext(), doc.uri) }.getOrNull()?.let { byteArray -> diff --git a/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt index 9a9d7b9bd..5547b76db 100644 --- a/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt +++ b/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt @@ -226,7 +226,7 @@ class OtherConfigFragment : BasePreferenceFragment(), if (uri.isContentScheme()) { val doc = DocumentFile.fromSingleUri(requireContext(), uri) doc?.name?.let { - var file = requireContext().externalFilesDir + var file = requireContext().externalFiles file = FileUtils.createFileIfNotExist(file, "covers", it) kotlin.runCatching { DocumentUtils.readBytes(requireContext(), doc.uri) @@ -247,7 +247,7 @@ class OtherConfigFragment : BasePreferenceFragment(), RealPathUtil.getPath(requireContext(), uri)?.let { path -> val imgFile = File(path) if (imgFile.exists()) { - var file = requireContext().externalFilesDir + var file = requireContext().externalFiles file = FileUtils.createFileIfNotExist(file, "covers", imgFile.name) file.writeBytes(imgFile.readBytes()) putPrefString(PreferKey.defaultCover, file.absolutePath) diff --git a/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt index 2a5e414ee..94eb8987b 100644 --- a/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt +++ b/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt @@ -225,7 +225,7 @@ class ThemeConfigFragment : BasePreferenceFragment(), if (uri.isContentScheme()) { val doc = DocumentFile.fromSingleUri(requireContext(), uri) doc?.name?.let { - var file = requireContext().externalFilesDir + var file = requireContext().externalFiles file = FileUtils.createFileIfNotExist(file, preferenceKey, it) kotlin.runCatching { DocumentUtils.readBytes(requireContext(), doc.uri) @@ -247,7 +247,7 @@ class ThemeConfigFragment : BasePreferenceFragment(), RealPathUtil.getPath(requireContext(), uri)?.let { path -> val imgFile = File(path) if (imgFile.exists()) { - var file = requireContext().externalFilesDir + var file = requireContext().externalFiles file = FileUtils.createFileIfNotExist(file, preferenceKey, imgFile.name) file.writeBytes(imgFile.readBytes()) putPrefString(preferenceKey, file.absolutePath) diff --git a/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt b/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt index c070e4361..22f205101 100644 --- a/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt +++ b/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt @@ -139,7 +139,7 @@ class FontSelectDialog : BaseDialogFragment(), private fun getLocalFonts(): ArrayList { val fontItems = arrayListOf() val fontDir = - FileUtils.createFolderIfNotExist(requireContext().externalFilesDir, "font") + FileUtils.createFolderIfNotExist(requireContext().externalFiles, "font") fontDir.listFiles { pathName -> pathName.name.lowercase(Locale.getDefault()).matches(fontRegex) }?.forEach { diff --git a/app/src/main/java/io/legado/app/utils/ContextExtensions.kt b/app/src/main/java/io/legado/app/utils/ContextExtensions.kt index f11a7f890..315c76c67 100644 --- a/app/src/main/java/io/legado/app/utils/ContextExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/ContextExtensions.kt @@ -224,10 +224,10 @@ val Context.sysBattery: Int return batteryStatus?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1 } -val Context.externalFilesDir: File +val Context.externalFiles: File get() = this.getExternalFilesDir(null) ?: this.filesDir -val Context.eCacheDir: File +val Context.externalCache: File get() = this.externalCacheDir ?: this.cacheDir fun Context.openUrl(url: String) { diff --git a/app/src/main/java/io/legado/app/utils/FileUtils.kt b/app/src/main/java/io/legado/app/utils/FileUtils.kt index 1e8c8b502..c0f8e0dda 100644 --- a/app/src/main/java/io/legado/app/utils/FileUtils.kt +++ b/app/src/main/java/io/legado/app/utils/FileUtils.kt @@ -105,8 +105,7 @@ object FileUtils { } fun getCachePath(): String { - return appCtx.externalCacheDir?.absolutePath - ?: appCtx.cacheDir.absolutePath + return appCtx.externalCache.absolutePath } fun getSdCardPath(): String { From 6df464a7088d71ff014ce873ee783cd899374b97 Mon Sep 17 00:00:00 2001 From: gedoor Date: Fri, 16 Jul 2021 09:34:04 +0800 Subject: [PATCH 08/10] =?UTF-8?q?js=E5=8F=AA=E8=83=BD=E8=AE=BF=E9=97=AEcac?= =?UTF-8?q?he=E6=96=87=E4=BB=B6=E5=A4=B9=E4=B8=8B=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/io/legado/app/help/JsExtensions.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/io/legado/app/help/JsExtensions.kt b/app/src/main/java/io/legado/app/help/JsExtensions.kt index 04915cf77..deccf818f 100644 --- a/app/src/main/java/io/legado/app/help/JsExtensions.kt +++ b/app/src/main/java/io/legado/app/help/JsExtensions.kt @@ -220,6 +220,8 @@ interface JsExtensions { return HtmlFormatter.formatKeepImg(str) } + //****************文件操作******************// + /** * 获取本地文件 * @param path 相对路径 @@ -361,6 +363,8 @@ interface JsExtensions { return null } + //******************文件操作************************// + /** * 解析字体,返回字体解析类 */ From c8305b4a535681ecabf309c285aaef801cf6a8f5 Mon Sep 17 00:00:00 2001 From: gedoor Date: Fri, 16 Jul 2021 11:06:14 +0800 Subject: [PATCH 09/10] =?UTF-8?q?web=E5=A4=87=E4=BB=BD=E4=B8=B4=E6=97=B6?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=A4=B9=E6=94=B9=E5=88=B0file=E4=B8=8B?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/io/legado/app/help/storage/BookWebDav.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/io/legado/app/help/storage/BookWebDav.kt b/app/src/main/java/io/legado/app/help/storage/BookWebDav.kt index 5af0231f0..6147ff1db 100644 --- a/app/src/main/java/io/legado/app/help/storage/BookWebDav.kt +++ b/app/src/main/java/io/legado/app/help/storage/BookWebDav.kt @@ -23,7 +23,7 @@ import java.util.* object BookWebDav { private const val defaultWebDavUrl = "https://dav.jianguoyun.com/dav/" private val bookProgressUrl = "${rootWebDavUrl}bookProgress/" - private val zipFilePath = "${FileUtils.getCachePath()}${File.separator}backup.zip" + private val zipFilePath = "${appCtx.externalFiles.absolutePath}${File.separator}backup.zip" private val rootWebDavUrl: String get() { From b4fc4e2e586021ea1cc7d7ab8dc5cfa588faa077 Mon Sep 17 00:00:00 2001 From: gedoor Date: Fri, 16 Jul 2021 12:26:05 +0800 Subject: [PATCH 10/10] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/io/legado/app/help/JsExtensions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/io/legado/app/help/JsExtensions.kt b/app/src/main/java/io/legado/app/help/JsExtensions.kt index deccf818f..802b450c6 100644 --- a/app/src/main/java/io/legado/app/help/JsExtensions.kt +++ b/app/src/main/java/io/legado/app/help/JsExtensions.kt @@ -267,7 +267,7 @@ interface JsExtensions { */ fun deleteFile(path: String) { val file = getFile(path) - FileUtils.delete(file) + FileUtils.delete(file, true) } /**