Browse Source

android: Complete support for android:extractNativeLibs=false

This is the non-legacy way to ship native libraries nowadays, leaving them in the .apk so that there doesn't need to be an extraction step and the app takes up less space.
rdb 1 month ago
parent
commit
e809c9eb92

+ 1 - 1
direct/src/dist/FreezeTool.py

@@ -114,7 +114,7 @@ ignoreImports = {
     'toml.encoder': ['numpy'],
     'py._builtin': ['__builtin__'],
 
-    'site': ['android_log'],
+    'site': ['android_support'],
 }
 
 if sys.version_info >= (3, 8):

+ 5 - 7
direct/src/dist/commands.py

@@ -192,14 +192,14 @@ SITE_PY_ANDROID = """
 # module.
 import sys, os
 from importlib import _bootstrap, _bootstrap_external
+from android_support import log_write as android_log_write
+from android_support import find_library
 
 class AndroidExtensionFinder:
     @classmethod
     def find_spec(cls, fullname, path=None, target=None):
-        soname = 'libpy.' + fullname + '.so'
-        path = os.path.join(sys.platlibdir, soname)
-
-        if os.path.exists(path):
+        path = find_library('py.' + fullname)
+        if path:
             loader = _bootstrap_external.ExtensionFileLoader(fullname, path)
             return _bootstrap.ModuleSpec(fullname, loader, origin=path)
 
@@ -210,8 +210,6 @@ sys.meta_path.append(AndroidExtensionFinder)
 from _frozen_importlib import _imp, FrozenImporter
 from io import RawIOBase, TextIOWrapper
 
-from android_log import write as android_log_write
-
 
 sys.frozen = True
 
@@ -869,7 +867,7 @@ class build_apps(setuptools.Command):
         if category:
             application.set('android:appCategory', category)
         application.set('android:debuggable', ('false', 'true')[self.android_debuggable])
-        application.set('android:extractNativeLibs', 'true')
+        application.set('android:extractNativeLibs', 'false')
         application.set('android:hardwareAccelerated', 'true')
 
         app_icon = self.icon_objects.get('*', self.icon_objects.get(self.macos_main_app))

+ 2 - 2
direct/src/dist/installers.py

@@ -232,10 +232,10 @@ def create_aab(command, basename, build_dir):
     config.optimizations.splits_config.Clear()
     if axml.extract_native_libs:
         config.optimizations.uncompress_native_libraries.enabled = False
-        config.optimizations.uncompress_native_libraries.alignment = \
-            UncompressNativeLibraries.PageAlignment.PAGE_ALIGNMENT_16K
     else:
         config.optimizations.uncompress_native_libraries.enabled = True
+        config.optimizations.uncompress_native_libraries.alignment = \
+            UncompressNativeLibraries.PageAlignment.PAGE_ALIGNMENT_16K
     config.compression.uncompressed_glob.append('res/raw/**')
     bundle.add_subfile('BundleConfig.pb', p3d.StringStream(config.SerializeToString()), 9)
 

+ 10 - 0
dtool/src/dtoolutil/load_dso.cxx

@@ -19,11 +19,15 @@ using std::string;
 static Filename resolve_dso(const DSearchPath &path, const Filename &filename) {
   if (filename.is_local()) {
     if ((path.get_num_directories()==1)&&(path.get_directory(0)=="<auto>")) {
+#ifdef ANDROID
+      return filename;
+#else
       // This is a special case, meaning to search in the same directory in
       // which libp3dtool.dll, or the exe, was started from.
       Filename dtoolpath = ExecutionEnvironment::get_dtool_name();
       DSearchPath spath(dtoolpath.get_dirname());
       return spath.find_file(filename);
+#endif
     } else {
       return path.find_file(filename);
     }
@@ -119,7 +123,13 @@ get_dso_symbol(void *handle, const string &name) {
 void *
 load_dso(const DSearchPath &path, const Filename &filename) {
   Filename abspath = resolve_dso(path, filename);
+#ifdef ANDROID
+  // We just try to load it on Android, because we can't verify right now
+  // whether it might just be an unextracted library.
+  if (abspath.empty()) {
+#else
   if (!abspath.is_regular_file()) {
+#endif
     // Make sure the error flag is cleared, to prevent a subsequent call to
     // load_dso_error() from returning a previously stored error.
     dlerror();

+ 2 - 2
makepanda/makepanda.py

@@ -5967,10 +5967,10 @@ if PkgSkip("PYTHON") == 0:
         TargetAdd('classes.dex', input='org/jnius/NativeInvocationHandler.class')
 
         PyTargetAdd('deploy-stubw_android_main.obj', opts=OPTS, input='android_main.cxx')
-        PyTargetAdd('deploy-stubw_android_log.obj', opts=OPTS, input='android_log.c')
+        PyTargetAdd('deploy-stubw_android_support.obj', opts=OPTS, input='android_support.cxx')
         PyTargetAdd('libdeploy-stubw.dll', input='android_native_app_glue.obj')
         PyTargetAdd('libdeploy-stubw.dll', input='deploy-stubw_android_main.obj')
-        PyTargetAdd('libdeploy-stubw.dll', input='deploy-stubw_android_log.obj')
+        PyTargetAdd('libdeploy-stubw.dll', input='deploy-stubw_android_support.obj')
         PyTargetAdd('libdeploy-stubw.dll', input=COMMON_PANDA_LIBS)
         PyTargetAdd('libdeploy-stubw.dll', input='libp3android.dll')
         PyTargetAdd('libdeploy-stubw.dll', opts=['DEPLOYSTUB', 'ANDROID'])

+ 1 - 1
panda/src/android/PandaActivity.java

@@ -132,7 +132,7 @@ public class PandaActivity extends NativeActivity {
     /**
      * Returns the path to some other native library.
      */
-    public String findLibraryPath(String libname) {
+    public String findLibrary(String libname) {
         BaseDexClassLoader classLoader = (BaseDexClassLoader)getClassLoader();
         return classLoader.findLibrary(libname);
     }

+ 26 - 0
panda/src/android/config_android.cxx

@@ -27,6 +27,7 @@ jmethodID jni_PandaActivity_readBitmap;
 jmethodID jni_PandaActivity_createBitmap;
 jmethodID jni_PandaActivity_compressBitmap;
 jmethodID jni_PandaActivity_showToast;
+jmethodID jni_PandaActivity_findLibrary;
 
 jclass    jni_Activity;
 jmethodID jni_Activity_setTitle;
@@ -83,6 +84,9 @@ jint JNI_OnLoad(JavaVM *jvm, void *reserved) {
   jni_PandaActivity_showToast = env->GetMethodID(jni_PandaActivity,
                    "showToast", "(Ljava/lang/String;I)V");
 
+  jni_PandaActivity_findLibrary = env->GetMethodID(jni_PandaActivity,
+                   "findLibrary", "(Ljava/lang/String;)Ljava/lang/String;");
+
   jni_Activity = env->FindClass("android/app/Activity");
   jni_Activity = (jclass) env->NewGlobalRef(jni_Activity);
   jni_Activity_setTitle = env->GetMethodID(jni_Activity,
@@ -144,6 +148,28 @@ void JNI_OnUnload(JavaVM *jvm, void *reserved) {
   }
 }
 
+/**
+ *
+ */
+Filename android_find_library(ANativeActivity *activity, const std::string &lib) {
+  Thread *thread = Thread::get_current_thread();
+  JNIEnv *env = thread->get_jni_env();
+  nassertr(env != nullptr, Filename());
+
+  jstring jlib = env->NewStringUTF(lib.c_str());
+  jstring jresult = (jstring)env->CallObjectMethod(activity->clazz, jni_PandaActivity_findLibrary, jlib);
+  env->DeleteLocalRef(jlib);
+
+  Filename result;
+  if (jresult != nullptr) {
+    const char *c_str = env->GetStringUTFChars(jresult, nullptr);
+    result = c_str;
+    env->ReleaseStringUTFChars(jresult, c_str);
+  }
+
+  return result;
+}
+
 /**
  * Sets the window title of the activity.
  */

+ 1 - 0
panda/src/android/config_android.h

@@ -42,6 +42,7 @@ extern jclass   jni_BitmapFactory_Options;
 extern jfieldID jni_BitmapFactory_Options_outWidth;
 extern jfieldID jni_BitmapFactory_Options_outHeight;
 
+EXPORT_CLASS Filename android_find_library(ANativeActivity *activity, const std::string &lib);
 EXPORT_CLASS void android_set_title(ANativeActivity *activity, const std::string &title);
 EXPORT_CLASS void android_show_toast(ANativeActivity *activity, const std::string &message, int duration);
 

+ 7 - 6
pandatool/src/deploy-stub/android_main.cxx

@@ -50,8 +50,8 @@ extern "C" {
   } blobinfo = {(uint64_t)-1};
 }
 
-// Defined in android_log.c
-extern "C" PyObject *PyInit_android_log();
+// Defined in android_support.cxx
+extern "C" PyObject *PyInit_android_support();
 
 /**
  * Maps the binary blob at the given memory address to memory, and returns the
@@ -250,7 +250,7 @@ void android_main(struct android_app *app) {
     if (dst_moddef->code != nullptr) {
       dst_moddef->code = (unsigned char *)((uintptr_t)dst_moddef->code + (uintptr_t)blob);
     }
-    __android_log_print(ANDROID_LOG_DEBUG, "Panda3D", "MOD: %s %p %d\n", dst_moddef->name, (void*)dst_moddef->code, dst_moddef->size);
+    //__android_log_print(ANDROID_LOG_DEBUG, "Panda3D", "MOD: %s %p %d\n", dst_moddef->name, (void*)dst_moddef->code, dst_moddef->size);
     dst_moddef++;
   }
   PyImport_FrozenModules = new_modules;
@@ -265,10 +265,10 @@ void android_main(struct android_app *app) {
     return;
   }
 
-  // Register the android_log module.
-  if (PyImport_AppendInittab("android_log", &PyInit_android_log) < 0) {
+  // Register the android_support module.
+  if (PyImport_AppendInittab("android_support", &PyInit_android_support) < 0) {
     android_cat.error()
-      << "Failed to register android_log module.\n";
+      << "Failed to register android_support module.\n";
     env->ReleaseStringUTFChars(libdir_jstr, libdir);
     return;
   }
@@ -279,6 +279,7 @@ void android_main(struct android_app *app) {
   config.buffered_stdio = 0;
   config.configure_c_stdio = 0;
   config.write_bytecode = 0;
+  config.module_search_paths_set = 1; // Leave sys.path empty
   PyConfig_SetBytesString(&config, &config.platlibdir, libdir);
   env->ReleaseStringUTFChars(libdir_jstr, libdir);
 

+ 27 - 7
pandatool/src/deploy-stub/android_log.c → pandatool/src/deploy-stub/android_support.cxx

@@ -6,7 +6,7 @@
  * license.  You should have received a copy of this license along
  * with this source code in a file named "LICENSE."
  *
- * @file android_log.c
+ * @file android_support.c
  * @author rdb
  * @date 2021-12-10
  */
@@ -17,12 +17,14 @@
 
 #include "Python.h"
 #include <android/log.h>
+#include "android_native_app_glue.h"
+#include "config_android.h"
 
 /**
  * Writes a message to the Android log.
  */
 static PyObject *
-_py_write(PyObject *self, PyObject *args) {
+_py_log_write(PyObject *self, PyObject *args) {
   int prio;
   char *tag;
   char *text;
@@ -33,14 +35,32 @@ _py_write(PyObject *self, PyObject *args) {
   return NULL;
 }
 
+/**
+ * Returns the path to a library, if it can be found.
+ */
+static PyObject *
+_py_find_library(PyObject *self, PyObject *args) {
+  char *lib;
+  if (PyArg_ParseTuple(args, "s", &lib)) {
+    Filename result = android_find_library(panda_android_app->activity, lib);
+    if (!result.empty()) {
+      return PyUnicode_FromStringAndSize(result.c_str(), (Py_ssize_t)result.length());
+    } else {
+      Py_RETURN_NONE;
+    }
+  }
+  return NULL;
+}
+
 static PyMethodDef python_simple_funcs[] = {
-  { "write", &_py_write, METH_VARARGS },
+  { "log_write", &_py_log_write, METH_VARARGS },
+  { "find_library", &_py_find_library, METH_VARARGS },
   { NULL, NULL }
 };
 
-static struct PyModuleDef android_log_module = {
+static struct PyModuleDef android_support_module = {
   PyModuleDef_HEAD_INIT,
-  "android_log",
+  "android_support",
   NULL,
   -1,
   python_simple_funcs,
@@ -48,6 +68,6 @@ static struct PyModuleDef android_log_module = {
 };
 
 __attribute__((visibility("default")))
-PyObject *PyInit_android_log() {
-  return PyModule_Create(&android_log_module);
+extern "C" PyObject *PyInit_android_support() {
+  return PyModule_Create(&android_support_module);
 }