1

I want to load custom .so dynamic for the NaticityActivity, but get error when NativeActivity.onCreate() call classLoader.findLibrary("UE4");

this is party of NativeActivity.onCreate()

    BaseDexClassLoader classLoader = (BaseDexClassLoader) getClassLoader();
    String path = classLoader.findLibrary(libname);

    if (path == null) {
        throw new IllegalArgumentException("Unable to find native library " + libname +
                                           " using classloader: " + classLoader.toString());
    }

    byte[] nativeSavedState = savedInstanceState != null
            ? savedInstanceState.getByteArray(KEY_NATIVE_SAVED_STATE) : null;

    mNativeHandle = loadNativeCode(path, funcname, Looper.myQueue(),
            getAbsolutePath(getFilesDir()), getAbsolutePath(getObbDir()),
            getAbsolutePath(getExternalFilesDir(null)),
            Build.VERSION.SDK_INT, getAssets(), nativeSavedState,
            classLoader, classLoader.getLdLibraryPath());

    if (mNativeHandle == 0) {
        throw new UnsatisfiedLinkError(
                "Unable to load native library \"" + path + "\": " + getDlError());
    }
    super.onCreate(savedInstanceState);


    //Hack classLoader nativeLibraryDirectories, add my .so file path


    UnrealHelper.RequestPermission(this);

    UnrealHelper.CopyFile(Environment.getExternalStorageDirectory().getPath() + "/libUE4.so", getFilesDir() + "/libUE4.so");

    String TestA = System.mapLibraryName("gnustl_shared");
    //libUE4.so
    String fileName = System.mapLibraryName("UE4");

    String TmpVal = "";
    BaseDexClassLoader classLoader = (BaseDexClassLoader) getClassLoader();
    try
    {
        Field pathListField = classLoader.getClass().getSuperclass().getDeclaredField("pathList");
        pathListField.setAccessible(true);
        Object pathListVal  = pathListField.get(classLoader);
        Field nativeLibPathField = pathListVal.getClass().getDeclaredField("nativeLibraryDirectories");
        nativeLibPathField.setAccessible(true);
        Object nativeLibPathVal = nativeLibPathField.get(pathListVal);
        ArrayList nativeLibraryDirectories = (ArrayList)nativeLibPathVal;
        //add my .so path to classLoader
        nativeLibraryDirectories.add(getFilesDir());
        //nativeLibPathField.set(pathListVal, nativeLibraryDirectories);
        //pathListField.set(classLoader, pathListVal);

        //ref: https://android.googlesource.com/platform/libcore-snapshot/+/ics-mr1/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
        //ref: https://android.googlesource.com/platform/libcore-snapshot/+/ics-mr1/dalvik/src/main/java/dalvik/system/DexPathList.java
        for (Object directory : nativeLibraryDirectories) {
            File file = new File((File)directory, fileName);
            if (file.exists() && file.isFile() && file.canRead()) {
                //is valid
                TmpVal = file.getPath();
            }
        }
    }
    catch(Exception Exp)
    {
        String ErrorMsg = Exp.toString();
        System.out.print(ErrorMsg);
    }

    //test the path added, but got null
    String path = classLoader.findLibrary("UE4");

enter image description here

shizhen
  • 12,251
  • 9
  • 52
  • 88
