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)
if (AppConfig.isCronet) {
//预下载Cronet so
CronetLoader.getInstance(this).preDownload()
CronetLoader.preDownload()
}
LanguageUtils.setConfiguration(this)
createNotificationChannels()
applyDayNight(this)

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

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

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