From 6e054c9aa42e678012affea62724884cfe45fe34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8E=E4=BF=8A?= <215613905@qq.com> Date: Mon, 10 Mar 2025 16:28:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96jar=E5=8A=A0=E8=BD=BD;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/github/catvod/crawler/JarLoader.java | 247 +++++++++++++++--- .../com/github/tvbox/osc/api/ApiConfig.java | 100 ++++--- .../tvbox/osc/ui/activity/PlayActivity.java | 4 +- .../tvbox/osc/ui/fragment/PlayFragment.java | 4 +- gradle.properties | 2 +- 5 files changed, 281 insertions(+), 76 deletions(-) diff --git a/app/src/main/java/com/github/catvod/crawler/JarLoader.java b/app/src/main/java/com/github/catvod/crawler/JarLoader.java index fe54c6afad..14518c223a 100644 --- a/app/src/main/java/com/github/catvod/crawler/JarLoader.java +++ b/app/src/main/java/com/github/catvod/crawler/JarLoader.java @@ -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.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 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) { diff --git a/app/src/main/java/com/github/tvbox/osc/api/ApiConfig.java b/app/src/main/java/com/github/tvbox/osc/api/ApiConfig.java index fb0d08e43a..f5c6f44c56 100644 --- a/app/src/main/java/com/github/tvbox/osc/api/ApiConfig.java +++ b/app/src/main/java/com/github/tvbox/osc/api/ApiConfig.java @@ -42,6 +42,8 @@ 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 void loadJar(boolean useCache, String spider, LoadConfigCallback callback } boolean isJarInImg = jarUrl.startsWith("img+"); + LOG.i("echo---jar_start"); jarUrl = jarUrl.replace("img+", ""); OkGo.get(jarUrl) .headers("User-Agent", userAgent) .headers("Accept", requestAccept) .execute(new AbsCallback() { - @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 response) { - if (response.body().exists()) { - if (jarLoader.load(response.body().getAbsolutePath())) { - callback.success(); - } else { - callback.error(""); + @Override + public void onSuccess(Response 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 response) { - super.onError(response); - callback.error(""); - } - }); + @Override + public void onError(Response 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 { diff --git a/app/src/main/java/com/github/tvbox/osc/ui/activity/PlayActivity.java b/app/src/main/java/com/github/tvbox/osc/ui/activity/PlayActivity.java index 153f28e558..4a6d530779 100644 --- a/app/src/main/java/com/github/tvbox/osc/ui/activity/PlayActivity.java +++ b/app/src/main/java/com/github/tvbox/osc/ui/activity/PlayActivity.java @@ -1368,7 +1368,7 @@ public void run() { 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 void run() { mSysWebView.stopLoading(); mSysWebView.loadUrl("about:blank"); if (destroy) { -// mSysWebView.clearCache(true); + mSysWebView.clearCache(true); mSysWebView.removeAllViews(); mSysWebView.destroy(); mSysWebView = null; diff --git a/app/src/main/java/com/github/tvbox/osc/ui/fragment/PlayFragment.java b/app/src/main/java/com/github/tvbox/osc/ui/fragment/PlayFragment.java index a837dc807c..af936296bc 100644 --- a/app/src/main/java/com/github/tvbox/osc/ui/fragment/PlayFragment.java +++ b/app/src/main/java/com/github/tvbox/osc/ui/fragment/PlayFragment.java @@ -1406,7 +1406,7 @@ public void run() { 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 void run() { mSysWebView.stopLoading(); mSysWebView.loadUrl("about:blank"); if (destroy) { -// mSysWebView.clearCache(true); + mSysWebView.clearCache(true); mSysWebView.removeAllViews(); mSysWebView.destroy(); mSysWebView = null; diff --git a/gradle.properties b/gradle.properties index 2b801b1423..8e1f9a6b7a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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