pull/1188/head
gedoor 3 years ago
parent 561835aab6
commit 1468a398cf
  1. 3
      app/src/main/java/io/legado/app/App.kt
  2. 3
      app/src/main/java/io/legado/app/help/http/HttpHelper.kt
  3. 4
      app/src/main/java/io/legado/app/help/http/cronet/CronetHelper.kt
  4. 472
      app/src/main/java/io/legado/app/help/http/cronet/CronetLoader.kt

@ -25,9 +25,8 @@ class App : MultiDexApplication() {
CrashHandler(this) CrashHandler(this)
if (AppConfig.isCronet) { if (AppConfig.isCronet) {
//预下载Cronet so //预下载Cronet so
CronetLoader.getInstance(this).preDownload() CronetLoader.preDownload()
} }
LanguageUtils.setConfiguration(this) LanguageUtils.setConfiguration(this)
createNotificationChannels() createNotificationChannels()
applyDayNight(this) applyDayNight(this)

@ -8,7 +8,6 @@ import okhttp3.ConnectionSpec
import okhttp3.Credentials import okhttp3.Credentials
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import splitties.init.appCtx
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.net.Proxy import java.net.Proxy
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@ -45,7 +44,7 @@ val okHttpClient: OkHttpClient by lazy {
.build() .build()
chain.proceed(request) chain.proceed(request)
}) })
if (AppConfig.isCronet && CronetLoader.getInstance(appCtx).install()) { if (AppConfig.isCronet && CronetLoader.install()) {
builder.addInterceptor(CronetInterceptor()) builder.addInterceptor(CronetInterceptor())
} }

