Flutter

Flutter 实践记录

2022-02-28  本文已影响0人  h2coder

最近使用Flutter做了一个模块的一个页面,虽然第一期做完后,上级觉得效果不满意,第二期要用原生重做,但还是记录一下。

项目配置

配置资源图片目录

在安卓原生中,添加资源图片,一般会放在drawable-xhdpidrawable-xxhdpi目录下,就可以通过R.drawable.xxx来使用。而Flutter则放在根目录的assets下,并且需要在pubspec.yaml中配置图片的所在目录。例如我的图片放在assets/images/album/,需要注意2点

flutter:
  ...

  # 配置资源文件目录
  assets:
    - assets/images/album/

  ...
/// 分割线
class PartingLine extends StatelessWidget {
  const PartingLine({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Image.asset(
        "assets/images/album/parting_line.png"
      ),
    );
  }
}

配置IconFont

一些图标,如果是纯色的,并且可以动态设置颜色,一般会使用IconFont,而不是切图,好处是减少体积,并且它本质是一个字体文件,可以动态设置颜色和大小。在Flutter中使用IconFont,也需要在pubspec.yaml中配置。

如果有多个iconFont,都需要在该文件中配置,并且路径要写全。

flutter:
  uses-material-design: true

  # 导入自定义的IconFont,路径要写全
  fonts:
    - family: myIconFont
      fonts:
        - asset: assets/fonts/my_iconfont.ttf
class Loading extends StatelessWidget {  
  /// 加载中
  static const int loading = 0xe635;

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 20,
      height: 20,
      child: Icon(
        IconData(loading, fontFamily: "myIconFont"),
        color: Colors.white,
        size: 15,
      ),
    );
  }
}

Flutter Json 生成模型

由于Dart中没有反射,所以不能像Java那样使用反射来解析Json并映射到Bean实体中,Flutter中使用生成代码的方式,生成Json解析代码,但一般我们不会手写,下面给出一个生成的网站json2dart

//只生成一次
flutter pub run build_runner build —delete-conflicting-outputs

//监听保存,持续生成
flutter pub run build_runner watch
dependencies:
  flutter:
    sdk: flutter

  # JSON解析
  json_annotation: ^4.3.0
  json_serializable: ^6.0.0
import 'dart:convert';

//要解析的Json字符串
var dataJson = '{"userName": "wally"}';
XxxBean.fromJson(
    //json转map
    json.decode(dataJson)
);

Flutter 打包命令

flutter build aar --no-tree-shake-icons

flutter build apk --no-tree-shake-icons

原生项目接入Flutter模块

原生项目接入Flutter模块有2种方式

综合下来,最好是结合使用,例如项目gradle.properties有一个布尔值,控制使用aar依赖还是本地依赖,这样开发Flutter模块的人员把开关打开,进行debug调试,而非Flutter模块的开发人员则设置为false,使用远程aar依赖。

aar方式依赖

控制台输入打包aar的命令,开始编译,最后控制台会输出以下信息。

大概意思就是生成的aar到了本地的maven仓库,需要在原生项目中,加入依赖来添加。

(步骤一、二:在根build.gradle中,allprojects 节点中 添加maven仓库地址)

1. Open <host>/app/build.gradle
2. Ensure you have the repositories configured, otherwise add them:

String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"
repositories {
  maven {
      url '/Users/charming/Desktop/Work/Android/flutterProject/build/host/outputs/repo'
  }
  maven {
      url "$storageUrl/download.flutter.io"
  }
}

(步骤三:在需要使用Flutter模块的module中,添加依赖)
3. Make the host app depend on the Flutter module:
dependencies {
  debugImplementation 'com.flutter.flutterProject:flutter_debug:1.0'
  profileImplementation 'com.flutter.flutterProject:flutter_profile:1.0'
  releaseImplementation 'com.flutter.flutterProject:flutter_release:1.0'
}

