优化jar加载;

pull/142/head
于俊 1 year ago
parent cba86340ae
commit 6e054c9aa4
  1. 247
      app/src/main/java/com/github/catvod/crawler/JarLoader.java
  2. 100
      app/src/main/java/com/github/tvbox/osc/api/ApiConfig.java
  3. 4
      app/src/main/java/com/github/tvbox/osc/ui/activity/PlayActivity.java
  4. 4
      app/src/main/java/com/github/tvbox/osc/ui/fragment/PlayFragment.java
  5. 2
      gradle.properties

@ -1,6 +1,11 @@
package com.github.catvod.crawler;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.NetworkOnMainThreadException;
import android.util.Log;
import com.github.tvbox.osc.base.App;
import com.github.tvbox.osc.util.MD5;
@ -12,11 +17,20 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import dalvik.system.DexClassLoader;
import okhttp3.Response;
@ -40,46 +54,209 @@ public class JarLoader {
return loadClassLoader(cache, "main");
}
// private boolean loadClassLoader1(String jar, String key) {
// boolean success = false;
// try {
// File cacheDir = new File(App.getInstance().getCacheDir().getAbsolutePath() + "/catvod_csp");
// if (!cacheDir.exists())
// cacheDir.mkdirs();
// DexClassLoader classLoader = new DexClassLoader(jar, cacheDir.getAbsolutePath(), null, App.getInstance().getClassLoader());
// // make force wait here, some device async dex load
// int count = 0;
// do {
// try {
// Class classInit = classLoader.loadClass("com.github.catvod.spider.Init");
// if (classInit != null) {
// Method method = classInit.getMethod("init", Context.class);
// method.invoke(null, App.getInstance());
// System.out.println("自定义爬虫代码加载成功!");
// success = true;
// try {
// Class proxy = classLoader.loadClass("com.github.catvod.spider.Proxy");
// Method mth = proxy.getMethod("proxy", Map.class);
// proxyMethods.put(key, mth);
// } catch (Throwable th) {
//
// }
// break;
// }
// Thread.sleep(200);
// } catch (Throwable th) {
// th.printStackTrace();
// }
// count++;
// } while (count < 5);
//
// if (success) {
// classLoaders.put(key, classLoader);
// }
// } catch (Throwable th) {
// th.printStackTrace();
// }
// return success;
// }
private boolean loadClassLoader(String jar, String key) {
boolean success = false;
try {
File cacheDir = new File(App.getInstance().getCacheDir().getAbsolutePath() + "/catvod_csp");
if (!cacheDir.exists())
cacheDir.mkdirs();
DexClassLoader classLoader = new DexClassLoader(jar, cacheDir.getAbsolutePath(), null, App.getInstance().getClassLoader());
// make force wait here, some device async dex load
int count = 0;
do {
try {
Class classInit = classLoader.loadClass("com.github.catvod.spider.Init");
if (classInit != null) {
Method method = classInit.getMethod("init", Context.class);
method.invoke(null, App.getInstance());
System.out.println("自定义爬虫代码加载成功!");
success = true;
try {
Class proxy = classLoader.loadClass("com.github.catvod.spider.Proxy");
Method mth = proxy.getMethod("proxy", Map.class);
proxyMethods.put(key, mth);
} catch (Throwable th) {
}
break;
}
Thread.sleep(200);
} catch (Throwable th) {
th.printStackTrace();
final String TAG = "JarLoader";
final File jarFile = new File(jar);
final AtomicBoolean success = new AtomicBoolean(false);
DexClassLoader classLoader = null;
// 1. 前置校验
if (!validateJarFile(jarFile, TAG)) return false;
// 2. 准备缓存目录
File cacheDir = prepareCacheDir(TAG);
if (cacheDir == null) return false;
classLoader = createDexClassLoader(jarFile, cacheDir, TAG);
if (classLoader == null) return false;
int retryCount = 0;
final int maxRetries = 2; // 减少重试次数,增加超时检测
final long retryInterval = 200; // 增加重试间隔
while (retryCount < maxRetries && !success.get()) {
try {
Class<?> initClass = classLoader.loadClass("com.github.catvod.spider.Init");
Method initMethod = initClass.getMethod("init", Context.class);
// 4.2 异步执行初始化(解决主线程网络问题)
executeInitInBackground(initMethod, success, TAG);
// 4.3 处理初始化结果
if (success.get()) {
handlePostInit(classLoader, key, TAG);
classLoaders.put(key, classLoader);
Log.i(TAG, "JAR加载成功: " + jar);
return true;
}
count++;
} while (count < 5);
} catch (ClassNotFoundException e) {
Log.w(TAG, "Init类未找到,重试: " + (++retryCount) + "/" + maxRetries);
sleep(retryInterval);
} catch (Exception e) {
Log.w(TAG, "Init类 加载失败");
break;
}
}
// 5. 清理资源
cleanupResources(classLoader, TAG);
return false;
}
// ------------------- 辅助方法 -------------------
private boolean validateJarFile(File jarFile, String tag) {
if (!jarFile.exists() || !jarFile.isFile() || jarFile.length() == 0) {
Log.e(tag, "JAR文件无效: " + jarFile);
return false;
}
return true;
}
if (success) {
classLoaders.put(key, classLoader);
private File prepareCacheDir(String tag) {
File cacheDir = new File(App.getInstance().getCacheDir(), "catvod_csp");
try {
if (!cacheDir.exists() && !cacheDir.mkdirs()) {
Log.e(tag, "目录创建失败: " + cacheDir);
return null;
}
} catch (Throwable th) {
th.printStackTrace();
return cacheDir;
} catch (SecurityException e) {
Log.e(tag, "目录访问拒绝: " + e.getMessage());
return null;
}
}
private DexClassLoader createDexClassLoader(File jarFile, File cacheDir, String tag) {
try {
return new DexClassLoader(
jarFile.getAbsolutePath(),
cacheDir.getAbsolutePath(),
null,
App.getInstance().getClassLoader()
);
} catch (Exception e) {
Log.e(tag, "类加载器创建失败", e);
return null;
}
}
private void executeInitInBackground(Method initMethod, AtomicBoolean successFlag, String tag) {
final CountDownLatch latch = new CountDownLatch(1);
final Throwable[] exceptionHolder = {null};
Executors.newSingleThreadExecutor().execute(() -> {
try {
initMethod.invoke(null, App.getInstance());
successFlag.set(true);
} catch (InvocationTargetException e) {
exceptionHolder[0] = e.getTargetException();
} catch (Exception e) {
exceptionHolder[0] = e;
} finally {
latch.countDown();
}
});
try {
if (!latch.await(6, TimeUnit.SECONDS)) {
Log.e(tag, "初始化超时");
throw new TimeoutException("初始化未在6秒内完成");
}
} catch (InterruptedException e) {
Log.e(tag, "线程中断", e);
Thread.currentThread().interrupt();
} catch (TimeoutException e) {
throw new RuntimeException(e);
}
if (exceptionHolder[0] != null) {
handleInitException(exceptionHolder[0], tag);
}
}
private void handlePostInit(ClassLoader loader, String key, String tag) {
// 主线程处理后续操作
new Handler(Looper.getMainLooper()).post(() -> {
try {
Class<?> proxyClass = loader.loadClass("com.github.catvod.spider.Proxy");
Method method = proxyClass.getMethod("proxy", Map.class);
proxyMethods.put(key, method);
Log.d(tag, "代理方法加载成功");
} catch (Exception e) {
Log.w(tag, "代理功能未启用: " + e.getMessage());
}
});
}
private void handleInitException(Throwable t, String tag) {
StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw));
Log.e(tag, "初始化失败: \n" +
"类型: " + t.getClass().getName() + "\n" +
"信息: " + (t.getMessage() != null ? t.getMessage() : "无详细消息") + "\n" +
"堆栈: \n" + sw.toString());
if (t instanceof NetworkOnMainThreadException) {
Log.w(tag, "建议: 第三方JAR包含主线程网络操作,请更新实现或联系开发者");
}
}
private void cleanupResources(DexClassLoader loader, String tag) {
if (loader != null) {
try {
Field pathList = loader.getClass().getSuperclass().getDeclaredField("pathList");
pathList.setAccessible(true);
Object dexPathList = pathList.get(loader);
Field dexElements = dexPathList.getClass().getDeclaredField("dexElements");
dexElements.setAccessible(true);
dexElements.set(dexPathList, new Object[0]);
} catch (Exception e) {
Log.w(tag, "资源清理失败", e);
}
}
}
private void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException ignored) {
}
return success;
}
private DexClassLoader loadJarInternal(String jar, String md5, String key) {

@ -42,6 +42,8 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
@ -313,51 +315,77 @@ public class ApiConfig {
}
boolean isJarInImg = jarUrl.startsWith("img+");
LOG.i("echo---jar_start");
jarUrl = jarUrl.replace("img+", "");
OkGo.<File>get(jarUrl)
.headers("User-Agent", userAgent)
.headers("Accept", requestAccept)
.execute(new AbsCallback<File>() {
@Override
public File convertResponse(okhttp3.Response response) throws Throwable {
File cacheDir = cache.getParentFile();
if (!cacheDir.exists())
cacheDir.mkdirs();
if (cache.exists())
cache.delete();
FileOutputStream fos = new FileOutputStream(cache);
if(isJarInImg) {
String respData = response.body().string();
byte[] imgJar = getImgJar(respData);
fos.write(imgJar);
} else {
fos.write(response.body().bytes());
}
fos.flush();
fos.close();
return cache;
}
@Override
public File convertResponse(okhttp3.Response response) throws Throwable {
File cacheDir = cache.getParentFile();
assert cacheDir != null;
if (!cacheDir.exists()) cacheDir.mkdirs();
if (cache.exists()) cache.delete();
// 3. 使用 try-with-resources 确保流关闭
assert response.body() != null;
try (FileOutputStream fos = new FileOutputStream(cache)) {
if (isJarInImg) {
String respData = response.body().string();
LOG.i("echo---jar Response: " + respData);
byte[] imgJar = getImgJar(respData);
if (imgJar == null || imgJar.length == 0) {
throw new IOException("Generated JAR data is empty");
}
fos.write(imgJar);
} else {
// 使用流式传输避免内存溢出
InputStream inputStream = response.body().byteStream();
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
}
fos.flush();
} catch (IOException e) {
return null;
}
return cache;
}
@Override
public void onSuccess(Response<File> response) {
if (response.body().exists()) {
if (jarLoader.load(response.body().getAbsolutePath())) {
callback.success();
} else {
callback.error("");
@Override
public void onSuccess(Response<File> response) {
File file = response.body();
if (file != null && file.exists()) {
LOG.i("echo---jar Trying to load: " + file.getAbsolutePath());
try {
if (jarLoader.load(file.getAbsolutePath())) {
callback.success();
} else {
LOG.e("echo---jar Loader returned false");
callback.error("JAR加载失败");
}
} catch (Exception e) {
LOG.e("echo---jar Loader threw exception: " + e.getMessage());
callback.error("加载异常: " + e.getMessage());
}
} else {
LOG.e("echo---jar File not found");
callback.error("文件不存在");
}
}
} else {
callback.error("");
}
}
@Override
public void onError(Response<File> response) {
super.onError(response);
callback.error("");
}
});
@Override
public void onError(Response<File> response) {
Throwable ex = response.getException();
if (ex != null) {
LOG.i("echo---jar Request failed: " + ex.getMessage());
}
callback.error(ex != null ? ex.getMessage() : "未知网络错误");
}
});
}
private void parseJson(String apiUrl, File f) throws Throwable {

@ -1368,7 +1368,7 @@ public class PlayActivity extends BaseActivity {
mXwalkWebView.stopLoading();
mXwalkWebView.loadUrl("about:blank");
if (destroy) {
// mXwalkWebView.clearCache(true);
mXwalkWebView.clearCache(true);
mXwalkWebView.removeAllViews();
mXwalkWebView.onDestroy();
mXwalkWebView = null;
@ -1378,7 +1378,7 @@ public class PlayActivity extends BaseActivity {
mSysWebView.stopLoading();
mSysWebView.loadUrl("about:blank");
if (destroy) {
// mSysWebView.clearCache(true);
mSysWebView.clearCache(true);
mSysWebView.removeAllViews();
mSysWebView.destroy();
mSysWebView = null;

@ -1406,7 +1406,7 @@ public class PlayFragment extends BaseLazyFragment {
mXwalkWebView.stopLoading();
mXwalkWebView.loadUrl("about:blank");
if (destroy) {
// mXwalkWebView.clearCache(true);
mXwalkWebView.clearCache(true);
mXwalkWebView.removeAllViews();
mXwalkWebView.onDestroy();
mXwalkWebView = null;
@ -1416,7 +1416,7 @@ public class PlayFragment extends BaseLazyFragment {
mSysWebView.stopLoading();
mSysWebView.loadUrl("about:blank");
if (destroy) {
// mSysWebView.clearCache(true);
mSysWebView.clearCache(true);
mSysWebView.removeAllViews();
mSysWebView.destroy();
mSysWebView = null;

@ -18,4 +18,4 @@ android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
IsDebug=true
org.gradle.jvmargs=-Xmx2048m --add-opens java.base/java.io=ALL-UNNAMED
#org.gradle.jvmargs=-Xmx2048m --add-opens java.base/java.io=ALL-UNNAMED

Loading…
Cancel
Save