diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java
index 01797d6768fb..cf81640759ec 100644
--- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java
+++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java
@@ -1110,21 +1110,19 @@ public void requestDartDeferredLibrary(int loadingUnitId) {
* @param loadingUnitId The loadingUnitId is assigned during compile time by gen_snapshot and is
* automatically retrieved when loadLibrary() is called on a dart deferred library. This is
* used to identify which Dart deferred library the resolved correspond to.
- * @param searchPaths An array of paths in which to look for valid dart shared libraries. This
- * supports paths within zipped apks as long as the apks are not compressed using the
- * `path/to/apk.apk!path/inside/apk/lib.so` format. Paths will be tried first to last and ends
- * when a library is sucessfully found. When the found library is invalid, no additional paths
- * will be attempted.
+ * @param sharedLibraryName File name of the .so file to be loaded, or if the file is not already
+ * in LD_LIBRARY_PATH, the full path to the file. The .so files in the lib/[abi] directory are
+ * already in LD_LIBRARY_PATH and in this case you only need to pass the file name.
*/
@UiThread
- public void loadDartDeferredLibrary(int loadingUnitId, @NonNull String[] searchPaths) {
+ public void loadDartDeferredLibrary(int loadingUnitId, @NonNull String sharedLibraryName) {
ensureRunningOnMainThread();
ensureAttachedToNative();
- nativeLoadDartDeferredLibrary(nativeShellHolderId, loadingUnitId, searchPaths);
+ nativeLoadDartDeferredLibrary(nativeShellHolderId, loadingUnitId, sharedLibraryName);
}
private native void nativeLoadDartDeferredLibrary(
- long nativeShellHolderId, int loadingUnitId, @NonNull String[] searchPaths);
+ long nativeShellHolderId, int loadingUnitId, @NonNull String sharedLibraryName);
/**
* Adds the specified AssetManager as an APKAssetResolver in the Flutter Engine's AssetManager.
diff --git a/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/DeferredComponentManager.java b/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/DeferredComponentManager.java
index f516cf38f56c..467dd8896a52 100644
--- a/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/DeferredComponentManager.java
+++ b/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/DeferredComponentManager.java
@@ -22,10 +22,11 @@
* This call retrieves a unique identifier called the loading unit id, which is assigned by
* gen_snapshot during compilation. The loading unit id is passed down through the engine and
* invokes installDeferredComponent. Once the feature module is downloaded, loadAssets and
- * loadDartLibrary should be invoked. loadDartLibrary should find shared library .so files for the
- * engine to open and pass the .so path to FlutterJNI.loadDartDeferredLibrary. loadAssets should
- * typically ensure the new assets are available to the engine's asset manager by passing an updated
- * Android AssetManager to the engine via FlutterJNI.updateAssetManager.
+ * loadDartLibrary should be invoked. loadDartLibrary should pass the file name of the shared
+ * library .so file to FlutterJNI.loadDartDeferredLibrary for the engine to dlopen, or if the file
+ * is not in LD_LIBRARY_PATH, it should find the shared library .so file and pass the full path.
+ * loadAssets should typically ensure the new assets are available to the engine's asset manager by
+ * passing an updated Android AssetManager to the engine via FlutterJNI.updateAssetManager.
*
*
The loadAssets and loadDartLibrary methods are separated out because they may also be called
* manually via platform channel messages. A full installDeferredComponent implementation should
@@ -182,14 +183,10 @@ public interface DeferredComponentManager {
* Load the .so shared library file into the Dart VM.
*
*
When the download of a deferred component module completes, this method should be called to
- * find the path .so library file. The path(s) should then be passed to
- * FlutterJNI.loadDartDeferredLibrary to be dlopen-ed and loaded into the Dart VM.
- *
- *
Specifically, APKs distributed by Android's app bundle format may vary by device and API
- * number, so FlutterJNI's loadDartDeferredLibrary accepts a list of search paths with can include
- * paths within APKs that have not been unpacked using the
- * `path/to/apk.apk!path/inside/apk/lib.so` format. Each search path will be attempted in order
- * until a shared library is found. This allows for the developer to avoid unpacking the apk zip.
+ * find the .so library file. The filenames, or path if it's not in LD_LIBRARY_PATH, should then
+ * be passed to FlutterJNI.loadDartDeferredLibrary to be dlopen-ed and loaded into the Dart VM.
+ * The .so files in the lib/[abi] directory are already in LD_LIBRARY_PATH and in this case you
+ * only need to pass the file name.
*
*
Upon successful load of the Dart library, the Dart future from the originating loadLibary()
* call completes and developers are able to use symbols and assets from the feature module.
diff --git a/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManager.java b/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManager.java
index 8ce4b4eba16b..ab5e6afc0efe 100644
--- a/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManager.java
+++ b/shell/platform/android/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManager.java
@@ -8,7 +8,6 @@
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
-import android.os.Build;
import android.util.SparseArray;
import android.util.SparseIntArray;
import androidx.annotation.NonNull;
@@ -23,14 +22,13 @@
import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterJNI;
+import io.flutter.embedding.engine.loader.ApplicationInfoLoader;
+import io.flutter.embedding.engine.loader.FlutterApplicationInfo;
import io.flutter.embedding.engine.systemchannels.DeferredComponentChannel;
-import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import java.util.Queue;
/**
* Flutter default implementation of DeferredComponentManager that downloads deferred component
@@ -43,6 +41,7 @@ public class PlayStoreDeferredComponentManager implements DeferredComponentManag
private @Nullable FlutterJNI flutterJNI;
private @Nullable DeferredComponentChannel channel;
private @NonNull Context context;
+ private @NonNull FlutterApplicationInfo flutterApplicationInfo;
// Each request to install a feature module gets a session ID. These maps associate
// the session ID with the loading unit and module name that was requested.
private @NonNull SparseArray sessionIdToName;
@@ -191,6 +190,7 @@ public PlayStoreDeferredComponentManager(
@NonNull Context context, @Nullable FlutterJNI flutterJNI) {
this.context = context;
this.flutterJNI = flutterJNI;
+ this.flutterApplicationInfo = ApplicationInfoLoader.load(context);
splitInstallManager = SplitInstallManagerFactory.create(context);
listener = new FeatureInstallStateUpdatedListener();
splitInstallManager.registerListener(listener);
@@ -322,10 +322,7 @@ public void loadAssets(int loadingUnitId, String moduleName) {
context = context.createPackageContext(context.getPackageName(), 0);
AssetManager assetManager = context.getAssets();
- flutterJNI.updateJavaAssetManager(
- assetManager,
- // TODO(garyq): Made the "flutter_assets" directory dynamic based off of DartEntryPoint.
- "flutter_assets");
+ flutterJNI.updateJavaAssetManager(assetManager, flutterApplicationInfo.flutterAssetsDir);
} catch (NameNotFoundException e) {
throw new RuntimeException(e);
}
@@ -341,54 +338,10 @@ public void loadDartLibrary(int loadingUnitId, String moduleName) {
}
// This matches/depends on dart's loading unit naming convention, which we use unchanged.
- String aotSharedLibraryName = "app.so-" + loadingUnitId + ".part.so";
+ String aotSharedLibraryName =
+ flutterApplicationInfo.aotSharedLibraryName + "-" + loadingUnitId + ".part.so";
- // Possible values: armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, mips, mips64
- String abi;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- abi = Build.SUPPORTED_ABIS[0];
- } else {
- abi = Build.CPU_ABI;
- }
- String pathAbi = abi.replace("-", "_"); // abis are represented with underscores in paths.
-
- // TODO(garyq): Optimize this apk/file discovery process to use less i/o and be more
- // performant and robust.
-
- // Search directly in APKs first
- List apkPaths = new ArrayList<>();
- // If not found in APKs, we check in extracted native libs for the lib directly.
- List soPaths = new ArrayList<>();
- Queue searchFiles = new LinkedList<>();
- searchFiles.add(context.getFilesDir());
- while (!searchFiles.isEmpty()) {
- File file = searchFiles.remove();
- if (file != null && file.isDirectory()) {
- for (File f : file.listFiles()) {
- searchFiles.add(f);
- }
- continue;
- }
- String name = file.getName();
- if (name.endsWith(".apk") && name.startsWith(moduleName) && name.contains(pathAbi)) {
- apkPaths.add(file.getAbsolutePath());
- continue;
- }
- if (name.equals(aotSharedLibraryName)) {
- soPaths.add(file.getAbsolutePath());
- }
- }
-
- List searchPaths = new ArrayList<>();
- for (String path : apkPaths) {
- searchPaths.add(path + "!lib/" + abi + "/" + aotSharedLibraryName);
- }
- for (String path : soPaths) {
- searchPaths.add(path);
- }
-
- flutterJNI.loadDartDeferredLibrary(
- loadingUnitId, searchPaths.toArray(new String[apkPaths.size()]));
+ flutterJNI.loadDartDeferredLibrary(loadingUnitId, aotSharedLibraryName);
}
public boolean uninstallDeferredComponent(int loadingUnitId, String moduleName) {
diff --git a/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java b/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java
index bc1f71af2b3f..4383d0db051a 100644
--- a/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java
+++ b/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java
@@ -16,17 +16,17 @@
import org.xmlpull.v1.XmlPullParserException;
/** Loads application information given a Context. */
-final class ApplicationInfoLoader {
+public final class ApplicationInfoLoader {
// XML Attribute keys supported in AndroidManifest.xml
- static final String PUBLIC_AOT_SHARED_LIBRARY_NAME =
+ public static final String PUBLIC_AOT_SHARED_LIBRARY_NAME =
FlutterLoader.class.getName() + '.' + FlutterLoader.AOT_SHARED_LIBRARY_NAME;
- static final String PUBLIC_VM_SNAPSHOT_DATA_KEY =
+ public static final String PUBLIC_VM_SNAPSHOT_DATA_KEY =
FlutterLoader.class.getName() + '.' + FlutterLoader.VM_SNAPSHOT_DATA_KEY;
- static final String PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY =
+ public static final String PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY =
FlutterLoader.class.getName() + '.' + FlutterLoader.ISOLATE_SNAPSHOT_DATA_KEY;
- static final String PUBLIC_FLUTTER_ASSETS_DIR_KEY =
+ public static final String PUBLIC_FLUTTER_ASSETS_DIR_KEY =
FlutterLoader.class.getName() + '.' + FlutterLoader.FLUTTER_ASSETS_DIR_KEY;
- static final String NETWORK_POLICY_METADATA_KEY = "io.flutter.network-policy";
+ public static final String NETWORK_POLICY_METADATA_KEY = "io.flutter.network-policy";
@NonNull
private static ApplicationInfo getApplicationInfo(@NonNull Context applicationContext) {
diff --git a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterApplicationInfo.java b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterApplicationInfo.java
index 33a9e4488f18..052235821389 100644
--- a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterApplicationInfo.java
+++ b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterApplicationInfo.java
@@ -11,13 +11,13 @@ public final class FlutterApplicationInfo {
private static final String DEFAULT_ISOLATE_SNAPSHOT_DATA = "isolate_snapshot_data";
private static final String DEFAULT_FLUTTER_ASSETS_DIR = "flutter_assets";
- final String aotSharedLibraryName;
- final String vmSnapshotData;
- final String isolateSnapshotData;
- final String flutterAssetsDir;
- final String domainNetworkPolicy;
- final String nativeLibraryDir;
- final boolean clearTextPermitted;
+ public final String aotSharedLibraryName;
+ public final String vmSnapshotData;
+ public final String isolateSnapshotData;
+ public final String flutterAssetsDir;
+ public final String domainNetworkPolicy;
+ public final String nativeLibraryDir;
+ public final boolean clearTextPermitted;
public FlutterApplicationInfo(
String aotSharedLibraryName,
diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc
index 4f957bbf62f8..444a761411f5 100644
--- a/shell/platform/android/platform_view_android_jni_impl.cc
+++ b/shell/platform/android/platform_view_android_jni_impl.cc
@@ -567,23 +567,19 @@ static void LoadDartDeferredLibrary(JNIEnv* env,
jobject obj,
jlong shell_holder,
jint jLoadingUnitId,
- jobjectArray jSearchPaths) {
+ jstring jSharedLibraryName) {
// Convert java->c++
intptr_t loading_unit_id = static_cast(jLoadingUnitId);
- std::vector search_paths =
- fml::jni::StringArrayToVector(env, jSearchPaths);
+ std::string sharedLibraryName =
+ fml::jni::JavaStringToString(env, jSharedLibraryName);
// Use dlopen here to directly check if handle is nullptr before creating a
// NativeLibrary.
- void* handle = nullptr;
- while (handle == nullptr && !search_paths.empty()) {
- std::string path = search_paths.back();
- handle = ::dlopen(path.c_str(), RTLD_NOW);
- search_paths.pop_back();
- }
+ void* handle = ::dlopen(sharedLibraryName.c_str(), RTLD_NOW);
if (handle == nullptr) {
LoadLoadingUnitFailure(loading_unit_id,
- "No lib .so found for provided search paths.", true);
+ "Shared library not found for the provided name.",
+ true);
return;
}
fml::RefPtr native_lib =
@@ -781,7 +777,7 @@ bool RegisterApi(JNIEnv* env) {
},
{
.name = "nativeLoadDartDeferredLibrary",
- .signature = "(JI[Ljava/lang/String;)V",
+ .signature = "(JILjava/lang/String;)V",
.fnPtr = reinterpret_cast(&LoadDartDeferredLibrary),
},
{
diff --git a/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java b/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java
index 3c80596684dd..77f04cf29f61 100644
--- a/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java
+++ b/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java
@@ -5,17 +5,22 @@
package io.flutter.embedding.engine.deferredcomponents;
import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
+import android.os.Bundle;
import androidx.annotation.NonNull;
import io.flutter.embedding.engine.FlutterJNI;
+import io.flutter.embedding.engine.loader.ApplicationInfoLoader;
import java.io.File;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -30,16 +35,17 @@ private class TestFlutterJNI extends FlutterJNI {
public int loadDartDeferredLibraryCalled = 0;
public int updateAssetManagerCalled = 0;
public int deferredComponentInstallFailureCalled = 0;
- public String[] searchPaths;
+ public String sharedLibraryName;
public int loadingUnitId;
public AssetManager assetManager;
+ public String assetBundlePath;
public TestFlutterJNI() {}
@Override
- public void loadDartDeferredLibrary(int loadingUnitId, @NonNull String[] searchPaths) {
+ public void loadDartDeferredLibrary(int loadingUnitId, @NonNull String sharedLibraryName) {
loadDartDeferredLibraryCalled++;
- this.searchPaths = searchPaths;
+ this.sharedLibraryName = sharedLibraryName;
this.loadingUnitId = loadingUnitId;
}
@@ -49,6 +55,7 @@ public void updateJavaAssetManager(
updateAssetManagerCalled++;
this.loadingUnitId = loadingUnitId;
this.assetManager = assetManager;
+ this.assetBundlePath = assetBundlePath;
}
@Override
@@ -75,11 +82,10 @@ public void installDeferredComponent(int loadingUnitId, String moduleName) {
@Test
public void downloadCallsJNIFunctions() throws NameNotFoundException {
TestFlutterJNI jni = new TestFlutterJNI();
- Context spyContext = spy(RuntimeEnvironment.systemContext);
+ Context spyContext = spy(RuntimeEnvironment.application);
doReturn(spyContext).when(spyContext).createPackageContext(any(), anyInt());
doReturn(null).when(spyContext).getAssets();
- String soTestPath = "test/path/app.so-123.part.so";
- doReturn(new File(soTestPath)).when(spyContext).getFilesDir();
+ String soTestPath = "libapp.so-123.part.so";
TestPlayStoreDeferredComponentManager playStoreManager =
new TestPlayStoreDeferredComponentManager(spyContext, jni);
jni.setDeferredComponentManager(playStoreManager);
@@ -90,47 +96,32 @@ public void downloadCallsJNIFunctions() throws NameNotFoundException {
assertEquals(jni.updateAssetManagerCalled, 1);
assertEquals(jni.deferredComponentInstallFailureCalled, 0);
- assertTrue(jni.searchPaths[0].endsWith(soTestPath));
- assertEquals(jni.searchPaths.length, 1);
+ assertEquals(jni.sharedLibraryName, soTestPath);
assertEquals(jni.loadingUnitId, 123);
+ assertEquals(jni.assetBundlePath, "flutter_assets");
}
@Test
- public void searchPathsAddsApks() throws NameNotFoundException {
+ public void downloadCallsJNIFunctionsWithFilenameFromManifest() throws NameNotFoundException {
TestFlutterJNI jni = new TestFlutterJNI();
- Context spyContext = spy(RuntimeEnvironment.systemContext);
+ Context spyContext = spy(RuntimeEnvironment.application);
doReturn(spyContext).when(spyContext).createPackageContext(any(), anyInt());
doReturn(null).when(spyContext).getAssets();
- String apkTestPath = "test/path/TestModuleName_armeabi_v7a.apk";
- doReturn(new File(apkTestPath)).when(spyContext).getFilesDir();
- TestPlayStoreDeferredComponentManager playStoreManager =
- new TestPlayStoreDeferredComponentManager(spyContext, jni);
- jni.setDeferredComponentManager(playStoreManager);
-
- assertEquals(jni.loadingUnitId, 0);
-
- playStoreManager.installDeferredComponent(123, "TestModuleName");
- assertEquals(jni.loadDartDeferredLibraryCalled, 1);
- assertEquals(jni.updateAssetManagerCalled, 1);
- assertEquals(jni.deferredComponentInstallFailureCalled, 0);
- assertTrue(jni.searchPaths[0].endsWith(apkTestPath + "!lib/armeabi-v7a/app.so-123.part.so"));
- assertEquals(jni.searchPaths.length, 1);
- assertEquals(jni.loadingUnitId, 123);
- }
-
- @Test
- public void invalidSearchPathsAreIgnored() throws NameNotFoundException {
- TestFlutterJNI jni = new TestFlutterJNI();
- Context spyContext = spy(RuntimeEnvironment.systemContext);
- doReturn(spyContext).when(spyContext).createPackageContext(any(), anyInt());
- doReturn(null).when(spyContext).getAssets();
- String apkTestPath = "test/path/invalidpath.apk";
- doReturn(new File(apkTestPath)).when(spyContext).getFilesDir();
+ Bundle bundle = new Bundle();
+ bundle.putString(ApplicationInfoLoader.PUBLIC_AOT_SHARED_LIBRARY_NAME, "custom_name.so");
+ bundle.putString(ApplicationInfoLoader.PUBLIC_FLUTTER_ASSETS_DIR_KEY, "custom_assets");
+ PackageManager packageManager = mock(PackageManager.class);
+ ApplicationInfo applicationInfo = mock(ApplicationInfo.class);
+ applicationInfo.metaData = bundle;
+ when(packageManager.getApplicationInfo(any(String.class), any(int.class)))
+ .thenReturn(applicationInfo);
+ doReturn(packageManager).when(spyContext).getPackageManager();
+
+ String soTestPath = "custom_name.so-123.part.so";
TestPlayStoreDeferredComponentManager playStoreManager =
new TestPlayStoreDeferredComponentManager(spyContext, jni);
jni.setDeferredComponentManager(playStoreManager);
-
assertEquals(jni.loadingUnitId, 0);
playStoreManager.installDeferredComponent(123, "TestModuleName");
@@ -138,14 +129,15 @@ public void invalidSearchPathsAreIgnored() throws NameNotFoundException {
assertEquals(jni.updateAssetManagerCalled, 1);
assertEquals(jni.deferredComponentInstallFailureCalled, 0);
- assertEquals(jni.searchPaths.length, 0);
+ assertEquals(jni.sharedLibraryName, soTestPath);
assertEquals(jni.loadingUnitId, 123);
+ assertEquals(jni.assetBundlePath, "custom_assets");
}
@Test
public void assetManagerUpdateInvoked() throws NameNotFoundException {
TestFlutterJNI jni = new TestFlutterJNI();
- Context spyContext = spy(RuntimeEnvironment.systemContext);
+ Context spyContext = spy(RuntimeEnvironment.application);
doReturn(spyContext).when(spyContext).createPackageContext(any(), anyInt());
AssetManager assetManager = spyContext.getAssets();
String apkTestPath = "blah doesn't matter here";
@@ -167,7 +159,7 @@ public void assetManagerUpdateInvoked() throws NameNotFoundException {
@Test
public void stateGetterReturnsUnknowByDefault() throws NameNotFoundException {
TestFlutterJNI jni = new TestFlutterJNI();
- Context spyContext = spy(RuntimeEnvironment.systemContext);
+ Context spyContext = spy(RuntimeEnvironment.application);
doReturn(spyContext).when(spyContext).createPackageContext(any(), anyInt());
TestPlayStoreDeferredComponentManager playStoreManager =
new TestPlayStoreDeferredComponentManager(spyContext, jni);