From c7980fc4e77bbd45ab6febf4e6084f3c5bca03c3 Mon Sep 17 00:00:00 2001 From: Nikolay Nesterov Date: Wed, 23 Jan 2019 06:54:54 -0800 Subject: [PATCH] Add support for app bundle (#26) Summary: PR is related to #19 **Problem** SoLoader cannot load `.so`libraries when an application is installed through [App Bundle](https://developer.android.com/platform/technology/app-bundle/) with config: ``` bundle { abi { enableSplit = true } } ``` The reason is that app bundle installation consists of multiple apks. For instance: - `base.apk` - `split_config.arm64_v8a.apk` - `split_config.xxxhdpi.apk` `SoLoader` class creates `ApkSoSource` pointing only to `base.apk`. But `*.so` files exist only in `split_config.arm64_v8a.apk` **Solution** Create separate `ApkSoSource` for each apk in the application folder. I couldn't figure out how to filter out apk without lib folder. **Testing** I haven't tried this with dynamic feature modules yet. So I can't say that it fully fixes #19. But at least it fixes facebook/fresco#2253 Any help with dynamic modules testing would be much appreciated. **P.S.** Due to some reasons, I can't wait for the new release of SoLoader lib, so I have published a temporary patched version to bintray in [my account](https://bintray.com/nnesterov/maven/patched-soloader) with different grouipId `com.avito.android:patched-soloader:0.1.0` If you are against publishing your lib in a different account, please let me know. I'll remove my version immediately. Pull Request resolved: https://github.com/facebook/SoLoader/pull/26 Reviewed By: oprisnik Differential Revision: D13669370 Pulled By: passy fbshipit-source-id: dd605bf54662a38cb1fd04461e1e962f76da7d51 --- java/com/facebook/soloader/ApkSoSource.java | 9 ++-- java/com/facebook/soloader/SoLoader.java | 56 +++++++++++++++++---- 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/java/com/facebook/soloader/ApkSoSource.java b/java/com/facebook/soloader/ApkSoSource.java index 5067398..a3f47ae 100644 --- a/java/com/facebook/soloader/ApkSoSource.java +++ b/java/com/facebook/soloader/ApkSoSource.java @@ -21,10 +21,7 @@ import android.util.Log; import java.io.File; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; import java.util.zip.ZipEntry; -import javax.annotation.Nullable; /** * {@link SoSource} that extracts libraries from an APK to the filesystem. @@ -47,10 +44,14 @@ public class ApkSoSource extends ExtractFromZipSoSource { private final int mFlags; public ApkSoSource(Context context, String name, int flags) { + this(context, new File(context.getApplicationInfo().sourceDir), name, flags); + } + + public ApkSoSource(Context context, File apkPath, String name, int flags) { super( context, name, - new File(context.getApplicationInfo().sourceDir), + apkPath, // The regular expression matches libraries that would ordinarily be unpacked // during installation. "^lib/([^/]+)/([^/]+\\.so)$"); diff --git a/java/com/facebook/soloader/SoLoader.java b/java/com/facebook/soloader/SoLoader.java index c70a769..7629a48 100644 --- a/java/com/facebook/soloader/SoLoader.java +++ b/java/com/facebook/soloader/SoLoader.java @@ -91,10 +91,10 @@ public class SoLoader { private static int sSoSourcesVersion = 0; - /** A backup SoSource to try if a lib file is corrupted */ + /** A backup SoSources to try if a lib file is corrupted */ @GuardedBy("sSoSourcesLock") @Nullable - private static UnpackingSoSource sBackupSoSource; + private static UnpackingSoSource[] sBackupSoSources; /** * A SoSource for the Context.ApplicationInfo.nativeLibsDir that can be updated if the application @@ -121,9 +121,14 @@ public class SoLoader { /** Wrapper for System.loadLlibrary. */ @Nullable private static SystemLoadLibraryWrapper sSystemLoadLibraryWrapper = null; - /** Name of the directory we use for extracted DSOs from built-in SO sources (APK, exopackage) */ + /** + * Name of the directory we use for extracted DSOs from built-in SO sources (main APK, exopackage) + */ private static final String SO_STORE_NAME_MAIN = "lib-main"; + /** Name of the directory we use for extracted DSOs from split APKs */ + private static final String SO_STORE_NAME_SPLITTED = "lib-"; + /** Enable the exopackage SoSource. */ public static final int SOLOADER_ENABLE_EXOPACKAGE = (1 << 0); @@ -230,7 +235,7 @@ private static void initSoSources(Context context, int flags, @Nullable SoFileLo // if ((flags & SOLOADER_ENABLE_EXOPACKAGE) != 0) { - sBackupSoSource = null; + sBackupSoSources = null; Log.d(TAG, "adding exo package source: " + SO_STORE_NAME_MAIN); soSources.add(0, new ExoSoSource(context, SO_STORE_NAME_MAIN)); } else { @@ -260,11 +265,34 @@ private static void initSoSources(Context context, int flags, @Nullable SoFileLo } if ((sFlags & SOLOADER_DISABLE_BACKUP_SOSOURCE) != 0) { - sBackupSoSource = null; + sBackupSoSources = null; } else { - sBackupSoSource = new ApkSoSource(context, SO_STORE_NAME_MAIN, apkSoSourceFlags); - Log.d(TAG, "adding backup source: " + sBackupSoSource.toString()); - soSources.add(0, sBackupSoSource); + + final File mainApkDir = new File(context.getApplicationInfo().sourceDir); + ArrayList backupSources = new ArrayList<>(); + ApkSoSource mainApkSource = + new ApkSoSource(context, mainApkDir, SO_STORE_NAME_MAIN, apkSoSourceFlags); + backupSources.add(mainApkSource); + Log.d(TAG, "adding backup source from : " + mainApkSource.toString()); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP + && context.getApplicationInfo().splitSourceDirs != null) { + Log.d(TAG, "adding backup sources from split apks"); + int splitIndex = 0; + for (String splitApkDir : context.getApplicationInfo().splitSourceDirs) { + ApkSoSource splittedApkSource = + new ApkSoSource( + context, + new File(splitApkDir), + SO_STORE_NAME_SPLITTED + (splitIndex++), + apkSoSourceFlags); + Log.d(TAG, "adding backup source: " + splittedApkSource.toString()); + backupSources.add(splittedApkSource); + } + } + + sBackupSoSources = backupSources.toArray(new UnpackingSoSource[backupSources.size()]); + soSources.addAll(0, backupSources); } } } @@ -651,11 +679,17 @@ private static void doLoadLibraryBySoName( for (int i = 0; result == SoSource.LOAD_RESULT_NOT_FOUND && i < sSoSources.length; ++i) { SoSource currentSource = sSoSources[i]; result = currentSource.loadLibrary(soName, loadFlags, oldPolicy); - if (result == SoSource.LOAD_RESULT_CORRUPTED_LIB_FILE && sBackupSoSource != null) { + if (result == SoSource.LOAD_RESULT_CORRUPTED_LIB_FILE && sBackupSoSources != null) { // Let's try from the backup source Log.d(TAG, "Trying backup SoSource for " + soName); - sBackupSoSource.prepare(soName); - result = sBackupSoSource.loadLibrary(soName, loadFlags, oldPolicy); + for (UnpackingSoSource backupSoSource : sBackupSoSources) { + backupSoSource.prepare(soName); + int resultFromBackup = backupSoSource.loadLibrary(soName, loadFlags, oldPolicy); + if (resultFromBackup == SoSource.LOAD_RESULT_LOADED) { + result = resultFromBackup; + break; + } + } break; } }