Android项目中集成Flutter,实现秒开Flutter模块

源代码
Flutter版本2.0.3
GitHub源代码

本文目标

成功在Android原生项目中集成Flutter

Warning

  • 从Flutter v1.1.7版本开始,Flutter module仅支持AndroidX应用
  • 在release模式下Flutter仅支持以下架构:x86_64,armeabi-v7a,arm64-v8a,不支持mips和x86,所以引入Flutter前需要选取Flutter支持的架构
android{
  //...
  defaultConfig {
        //配置支持的动态库类型
        ndk {
            abiFilters 'x86_64','armeabi-v7a', 'arm64-v8a'
        }
    }
}

混合开发的一些适用场景

  • 在原有项目中加入Flutter页面
  • 原生页面中嵌入Flutter模块
  • 在Flutter项目中嵌入原生模块

主要步骤

  • 创建Flutter module
  • 为已存在的Android项目添加Flutter module依赖
  • 早Kotlin/Java中调用Flutter module
  • 编写Dart代码
  • 运行项目
  • 热重启/重新加载
  • 调试Dart代码
  • 发布应用

请把所有的项目都放在同一个文件夹内

- WorkProject
    -  AndroidProject
    -  iOSProject
    -  flutrter_module

WorkProject下面分别是原生Android模块,原生iOS模块,flutter模块,并且这三个模块是并列结构

创建Flutter module

在做混合开发之前我们需要创建一个Flutter module
这个时候需要

  cd xxx/WorkProject /

创建flutter_module

flutter create -t module flutter_module

如果要指定包名

flutter create -t module --org com.example flutter_module

然后就会创建成功

  • .android - flutter_module的Android宿主工程
  • .ios - flutter_module的iOS宿主工程
  • lib - flutter_module的Dart部分代码
  • pubspec.yaml - flutter_module的项目依赖配置文件
    因为宿主工程的存在,我们这个flutter_module在布甲额外的配置的情况下是可以独立运行的,通过安装了Flutter和Dart插件的AndroidStudio打开这个flutter_module项目,通过运行按钮可以直接运行

构建flutter aar(非必须)

可以通过如下命令构建aar

cd .android/
./gradlew flutter:assembleRelease

这会在.android/Flutter/build/outputs/aar/中生成一个flutter-release.aar归档文件

为已存在的Android用意添加Flutter module依赖

打开我们的Android项目的 settings.gradle添加如下代码

setBinding(new Binding([gradle: this]))                              
evaluate(new File(                                                 
        settingsDir.parentFile,                                      
        'flutter_module/.android/include_flutter.groovy'                     
))

//可选,主要作用是可以在当前AS的Project下显示flutter_module以方便查看和编写Dart代码
include ':flutter_module'
project(':flutter_module').projectDir = new File('../flutter_module')

setBinding与evaluate允许Flutter模块包括它自己在内的任何Flutter插件,在setting.gradle中以类似:flutter package_info :video_player的方式存在

添加:flutter依赖

dependencies {
  implementation project(':flutter')
}

添加Java8编译选项

因为Flutter的Android engine使用了Java8的特性,所有在引入Flutter时需要配置你的项目的Java8编译选项

//在app的build.gradle文件的android{}节点下添加
android {
    compileOptions {
        sourceCompatibility = 1.8
        targetCompatibility = 1.8
    }
}

在Kotlin中调用Flutter module

支持,我们已经为我们的Android项目添加了Flutter所必须的依赖,接下来我们来看如何在项目中以Kotlin的方式在Fragment中调用Flutter模块,在这里我们能做到让Flutter优化提升加载速度,实现秒开Flutter模块

原生Kotlin端代码

/**
 * flutter抽象的基类fragment,具体的业务类fragment可以继承
 **/
abstract class FlutterFragment(moduleName: String) : IBaseFragment() {

    private val flutterEngine: FlutterEngine?
    private lateinit var flutterView: FlutterView

    init {
        flutterEngine =FlutterCacheManager.instance!!.getCachedFlutterEngine(AppGlobals.get(), moduleName)
    }

    override fun getLayoutId(): Int {
        return R.layout.fragment_flutter
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        (mLayoutView as ViewGroup).addView(createFlutterView(activity!!))
    }

    private fun createFlutterView(context: Context): FlutterView {
        val flutterTextureView = FlutterTextureView(activity!!)
        flutterView = FlutterView(context, flutterTextureView)
        return flutterView
    }

    /**
     * 设置标题
     */
    fun setTitle(titleStr: String) {
        rl_title.visibility = View.VISIBLE
        title_line.visibility = View.VISIBLE
        title.text = titleStr
        title.setOnClickListener {

        }
    }

    /**
     * 生命周期告知flutter
     */
    override fun onStart() {
        flutterView.attachToFlutterEngine(flutterEngine!!)
        super.onStart()
    }

