Skip to content

Commit

Permalink
Pass the filename directly to JNI for loading deferred component. (#2…
Browse files Browse the repository at this point in the history
…3824)

When .so files are in the lib/ path in the APK, it can be dlopen-ed
directly using just the filename. We don't need to search for the file.
The interface has thus been changed to accept a single path instead of a
search directory.

Also instead of hardcoding the .so basename and assets directory, read
them from FlutterApplicationInfo instead.
  • Loading branch information
chingjun committed Jan 22, 2021
1 parent 9223073 commit 7c19824
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 139 deletions.
Expand Up @@ -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.
Expand Down
Expand Up @@ -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.
*
* <p>The loadAssets and loadDartLibrary methods are separated out because they may also be called
* manually via platform channel messages. A full installDeferredComponent implementation should
Expand Down Expand Up @@ -182,14 +183,10 @@ public interface DeferredComponentManager {
* Load the .so shared library file into the Dart VM.
*
* <p>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.
*
* <p>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.
*
* <p>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.
Expand Down
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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<String> sessionIdToName;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand All @@ -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<String> apkPaths = new ArrayList<>();
// If not found in APKs, we check in extracted native libs for the lib directly.
List<String> soPaths = new ArrayList<>();
Queue<File> 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<String> 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) {
Expand Down
Expand Up @@ -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) {
Expand Down
Expand Up @@ -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,
Expand Down
18 changes: 7 additions & 11 deletions shell/platform/android/platform_view_android_jni_impl.cc
Expand Up @@ -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<intptr_t>(jLoadingUnitId);
std::vector<std::string> 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<fml::NativeLibrary> native_lib =
Expand Down Expand Up @@ -781,7 +777,7 @@ bool RegisterApi(JNIEnv* env) {
},
{
.name = "nativeLoadDartDeferredLibrary",
.signature = "(JI[Ljava/lang/String;)V",
.signature = "(JILjava/lang/String;)V",
.fnPtr = reinterpret_cast<void*>(&LoadDartDeferredLibrary),
},
{
Expand Down

0 comments on commit 7c19824

Please sign in to comment.