搜索小调;
去除jar加载时的冗余逻辑;
pull/144/head
21561 1 year ago
parent 2bb0732ff7
commit bff0dba4ed
  1. 224
      app/src/main/java/com/github/catvod/crawler/JarLoader.java
  2. 3
      app/src/main/java/com/github/tvbox/osc/api/ApiConfig.java
  3. 24
      app/src/main/java/com/github/tvbox/osc/ui/activity/SearchActivity.java
  4. 21
      app/src/main/java/com/github/tvbox/osc/viewmodel/SourceViewModel.java

@ -1,9 +1,6 @@
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;
@ -17,21 +14,11 @@ 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.Objects;
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;
@ -71,177 +58,66 @@ public class JarLoader {
}
private boolean loadClassLoader(String jar, String key) {
final String TAG = "JarLoader";
if (classLoaders.containsKey(key)){
Log.i(TAG, "loadClassLoader jar缓存: " + key);
Log.i("JarLoader", "echo-loadClassLoader jar缓存: " + key);
return true;
}
final File jarFile = new File(jar);
final AtomicBoolean success = new AtomicBoolean(false);
DexClassLoader classLoader;
// 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;
}
} 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;
}
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;
}
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();
}
});
boolean success = false;
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包含主线程网络操作,请更新实现或联系开发者");
}
}
File cacheDir = new File(App.getInstance().getCacheDir().getAbsolutePath() + "/catvod_csp");
if (!cacheDir.exists())
cacheDir.mkdirs();
final DexClassLoader classLoader = new DexClassLoader(jar, cacheDir.getAbsolutePath(), null, App.getInstance().getClassLoader());
int count = 0;
do {
try {
final Class<?> classInit = classLoader.loadClass("com.github.catvod.spider.Init");
if (classInit != null) {
final Method initMethod = classInit.getMethod("init", Context.class);
// 在子线程中调用 init 方法,避免网络请求在主线程中执行
Thread initThread = new Thread(new Runnable() {
@Override
public void run() {
try {
initMethod.invoke(null, App.getInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
});
initThread.start();
initThread.join();
Log.i("JarLoader", "echo-自定义爬虫代码加载成功!");
success = true;
try {
Class<?> proxy = classLoader.loadClass("com.github.catvod.spider.Proxy");
Method proxyMethod = proxy.getMethod("proxy", Map.class);
proxyMethods.put(key, proxyMethod);
} catch (Throwable th) {
// 可以记录错误日志
th.printStackTrace();
}
break;
}
Thread.sleep(200);
} catch (Throwable th) {
th.printStackTrace();
}
count++;
} while (count < 2);
private void cleanupResources(DexClassLoader loader, String tag) {
if (loader != null) {
try {
Field pathList = Objects.requireNonNull(loader.getClass().getSuperclass()).getDeclaredField("pathList");
pathList.setAccessible(true);
Object dexPathList = pathList.get(loader);
assert dexPathList != null;
Field dexElements = dexPathList.getClass().getDeclaredField("dexElements");
dexElements.setAccessible(true);
dexElements.set(dexPathList, new Object[0]);
} catch (Exception e) {
Log.w(tag, "资源清理失败", e);
if (success) {
classLoaders.put(key, classLoader);
}
} catch (Throwable th) {
th.printStackTrace();
}
}
private void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException ignored) {
}
return success;
}
private DexClassLoader loadJarInternal(String jar, String md5, String key) {
if (classLoaders.containsKey(key)){
Log.i("JarLoader", "loadJarInternal jar缓存: " + key);
Log.i("JarLoader", "echo-loadJarInternal jar缓存: " + key);
return classLoaders.get(key);
}
File cache = new File(App.getInstance().getFilesDir().getAbsolutePath() + "/" + key + ".jar");
@ -280,7 +156,7 @@ public class JarLoader {
public Spider getSpider(String key, String cls, String ext, String jar) {
if (spiders.containsKey(key)) {
Log.i("JarLoader", "getSpider spider缓存: " + key);
Log.i("JarLoader", "echo-getSpider spider缓存: " + key);
return spiders.get(key);
}
String clsKey = cls.replace("csp_", "");

@ -262,6 +262,7 @@ public class ApiConfig {
public void onSuccess(Response<String> response) {
try {
String json = response.body();
LOG.i("echo-ConfigJson"+json);
parseJson(apiUrl, json);
FileUtils.saveCache(cache,json);
callback.success();
@ -406,8 +407,6 @@ public class ApiConfig {
}
private void parseJson(String apiUrl, String jsonStr) {
LOG.i("echo-parseJson"+jsonStr);
JsonObject infoJson = gson.fromJson(jsonStr, JsonObject.class);
// spider
spider = DefaultConfig.safeJsonString(infoJson, "spider", "");

@ -103,24 +103,6 @@ public class SearchActivity extends BaseActivity {
isSearchBack = false;
}
/*
* 禁止软键盘
* @param activity Activity
*/
public static void disableKeyboard(Activity activity) {
hasKeyBoard = false;
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
}
/*
* 启用软键盘
* @param activity Activity
*/
public static void enableKeyboard(Activity activity) {
hasKeyBoard = true;
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
}
public void openSystemKeyBoard() {
InputMethodManager imm = (InputMethodManager) this.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(this.getCurrentFocus(), InputMethodManager.SHOW_FORCED);
@ -256,8 +238,8 @@ public class SearchActivity extends BaseActivity {
@Override
public void onClick(View v) {
FastClickCheckUtil.check(v);
etSearch.setText("");
initData();
etSearch.setText("");
}
});
@ -273,6 +255,10 @@ public class SearchActivity extends BaseActivity {
bundle.putString("title", wd);
jumpActivity(FastSearchActivity.class, bundle);
} else {
InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.hideSoftInputFromWindow(etSearch.getWindowToken(), 0);
}
search(wd);
}
} else {

@ -318,7 +318,7 @@ public class SourceViewModel extends ViewModel {
}
// categoryContent
public void getList(MovieSort.SortData sortData, int page) {
LOG.i("getList:");
LOG.i("echo-getList:");
SourceBean homeSourceBean = ApiConfig.get().getHomeSourceBean();
int type = homeSourceBean.getType();
if (type == 3) {
@ -373,11 +373,13 @@ public class SourceViewModel extends ViewModel {
});
}else if (type == 4) {
String ext= "";
String extend=homeSourceBean.getExt();
extend=getFixUrl(extend);
if(URLEncoder.encode(extend).length()>1000)extend="";
if (sortData.filterSelect != null && sortData.filterSelect.size() > 0) {
try {
String selectExt = new JSONObject(sortData.filterSelect).toString();
ext = Base64.encodeToString(selectExt.getBytes("UTF-8"), Base64.DEFAULT | Base64.NO_WRAP);
LOG.i(ext);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
@ -391,6 +393,7 @@ public class SourceViewModel extends ViewModel {
.params("t", sortData.id)
.params("pg", page)
.params("ext", ext)
.params("extend", extend)
.execute(new AbsCallback<String>() {
@Override
public String convertResponse(okhttp3.Response response) throws Throwable {
@ -404,7 +407,7 @@ public class SourceViewModel extends ViewModel {
@Override
public void onSuccess(Response<String> response) {
String json = response.body();
LOG.i(json);
LOG.i("echo-list:"+json);
json(listResult, json, homeSourceBean.getKey());
}
@ -564,10 +567,14 @@ public class SourceViewModel extends ViewModel {
}
});
} else if (type == 0 || type == 1|| type == 4) {
String extend=sourceBean.getExt();
extend=getFixUrl(extend);
if(URLEncoder.encode(extend).length()>1000)extend="";
OkGo.<String>get(sourceBean.getApi())
.tag("detail")
.params("ac", type == 0 ? "videolist" : "detail")
.params("ids", id)
.params("extend", extend)
.execute(new AbsCallback<String>() {
@Override
@ -652,10 +659,14 @@ public class SourceViewModel extends ViewModel {
}
});
}else if (type == 4) {
String extend=sourceBean.getExt();
extend=getFixUrl(extend);
if(URLEncoder.encode(extend).length()>1000)extend="";
OkGo.<String>get(sourceBean.getApi())
.params("wd", wd)
.params("ac" ,"detail")
.params("quick" ,"false")
.params("extend" ,extend)
.tag("search")
.execute(new AbsCallback<String>() {
@Override
@ -730,10 +741,14 @@ public class SourceViewModel extends ViewModel {
}
});
}else if (type == 4) {
String extend=sourceBean.getExt();
extend=getFixUrl(extend);
if(URLEncoder.encode(extend).length()>1000)extend="";
OkGo.<String>get(sourceBean.getApi())
.params("wd", wd)
.params("ac" ,"detail")
.params("quick" ,"true")
.params("extend" ,extend)
.tag("search")
.execute(new AbsCallback<String>() {
@Override

Loading…
Cancel
Save