(步骤四:在可运行模块,例如app下,添加一个名叫profile的类型)
4. Add the `profile` build type:

android {
  buildTypes {
    profile {
      initWith debug
    }
  }
}

因为上面指定的maven地址是绝对路径,所以另外一个同事的电脑上,没有这个flutter项目的话,aar就不存在,就会找不到依赖,解决方案有2种:

改良后的aar依赖方式

由于我的公司没有私有maven服务器,所以我把打包的aar手动拷贝到原生项目中的flutter文件夹,然后指定这个目录来依赖,通过Jenkins打包的时候,就不需要配置flutter环境和flutter项目

allprojects {
    //Flutter依赖
    String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"

    repositories {
        //Flutter打包的aar路径,根目录的flutter/repo
        //-------- 重点:每次flutter打包为aar后,会生成本地maven仓库文件夹,要手动把repo拷贝进原生工程目录 --------
        maven {
            url "file://${getRootDir()}/flutter/repo"
        }
        //-------- 重点:每次flutter打包为aar后,会生成本地maven仓库文件夹,要手动把repo拷贝进原生工程目录 --------
        maven {
            url "$storageUrl/download.flutter.io"
        }
    }
}

本地依赖Flutter模块

上面的aar依赖方式,不需要Flutter环境和Flutter项目,好处是提高编译速度,但不可断点debug,开发中及其不便利,所以配置一个开关,切换本地依赖和aar依赖

#是否本地依赖Flutter模块(需要把Flutter工程,放在和原生工程平行的目录下)
isLocalFlutterModule=false
#指定Flutter宿主模块的名称,默认为app,因为我的原生工程的可运行模块并不是app,所以需要在这里指定,不指定会报错!
flutter.hostAppProjectName=flutterProject
//本地Flutter模块依赖配置
if (isLocalFlutterModule.toBoolean()) {
    setBinding(new Binding([gradle: this]))
    //相对路径依赖,指定Flutter工程
    evaluate(new File(
            settingsDir.parentFile,
            '/flutterProject/.android/include_flutter.groovy'
    ))
    include ': flutterProject'
    project(': flutterProject').projectDir = new File('../flutterProject')
}
dependencies {
    if (isLocalFlutterModule.toBoolean()) {
        //直接依赖,Flutter模块工程的源码模块,开发、联调时使用
        implementation project(':flutter')
    } else {
        //aar方式依赖    
        debugApi 'com.flutter.flutterProject:flutter_debug:1.0'
        releaseApi 'com.flutter.flutterProject:flutter_release:1.0'
    }
}

Flutter与原生之间交互

Flutter和原生之间,不可避免的就是交互和通信。方法的参数是一个Object,一般我们定义Json字符串来通信,调用或被调用时用Json解析参数即可,方法的返回值也是一个Json字符串。

方法要注册给Flutter引擎,而一般Flutter引擎有2种创建方式

新建Flutter页面

一般多个Flutter页面,在一个ActivityFragmentView上,Flutter给我们提供了FlutterActivityFlutterFragmentActivityFlutterFragmentFlutterView,这里只介绍常用的FlutterFragmentActivity

新建一个MyFlutterActivity,继承于FlutterFragmentActivity,复写configureFlutterEngine()方法,该方法在Flutter引擎创建完毕后回调,所以在这个方法中可以注册一些原生方法给Flutter端调用

public class MyFlutterActivity extends FlutterFragmentActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        super.configureFlutterEngine(flutterEngine);
    }
}

创建Flutter引擎

手动创建的方式,可在Application的onCreate()创建

/**
 * Flutter引擎Id
 */
public static final String APP_FLUTTER_ENGINE_ID = "app_flutter";

