本文首發於微信公衆號——世界上有意思的事,搬運轉載請註明出處,不然將追究版權責任。java
又有兩個月沒有發文了,最近我司逐漸開始在成熟的項目中引入 Flutter 做爲一種新的開發方式。做爲比較早吃螃蟹的人,我也在組內爲三四十個同窗作了一次 Flutter 相關的分享。由於涉及了一些內部信息因此等我脫敏整理好以後會用專門的一篇文章進行相關的分享,你們能夠開始期待了,哈哈。至於本篇文章,我會講一些有意思的東西——成熟項目的Flutter快速引入 與 Flutter、Native混合開發,但願你們能多多點贊關注。android
閱讀須知:git
本文分爲如下章節,讀者可按需閱讀:程序員
Flutter測試項目Githubgithub
如今不少教程都停留在建立一個新的 Flutter 項目而後開始介紹如何使用這個項目開發 Flutter。可是其實咱們目前大部分使用 Flutter 的場景都是基於已經成熟的項目。咱們不可能由於使用 Flutter 而將原來的項目推到重來。這一節我就來介紹一種成熟項目無縫接入 Flutter 的方式。本章須要你們結合上面提到的 Github 項目代碼食用。api
從上面的介紹來看,閒魚、美團的實踐方式彷佛有着一些不方便之處。好比說不能動態更新 Flutter 代碼、Flutter 的 AAR 和主工程一塊兒編譯太具備侵入性等等(這裏只是我本身淺薄的見解,有異議的同窗能夠在評論區提出)。因此我這一節要介紹一種侵入性很是小的接入 Flutter 的方式,簡單來講就一句話:動態加載 Flutter 生成的 Apk。接下來我會結合前面提到的兩個 github 項目裏的代碼進行講解,你們必定要把這兩個項目 clone 下來,固然能點個 star 就更好了。安全
----代碼塊1,本文發自簡書、掘金:什麼時候夕-----
project.afterEvaluate {
android.applicationVariants.all { variant ->
def variantName = variant.name.capitalize()
def buildTask = project.tasks.findByName("assemble${variantName}")
if (buildTask) {
def outputApk = variant.outputs[0].outputFile.path
def classEntry = "*.dex"
def soEntry = "lib/*"
def metaEntry = "META-INF/*"
def licenseEntry = "assets/flutter_assets/LICENSE"
buildTask.doLast {
println variant.outputs[0].outputFile.length()
exec {
commandine 'sh', '-c', "zip -d ${outputApk} ${classEntry}"
}
exec {
commandLine 'sh', '-c', "zip -d ${outputApk} ${soEntry}"
}
exec {
commandLine 'sh', '-c', "zip -d ${outputApk} ${metaEntry}"
}
exec {
commandLine 'sh', '-c', "zip -d ${outputApk} ${licenseEntry}"
}
}
}
}
}
複製代碼
----代碼塊2,本文發自簡書、掘金:什麼時候夕-----
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RxPermissions permissions = new RxPermissions(this);
permissions.setLogging(true);
permissions.request(Manifest.permission.READ_EXTERNAL_STORAGE)
.subscribe(aBoolean -> FlutterContainer.init(getApplication(), "/storage/emulated/0/flutter1.apk"));
findViewById(R.id.aaa).setOnClickListener(v -> startActivity(new Intent(MainActivity.this, Main2Activity.class)));
}
}
複製代碼
----代碼塊3,本文發自簡書、掘金:什麼時候夕-----
public class FlutterContainer {
private static final String TAG = "FlutterContainer";
private static boolean sInitialized = false;
private static Context sApplicationContext;
private static String sFlutterInstallPath = "";
public static void init(@NonNull Application applicationContext, @NonNull FlutterEngine.PrepareFlutterPackage prepareFlutterPackage) {
init(applicationContext, null, prepareFlutterPackage, null);
}
public static void init(@NonNull Application applicationContext, @NonNull String flutterInstallPath) {
init(applicationContext, flutterInstallPath, null, null);
}
public static void init(@NonNull Application applicationContext, @NonNull String flutterInstallPath, @Nullable FlutterEngine.Callback startCallBack) {
init(applicationContext, flutterInstallPath, null, startCallBack);
}
public static void init(@NonNull Application applicationContext, @NonNull FlutterEngine.PrepareFlutterPackage prepareFlutterPackage, @Nullable FlutterEngine.Callback startCallBack) {
init(applicationContext, null, prepareFlutterPackage, startCallBack);
}
/** * 只能在 app 啓動的時候初始化一次 * * @param applicationContext */
private static void init(@NonNull Application applicationContext, @Nullable String flutterInstallPath, @Nullable FlutterEngine.PrepareFlutterPackage prepareFlutterPackage, @Nullable FlutterEngine.Callback startCallBack) {
if (sInitialized) {
return;
}
new FlutterManager(applicationContext);
sInitialized = true;
sApplicationContext = applicationContext;
if (!TextUtils.isEmpty(flutterInstallPath)) {
upgradeFlutterPackage(flutterInstallPath, startCallBack);
} else if (prepareFlutterPackage != null) {
upgradeFlutterPackage(prepareFlutterPackage, startCallBack);
} else {
Log.i(TAG, "FlutterContainer init no flutter package");
}
}
/** * @param flutterInstallPath */
public static void upgradeFlutterPackage(@NonNull String flutterInstallPath, @Nullable FlutterEngine.Callback startCallBack) {
if (!sInitialized) {
return;
}
FlutterManager.getInstance().resetFlutterPackage();
sFlutterInstallPath = flutterInstallPath;
FlutterManager.getInstance().getFlutterEngine().startFast(startCallBack);
}
複製代碼
----代碼塊4,本文發自簡書、掘金:什麼時候夕-----
public class FlutterManager {
private static FlutterManager sInstance;
private final FlutterEngine mFlutterEngine;
private final FlutterContextWrapper mFlutterContextWrapper;
private final Context mContext;
FlutterManager(Application context) {
sInstance = this; // 簡單單例, 線程並不安全, 邏輯保證
mFlutterEngine = new FlutterEngine(context);
mFlutterContextWrapper = new FlutterContextWrapper(context);
mContext = context;
}
public static FlutterManager getInstance() {
return sInstance;
}
public void registerChannel(BinaryMessenger messenger, String channel, BaseHandler handler) {
new MethodChannel(messenger, channel + ".method").setMethodCallHandler(handler);
if (handler.mEnableEventChannel) {
new EventChannel(messenger, channel + ".event").setStreamHandler(handler);
}
}
FlutterEngine getFlutterEngine() {
return mFlutterEngine;
}
public FlutterContextWrapper getFlutterContextWrapper() {
return mFlutterContextWrapper;
}
/** * 是否有 Flutter 包可用 * * @return */
public boolean isFlutterAvailable() {
File activeApk = new File(FlutterContainer.getFlutterInstallPath());
return activeApk.isFile();
}
/** * 若是要使用新的 Flutter 包,那麼須要重置一下 */
void resetFlutterPackage() {
mFlutterContextWrapper.reset();
}
}
複製代碼
----代碼塊5,本文發自簡書、掘金:什麼時候夕-----
public class FlutterContextWrapper extends ContextWrapper {
private AssetManager sAssets;
FlutterContextWrapper(Context base) {
super(base);
}
public void reset() {
sAssets = null; // 在每次安裝flutter包以後,須要從新建立新的assets
}
@Override
public Resources getResources() {
return new Resources(getAssets(), super.getResources().getDisplayMetrics(),
super.getResources().getConfiguration());
}
@Override
public AssetManager getAssets() {
if (sAssets != null) {
return sAssets;
}
File activeApk = new File(FlutterContainer.getFlutterInstallPath());
if (!activeApk.isFile()) {
return super.getAssets();
}
sAssets = ReflectionUtil.newInstance(AssetManager.class);
ReflectionUtil.callMethod(sAssets, "addAssetPath", activeApk.getPath());
return sAssets;
}
@Override
public PackageManager getPackageManager() {
return new FlutterPackageManager(super.getPackageManager());
}
}
複製代碼
----代碼塊6,本文發自簡書、掘金:什麼時候夕-----
class FlutterEngine {
private static boolean sInitialized; // 全局標記引擎已經啓動
private final Context mContext;
FlutterEngine(Context context) {
mContext = context;
}
/** * 快速啓動模式,表示已經有包了 */
void startFast(@Nullable Callback callback) {
if (sInitialized) {
// 須要儘快啓動,因此須要去重
callback(callback, null);
return;
}
if (FlutterManager.getInstance().isFlutterAvailable()) { // 當前有可用包
startFlutterInitialization();
ensureInitializationComplete();
callback(callback, null);
} else {
DebugUtil.logError(new RuntimeException("startFast but no available package"));
}
}
/** * 慢速啓動模式, 表示沒有報,須要準備 */
void startSlow(@Nullable Callback callback, @NonNull PrepareFlutterPackage prepareFlutterPackage) {
Single.fromCallable(() -> {
// 此處不去重, 不論是否sInitialized都從新初始化, 保證使用最新flutter包.
prepareFlutterPackage.prepareFlutterPackage();
return new Object();
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(o -> {
startFlutterInitialization();
ensureInitializationComplete();
callback(callback, null);
}, throwable -> {
throwable.printStackTrace();
callback(callback, throwable);
});
}
private static void callback(@Nullable Callback callback, Throwable t) {
if (callback != null) {
callback.onCompleted(t);
}
}
private void startFlutterInitialization() { // 不阻塞UI
// Flutter SDK的start方法能夠屢次調用, 他的主要做用是解壓資源, 所以不用作去重
FlutterMain.startInitialization(FlutterManager.getInstance().getFlutterContextWrapper());
}
private void ensureInitializationComplete() {
FlutterMain.ensureInitializationComplete(mContext, null);
sInitialized = true; // 已經初始化
}
// 啓動回調
public interface Callback {
/** * 初始化完成. * * @param e 成功爲null,失敗不爲null. */
void onCompleted(Throwable e);
}
// 準備 Flutter 包的回調
public interface PrepareFlutterPackage {
String prepareFlutterPackage();
}
}
複製代碼
前面完了在成熟項目中無縫引入 Flutter 的方式,這一章咱們再來講說 Flutter 和 Native 混合開發的方式。可能會混合開發不是很簡單嗎,直接嵌入一個 Flutter 的 Activity/Fragment 就能將其做爲容器運行 Flutter 了。其實這樣的想法太過理想化,若是個人一個 Acitivity/Fragment 中 Flutter 和 Native 都須要有呢?這一章我我就是要來解決這個問題,你們隨我一塊兒往下看。服務器
爲了解決數據傳遞的昂貴耗損,我想了另一個辦法來繞過這個問題。本小結須要結合Flutter容器項目食用。微信
1.咱們首先得了解 Flutter 在 Android 端渲染的幾個前置知識:
2.在瞭解了混合開發的思想以後代碼上就很是簡單了。
----代碼塊7,本文發自簡書、掘金:什麼時候夕-----
public class FlutterTextureBaseFragment extends FlutterFragment {
protected FlutterView mFlutterView;
protected FlutterContextWrapper mFlutterContextWrapper;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
mFlutterView = ViewUtil.getFlutterView(view);
mFlutterContextWrapper = new FlutterContextWrapper(getContext());
return mFlutterView;
}
@Nullable
public FlutterView getFlutterView() {
return mFlutterView;
}
public static class TextureBuilder extends FlutterFragment.Builder {
@NonNull
public <T extends FlutterFragment> T build() {
try {
T frag = (T) FlutterTextureBaseFragment.class.newInstance();
if (frag == null) {
throw new RuntimeException("The FlutterFragment subclass sent in the constructor (" + FlutterTextureBaseFragment.class.getCanonicalName() + ") does not match the expected return type.");
} else {
Bundle args = this.createArgs();
frag.setArguments(args);
return frag;
}
} catch (Exception var3) {
throw new RuntimeException("Could not instantiate FlutterFragment subclass (" + FlutterTextureBaseFragment.class.getName() + ")", var3);
}
}
}
@Override
public Context getContext() {
if (mFlutterContextWrapper == null) {
return super.getContext();
} else {
return mFlutterContextWrapper;
}
}
}
複製代碼
----代碼塊8,本文發自簡書、掘金:什麼時候夕-----
public class FlutterTextureBaseActivity extends FlutterActivity {
protected FlutterView mFlutterView;
protected FlutterTextureBaseFragment mFlutterTextureBaseFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Nullable
public ViewGroup getFlutterViewParent() {
getFlutterView();
if (mFlutterView == null) {
return null;
} else {
return (ViewGroup) mFlutterView.getParent();
}
}
@Nullable
public FlutterView getFlutterView() {
if (mFlutterTextureBaseFragment == null) {
return null;
} else if (mFlutterTextureBaseFragment.getView() != null) {
mFlutterView = mFlutterTextureBaseFragment.getFlutterView();
return mFlutterView;
} else {
return null;
}
}
@Nullable
public FlutterTextureBaseFragment getFlutterTextureBaseFragment() {
return mFlutterTextureBaseFragment;
}
@NonNull
protected FlutterTextureBaseFragment createFlutterFragment() {
mFlutterTextureBaseFragment = (new FlutterTextureBaseFragment.TextureBuilder())
.dartEntrypoint(this.getDartEntrypoint())
.initialRoute(this.getInitialRoute())
.appBundlePath(this.getAppBundlePath())
.flutterShellArgs(FlutterShellArgs.fromIntent(this.getIntent()))
.renderMode(FlutterView.RenderMode.texture)
.transparencyMode(FlutterView.TransparencyMode.opaque)
.build();
return mFlutterTextureBaseFragment;
}
}
複製代碼
最近更新文章的頻率變長了,一個是由於工做比較忙,另外一個緣由則是我但願能寫出更多的乾貨給讀者。因此但願你們可以持續關注個人掘金、簡書、微信公衆號,我也會一直分享原創精品文章給你們。
不販賣焦慮,也不標題黨。分享一些這個世界上有意思的事情。題材包括且不限於:科幻、科學、科技、互聯網、程序員、計算機編程。下面是個人微信公衆號:世界上有意思的事,乾貨多多等你來看。