未验证 提交 7c19824c 编写于 作者: L Lau Ching Jun 提交者: GitHub

Pass the filename directly to JNI for loading deferred component. (#23824)

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.
上级 92230737
......@@ -1110,21 +1110,19 @@ public class FlutterJNI {
* @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.
......
......@@ -22,10 +22,11 @@ import io.flutter.embedding.engine.systemchannels.DeferredComponentChannel;
* 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
......@@ -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.
......
......@@ -8,7 +8,6 @@ import android.annotation.SuppressLint;
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.SplitInstallErrorCode;
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<String> sessionIdToName;
......@@ -191,6 +190,7 @@ public class PlayStoreDeferredComponentManager implements DeferredComponentManag
@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 class PlayStoreDeferredComponentManager implements DeferredComponentManag
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 class PlayStoreDeferredComponentManager implements DeferredComponentManag
}
// 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) {
......
......@@ -16,17 +16,17 @@ import org.json.JSONArray;
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) {
......
......@@ -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,
......
......@@ -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 =
......@@ -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),
},
{
......
......@@ -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 @@ public class PlayStoreDeferredComponentManagerTest {
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 class PlayStoreDeferredComponentManagerTest {
updateAssetManagerCalled++;
this.loadingUnitId = loadingUnitId;
this.assetManager = assetManager;
this.assetBundlePath = assetBundlePath;
}
@Override
......@@ -75,11 +82,10 @@ public class PlayStoreDeferredComponentManagerTest {
@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 class PlayStoreDeferredComponentManagerTest {
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 class PlayStoreDeferredComponentManagerTest {
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 class PlayStoreDeferredComponentManagerTest {
@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);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册