Android开发学习之路-Flutter混合开发实践

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6


前言

为了迎合新技术,并且可以一端开发两端受用,于是乎在引入新的功能模块的时候,尝鲜使用flutter来进行开发。众所周知,在尝鲜新技术的时候总会有种畏惧感,人类总是喜欢使用熟悉的东西,而作为程序员更加喜欢用自己擅长的技术,但纵使有千般难,万般坑,既然选择了,总要把他跨过,踩完。

1. Flutter工程介绍

1.1 模块介绍

目前工程包括基础模块和kds模块,两个模块都是单独的plugin,并且test依赖base插件。

  • CCDFlutterBase – flutter基础模块,包括
  • channel ---- 通信相关
  • data ---- 数据
  • int ---- 国际化
  • network ---- 网络请求封装
  • res ---- color,style等资源
  • sp ---- 存储
  • utils ---- 工具类
  • widget ---- 一些公用的widget
  • CCDTest – 相关业务

考虑到后续flutter工程会越来越多,所以使用repo来统一管理flutter工程。

1.2 flutter boost

为了原生和flutter路由跳转的方便,这里引入了闲鱼的开源项目​​flutter_boost​​。

对kds工程做了如下方式的修改,在单独运行的时候可以选择需要运行的页面,比如使用KdsOrder(),而在打包的时候,需要使用MyApp(),这个需要手动修改。

void main() => runApp(MyApp());
//void main() => runApp(Order());
//void main() => runApp(Setting());

class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();

///register page widget builders,the key is pageName
FlutterBoost.singleton.registerPageBuilders({
'ccd://Order': (pageName, params, _) => KdsOrder(),
'ccd://Setting': (pageName, params, _) => KdsSetting(),
});

///query current top page and load it
FlutterBoost.handleOnStartPage();
}

@override
Widget build(BuildContext context) => MaterialApp(
title: 'Flutter CloudCashDesk',
builder: FlutterBoost.init(),

///init container manager
home: Container());
}

1.3 通信

获取原生中的数据,暂时比较简单,只需要网络请求的数据,后续两边的交互,事件的触发,需要加更多的功能。

/// 获取sp信息,实时等待,调用方法:
/// UserModel userModel = await CCDMethodChannel.getUserInfo();
static Future<UserModel> getUserInfo() async {
try {
final String result =
await methodChannel.invokeMethod(ChannelConstant.get_user_info);
Map map = json.decode(result);
if (map == null) return null;
return UserModel.fromJson(map);
} on PlatformException {
print('Failed to get user.');
return null;
}
}

1.4 国际化

MaterialApp中加入国际化配置

//国际化
locale: _locale,
localizationsDelegates: [
CustomLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
//dialog
const FallbackCupertinoLocalisationsDelegate()
],
supportedLocales: CustomLocalizations.supportedLocales,

若使用了cupertino dialog需要加入

class FallbackCupertinoLocalisationsDelegate
extends LocalizationsDelegate<CupertinoLocalizations> {
const FallbackCupertinoLocalisationsDelegate();

@override
bool isSupported(Locale locale) => true;

@override
Future<CupertinoLocalizations> load(Locale locale) =>
DefaultCupertinoLocalizations.load(locale);

@override
bool shouldReload(FallbackCupertinoLocalisationsDelegate old) => false;
}

定义String

class Ids {
static String test1 = 'test1';
static String test2 = 'test2';
}

/// 简单多语言资源.
Map<String, Map<String, String>> localizedSimpleValues = {
// 'en': {
// Ids.test1: 'test1',
// Ids.test2: 'test2',
// },
'zh': {
Ids.test1: '测试1',
Ids.test2: '测试2',
},
};

在代码中引用string

IntlUtil.getString(context, Ids.test1)

1.5 网络

  • dio,只需要传入对应的网络api接口名和参数
  • charles代理设置
    因为dio的网络请求默认不走设置中的代理,所以需要设置代理的ip地址和端口,一般默认端口8888,所以只需要设置ip地址,由于不同人使用的charles地址不一样,需要有一个设置入口,存储方式如下:
SpUtil.putString(SpHelper.charlesIp, ip_addr);

这里提供一个dialog用于设置代理ip

DialogUtils.showCharlesIpAddrDialog

1.6 资源文件

资源文件包括常用的颜色,字体大小和样式等。

使用方式,比如字体颜色是#333的使用

CCDColor.text_333

字体大小是15sp

Dimens.font_sp15

样式类型是列表的描述

CCDTextStyles.listDesc

1.7 存储

初始化

await SpUtil.getInstance();

存储用户数据