霜月幻夜
  • 85
  • 2
  • 10
  • Share where you added the `.so` files in the directory, how and where you are calling the method `System.load()` with complete parameter information i.e. the name of the `.so` file. – Abbas Dec 14 '18 at 05:17
  • How is your Java side calling the `System.load(MySoFilePath)`? – shizhen Dec 14 '18 at 05:24
  • String LibPath = getFilesDir().getPath(); boolean CopyRet = CopyFile(Environment.getExternalStorageDirectory().getPath() + "/libXXX.so", LibPath + "/libXXX.so"); System.load(LibPath + "/libXXX.so"); – 霜月幻夜 Dec 14 '18 at 06:08
  • [Runtime permission perhaps?](https://stackoverflow.com/q/42335973/295004) – Morrison Chang Dec 14 '18 at 06:22

2 Answers2

1

You have to package your shared libraries inside your apk so that System.loadLibrary("your-lib-name") can find it. Note that System.loadLibrary will only accept library name, NOT the full path.


For System.load(), I have tried below steps, it works well. You can try on your project to see how it goes.

Step 1:

Ensure that your app permission for external storage is configured inside your manifest.xml, see below:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

And ensure you have granted those permissions.

enter image description here

Step 2:

Assume your downloaded .so file is at /Download/ of your external SD card, i.e. /Download/libnative-lib.so. Below code snippet will copy the libnative-lib.so to /data/data/<your-app-id>/files/libnative-lib2.so and the load this libnative-lib2.so will succeed.

    String path_sd_card = Environment.getExternalStorageDirectory().getAbsolutePath();

    FileOutputStream outputStream;
    FileInputStream inputStream;

    // 1. This path works.
    //System.load("/data/data/com.arophix.jniexample/files/libnative-lib.so");
    String filesDir = getFilesDir().getAbsolutePath();

    try {
        inputStream = new FileInputStream(new File(path_sd_card + "/Download/libnative-lib.so"));
        outputStream = new FileOutputStream(new File(filesDir + "/libnative-lib2.so"));//openFileOutput("libnative-lib2.so", Context.MODE_PRIVATE);

        FileChannel inChannel = inputStream.getChannel();
        FileChannel outChannel = outputStream.getChannel();
        inChannel.transferTo(0, inChannel.size(), outChannel);
        inputStream.close();
        outputStream.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

    // This path works
    System.load(filesDir + "/libnative-lib2.so"); 

Note: Verified on Android Emulator Nexus 6P API 23.

shizhen
  • 12,251
  • 9
  • 52
  • 88
  • So file is downloaded after app start, rather than package into apk – 霜月幻夜 Dec 14 '18 at 07:08
  • @霜月幻夜, Does your app run on rooted devices? – shizhen Dec 18 '18 at 01:34
  • I have test on both rooted and un rooted – 霜月幻夜 Dec 18 '18 at 05:31
  • @霜月幻夜, check my updated content. Not sure why you have to use classloader, but even without it, you can load your external `.so` as well. – shizhen Dec 18 '18 at 06:24
  • Thanks for your demo. Now I use a tool to copy .so to [/data/app/com.gitlib.ClassicRPG.ClassicRPG-U2EnYHVGaSSWOIzEwI3lIA==/lib/arm/libUE4.so], its works well. but in java, I have no permission to copy a file into such duirectory... – 霜月幻夜 Dec 18 '18 at 06:50
  • The System.load() works for me, but in NativeActivity.onCreate(), it calls classLoader.findLibrary(). even I hack the nativeLibraryDirectory, it couldn.t find the so file. – 霜月幻夜 Dec 18 '18 at 06:53
  • what is the permission error like for copying a file into `/files/` dir? Have you tried my step 1? – shizhen Dec 18 '18 at 07:00
  • I add sdcard read/write permission, I can copy so from sdcard to getFileDir(), but not new File(getPackageCodePath()).getParent(); – 霜月幻夜 Dec 18 '18 at 07:15
  • In C++, I can not fopen() ["/data/app/com.example.liuxiqiang.myapp-DoFliIp-cSNP6pJUMZL2wg==/lib/arm64/libUE4.so"] with("wb+") , too. just another test, package name is not matter. – 霜月幻夜 Dec 18 '18 at 07:18
  • "but not new File(getPackageCodePath()).getParent(); ", Only some dirs are available for this kind of file copy operation. – shizhen Dec 18 '18 at 07:37
  • still confused about classLoader.findLibrary(), in my opion, the function parse all [nativeLibraryDirectories] to find so file. I have added my file dir(not dcard), but no effect. In Runtime.java loadLibrary0(), I noticed some code if (IoUtils.canOpenReadOnly(candidate)) { String error = nativeLoad(candidate, loader); if (error == null) { return; // We successfully loaded the library. Job done. } lastError = error; }. it detect by call canOpenReadOnly(); but in DexPahList.findLibrary no such limit. – 霜月幻夜 Dec 18 '18 at 07:45
  • `classLoader.findLibrary()` Returns the absolute path name of a native library. The VM invokes this method to locate the native libraries that belong to classes loaded with this class loader. If this method returns null, the VM searches the library along the path specified as the "java.library.path" property. – shizhen Dec 18 '18 at 08:16
0

The SO library can be dynamically loaded on Android via System.load()

The following code successfully loads the OpenCV library and calls the SO library function to get the version number

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    //Copy "libopencv_java3.so" to App directory and return the full path of the SO file
    String pathOpenCV = FileUtil.loadAssetFile(this, "libopencv_java3.so");
    try {
        System.load(pathOpenCV);
    } catch (Exception e) {
        android.util.Log.e("System.Load", e.toString());
    }

    //All version number returns correctly
    int vMajor = Core.getVersionMajor_0();
    int vMinor = Core.getVersionMinor_0();
    int vRev = Core.getVersionRevision_0();
}

See result:

enter image description here

You may need to check your app permissions.

shizhen
  • 12,251
  • 9
  • 52
  • 88
  • Hi, the System.load() works for, but my Activity must extend from NativeActivity, in NativeActivity.onCreate(), it called BaseDexClassLoader classLoader = (BaseDexClassLoader) getClassLoader(); String path = classLoader.findLibrary(libname); if (path == null) { throw new IllegalArgumentException("Unable to find native library " + libname + " using classloader: " + classLoader.toString()); } this will crash my app. – 霜月幻夜 Dec 17 '18 at 02:14
  • Is there a way to set custom classLoader for the context? – 霜月幻夜 Dec 17 '18 at 02:29
  • @霜月幻夜, you'd better give a complete context of your problem by updating your question. – shizhen Dec 17 '18 at 07:14
  • I don't know how classLoader works in c++, is there a way to debug the code such as src/main/java/dalvik/system/DexPathList.java in Android Studio? – 霜月幻夜 Dec 17 '18 at 08:26
  • It seems that exception is thrown by your code, `if (path == null) { throw ... }`. Are you sure the SO file exists? _The call System.load(name) is effectively equivalent to the call: Runtime.getRuntime().load(name)_ –  Dec 18 '18 at 04:38