5package io.flutter.embedding.engine.deferredcomponents;
7import android.annotation.SuppressLint;
9import android.content.pm.ApplicationInfo;
10import android.content.pm.PackageManager;
11import android.content.pm.PackageManager.NameNotFoundException;
12import android.content.res.AssetManager;
16import android.util.SparseIntArray;
17import androidx.annotation.NonNull;
18import androidx.annotation.Nullable;
19import com.google.android.play.core.splitinstall.SplitInstallException;
20import com.google.android.play.core.splitinstall.SplitInstallManager;
21import com.google.android.play.core.splitinstall.SplitInstallManagerFactory;
22import com.google.android.play.core.splitinstall.SplitInstallRequest;
23import com.google.android.play.core.splitinstall.SplitInstallSessionState;
24import com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener;
25import com.google.android.play.core.splitinstall.model.SplitInstallErrorCode;
26import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus;
28import io.flutter.embedding.engine.FlutterJNI;
29import io.flutter.embedding.engine.loader.ApplicationInfoLoader;
30import io.flutter.embedding.engine.loader.FlutterApplicationInfo;
31import io.flutter.embedding.engine.systemchannels.DeferredComponentChannel;
33import java.util.ArrayList;
34import java.util.HashMap;
35import java.util.LinkedList;
38import java.util.Queue;
45 private static final String TAG =
"PlayStoreDeferredComponentManager";
50 private @NonNull SplitInstallManager splitInstallManager;
53 private @NonNull Context context;
57 private @NonNull SparseArray<String> sessionIdToName;
58 private @NonNull SparseIntArray sessionIdToLoadingUnitId;
59 private @NonNull SparseArray<String> sessionIdToState;
60 private @NonNull Map<String, Integer> nameToSessionId;
65 private FeatureInstallStateUpdatedListener listener;
67 private class FeatureInstallStateUpdatedListener
implements SplitInstallStateUpdatedListener {
68 @SuppressLint(
"DefaultLocale")
69 public void onStateUpdate(@NonNull SplitInstallSessionState
state) {
70 int sessionId =
state.sessionId();
71 if (sessionIdToName.get(sessionId) !=
null) {
72 switch (
state.status()) {
73 case SplitInstallSessionStatus.FAILED:
78 "Module \"%s\" (sessionId %d) install failed with: %s",
79 sessionIdToName.get(sessionId), sessionId,
state.errorCode()));
80 flutterJNI.deferredComponentInstallFailure(
81 sessionIdToLoadingUnitId.get(sessionId),
82 "Module install failed with " +
state.errorCode(),
84 if (channel !=
null) {
85 channel.completeInstallError(
86 sessionIdToName.get(sessionId),
87 "Android Deferred Component failed to install.");
89 sessionIdToName.delete(sessionId);
90 sessionIdToLoadingUnitId.delete(sessionId);
91 sessionIdToState.put(sessionId,
"failed");
94 case SplitInstallSessionStatus.INSTALLED:
99 "Module \"%s\" (sessionId %d) install successfully.",
100 sessionIdToName.get(sessionId), sessionId));
101 loadAssets(sessionIdToLoadingUnitId.get(sessionId), sessionIdToName.get(sessionId));
106 if (sessionIdToLoadingUnitId.get(sessionId) > 0) {
108 sessionIdToLoadingUnitId.get(sessionId), sessionIdToName.get(sessionId));
110 if (channel !=
null) {
111 channel.completeInstallSuccess(sessionIdToName.get(sessionId));
113 sessionIdToName.delete(sessionId);
114 sessionIdToLoadingUnitId.delete(sessionId);
115 sessionIdToState.put(sessionId,
"installed");
118 case SplitInstallSessionStatus.CANCELED:
123 "Module \"%s\" (sessionId %d) install canceled.",
124 sessionIdToName.get(sessionId), sessionId));
125 if (channel !=
null) {
126 channel.completeInstallError(
127 sessionIdToName.get(sessionId),
128 "Android Deferred Component installation canceled.");
130 sessionIdToName.delete(sessionId);
131 sessionIdToLoadingUnitId.delete(sessionId);
132 sessionIdToState.put(sessionId,
"cancelled");
135 case SplitInstallSessionStatus.CANCELING:
140 "Module \"%s\" (sessionId %d) install canceling.",
141 sessionIdToName.get(sessionId), sessionId));
142 sessionIdToState.put(sessionId,
"canceling");
145 case SplitInstallSessionStatus.PENDING:
150 "Module \"%s\" (sessionId %d) install pending.",
151 sessionIdToName.get(sessionId), sessionId));
152 sessionIdToState.put(sessionId,
"pending");
155 case SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION:
160 "Module \"%s\" (sessionId %d) install requires user confirmation.",
161 sessionIdToName.get(sessionId), sessionId));
162 sessionIdToState.put(sessionId,
"requiresUserConfirmation");
165 case SplitInstallSessionStatus.DOWNLOADING:
170 "Module \"%s\" (sessionId %d) downloading.",
171 sessionIdToName.get(sessionId), sessionId));
172 sessionIdToState.put(sessionId,
"downloading");
175 case SplitInstallSessionStatus.DOWNLOADED:
180 "Module \"%s\" (sessionId %d) downloaded.",
181 sessionIdToName.get(sessionId), sessionId));
182 sessionIdToState.put(sessionId,
"downloaded");
185 case SplitInstallSessionStatus.INSTALLING:
190 "Module \"%s\" (sessionId %d) installing.",
191 sessionIdToName.get(sessionId), sessionId));
192 sessionIdToState.put(sessionId,
"installing");
203 @NonNull Context context, @Nullable
FlutterJNI flutterJNI) {
204 this.context = context;
205 this.flutterJNI = flutterJNI;
207 splitInstallManager = SplitInstallManagerFactory.create(context);
208 listener =
new FeatureInstallStateUpdatedListener();
209 splitInstallManager.registerListener(listener);
210 sessionIdToName =
new SparseArray<>();
211 sessionIdToLoadingUnitId =
new SparseIntArray();
212 sessionIdToState =
new SparseArray<>();
213 nameToSessionId =
new HashMap<>();
217 initLoadingUnitMappingToComponentNames();
221 this.flutterJNI = flutterJNI;
224 private boolean verifyJNI() {
225 if (flutterJNI ==
null) {
228 "No FlutterJNI provided. `setJNI` must be called on the DeferredComponentManager before attempting to load dart libraries or invoking with platform channels.");
235 this.channel = channel;
239 private ApplicationInfo getApplicationInfo() {
243 .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
244 }
catch (NameNotFoundException
e) {
245 throw new RuntimeException(
e);
259 private void initLoadingUnitMappingToComponentNames() {
261 ApplicationInfo applicationInfo = getApplicationInfo();
262 if (applicationInfo !=
null) {
263 Bundle metaData = applicationInfo.metaData;
264 if (metaData !=
null) {
265 String rawMappingString = metaData.getString(
MAPPING_KEY,
null);
266 if (rawMappingString ==
null) {
269 "No loading unit to dynamic feature module name found. Ensure '"
271 +
"' is defined in the base module's AndroidManifest.");
274 if (rawMappingString.equals(
"")) {
278 for (String entry : rawMappingString.split(
",")) {
280 String[] splitEntry = entry.split(
":", -1);
281 int loadingUnitId = Integer.parseInt(splitEntry[0]);
283 if (splitEntry.length > 2) {
292 String resolvedComponentName =
294 if (resolvedComponentName ==
null) {
296 TAG,
"Deferred component name was null and could not be resolved from loading unit id.");
301 if (resolvedComponentName.equals(
"") && loadingUnitId > 0) {
307 SplitInstallRequest request =
308 SplitInstallRequest.newBuilder().addModule(resolvedComponentName).build();
314 .startInstall(request)
317 .addOnSuccessListener(
319 sessionIdToName.put(sessionId, resolvedComponentName);
320 sessionIdToLoadingUnitId.put(sessionId, loadingUnitId);
321 if (nameToSessionId.containsKey(resolvedComponentName)) {
322 sessionIdToState.remove(nameToSessionId.get(resolvedComponentName));
324 nameToSessionId.put(resolvedComponentName, sessionId);
325 sessionIdToState.put(sessionId,
"Requested");
327 .addOnFailureListener(
329 switch (((SplitInstallException) exception).getErrorCode()) {
330 case SplitInstallErrorCode.NETWORK_ERROR:
331 flutterJNI.deferredComponentInstallFailure(
333 "Install of deferred component module \""
335 +
"\" failed with a network error",
338 case SplitInstallErrorCode.MODULE_UNAVAILABLE:
339 flutterJNI.deferredComponentInstallFailure(
341 "Install of deferred component module \""
343 +
"\" failed as it is unavailable",
347 flutterJNI.deferredComponentInstallFailure(
350 "Install of deferred component module \"%s\" failed with error %d: %s",
352 ((SplitInstallException) exception).getErrorCode(),
353 ((SplitInstallException) exception).getMessage()),
362 int loadingUnitId, @Nullable String componentName) {
363 String resolvedComponentName =
365 if (resolvedComponentName ==
null) {
367 TAG,
"Deferred component name was null and could not be resolved from loading unit id.");
370 if (!nameToSessionId.containsKey(resolvedComponentName)) {
371 if (splitInstallManager.getInstalledModules().contains(resolvedComponentName)) {
372 return "installedPendingLoad";
376 int sessionId = nameToSessionId.get(resolvedComponentName);
377 return sessionIdToState.get(sessionId);
380 public void loadAssets(
int loadingUnitId, @NonNull String componentName) {
389 context = context.createPackageContext(context.getPackageName(), 0);
391 AssetManager assetManager = context.getAssets();
392 flutterJNI.updateJavaAssetManager(assetManager, flutterApplicationInfo.flutterAssetsDir);
393 }
catch (NameNotFoundException
e) {
394 throw new RuntimeException(
e);
403 if (loadingUnitId < 0) {
408 if (aotSharedLibraryName ==
null) {
410 aotSharedLibraryName =
411 flutterApplicationInfo.aotSharedLibraryName +
"-" + loadingUnitId +
".part.so";
415 String abi =
Build.SUPPORTED_ABIS[0];
416 String pathAbi = abi.replace(
"-",
"_");
426 Queue<File> searchFiles =
new LinkedList<>();
428 searchFiles.add(context.getFilesDir());
433 for (String
path : context.getApplicationInfo().splitSourceDirs) {
434 searchFiles.add(
new File(
path));
437 while (!searchFiles.isEmpty()) {
438 File
file = searchFiles.remove();
439 if (
file !=
null &&
file.isDirectory() &&
file.listFiles() !=
null) {
440 for (File
f :
file.listFiles()) {
448 if (
name.endsWith(
".apk")
449 && (
name.startsWith(componentName) ||
name.startsWith(
"split_config"))
450 &&
name.contains(pathAbi)) {
451 apkPaths.add(
file.getAbsolutePath());
454 if (
name.equals(aotSharedLibraryName)) {
455 soPaths.add(
file.getAbsolutePath());
463 searchPaths.add(aotSharedLibraryName);
465 for (String
path : apkPaths) {
466 searchPaths.add(
path +
"!lib/" + abi +
"/" + aotSharedLibraryName);
468 for (String
path : soPaths) {
469 searchPaths.add(
path);
472 flutterJNI.loadDartDeferredLibrary(
473 loadingUnitId, searchPaths.toArray(
new String[searchPaths.size()]));
477 String resolvedComponentName =
479 if (resolvedComponentName ==
null) {
481 TAG,
"Deferred component name was null and could not be resolved from loading unit id.");
485 modulesToUninstall.add(resolvedComponentName);
486 splitInstallManager.deferredUninstall(modulesToUninstall);
487 if (nameToSessionId.get(resolvedComponentName) !=
null) {
488 sessionIdToState.delete(nameToSessionId.get(resolvedComponentName));
494 splitInstallManager.unregisterListener(listener);
static void d(@NonNull String tag, @NonNull String message)
static void e(@NonNull String tag, @NonNull String message)
SparseArray< String > loadingUnitIdToComponentNames
PlayStoreDeferredComponentManager( @NonNull Context context, @Nullable FlutterJNI flutterJNI)
void setDeferredComponentChannel(@NonNull DeferredComponentChannel channel)
static final String MAPPING_KEY
SparseArray< String > loadingUnitIdToSharedLibraryNames
void installDeferredComponent(int loadingUnitId, @Nullable String componentName)
void loadDartLibrary(int loadingUnitId, @NonNull String componentName)
void setJNI(@NonNull FlutterJNI flutterJNI)
String getDeferredComponentInstallState(int loadingUnitId, @Nullable String componentName)
void loadAssets(int loadingUnitId, @NonNull String componentName)
boolean uninstallDeferredComponent(int loadingUnitId, @Nullable String componentName)
static FlutterApplicationInfo load(@NonNull Context applicationContext)
def Build(configs, env, options)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
DEF_SWITCHES_START aot vmservice shared library name