    override fun onResume() {
        super.onResume()
        //for flutter >= v1.17
        flutterEngine!!.lifecycleChannel.appIsResumed()
    }

    override fun onPause() {
        super.onPause()
        flutterEngine!!.lifecycleChannel.appIsInactive()
    }

    override fun onStop() {
        super.onStop()
        flutterEngine!!.lifecycleChannel.appIsPaused()
    }

    override fun onDetach() {
        super.onDetach()
        flutterEngine!!.lifecycleChannel.appIsDetached()
    }

    override fun onDestroy() {
        super.onDestroy()
        flutterView.detachFromFlutterEngine()
    }
}

R.layout.fragment_flutter的布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <RelativeLayout
        android:id="@+id/rl_title"
        android:visibility="gone"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_45"
        android:background="@color/color_white"
        android:gravity="center_vertical"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_centerInParent="true"
            android:layout_gravity="center"
            android:gravity="center"
            android:textColor="@color/color_000"
            android:textSize="16sp" />
    </RelativeLayout>

    <View
        android:id="@+id/title_line"
        android:visibility="gone"
        android:layout_width="match_parent"
        android:layout_height="2px"
        android:background="@color/color_eee" />
</LinearLayout>

/**
 * flutter缓存管理,主要是管理多个flutter引擎
 **/
class FlutterCacheManager private constructor() {

    /**
     * 伴生对象,保持单例
     */
    companion object {

        //喜欢页面,默认是flutter启动的主入口
        const val MODULE_NAME_FAVORITE = "main"
        //推荐页面
        const val MODULE_NAME_RECOMMEND = "recommend"

        @JvmStatic
        @get:Synchronized
        var instance: FlutterCacheManager? = null
            get() {
                if (field == null) {
                    field = FlutterCacheManager()
                }
                return field
            }
            private set
    }

    /**
     * 空闲时候预加载Flutter
     */
    fun preLoad(context: Context){
        //在线程空闲时执行预加载任务
        Looper.myQueue().addIdleHandler {
            initFlutterEngine(context, MODULE_NAME_FAVORITE)
            initFlutterEngine(context, MODULE_NAME_RECOMMEND)
            false
        }
    }

    /**
     * 初始化Flutter
     */
    private fun initFlutterEngine(context: Context, moduleName: String): FlutterEngine {
        //flutter 引擎
        val flutterLoader: FlutterLoader = FlutterInjector.instance().flutterLoader()
        val flutterEngine = FlutterEngine(context,flutterLoader, FlutterJNI())
        flutterEngine.dartExecutor.executeDartEntrypoint(
            DartExecutor.DartEntrypoint(
                flutterLoader.findAppBundlePath(),
                moduleName
            )
        )
        //存到引擎缓存中
        FlutterEngineCache.getInstance().put(moduleName,flutterEngine)
        return flutterEngine
    }

    /**
     * 获取缓存的flutterEngine
     */
    fun getCachedFlutterEngine(context: Context?, moduleName: String):FlutterEngine{
        var flutterEngine = FlutterEngineCache.getInstance()[moduleName]
        if(flutterEngine==null && context!=null){
            flutterEngine=initFlutterEngine(context,moduleName)
        }
        return flutterEngine!!
    }

}

具体业务类使用

//在app初始化中初始一下
public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        FlutterCacheManager.getInstance().preLoad(this);
    }
}

收藏页面

class FavoriteFragment : FlutterFragment(FlutterCacheManager.MODULE_NAME_FAVORITE) {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        setTitle(getString(R.string.title_favorite))
    }
}

推荐页面

class RecommendFragment : FlutterFragment(FlutterCacheManager.MODULE_NAME_RECOMMEND) {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        setTitle(getString(R.string.title_recommend))
    }
}

Dart端代码

import 'package:flutter/material.dart';
import 'package:flutter_module/favorite_page.dart';
import 'package:flutter_module/recommend_page.dart';


//至少要有一个入口,而且这下面的man() 和 recommend()函数名字 要和FlutterCacheManager中定义的对应上
void main() => runApp(MyApp(FavoritePage()));

//必须加注解
@pragma('vm:entry-point')
void recommend() => runApp(MyApp(RecommendPage()));

class MyApp extends StatelessWidget {
  final Widget page;
  const MyApp(this.page);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        body: page,
      ),
    );
  }
}

Dart侧收藏页面

import 'package:flutter/material.dart';

class FavoritePage extends StatefulWidget {
  @override
  _FavoritePageState createState() => _FavoritePageState();
}

class _FavoritePageState extends State<FavoritePage> {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text("收藏"),
    );
  }
}

Dart侧推荐页面

import 'package:flutter/material.dart';

class RecommendPage extends StatefulWidget {
  @override
  _RecommendPageState createState() => _RecommendPageState();
}

class _RecommendPageState extends State<RecommendPage> {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text("推荐"),
    );
  }
}

最终效果

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容