mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Pass the filename directly to JNI for loading deferred component. (flutter/engine#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.
This commit is contained in:
parent
3e24724755
commit
1181890b8e
@ -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,23 +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();
|
||||
|
||||
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");
|
||||
@ -114,38 +129,15 @@ public class PlayStoreDeferredComponentManagerTest {
|
||||
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();
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user