查看原文
其他

Flutter系列之混合开发Android篇

jzman 躬行之 2022-08-26
前面几篇文章介绍了 Navigator 组件、Flex 布局、图片加载、Widget 生命周期等 Flutter 开发基础知识, 文章链接如下:
下面介绍一下 Flutter 混合开发模式, 以及如何在 Android 现有项目中添加 Flutter 模块等,主要内容如下:
  1. Flutter混合开发模式

  2. Flutter Module的创建方式

  3. 添加Flutter的几种方式

  4. 添加单个Flutter页面

  5. 添加FlutterFragment

  6. Flutter与Android互相跳转

Flutter混合开发模式

Flutetr 混合开发一般有两种方式:
  1. 直接将原生项目作为 Flutter 项目的子项目,Flutter 默认会创建 android 和 ios 的工程目录;

  2. 创建 Flutter Module 作为依赖添加到现有的原生项目中。

第二种方式相较第一种方式更解耦,尤其是针对现有项目改造成本更小,下文中将以第二种方式为主。

Flutter Module的创建方式

创建 Flutter Module 有两种方式:
  1. 使用命令创建 Flutter Module

flutter create -t module --org com.manu.flutter flutter_module_one
  1. 使用 As 创建 Flutter Module

在 As 中选择 File->New->New Flutter Project,选择 Flutter Module 创建 Flutter Module 子项目,如下:

添加Flutter的几种方式

这里的添加方式指的是第二种方式,以 Flutter Module 的方式将 Flutter 模块添加到现有项目中,在 Android 现有项目中添加 Flutter 有两种方式:
  1. 以 aar 的方式集成的 Android 现有项目中:

创建好 Flutter Module 之后需要将其编译成 aar 的形式,可以通过如下命令进行 aar 的编译:
// cd到Flutter Module根目录
cd flutter_module
flutter build aar
在 Android 中也可以通过 As 工具来编译 aar,选择 Build->Flutter->Build AAR 来进行 aar 的编译。
然后根据提示在主项目工程的 build.grade 文件中进行相关配置,参考如下:
repositories {
    maven {
        url 'G:/xxx/flutter_module_one/build/host/outputs/repo'
    }
    maven {
        url 'https://storage.googleapis.com/download.flutter.io'
    }
}

buildTypes {
    profile {
        initWith debug
    }
}

dependencies {
    debugImplementation 'com.manu.flutter.flutter_module_one:flutter_debug:1.0'
    profileImplementation 'com.manu.flutter.flutter_module_one:flutter_profile:1.0'
    releaseImplementation 'com.manu.flutter.flutter_module_one:flutter_release:1.0'
}
  1. 以 Flutet module 的方式集成到现有 Android 项目中:

在 setting.gradle 文件中配置 flutter module 如下:
include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File(
  settingsDir,
  'flutter_module_two/.android/include_flutter.groovy'
))
然后在 build.gradle 文件中添加 flutter module 的依赖,如下:
dependencies {
  implementation project(':flutter')
}

添加单个Flutter页面

创建一个 Activity 继承 FlutterActivity 并在 AndroidManifest.xml 文件中声明:
<activity
    android:name=".AgentActivity"
    android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
    android:hardwareAccelerated="true"
    android:windowSoftInputMode="adjustResize">

</activity>
那么如何启动这个 FlutterActivity 呢,如下:
// 默认路由 /
myButton.setOnClickListener {
  startActivity(
    FlutterActivity.createDefaultIntent(this)
  )
}

// 自定义路由
myButton.setOnClickListener {
  startActivity(
    FlutterActivity
      .withNewEngine()
      .initialRoute("/my_route")
      .build(this)
  )
}
上述代码会在内部创建自己的 FlutterEngine 实例,每个 FlutterActivity 都创建自己的 FlutterEngine,这意味着启动一个标准的 FlutterActivity 会在界面可见时出现一短暂的延迟,可以选择使用预缓存的 FlutterEngine 来减小其延迟,实际上在内部会先检查是否存在预缓存的 FlutterEngine,如果存在则使用该 FlutterEngine,否则继续使用非预缓存的 FlutterEngine,其源码判断如下:
/* package */ void setupFlutterEngine() {
Log.v(TAG, "Setting up FlutterEngine.");

 // 1. 检查预缓存的FlutterEngine
String cachedEngineId = host.getCachedEngineId();
if (cachedEngineId != null) {
  flutterEngine = FlutterEngineCache.getInstance().get(cachedEngineId);
  isFlutterEngineFromHost = true;
  if (flutterEngine == null) {
    throw new IllegalStateException(
        "The requested cached FlutterEngine did not exist in the FlutterEngineCache: '"
            + cachedEngineId
            + "'");
  }
  return;
}
// 2. 是否有自定义的FlutterEngine
// Second, defer to subclasses for a custom FlutterEngine.
flutterEngine = host.provideFlutterEngine(host.getContext());
if (flutterEngine != null) {
  isFlutterEngineFromHost = true;
  return;
}

Log.v(
    TAG,
    "No preferred FlutterEngine was provided. Creating a new FlutterEngine for"
        + " this FlutterFragment.");
// 3. 创建新的FlutterEngine
flutterEngine =
    new FlutterEngine(
        host.getContext(),
        host.getFlutterShellArgs().toArray(),
        /*automaticallyRegisterPlugins=*/ false);
isFlutterEngineFromHost = false;
}
预缓存的 FlutterEngine 的使用方式就不再赘述,可自行查看官网。

