Skip to content

Commit

Permalink
优化jar加载;
Browse files Browse the repository at this point in the history
  • Loading branch information
q215613905 committed Mar 10, 2025
1 parent cba8634 commit 6e054c9
Show file tree
Hide file tree
Showing 5 changed files with 281 additions and 76 deletions.
247 changes: 212 additions & 35 deletions app/src/main/java/com/github/catvod/crawler/JarLoader.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -12,11 +17,20 @@
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;
Expand All @@ -40,46 +54,209 @@ public boolean load(String cache) {
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) {
Expand Down
Loading

0 comments on commit 6e054c9

Please sign in to comment.