UserModel userModel = await CCDMethodChannel.getUserInfo();
SpHelper.putObject<UserModel>(SpHelper.userInfo, userModel);

获取用户数据

UserModel userModel = SpHelper.getObject<UserModel>(SpHelper.userInfo);

2. Android引入方式

Flutter有多种引入方式,常见的是:

  • 源码集成:使用flutter modle方式将flutter整体框架依赖和打包脚本都集成到主项目中。
  • aar集成:把所有的项目都以aar的完整库集成形式添加到主项目。

第一种方式虽然编译运行非常方便,但是会对主工程有侵入。

第二种方式flutter独立运行,最终所有代码和资源打包后依赖,对主工程无任何的侵入,但是调试两端通信会比较不方便。

考虑到不是所有的同学都在本地安装了flutter,并且jenkins打包也可能会遇到问题,这里采用第二种aar的方式。

3. Android打包实现

考虑到flutter的独立运行和打包两种方式的共同体存在,使用gradle.properties添加变量FLUTTER_AAR控制来实现:

FLUTTER_AAR = false

默认是flutter以apk方式运行,如需打包,则只要修改其变量为true即可打包上传到nexus仓库,并可以在android项目中直接引用。

  • 修改app的build.gradle

区分指定类型

if (FLUTTER_AAR == "true") {
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}

区分applicationid

if (FLUTTER_AAR == "false") {
applicationId "com.zmsoft.flutter.kds.kds_example"
}

区分AndroidManifest

if (FLUTTER_AAR == "false") {
sourceSets {
main {
manifest.srcFile "src/main/AndroidManifest.xml"
}
}
} else {
sourceSets {
main {
manifest.srcFile "src/main/library/AndroidManifest.xml"
}
}
}

由于flutter工程会依赖第三方插件,在android中引入nexus的时候会出依赖相关的问题,所以这里使用fat-aar把所有依赖的工程打成一个aar的包。所以需要做如下修改:

工程的build.gradle中buildscript的dependencies中添加

if (FLUTTER_AAR == "true") {
classpath 'com.kezong:fat-aar:1.1.7'
}

app的build.gradle中加入:

apply plugin: 'com.kezong.fat-aar'

app的build.gradle的dependencies添加

if (FLUTTER_AAR == "true") {
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, _ ->
println name
embed project(path: ":$name", configuration: 'default')
}
}

至此可以实现apk和aar的两种方式切换

  • 上传nexus仓库

这里为了方便开发先使用本地的url

def mavenRepositoryUrl = 'file:///Users/jaredchen/Documents/repo'
def NEXUS_USERNAME = 'xxxxx'
def NEXUS_PASSWORD = 'xxxxx

uploadArchives {
repositories.mavenDeployer {
repository(url: mavenRepositoryUrl) {
authentication(userName: NEXUS_USERNAME, password: NEXUS_PASSWORD)
}
pom.groupId = 'com.test.modlue'
pom.artifactId = 'my-flutter'
pom.version = '1.0.1'
pom.whenConfigured { pom ->
pom.dependencies.remove(pom.dependencies.find { dep -> dep.artifactId == 'shared_preferences' })
pom.dependencies.remove(pom.dependencies.find { dep -> dep.artifactId == 'base_app_flutter' })
pom.dependencies.remove(pom.dependencies.find { dep -> dep.artifactId == 'test' })
pom.dependencies.remove(pom.dependencies.find { dep -> dep.artifactId == 'xservice_kit' })
pom.dependencies.remove(pom.dependencies.find { dep -> dep.artifactId == 'flutter_boost' })
}
}
}

因为是把所有的plugin打包成一个aar,这里需要去除对应模块的pom依赖,要不然依赖会有各种问题。

至此可以实现aar的发布。

4. Android使用方式

  • 在build.gradle中引用远程的aar包
implementation 'com.test.modlue:my-flutter:1.0.1'
  • flutter boost
class FlutterSetting : BoostFlutterActivity(), CCDMethodChannel.ChannelInterface {

override fun getContainerParams(): MutableMap<Any?, Any?>? {
return null
}

override fun getContainerName(): String {
return FlutterRouterConstant.FlutterKDSSetting.ContainerName
}

override fun onRegisterPlugins(p0: PluginRegistry?) {
GeneratedPluginRegistrant.registerWith(p0)
CCDMethodChannel.getInstance().setChannelInterface(this)
}

override fun getUserInfo(): String {
return FlutterChannelHelper.getUserInfo()
}
}

这里只需要return在flutter工程中的containername。就可以渲染flutter中编写好的代码,当然也支持fragment。


阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: android