貼個效果圖奔脐,這里并非跳轉到兩個不同頁面=。=
上篇結尾分析過加載邏輯在
FlutterLoader
private static FlutterLoader instance;
/**
* Returns a singleton {@code FlutterLoader} instance.
* <p>
* The returned instance loads Flutter native libraries in the standard way. A singleton object
* is used instead of static methods to facilitate testing without actually running native
* library linking.
*/
@NonNull
public static FlutterLoader getInstance() {
if (instance == null) {
instance = new FlutterLoader();
}
return instance;
}
熟悉的單例亡电,hook這個單例變?yōu)樽远x的MyFlutterLoader。重寫startInitialization()绳泉、ensureInitializationComplete()方法逊抡。下面上代碼,默認大家已經(jīng)集成flutter作為module零酪。
MyFlutterLoader
package com.chenxuan.flutter112.hotfix;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import io.flutter.BuildConfig;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.loader.FlutterLoader;
import io.flutter.util.PathUtils;
import io.flutter.view.VsyncWaiter;
public class MyFlutterLoader extends FlutterLoader {
private static final String TAG = "FlutterLoader";
private static final String AOT_SHARED_LIBRARY_NAME = "aot-shared-library-name";
private static final String SNAPSHOT_ASSET_PATH_KEY = "snapshot-asset-path";
private static final String VM_SNAPSHOT_DATA_KEY = "vm-snapshot-data";
private static final String ISOLATE_SNAPSHOT_DATA_KEY = "isolate-snapshot-data";
private static final String FLUTTER_ASSETS_DIR_KEY = "flutter-assets-dir";
private static final String PUBLIC_AOT_SHARED_LIBRARY_NAME =
FlutterLoader.class.getName() + '.' + AOT_SHARED_LIBRARY_NAME;
private static final String PUBLIC_VM_SNAPSHOT_DATA_KEY =
FlutterLoader.class.getName() + '.' + VM_SNAPSHOT_DATA_KEY;
private static final String PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY =
FlutterLoader.class.getName() + '.' + ISOLATE_SNAPSHOT_DATA_KEY;
private static final String PUBLIC_FLUTTER_ASSETS_DIR_KEY =
FlutterLoader.class.getName() + '.' + FLUTTER_ASSETS_DIR_KEY;
// Resource names used for components of the precompiled snapshot.
private static final String DEFAULT_AOT_SHARED_LIBRARY_NAME = "libapp.so";
private static final String DEFAULT_VM_SNAPSHOT_DATA = "vm_snapshot_data";
private static final String DEFAULT_ISOLATE_SNAPSHOT_DATA = "isolate_snapshot_data";
private static final String DEFAULT_LIBRARY = "libflutter.so";
private static final String DEFAULT_KERNEL_BLOB = "kernel_blob.bin";
private static final String DEFAULT_FLUTTER_ASSETS_DIR = "flutter_assets";
private String aotSharedLibraryName = DEFAULT_AOT_SHARED_LIBRARY_NAME;
private String vmSnapshotData = DEFAULT_VM_SNAPSHOT_DATA;
private String isolateSnapshotData = DEFAULT_ISOLATE_SNAPSHOT_DATA;
private String flutterAssetsDir = DEFAULT_FLUTTER_ASSETS_DIR;
private boolean initialized = false;
private Settings settings;
@Nullable
private ResourceExtractor resourceExtractor;
@Override
public void startInitialization(@NotNull Context applicationContext) {
startInitialization(applicationContext, new Settings());
}
@Override
public void startInitialization(@NotNull Context applicationContext, @NotNull Settings settings) {
Log.d("chenxuan-------", "method---" + "startInitialization");
// Do not run startInitialization more than once.
if (this.settings != null) {
return;
}
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("startInitialization must be called on the main thread");
}
this.settings = settings;
long initStartTimestampMillis = SystemClock.uptimeMillis();
initConfig(applicationContext);
initResources(applicationContext);
System.loadLibrary("flutter");
VsyncWaiter
.getInstance((WindowManager) applicationContext.getSystemService(Context.WINDOW_SERVICE))
.init();
// We record the initialization time using SystemClock because at the start of the
// initialization we have not yet loaded the native library to call into dart_tools_api.h.
// To get Timeline timestamp of the start of initialization we simply subtract the delta
// from the Timeline timestamp at the current moment (the assumption is that the overhead
// of the JNI call is negligible).
long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
FlutterJNI.nativeRecordStartTimestamp(initTimeMillis);
}
@Override
public void ensureInitializationComplete(@NotNull Context applicationContext, String[] args) {
Log.d("chenxuan-------", "method---" + "ensureInitializationComplete");
if (initialized) {
return;
}
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("ensureInitializationComplete must be called on the main thread");
}
if (settings == null) {
throw new IllegalStateException("ensureInitializationComplete must be called after startInitialization");
}
try {
if (resourceExtractor != null) {
resourceExtractor.waitForCompletion();
}
List<String> shellArgs = new ArrayList<>();
shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat");
ApplicationInfo applicationInfo = getApplicationInfo(applicationContext);
shellArgs.add("--icu-native-lib-path=" + applicationInfo.nativeLibraryDir + File.separator + DEFAULT_LIBRARY);
if (args != null) {
Collections.addAll(shellArgs, args);
}
String kernelPath = null;
if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
String snapshotAssetPath = PathUtils.getDataDirectory(applicationContext) + File.separator + flutterAssetsDir;
kernelPath = snapshotAssetPath + File.separator + DEFAULT_KERNEL_BLOB;
shellArgs.add("--" + SNAPSHOT_ASSET_PATH_KEY + "=" + snapshotAssetPath);
shellArgs.add("--" + VM_SNAPSHOT_DATA_KEY + "=" + vmSnapshotData);
shellArgs.add("--" + ISOLATE_SNAPSHOT_DATA_KEY + "=" + isolateSnapshotData);
} else {
//shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME + "=" + "fix.so");
//關鍵點
File dir = applicationContext.getDir("libs", Activity.MODE_PRIVATE);
String libPath = dir.getAbsolutePath() + File.separator + "fix.so";
shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME + "=" + libPath);
Log.d("chenxuan-------", libPath);
// Most devices can load the AOT shared library based on the library name
// with no directory path. Provide a fully qualified path to the library
// as a workaround for devices where that fails.
shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME + "=" + applicationInfo.nativeLibraryDir + File.separator + aotSharedLibraryName);
}
shellArgs.add("--cache-dir-path=" + PathUtils.getCacheDirectory(applicationContext));
if (settings.getLogTag() != null) {
shellArgs.add("--log-tag=" + settings.getLogTag());
}
String appStoragePath = PathUtils.getFilesDir(applicationContext);
String engineCachesPath = PathUtils.getCacheDirectory(applicationContext);
FlutterJNI.nativeInit(applicationContext, shellArgs.toArray(new String[0]),
kernelPath, appStoragePath, engineCachesPath);
initialized = true;
} catch (Exception e) {
Log.e(TAG, "Flutter initialization failed.", e);
throw new RuntimeException(e);
}
}
/**
* Initialize our Flutter config values by obtaining them from the
* manifest XML file, falling back to default values.
*/
private void initConfig(@NonNull Context applicationContext) {
Bundle metadata = getApplicationInfo(applicationContext).metaData;
// There isn't a `<meta-data>` tag as a direct child of `<application>` in
// `AndroidManifest.xml`.
if (metadata == null) {
return;
}
aotSharedLibraryName = metadata.getString(PUBLIC_AOT_SHARED_LIBRARY_NAME, DEFAULT_AOT_SHARED_LIBRARY_NAME);
flutterAssetsDir = metadata.getString(PUBLIC_FLUTTER_ASSETS_DIR_KEY, DEFAULT_FLUTTER_ASSETS_DIR);
vmSnapshotData = metadata.getString(PUBLIC_VM_SNAPSHOT_DATA_KEY, DEFAULT_VM_SNAPSHOT_DATA);
isolateSnapshotData = metadata.getString(PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY, DEFAULT_ISOLATE_SNAPSHOT_DATA);
}
/**
* Extract assets out of the APK that need to be cached as uncompressed
* files on disk.
*/
private void initResources(@NonNull Context applicationContext) {
new ResourceCleaner(applicationContext).start();
if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
final String dataDirPath = PathUtils.getDataDirectory(applicationContext);
final String packageName = applicationContext.getPackageName();
final PackageManager packageManager = applicationContext.getPackageManager();
final AssetManager assetManager = applicationContext.getResources().getAssets();
resourceExtractor = new ResourceExtractor(dataDirPath, packageName, packageManager, assetManager);
// In debug/JIT mode these assets will be written to disk and then
// mapped into memory so they can be provided to the Dart VM.
resourceExtractor
.addResource(fullAssetPathFrom(vmSnapshotData))
.addResource(fullAssetPathFrom(isolateSnapshotData))
.addResource(fullAssetPathFrom(DEFAULT_KERNEL_BLOB));
resourceExtractor.start();
}
}
@NonNull
private ApplicationInfo getApplicationInfo(@NonNull Context applicationContext) {
try {
return applicationContext
.getPackageManager()
.getApplicationInfo(applicationContext.getPackageName(), PackageManager.GET_META_DATA);
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(e);
}
}
@NonNull
private String fullAssetPathFrom(@NonNull String filePath) {
return flutterAssetsDir + File.separator + filePath;
}
}
三個無法導包的類ResourceCleaner冒嫡、ResourceExtractor、ResourcePaths四苇,復制一下孝凌。
ResourceExtractor
package com.chenxuan.flutter112.hotfix;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.os.AsyncTask;
import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import com.chenxuan.flutter112.BuildConfig;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import static java.util.Arrays.asList;
public class ResourceExtractor {
private static final String TAG = "ResourceExtractor";
private static final String TIMESTAMP_PREFIX = "res_timestamp-";
private static final String[] SUPPORTED_ABIS = getSupportedAbis();
@SuppressWarnings("deprecation")
static long getVersionCode(@NonNull PackageInfo packageInfo) {
// Linter needs P (28) hardcoded or else it will fail these lines.
if (Build.VERSION.SDK_INT >= 28) {
return packageInfo.getLongVersionCode();
} else {
return packageInfo.versionCode;
}
}
private static class ExtractTask extends AsyncTask<Void, Void, Void> {
@NonNull
private final String mDataDirPath;
@NonNull
private final HashSet<String> mResources;
@NonNull
private final AssetManager mAssetManager;
@NonNull
private final String mPackageName;
@NonNull
private final PackageManager mPackageManager;
ExtractTask(@NonNull String dataDirPath,
@NonNull HashSet<String> resources,
@NonNull String packageName,
@NonNull PackageManager packageManager,
@NonNull AssetManager assetManager) {
mDataDirPath = dataDirPath;
mResources = resources;
mAssetManager = assetManager;
mPackageName = packageName;
mPackageManager = packageManager;
}
@Override
protected Void doInBackground(Void... unused) {
final File dataDir = new File(mDataDirPath);
final String timestamp = checkTimestamp(dataDir, mPackageManager, mPackageName);
if (timestamp == null) {
return null;
}
deleteFiles(mDataDirPath, mResources);
if (!extractAPK(dataDir)) {
return null;
}
if (timestamp != null) {
try {
new File(dataDir, timestamp).createNewFile();
} catch (IOException e) {
Log.w(TAG, "Failed to write resource timestamp");
}
}
return null;
}
/// Returns true if successfully unpacked APK resources,
/// otherwise deletes all resources and returns false.
@WorkerThread
private boolean extractAPK(@NonNull File dataDir) {
for (String asset : mResources) {
try {
final String resource = "assets/" + asset;
final File output = new File(dataDir, asset);
if (output.exists()) {
continue;
}
if (output.getParentFile() != null) {
output.getParentFile().mkdirs();
}
try (InputStream is = mAssetManager.open(asset);
OutputStream os = new FileOutputStream(output)) {
copy(is, os);
}
if (io.flutter.BuildConfig.DEBUG) {
Log.i(TAG, "Extracted baseline resource " + resource);
}
} catch (FileNotFoundException fnfe) {
continue;
} catch (IOException ioe) {
Log.w(TAG, "Exception unpacking resources: " + ioe.getMessage());
deleteFiles(mDataDirPath, mResources);
return false;
}
}
return true;
}
}
@NonNull
private final String mDataDirPath;
@NonNull
private final String mPackageName;
@NonNull
private final PackageManager mPackageManager;
@NonNull
private final AssetManager mAssetManager;
@NonNull
private final HashSet<String> mResources;
private ExtractTask mExtractTask;
ResourceExtractor(@NonNull String dataDirPath,
@NonNull String packageName,
@NonNull PackageManager packageManager,
@NonNull AssetManager assetManager) {
mDataDirPath = dataDirPath;
mPackageName = packageName;
mPackageManager = packageManager;
mAssetManager = assetManager;
mResources = new HashSet<>();
}
ResourceExtractor addResource(@NonNull String resource) {
mResources.add(resource);
return this;
}
ResourceExtractor addResources(@NonNull Collection<String> resources) {
mResources.addAll(resources);
return this;
}
ResourceExtractor start() {
if (io.flutter.BuildConfig.DEBUG && mExtractTask != null) {
Log.e(TAG, "Attempted to start resource extraction while another extraction was in progress.");
}
mExtractTask = new ResourceExtractor.ExtractTask(mDataDirPath, mResources, mPackageName, mPackageManager, mAssetManager);
mExtractTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
return this;
}
void waitForCompletion() {
if (mExtractTask == null) {
return;
}
try {
mExtractTask.get();
} catch (CancellationException | ExecutionException | InterruptedException e) {
deleteFiles(mDataDirPath, mResources);
}
}
private static String[] getExistingTimestamps(File dataDir) {
return dataDir.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith(TIMESTAMP_PREFIX);
}
});
}
private static void deleteFiles(@NonNull String dataDirPath, @NonNull HashSet<String> resources) {
final File dataDir = new File(dataDirPath);
for (String resource : resources) {
final File file = new File(dataDir, resource);
if (file.exists()) {
file.delete();
}
}
final String[] existingTimestamps = getExistingTimestamps(dataDir);
if (existingTimestamps == null) {
return;
}
for (String timestamp : existingTimestamps) {
new File(dataDir, timestamp).delete();
}
}
// Returns null if extracted resources are found and match the current APK version
// and update version if any, otherwise returns the current APK and update version.
private static String checkTimestamp(@NonNull File dataDir,
@NonNull PackageManager packageManager,
@NonNull String packageName) {
PackageInfo packageInfo = null;
try {
packageInfo = packageManager.getPackageInfo(packageName, 0);
} catch (PackageManager.NameNotFoundException e) {
return TIMESTAMP_PREFIX;
}
if (packageInfo == null) {
return TIMESTAMP_PREFIX;
}
String expectedTimestamp =
TIMESTAMP_PREFIX + getVersionCode(packageInfo) + "-" + packageInfo.lastUpdateTime;
final String[] existingTimestamps = getExistingTimestamps(dataDir);
if (existingTimestamps == null) {
if (io.flutter.BuildConfig.DEBUG) {
Log.i(TAG, "No extracted resources found");
}
return expectedTimestamp;
}
if (existingTimestamps.length == 1) {
if (io.flutter.BuildConfig.DEBUG) {
Log.i(TAG, "Found extracted resources " + existingTimestamps[0]);
}
}
if (existingTimestamps.length != 1
|| !expectedTimestamp.equals(existingTimestamps[0])) {
if (BuildConfig.DEBUG) {
Log.i(TAG, "Resource version mismatch " + expectedTimestamp);
}
return expectedTimestamp;
}
return null;
}
private static void copy(@NonNull InputStream in, @NonNull OutputStream out) throws IOException {
byte[] buf = new byte[16 * 1024];
for (int i; (i = in.read(buf)) >= 0; ) {
out.write(buf, 0, i);
}
}
@SuppressWarnings("deprecation")
private static String[] getSupportedAbis() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return Build.SUPPORTED_ABIS;
} else {
ArrayList<String> cpuAbis = new ArrayList<String>(asList(Build.CPU_ABI, Build.CPU_ABI2));
cpuAbis.removeAll(asList(null, ""));
return cpuAbis.toArray(new String[0]);
}
}
}
ResourceCleaner
package com.chenxuan.flutter112.hotfix;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Handler;
import android.util.Log;
import java.io.File;
import java.io.FilenameFilter;
import io.flutter.BuildConfig;
public class ResourceCleaner {
private static final String TAG = "ResourceCleaner";
private static final long DELAY_MS = 5000;
private static class CleanTask extends AsyncTask<Void, Void, Void> {
private final File[] mFilesToDelete;
CleanTask(File[] filesToDelete) {
mFilesToDelete = filesToDelete;
}
boolean hasFilesToDelete() {
return mFilesToDelete != null && mFilesToDelete.length > 0;
}
@Override
protected Void doInBackground(Void... unused) {
if (BuildConfig.DEBUG) {
Log.i(TAG, "Cleaning " + mFilesToDelete.length + " resources.");
}
for (File file : mFilesToDelete) {
if (file.exists()) {
deleteRecursively(file);
}
}
return null;
}
private void deleteRecursively(File parent) {
if (parent.isDirectory()) {
for (File child : parent.listFiles()) {
deleteRecursively(child);
}
}
parent.delete();
}
}
private final Context mContext;
ResourceCleaner(Context context) {
mContext = context;
}
void start() {
File cacheDir = mContext.getCacheDir();
if (cacheDir == null) {
return;
}
final CleanTask task = new CleanTask(cacheDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
boolean result = name.startsWith(ResourcePaths.TEMPORARY_RESOURCE_PREFIX);
return result;
}
}));
if (!task.hasFilesToDelete()) {
return;
}
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}, DELAY_MS);
}
}
ResourcePaths
package com.chenxuan.flutter112.hotfix;
import android.content.Context;
import java.io.File;
import java.io.IOException;
public class ResourcePaths {
// The filename prefix used by Chromium temporary file APIs.
public static final String TEMPORARY_RESOURCE_PREFIX = ".org.chromium.Chromium.";
// Return a temporary file that will be cleaned up by the ResourceCleaner.
public static File createTempFile(Context context, String suffix) throws IOException {
return File.createTempFile(TEMPORARY_RESOURCE_PREFIX, "_" + suffix,
context.getCacheDir());
}
}
MainActivity添加兩個按鈕,模擬熱更新
package com.chenxuan.flutter112
import android.Manifest
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import com.chenxuan.flutter112.hotfix.FileUtils
import com.chenxuan.flutter112.hotfix.MyFlutterLoader
import com.tbruyelle.rxpermissions2.RxPermissions
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.loader.FlutterLoader
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val request = RxPermissions(this)
.request(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
)
.subscribe { granted ->
if (granted) {
init()
} else {
Toast.makeText(this,"請打開權限", Toast.LENGTH_SHORT).show()
}
}
}
private fun init() {
tv1.setOnClickListener {
startActivity(
FlutterActivity.createDefaultIntent(this)
)
}
tv2.setOnClickListener {
//將fix.so push到手機根目錄月腋,復制到data/data/package/app_libs/下
FileUtils.copy(this@MainActivity, "fix.so")
hookFlutterLoader()
startActivity(
FlutterActivity.createDefaultIntent(this)
)
}
}
private fun hookFlutterLoader() {
try {
val clazz = Class.forName("io.flutter.embedding.engine.loader.FlutterLoader")
val instance = clazz.newInstance() as FlutterLoader
val field = clazz.getDeclaredField("instance")
field.isAccessible = true
field.set(instance, MyFlutterLoader())
} catch (e: Exception) {
e.printStackTrace()
}
}
}
FileUtils
package com.chenxuan.flutter112.hotfix;
import android.app.Activity;
import android.content.Context;
import android.os.Environment;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileUtils {
///拷貝fix.so到私有目錄
public static String copy(Context context, String fileName) {
try {
File dir = context.getDir("libs", Activity.MODE_PRIVATE);
File original = new File(dir.getAbsolutePath() + File.separator + fileName);
if (original.exists()) {
boolean delete = original.delete();
}
if (!original.exists()) {
boolean bool = original.createNewFile();
if (bool) {
String path = Environment.getExternalStorageDirectory().toString();
FileInputStream fileInputStream = new FileInputStream(new File(path + File.separator + fileName));
FileOutputStream fileOutputStream = new FileOutputStream(original);
byte[] buffer = new byte[fileInputStream.available()];
int count;
while ((count = fileInputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, count);
}
fileInputStream.close();
fileOutputStream.flush();
fileOutputStream.close();
return original.getAbsolutePath();
}
}
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
}
接下來動手測試:
打release包安裝apk蟀架。改動flutter module中main.dart代碼瓣赂,打新包,解壓apk片拍。將解壓目錄lib下libapp.so重命名為fix.so并push到手機根目錄煌集。點擊tv1啟動正常flutter頁面,此時flutter頁面按鈕點擊自增1捌省;重啟APP點擊tv2加載fix.so熱更新生效苫纤,此時flutter頁面按鈕點擊自增100。
建議打armeabi-v7a單平臺包纲缓,打多平臺包需要把fix.so拷貝到對應的平臺目錄卷拘。例如:data/data/package/app_libs/armeabi-v7a/fix.so,單平臺直接拷貝到data/data/package/app_libs/下即可祝高。
app下build.gradle
defaultConfig {
...
ndk {
abiFilters 'armeabi-v7a'
}
}
這里提供一個思路栗弟,可自行實現(xiàn)熱更新。核心不變工闺,操作shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME + "=" + aotSharedLibraryName)
替換aotSharedLibraryName乍赫。
FlutterLoader.ensureInitializationComplete()
接收數(shù)組參數(shù)args
public void ensureInitializationComplete(@NonNull Context applicationContext, @Nullable String[] args)
追本溯源,該參數(shù)在FlutterActivityAndFragmentDelegate.setupFlutterEngine()
中傳入
void setupFlutterEngine() {
...
flutterEngine = new FlutterEngine(host.getContext(), host.getFlutterShellArgs().toArray());
}
host是FlutterActivity斤寂,FlutterActivity.getFlutterShellArgs()
@Override
public FlutterShellArgs getFlutterShellArgs() {
return FlutterShellArgs.fromIntent(getIntent());
}
可以在這里做文章耿焊,提前插入fix.so揪惦,可自行實現(xiàn)遍搞。