//创建一个引擎实例
FlutterEngine flutterEngine = new FlutterEngine(context.getApplicationContext());
//配置初始路由
flutterEngine.getNavigationChannel().setInitialRoute("/");
//指定Dart代码,预热引擎
flutterEngine.getDartExecutor().executeDartEntrypoint(
        DartExecutor.DartEntrypoint.createDefault()
);
//缓存引擎,提供给FlutterActivity或FlutterFragment调用
FlutterEngineCache.getInstance().put(APP_FLUTTER_ENGINE_ID, flutterEngine);
FlutterFragmentActivity.CachedEngineIntentBuilder builder = new FlutterFragmentActivity
        //通过唯一ID,指定上面创建的Flutter引擎,并指定我们自己定义的FlutterFragmentActivity
        .CachedEngineIntentBuilder(MyFlutterActivity.class, FlutterConfig.APP_FLUTTER_ENGINE_ID);
Intent intent = builder.build(context);
startActivity(intent);

Flutter调原生方法

拿到Flutter引擎实例,就可以注册原生方法了,例如手动创建FlutterEngine实例的时候就已经有实例可用了。而通过FlutterFragmentActivity的方式的话,则复写configureFlutterEngine方法来获取。

方法调用,通过MethodChannel,并且需要指定一个名称,这个名称需要原生端和Flutter对应,否则调不通。
给MethodChannel设置一个MethodCallHandler回调对象,回调时传入的MethodCall可以拿到被调用的方法名以及方法参数,MethodChannel.Result为原生方法调用后的结果。

/**
 * 全局唯一的通道名称
 */
public static final String CHANNEL_NAME = "com.flutter.flutterProject";

MethodChannel methodChannel = new MethodChannel(engine.getDartExecutor().getBinaryMessenger(), FlutterConfig.CHANNEL_NAME);
methodChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
    @Override
    public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
        //方法名
        String methodName = call.method;
        //方法参数
        Object arguments = call.arguments();
        //执行对应的方法
        if(methodName.equals("showToast")) {
            Toast.makeText(getApplicationContext(), arguments.toString(), Toast.LENGTH_SHORT).show();
            //根据业务,回调Flutter端告知调用结果
            if (isSuccess) {
                result.success("success");
            } else {
                result.error("-1", "调用失败", "");
            }
        }
    }
});
/// 给native发消息,此处应和客户端名称保持一致
static const String _NATIVE_CHANNEL_NAME = "com.flutter.flutterProject";

//创建MethodChannel
var MethodChannel methodChannel = const MethodChannel(_NATIVE_CHANNEL_NAME);
//调用原生方法,并获取返回结果
T resultValue = await methodChannel.invokeMethod("toast", "我是Toast的内容");

原生调Flutter方法

/// 给native发消息,此处应和客户端名称保持一致
static const String _NATIVE_CHANNEL_NAME = "com.flutter.flutterProject";

//创建MethodChannel
var MethodChannel methodChannel = const MethodChannel(_NATIVE_CHANNEL_NAME);
methodChannel.setMethodCallHandler((MethodCall handler) {
    return Future(() {
      //被调用的Flutter方法名
      var methodName = handler.method;
      //传递的参数
      var arguments = handler.arguments
      if("flutterMethodName" == methodName) {
        //...处理
      }
    });
});
/**
 * 全局唯一的通道名称
 */
public static final String CHANNEL_NAME = "com.flutter.flutterProject";

DartExecutor dartExecutor = flutterEngine.getDartExecutor();
BinaryMessenger messenger = dartExecutor.getBinaryMessenger();
//创建MethodChannel
MethodChannel methodChannel = new MethodChannel(messenger, CHANNEL_NAME);
//调用Flutter方法
String methodName = "flutterMethodName";
String parmas = "我是参数";
methodChannel.invokeMethod(methodName, parmas, new MethodChannel.Result() {
    @Override
    public void success(@Nullable Object result) {
    }

    @Override
    public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
    }

    @Override
    public void notImplemented() {
    }
});
上一篇下一篇

猜你喜欢

热点阅读