添加FlutterFragment

同样的在 Android 现有项目中添加 FlutterFragment,为了后续通信方便也应该自定义 Fragment 继承 FlutterFragment,然后将其添加到某个 Activity 中,如下:
class AgentActivity2 : FragmentActivity() {
    private val flutterFragmentTag = "flutter_fragment_tag"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_agent2)
        val fragmentManager = supportFragmentManager
        var flutterFragment = fragmentManager.findFragmentByTag(flutterFragmentTag)
        if (flutterFragment == null){
//            flutterFragment = FlutterFragment.createDefault()
            flutterFragment = MFlutterFragment
                .withNewEngine()
                ?.build()
            if (flutterFragment != null) {
                fragmentManager.beginTransaction()
                    .add(R.id.ff_container,flutterFragment,flutterFragmentTag)
                    .commit()
            }
        }
    }
}
跳转添加 FlutterFragment 的 Activity 使用的 Intent 即可,如下:
// 跳转添加Fragment的Activyt
val intent = Intent(this@LaunchActivity,AgentActivity2::class.java)
startActivity(intent)

Flutter与Android互相跳转

Flutter 与 Android 互相跳转,上文中基本都是原生 Android 跳转 FlutterActivity 或者是添加 FlutterFragment 的 Activity,那么 Flutter 页面如何跳转到原生 Activity 呢。
涉及到 Flutter 与原生的通信机制,主要包括 MethodChannel、EventChannel 以及 BasicMessageChannel,这一块内容比较多,一小节肯定介绍不完,这里只简单介绍一下使用 MethodChannel,MethodChannel 主要用来传递方法调用,通过 MethodChannel 可以在 Flutter 页面调用 Android 原生 API 提供的方法。
主要介绍一下使用 MethodChannel 来实现 Flutter 向原生 Android 的跳转,无论是单个 Flutter 页面还是添加的是一个 FlutterFragment,都需要分别继承 FlutterActivity 和 FlutterFragment,然后重写 configureFlutterEngine 方法,参考如下:
// FlutterActivity
class AgentActivity : FlutterActivity() {
    private val tag = AgentActivity::class.java.simpleName;
    private val channel = "com.manu.startMainActivity"

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        Log.d(tag,"configureFlutterEngine")
        // 注册MethodChannel,用来监听Flutter页面的方法调用
        MethodChannel(flutterEngine.dartExecutor, channel)
            .setMethodCallHandler { methodCall: MethodCall, result: MethodChannel.Result ->
                if ("startMainActivity" == methodCall.method) {
                    MainActivity.startMainActivity(this)
                    result.success("success")
                } else {
                    result.notImplemented()
                }
            }
    }

    companion object{
        /**
         * 重新创建NewEngineIntentBuilder才能保证生效
         */

        fun withNewEngine(): MNewEngineIntentBuilder? {
            return MNewEngineIntentBuilder(AgentActivity::class.java)
        }
    }

    /**
     * 自定义NewEngineIntentBuilder
     */

    class MNewEngineIntentBuilder(activityClass: Class<out FlutterActivity?>?) :
        NewEngineIntentBuilder(activityClass!!)
}

// 同理FlutterFragment也一样
// 省略 ...
记得一定要重写 withNewEngine 方法,否则 Flutter 跳转原生 Activity 失败,Flutter 页面 invokeMethod 来进行方法调用,具体如下:
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
          appBar: AppBar(
            title: Text("Flutter Page"),
            centerTitle: true,
          ),
          body: PageWidget()
      ),
      routes: <String,WidgetBuilder>{

      },
    );
  }
}

/// Stateful Widget
class PageWidget extends StatefulWidget {

  @override
  State<StatefulWidget> createState() {
    return _PageState();
  }
}

/// State
class _PageState extends State<PageWidget> {
  MethodChannel platform;
  @override
  void initState() {
    super.initState();
    platform = new MethodChannel('com.manu.startMainActivity');
  }
  @override
  Widget build(BuildContext context) {
    return RaisedButton(
        onPressed: () {
          _startMainActivity();
        },
        child:  Text("Flutter to Android"),
    );
  }
  /// 跳转到原生Activity
  void _startMainActivity(){
    platform.invokeMethod('startMainActivity').then((value) {
      print("value:startMainActivity");
    }).catchError((e) {
      print(e.message);
    });
  }
}
此外,关于 Flutter 与原生通信机制将在后续的文章中进行介绍,公众号后台回复关键字【加群】进微信交流群,回复关键字【混合开发】获取源码链接。 
推荐阅读:

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存