Android开发Android开发经验谈Android技术知识

成熟项目的Flutter快速引入以及Flutter、Native

2019-12-17  本文已影响0人  zhx喜籽

阅读须知:

本文分为以下章节,读者可按需阅读:

Flutter测试项目Github

Flutter容器项目Github

一、成熟项目的Flutter快速引入

现在很多教程都停留在创建一个新的 Flutter 项目然后开始介绍如何使用这个项目开发 Flutter。但是其实我们目前大部分使用 Flutter 的场景都是基于已经成熟的项目。我们不可能因为使用 Flutter 而将原来的项目推到重来。这一节我就来介绍一种成熟项目无缝接入 Flutter 的方式。本章需要大家结合上面提到的 Github 项目代码食用。

1.闲鱼以及美团的实践

2.我的实践

从上面的介绍来看,闲鱼、美团的实践方式似乎有着一些不方便之处。比如说不能动态更新 Flutter 代码、Flutter 的 AAR 和主工程一起编译太具有侵入性等等(这里只是我自己浅薄的看法,有异议的同学可以在评论区提出)。所以我这一节要介绍一种侵入性非常小的接入 Flutter 的方式,简单来说就一句话:动态加载 Flutter 生成的 Apk接下来我会结合前面提到的两个 github 项目里的代码进行讲解,大家一定要把这两个项目 clone 下来,当然能点个 star 就更好了。

(1).创建Flutter测试项目

image
----代码块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).创建Flutter容器项目

image
----代码块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、Native混合开发

前面完了在成熟项目中无缝引入 Flutter 的方式,这一章我们再来说说 Flutter 和 Native 混合开发的方式。可能会混合开发不是很简单吗,直接嵌入一个 Flutter 的 Activity/Fragment 就能将其作为容器运行 Flutter 了。其实这样的想法太过理想化,如果我的一个 Acitivity/Fragment 中 Flutter 和 Native 都需要有呢?这一章我我就是要来解决这个问题,大家随我一起往下看。

1.Flutter、Native混合开发场景以及闲鱼的实践

2.我的实践

为了解决数据传递的昂贵耗损,我想了另外一个办法来绕过这个问题。本小结需要结合Flutter容器项目食用。

----代码块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;
  }
}

image

作者:何时夕
链接:https://www.jianshu.com/p/9f578d50ae94
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

上一篇 下一篇

猜你喜欢

热点阅读