From b8af9003168d49ffc1ed7acae5ffc2f2bdb7cbe5 Mon Sep 17 00:00:00 2001 From: jhengazuji Date: Sat, 22 Nov 2025 00:49:39 +0800 Subject: [PATCH] Fix armeabi-v7a py load --- app/build.gradle | 3 - .../android/tv/api/loader/PyLoader.java | 2 +- .../main/java/com/fongmi/chaquo/Loader.java | 11 +- .../main/java/com/fongmi/chaquo/Platform.java | 180 ++++++++++++++++++ 4 files changed, 185 insertions(+), 11 deletions(-) create mode 100644 chaquo/src/main/java/com/fongmi/chaquo/Platform.java diff --git a/app/build.gradle b/app/build.gradle index 15bfbbb2a..c83114164 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -54,9 +54,6 @@ android { } packagingOptions { - jniLibs { - useLegacyPackaging true - } resources { exclude 'META-INF/beans.xml' exclude 'META-INF/versions/9/OSGI-INF/MANIFEST.MF' diff --git a/app/src/main/java/com/fongmi/android/tv/api/loader/PyLoader.java b/app/src/main/java/com/fongmi/android/tv/api/loader/PyLoader.java index 2bb6314ef..e96a327b2 100644 --- a/app/src/main/java/com/fongmi/android/tv/api/loader/PyLoader.java +++ b/app/src/main/java/com/fongmi/android/tv/api/loader/PyLoader.java @@ -31,7 +31,7 @@ public class PyLoader { public Spider getSpider(String key, String api, String ext) { try { if (spiders.containsKey(key)) return spiders.get(key); - Spider spider = loader.spider(App.get(), api); + Spider spider = loader.spider(api); spider.init(App.get(), ext); spiders.put(key, spider); return spider; diff --git a/chaquo/src/main/java/com/fongmi/chaquo/Loader.java b/chaquo/src/main/java/com/fongmi/chaquo/Loader.java index 895059beb..6d9be99b1 100644 --- a/chaquo/src/main/java/com/fongmi/chaquo/Loader.java +++ b/chaquo/src/main/java/com/fongmi/chaquo/Loader.java @@ -1,23 +1,20 @@ package com.fongmi.chaquo; -import android.content.Context; - import com.chaquo.python.PyObject; import com.chaquo.python.Python; -import com.chaquo.python.android.AndroidPlatform; import com.github.catvod.utils.Path; public class Loader { private PyObject app; - private void init(Context context) { - if (!Python.isStarted()) Python.start(new AndroidPlatform(context)); + private void init() { + if (!Python.isStarted()) Python.start(Platform.create()); app = Python.getInstance().getModule("app"); } - public Spider spider(Context context, String api) { - if (app == null) init(context); + public Spider spider(String api) { + if (app == null) init(); PyObject obj = app.callAttr("spider", Path.py().getAbsolutePath(), api); return new Spider(app, obj, api); } diff --git a/chaquo/src/main/java/com/fongmi/chaquo/Platform.java b/chaquo/src/main/java/com/fongmi/chaquo/Platform.java new file mode 100644 index 000000000..f79cf67b6 --- /dev/null +++ b/chaquo/src/main/java/com/fongmi/chaquo/Platform.java @@ -0,0 +1,180 @@ +package com.fongmi.chaquo; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.AssetManager; +import android.os.Build; + +import com.chaquo.python.Python; +import com.chaquo.python.internal.Common; +import com.github.catvod.Init; + +import org.jetbrains.annotations.NotNull; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class Platform extends Python.Platform { + + private static final String[] OBSOLETE_FILES = {"app.zip", "requirements.zip", "chaquopy.mp3", "stdlib.mp3", "chaquopy.zip", "lib-dynload", "stdlib.zip", "bootstrap.zip", "stdlib-common.zip", "ticket.txt"}; + private static final String[] OBSOLETE_CACHE = {"AssetFinder"}; + + private final SharedPreferences sp; + private final JSONObject buildJson; + private final AssetManager am; + private final Context context; + private String ABI; + + public static Platform create() { + return new Platform(); + } + + public Platform() { + this.context = Init.context(); + this.sp = context.getSharedPreferences(Common.ASSET_DIR, Context.MODE_PRIVATE); + this.am = context.getAssets(); + try { + try (InputStream is = am.open(Common.ASSET_DIR + "/" + Common.ASSET_BUILD_JSON)) { + buildJson = new JSONObject(streamToString(is)); + } + loadNativeLibs(); + } catch (IOException | JSONException e) { + throw new RuntimeException(e); + } + List supportedAbis = new ArrayList<>(); + Collections.addAll(supportedAbis, Build.SUPPORTED_ABIS); + Collections.addAll(supportedAbis, Build.CPU_ABI, Build.CPU_ABI2); + for (String abi : supportedAbis) { + try (InputStream ignored = am.open(Common.ASSET_DIR + "/" + Common.assetZip(Common.ASSET_STDLIB, abi))) { + ABI = abi; + break; + } catch (IOException ignored) { + } + } + if (ABI == null) throw new RuntimeException("No supported ABI found in: " + supportedAbis); + } + + @Override + public @NotNull String getPath() { + String assetDir = new File(context.getFilesDir(), Common.ASSET_DIR).getAbsolutePath(); + List pathAssets = Arrays.asList(Common.assetZip(Common.ASSET_STDLIB, Common.ABI_COMMON), Common.assetZip(Common.ASSET_BOOTSTRAP), Common.ASSET_BOOTSTRAP_NATIVE + "/" + ABI); + String pythonPath = pathAssets.stream().map(asset -> assetDir + "/" + asset).collect(Collectors.joining(":")); + List extractionList = new ArrayList<>(pathAssets); + extractionList.add(Common.ASSET_CACERT); + try { + deleteObsolete(context.getFilesDir(), OBSOLETE_FILES); + deleteObsolete(context.getCacheDir(), OBSOLETE_CACHE); + extractAssets(extractionList); + } catch (IOException | JSONException e) { + throw new RuntimeException(e); + } + return pythonPath; + } + + @Override + public void onStart(@NotNull Python py) { + String[] appPath = {Common.ASSET_APP, Common.ASSET_REQUIREMENTS, Common.ASSET_STDLIB + "-" + ABI}; + py.getModule("java.android").callAttr("initialize", context, buildJson, appPath); + } + + private void deleteObsolete(File baseDir, String[] filenames) { + for (String filename : filenames) { + filename = filename.replace("", ABI); + deleteRecursive(new File(baseDir, Common.ASSET_DIR + "/" + filename)); + } + } + + private void extractAssets(List assets) throws IOException, JSONException { + JSONObject assetsJson = buildJson.getJSONObject("assets"); + Set unextracted = new HashSet<>(assets); + Set directories = new HashSet<>(); + SharedPreferences.Editor spe = sp.edit(); + Iterator keys = assetsJson.keys(); + while (keys.hasNext()) { + String path = keys.next(); + for (String ea : assets) { + if (path.equals(ea) || path.startsWith(ea + "/")) { + extractAsset(assetsJson, spe, path); + unextracted.remove(ea); + if (path.startsWith(ea + "/")) directories.add(ea); + break; + } + } + } + if (!unextracted.isEmpty()) throw new RuntimeException("Failed to find assets: " + unextracted); + for (String dir : directories) cleanExtractedDir(dir, assetsJson); + spe.apply(); + } + + private void extractAsset(JSONObject assetsJson, SharedPreferences.Editor spe, String path) throws IOException, JSONException { + String fullPath = Common.ASSET_DIR + "/" + path; + File outFile = new File(context.getFilesDir(), fullPath); + String spKey = "asset." + path; + String newHash = assetsJson.getString(path); + if (outFile.exists() && sp.getString(spKey, "").equals(newHash)) return; + outFile.delete(); + File outDir = outFile.getParentFile(); + if (outDir != null && !outDir.exists() && !outDir.mkdirs()) throw new IOException("Failed to create " + outDir); + File tmpFile = new File(outDir, outFile.getName() + ".tmp"); + tmpFile.delete(); + try (InputStream in = am.open(fullPath); OutputStream out = new FileOutputStream(tmpFile)) { + transferStream(in, out); + } + if (!tmpFile.renameTo(outFile)) throw new IOException("Failed to rename " + tmpFile); + spe.putString(spKey, newHash); + } + + private void cleanExtractedDir(String dir, JSONObject assetsJson) { + File outDir = new File(context.getFilesDir(), Common.ASSET_DIR + "/" + dir); + File[] list = outDir.listFiles(); + if (list == null) return; + Arrays.stream(list).forEach(outFile -> { + String name = outFile.getName(); + if (outFile.isDirectory()) { + cleanExtractedDir(dir + "/" + name, assetsJson); + } else if (!assetsJson.has(dir + "/" + name)) { + outFile.delete(); + } + }); + } + + private void deleteRecursive(File file) { + File[] children = file.listFiles(); + if (children != null) Arrays.stream(children).forEach(this::deleteRecursive); + file.delete(); + } + + private void transferStream(InputStream in, OutputStream out) throws IOException { + int len; + byte[] buffer = new byte[8192]; + while ((len = in.read(buffer)) != -1) out.write(buffer, 0, len); + } + + private String streamToString(InputStream in) throws IOException { + ByteArrayOutputStream result = new ByteArrayOutputStream(); + transferStream(in, result); + return result.toString(StandardCharsets.UTF_8.name()); + } + + private void loadNativeLibs() throws JSONException { + String pythonVer = buildJson.getString("python_version"); + for (String lib : new String[]{"crypto_chaquopy", "ssl_chaquopy", "sqlite3_chaquopy", "python" + pythonVer, "chaquopy_java"}) { + System.loadLibrary(lib); + } + } +} \ No newline at end of file