|
|
|
|
@ -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) { |
|
|
|
|
|