@ -19,11 +19,11 @@ import java.util.concurrent.Executors
val executor: Executor by lazy { Executors.newSingleThreadExecutor() } val executor: Executor by lazy { Executors.newSingleThreadExecutor() }
val cronetEngine: ExperimentalCronetEngine by lazy { val cronetEngine: ExperimentalCronetEngine by lazy {
CronetLoader.getInstance(appCtx).preDownload() CronetLoader.preDownload()
val builder = ExperimentalCronetEngine.Builder(appCtx) val builder = ExperimentalCronetEngine.Builder(appCtx)
//设置自定义so库加载 //设置自定义so库加载
.setLibraryLoader(CronetLoader.getInstance(appCtx)) .setLibraryLoader(CronetLoader)
//设置缓存路径 //设置缓存路径
.setStoragePath(appCtx.externalCacheDir?.absolutePath) .setStoragePath(appCtx.externalCacheDir?.absolutePath)
//设置缓存模式 //设置缓存模式

@ -1,219 +1,182 @@
package io.legado.app.help.http.cronet; package io.legado.app.help.http.cronet
import android.annotation.SuppressLint; import android.annotation.SuppressLint
import android.content.Context; import android.content.Context
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo
import android.os.Build; import android.os.Build
import android.text.TextUtils; import android.text.TextUtils
import android.util.Log; import android.util.Log
import org.chromium.net.CronetEngine
import org.chromium.net.CronetEngine; import org.chromium.net.impl.ImplVersion
import org.chromium.net.impl.ImplVersion; import splitties.init.appCtx
import java.io.*
import java.io.ByteArrayOutputStream; import java.math.BigInteger
import java.io.File; import java.net.HttpURLConnection
import java.io.FileInputStream; import java.net.URL
import java.io.FileOutputStream; import java.security.MessageDigest
import java.io.IOException; import java.util.*
import java.io.InputStream; import java.util.concurrent.Executor
import java.io.OutputStream; import java.util.concurrent.Executors
import java.lang.reflect.Field;
import java.math.BigInteger; object CronetLoader : CronetEngine.Builder.LibraryLoader() {
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class CronetLoader extends CronetEngine.Builder.LibraryLoader {
//https://storage.googleapis.com/chromium-cronet/android/92.0.4515.127/Release/cronet/libs/arm64-v8a/libcronet.92.0.4515.127.so //https://storage.googleapis.com/chromium-cronet/android/92.0.4515.127/Release/cronet/libs/arm64-v8a/libcronet.92.0.4515.127.so
//https://cdn.jsdelivr.net/gh/ag2s20150909/cronet-repo@92.0.4515.127/cronet/92.0.4515.127/arm64-v8a/libcronet.92.0.4515.127.so.js //https://cdn.jsdelivr.net/gh/ag2s20150909/cronet-repo@92.0.4515.127/cronet/92.0.4515.127/arm64-v8a/libcronet.92.0.4515.127.so.js
private final String soName = "libcronet." + ImplVersion.getCronetVersion() + ".so"; private const val TAG = "CronetLoader"
private final String soUrl; private val soName = "libcronet." + ImplVersion.getCronetVersion() + ".so"
private final String md5Url; private val soUrl: String
private final File soFile; private val md5Url: String
private final File downloadFile; private val soFile: File
private String CPU_ABI; private val downloadFile: File
private String md5; private var cpuAbi: String? = null
private static final String TAG = "CronetLoader"; private var md5: String? = null
var download = false
private static CronetLoader instance; private var executor: Executor = Executors.newSingleThreadExecutor()
public static CronetLoader getInstance(Context context) {
if (instance == null) { init {
synchronized (CronetLoader.class) { soUrl = ("https://storage.googleapis.com/chromium-cronet/android/"
if (instance == null) {
instance = new CronetLoader(context);
}
}
}
return instance;
}
CronetLoader(Context context) {
Context mContext = context.getApplicationContext();
soUrl = "https://storage.googleapis.com/chromium-cronet/android/"
+ ImplVersion.getCronetVersion() + "/Release/cronet/libs/" + ImplVersion.getCronetVersion() + "/Release/cronet/libs/"
+ getCpuAbi(mContext) + "/" + soName; + getCpuAbi(appCtx) + "/" + soName)
md5Url = "https://cdn.jsdelivr.net/gh/ag2s20150909/cronet-repo@" + md5Url = ("https://cdn.jsdelivr.net/gh/ag2s20150909/cronet-repo@" +
ImplVersion.getCronetVersion() + "/cronet/" + ImplVersion.getCronetVersion() + "/" ImplVersion.getCronetVersion() + "/cronet/" + ImplVersion.getCronetVersion() + "/"
+ getCpuAbi(mContext) + "/" + soName + ".js"; + getCpuAbi(appCtx) + "/" + soName + ".js")
File dir = mContext.getDir("lib", Context.MODE_PRIVATE); val dir = appCtx.getDir("lib", Context.MODE_PRIVATE)
soFile = new File(dir + "/" + getCpuAbi(mContext), soName); soFile = File(dir.toString() + "/" + getCpuAbi(appCtx), soName)
downloadFile = File(appCtx.cacheDir.toString() + "/so_download", soName)
downloadFile = new File(mContext.getCacheDir() + "/so_download", soName); Log.e(TAG, "soName+:$soName")
Log.e(TAG, "soName+:" + soName); Log.e(TAG, "destSuccessFile:$soFile")
Log.e(TAG, "destSuccessFile:" + soFile); Log.e(TAG, "tempFile:$downloadFile")
Log.e(TAG, "tempFile:" + downloadFile); Log.e(TAG, "soUrl:$soUrl")
Log.e(TAG, "soUrl:" + soUrl);
} }
public boolean install() { fun install(): Boolean {
return soFile.exists(); return soFile.exists()
} }
public void preDownload() { fun preDownload() {
new Thread(() -> { Thread {
md5 = getUrlMd5(md5Url); md5 = getUrlMd5(md5Url)
if (soFile.exists() && Objects.equals(md5, getFileMD5(soFile))) { if (soFile.exists() && md5 == getFileMD5(soFile)) {
Log.e(TAG, "So 库已存在"); Log.e(TAG, "So 库已存在")
} else { } else {
download(soUrl, md5, downloadFile, soFile); download(soUrl, md5, downloadFile, soFile)
} }
Log.e(TAG, soName)
Log.e(TAG, soName); }.start()
}).start();
} }
@SuppressLint("UnsafeDynamicallyLoadedCode") @SuppressLint("UnsafeDynamicallyLoadedCode")
@Override override fun loadLibrary(libName: String) {
public void loadLibrary(String libName) { Log.e(TAG, "libName:$libName")
Log.e(TAG, "libName:" + libName); val start = System.currentTimeMillis()
long start = System.currentTimeMillis(); @Suppress("SameParameterValue")
try { try {
//非cronet的so调用系统方法加载 //非cronet的so调用系统方法加载
if (!libName.contains("cronet")) { if (!libName.contains("cronet")) {
System.loadLibrary(libName); System.loadLibrary(libName)
return; return
} }
//以下逻辑为cronet加载,优先加载本地,否则从远程加载 //以下逻辑为cronet加载,优先加载本地,否则从远程加载
//首先调用系统行为进行加载 //首先调用系统行为进行加载
System.loadLibrary(libName); System.loadLibrary(libName)
Log.i(TAG, "load from system"); Log.i(TAG, "load from system")
} catch (e: Throwable) {
} catch (Throwable e) {
//如果找不到,则从远程下载 //如果找不到,则从远程下载
//删除历史文件 //删除历史文件
deleteHistoryFile(Objects.requireNonNull(soFile.getParentFile()), soFile); deleteHistoryFile(Objects.requireNonNull(soFile.parentFile), soFile)
md5 = getUrlMd5(md5Url); md5 = getUrlMd5(md5Url)
Log.i(TAG, "soMD5:" + md5); Log.i(TAG, "soMD5:$md5")
if (md5 == null || md5!!.length != 32 || soUrl.isEmpty()) {
if (md5 == null || md5.length() != 32 || soUrl.length() == 0) {
//如果md5或下载的url为空,则调用系统行为进行加载 //如果md5或下载的url为空,则调用系统行为进行加载
System.loadLibrary(libName); System.loadLibrary(libName)
return; return
} }
if (!soFile.exists() || !soFile.isFile) {
soFile.delete()
if (!soFile.exists() || !soFile.isFile()) { download(soUrl, md5, downloadFile, soFile)
//noinspection ResultOfMethodCallIgnored
soFile.delete();
download(soUrl, md5, downloadFile, soFile);
//如果文件不存在或不是文件,则调用系统行为进行加载 //如果文件不存在或不是文件,则调用系统行为进行加载
System.loadLibrary(libName); System.loadLibrary(libName)
return; return
} }
if (soFile.exists()) { if (soFile.exists()) {
//如果文件存在,则校验md5值 //如果文件存在,则校验md5值
String fileMD5 = getFileMD5(soFile); val fileMD5 = getFileMD5(soFile)
if (fileMD5 != null && fileMD5.equalsIgnoreCase(md5)) { if (fileMD5 != null && fileMD5.equals(md5, ignoreCase = true)) {
//md5值一样,则加载 //md5值一样,则加载
System.load(soFile.getAbsolutePath()); System.load(soFile.absolutePath)
Log.e(TAG, "load from:" + soFile); Log.e(TAG, "load from:$soFile")
return; return
} }
//md5不一样则删除 //md5不一样则删除
//noinspection ResultOfMethodCallIgnored soFile.delete()
soFile.delete();
} }
//不存在则下载 //不存在则下载
download(soUrl, md5, downloadFile, soFile); download(soUrl, md5, downloadFile, soFile)
//使用系统加载方法 //使用系统加载方法
System.loadLibrary(libName); System.loadLibrary(libName)
} finally { } finally {
Log.e(TAG, "time:" + (System.currentTimeMillis() - start)); Log.e(TAG, "time:" + (System.currentTimeMillis() - start))
} }
} }
@SuppressLint("DiscouragedPrivateApi") @SuppressLint("DiscouragedPrivateApi")
private String getCpuAbi(Context context) { private fun getCpuAbi(context: Context): String? {
if (CPU_ABI != null) { if (cpuAbi != null) {
return CPU_ABI; return cpuAbi
} }
// 5.0以上Application才有primaryCpuAbi字段 // 5.0以上Application才有primaryCpuAbi字段
try { try {
ApplicationInfo appInfo = context.getApplicationInfo(); val appInfo = context.applicationInfo
Field abiField = ApplicationInfo.class.getDeclaredField("primaryCpuAbi"); val abiField = ApplicationInfo::class.java.getDeclaredField("primaryCpuAbi")
abiField.setAccessible(true); abiField.isAccessible = true
CPU_ABI = (String) abiField.get(appInfo); cpuAbi = abiField[appInfo] as String
} catch (Exception e) { } catch (e: Exception) {
e.printStackTrace(); e.printStackTrace()
} }
if (TextUtils.isEmpty(CPU_ABI)) { if (TextUtils.isEmpty(cpuAbi)) {
CPU_ABI = Build.SUPPORTED_ABIS[0]; cpuAbi = Build.SUPPORTED_ABIS[0]
} }
//貌似只有这个过时了的API能获取当前APP使用的ABI //貌似只有这个过时了的API能获取当前APP使用的ABI
return CPU_ABI; return cpuAbi
} }
@Suppress("SameParameterValue")
private String getUrlMd5(String url) { private fun getUrlMd5(url: String): String? {
if (md5 != null && md5.length() == 32) { if (md5 != null && md5!!.length == 32) {
return md5; return md5
} }
InputStream inputStream; val inputStream: InputStream
OutputStream outputStream; val outputStream: OutputStream
try { return try {
outputStream = new ByteArrayOutputStream(); outputStream = ByteArrayOutputStream()
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); val connection = URL(url).openConnection() as HttpURLConnection
inputStream = connection.getInputStream(); inputStream = connection.inputStream
byte[] buffer = new byte[1024]; val buffer = ByteArray(1024)
int read; var read: Int
while ((read = inputStream.read(buffer)) != -1) { while (inputStream.read(buffer).also { read = it } != -1) {
outputStream.write(buffer, 0, read); outputStream.write(buffer, 0, read)
outputStream.flush(); outputStream.flush()
} }
return outputStream.toString(); outputStream.toString()
} catch (e: IOException) {
} catch (IOException e) { null
return null;
} }
} }
/** /**
* 删除历史文件 * 删除历史文件
*/ */
private static void deleteHistoryFile(File dir, File currentFile) { private fun deleteHistoryFile(dir: File, currentFile: File?) {
File[] files = dir.listFiles(); val files = dir.listFiles()
if (files != null && files.length > 0) { @Suppress("SameParameterValue")
for (File f : files) { if (files != null && files.isNotEmpty()) {
if (f.exists() && (currentFile == null || !f.getAbsolutePath().equals(currentFile.getAbsolutePath()))) { for (f in files) {
boolean delete = f.delete(); if (f.exists() && (currentFile == null || f.absolutePath != currentFile.absolutePath)) {
Log.e(TAG, "delete file: " + f + " result: " + delete); val delete = f.delete()
Log.e(TAG, "delete file: $f result: $delete")
if (!delete) { if (!delete) {
f.deleteOnExit(); f.deleteOnExit()
} }
} }
} }
@ -223,158 +186,163 @@ public class CronetLoader extends CronetEngine.Builder.LibraryLoader {
/** /**
* 下载文件 * 下载文件
*/ */
private static boolean downloadFileIfNotExist(String url, File destFile) { private fun downloadFileIfNotExist(url: String, destFile: File): Boolean {
InputStream inputStream = null; var inputStream: InputStream? = null
OutputStream outputStream = null; var outputStream: OutputStream? = null
try { try {
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); val connection = URL(url).openConnection() as HttpURLConnection
inputStream = connection.getInputStream(); inputStream = connection.inputStream
if (destFile.exists()) { if (destFile.exists()) {
return true; return true
} }
destFile.getParentFile().mkdirs(); destFile.parentFile!!.mkdirs()
destFile.createNewFile(); destFile.createNewFile()
outputStream = new FileOutputStream(destFile); outputStream = FileOutputStream(destFile)
byte[] buffer = new byte[32768]; val buffer = ByteArray(32768)
int read; var read: Int
while ((read = inputStream.read(buffer)) != -1) { while (inputStream.read(buffer).also { read = it } != -1) {
outputStream.write(buffer, 0, read); outputStream.write(buffer, 0, read)
outputStream.flush(); outputStream.flush()
} }
return true; return true
} catch (Throwable e) { } catch (e: Throwable) {
e.printStackTrace(); e.printStackTrace()
if (destFile.exists() && !destFile.delete()) { if (destFile.exists() && !destFile.delete()) {
destFile.deleteOnExit(); destFile.deleteOnExit()
} }
} finally { } finally {
if (inputStream != null) { if (inputStream != null) {
try { try {
inputStream.close(); inputStream.close()
} catch (IOException e) { } catch (e: IOException) {
e.printStackTrace(); e.printStackTrace()
} }
} }
if (outputStream != null) { if (outputStream != null) {
try { try {
outputStream.close(); outputStream.close()
} catch (IOException e) { } catch (e: IOException) {
e.printStackTrace(); e.printStackTrace()
} }
} }
} }
return false; return false
} }
static boolean download = false;
static Executor executor = Executors.newSingleThreadExecutor();
/** /**
* 下载并拷贝文件 * 下载并拷贝文件
*/ */
private static synchronized void download(final String url, final String md5, final File downloadTempFile, final File destSuccessFile) { @Suppress("SameParameterValue")
@Synchronized
private fun download(
url: String,
md5: String?,
downloadTempFile: File,
destSuccessFile: File
) {
if (download) { if (download) {
return; return
} }
download = true; download = true
executor.execute(() -> { executor.execute {
boolean result = downloadFileIfNotExist(url, downloadTempFile); val result = downloadFileIfNotExist(url, downloadTempFile)
Log.e(TAG, "download result:" + result); Log.e(TAG, "download result:$result")
//文件md5再次校验 //文件md5再次校验
String fileMD5 = getFileMD5(downloadTempFile); val fileMD5 = getFileMD5(downloadTempFile)
if (md5 != null && !md5.equalsIgnoreCase(fileMD5)) { if (md5 != null && !md5.equals(fileMD5, ignoreCase = true)) {
boolean delete = downloadTempFile.delete(); val delete = downloadTempFile.delete()
if (!delete) { if (!delete) {
downloadTempFile.deleteOnExit(); downloadTempFile.deleteOnExit()
} }
download = false; download = false
return; return@execute
} }
Log.e(TAG, "download success, copy to " + destSuccessFile); Log.e(TAG, "download success, copy to $destSuccessFile")
//下载成功拷贝文件 //下载成功拷贝文件
copyFile(downloadTempFile, destSuccessFile); copyFile(downloadTempFile, destSuccessFile)
File parentFile = downloadTempFile.getParentFile(); val parentFile = downloadTempFile.parentFile
deleteHistoryFile(parentFile, null); @Suppress("SameParameterValue")
}); deleteHistoryFile(parentFile!!, null)
}
} }
/** /**
* 拷贝文件 * 拷贝文件
*/ */
private static boolean copyFile(File source, File dest) { private fun copyFile(source: File?, dest: File?): Boolean {
if (source == null || !source.exists() || !source.isFile() || dest == null) { if (source == null || !source.exists() || !source.isFile || dest == null) {
return false; return false
} }
if (source.getAbsolutePath().equals(dest.getAbsolutePath())) { if (source.absolutePath == dest.absolutePath) {
return true; return true
} }
FileInputStream is = null; var fileInputStream: FileInputStream? = null
FileOutputStream os = null; var os: FileOutputStream? = null
File parent = dest.getParentFile(); val parent = dest.parentFile
if (parent != null && (!parent.exists())) { if (parent != null && !parent.exists()) {
boolean mkdirs = parent.mkdirs(); val mkdirs = parent.mkdirs()
if (!mkdirs) { if (!mkdirs) {
mkdirs = parent.mkdirs(); parent.mkdirs()
} }
} }
try { try {
is = new FileInputStream(source); fileInputStream = FileInputStream(source)
os = new FileOutputStream(dest, false); os = FileOutputStream(dest, false)
val buffer = ByteArray(1024 * 512)
byte[] buffer = new byte[1024 * 512]; var length: Int
int length; while (fileInputStream.read(buffer).also { length = it } > 0) {
while ((length = is.read(buffer)) > 0) { os.write(buffer, 0, length)
os.write(buffer, 0, length);
} }
return true; return true
} catch (Exception e) { } catch (e: Exception) {
e.printStackTrace(); e.printStackTrace()
} finally { } finally {
if (is != null) { if (fileInputStream != null) {
try { try {
is.close(); fileInputStream.close()
} catch (Exception e) { } catch (e: Exception) {
e.printStackTrace(); e.printStackTrace()
} }
} }
if (os != null) { if (os != null) {
try { try {
os.close(); os.close()
} catch (Exception e) { } catch (e: Exception) {
e.printStackTrace(); e.printStackTrace()
} }
} }
} }
return false; return false
} }
/** /**
* 获得文件md5 * 获得文件md5
*/ */
private static String getFileMD5(File file) { private fun getFileMD5(file: File): String? {
FileInputStream fileInputStream = null; var fileInputStream: FileInputStream? = null
try { try {
fileInputStream = new FileInputStream(file); fileInputStream = FileInputStream(file)
MessageDigest md5 = MessageDigest.getInstance("MD5"); val md5 = MessageDigest.getInstance("MD5")
byte[] buffer = new byte[1024]; val buffer = ByteArray(1024)
int numRead = 0; var numRead: Int
while ((numRead = fileInputStream.read(buffer)) > 0) { while (fileInputStream.read(buffer).also { numRead = it } > 0) {
md5.update(buffer, 0, numRead); md5.update(buffer, 0, numRead)
} }
return String.format("%032x", new BigInteger(1, md5.digest())).toLowerCase(); return String.format("%032x", BigInteger(1, md5.digest())).lowercase()
} catch (Exception | OutOfMemoryError e) { } catch (e: Exception) {
e.printStackTrace(); e.printStackTrace()
} catch (e: OutOfMemoryError) {
e.printStackTrace()
} finally { } finally {
if (fileInputStream != null) { if (fileInputStream != null) {
try { try {
fileInputStream.close(); fileInputStream.close()
} catch (Exception e) { } catch (e: Exception) {
e.printStackTrace(); e.printStackTrace()
} }
} }
} }
return null; return null
} }
}
}
Loading…
Cancel
Save