Flutter Android IOS 三端共用同一份配置文件

Posted freeCodeSunny

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter Android IOS 三端共用同一份配置文件相关的知识,希望对你有一定的参考价值。

前言

Flutter 是 Google 使用 Dart 语言开发的一套移动应用开发框架,介绍Flutter开发的文章已经汗牛充栋, 所以这里我们主要不是来讨论怎么去开发一个Flutter应用,而是来解决在开发过程中遇到的问题。

现状

环境配置

在应用开发过程中, 我们往往是有很多配置的,比如开发环境,一般我们分为线上和测试,在不同的环境中链接不同的服务器,也可能根据不同的环境本地加载不同的代码,以前我们做android或者ios 在本地创建一个配置文件,比如我们在Android下面创建一个config.properties

# env, switch
ENV=ONLINE
#ENV=TEST

应用在打包的时候读取配置,注入BuildConfig自动调整环境:

def configProperties = new Properties()
def versionPropertiesFile = rootProject.file('../config.properties')
if (versionPropertiesFile.exists()) 
    versionPropertiesFile.withReader('UTF-8') 
        reader -> configProperties.load(reader)
    

def evn = configProperties.getProperty("ENV")
if (evn == null) 
    evn = 'TEST'


// 在应用的build.grade的defaultConfig节点根据环境注入我们的需要的配置,也可以同时注入BuildConfig
defaultConfig 
    applicationId "xxxxxx"
    minSdkVersion 21
    targetSdkVersion 28
    versionCode flutterVersionCode.toInteger()
    versionName flutterVersionName
    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

    ndk 
        abiFilters 'armeabi-v7a', 'arm64-v8a'
    

    manifestPlaceholders = [COM_NETEASE_NIM_APP_KEY: evn == 'ONLINE' ? onlineKey : testKey]

上述方式就需要Android一份配置,IOS一份配置,每次有变更,两边都需要配置和修改。

版本配置

我们知道在Android中我们配置版本号,在应用的build.gradle的defaultConfig节点配置versionCode和versionName:

android 
    compileSdkVersion 28

    sourceSets 
        main.java.srcDirs += 'src/main/kotlin'
    

    lintOptions 
        disable 'InvalidPackage'
    

    defaultConfig 
        applicationId "xxxxx"
        minSdkVersion 21
        targetSdkVersion 28
        versionCode 5
        versionName 1.0.0
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        ndk 
            abiFilters 'armeabi-v7a', 'arm64-v8a'
        
    

而在IOS中我们需要在Info.plist中进行配置CFBundleShortVersionString与CFBundleVersion,一般我们在xcode的runner-General手动配置或者脚读取:

上面只是Android端和IOS端,目前我们已经采用Flutter来进行开发,在Flutter中很多时候我们也有需要读取当前的版本号,这时我们怎么办? 我们一般都采用读取平台的版本来实现,这里我们可以采用在pubspec中添加dependencies:

dependencies:
  flutter:
    sdk: flutter
    
  package_info: ^0.4.0+13

然后在代码中进行读取,我们新建一个package_info.dart文件:

class PackageInfoUtil 
  static PackageInfoUtil _singleton;

  static PackageInfo _packageInfo;
  
  factory PackageInfoUtil() => _singleton ?? (_singleton = PackageInfoUtil._internal());

  PackageInfoUtil._internal() 
    _preLoad();
  

  Future<void> _preLoad() async 
    if (_packageInfo == null) 
      _packageInfo = await PackageInfo.fromPlatform();
    
  

  String get versionCode 
    return _packageInfo?.buildNumber;
  

  String get versionName 
    return _packageInfo?.version;
  

解决方案

有没有一种方法可以三端只采用一份配置,就可以自动配置各种环境与版本号?

flutter端

这里我们知道flutter中我们可以配置asset文件,这里我们可以自定义一个配置文件到asset里面,我们在flutter工程中创建一个asset目录,预置一份配置文件,这里文件我们可以采用json格式,也可以采用properties格式,json格式便于flutter解析,但是proterties便于Android的gradle解析,这里我们采用properties格式来配置,只不过需要flutter内部需要我们来手动解析:

  • 我们首先在flutter工程中新建一个assets目录,创建一个config.properties 文件,这里time表示编译时间,还可以注入git号等信息。
# version
versionName=0.3.1
versionCode=5
# env, switch
ENV=ONLINE
#ENV=TEST
# time
time=2020-04-07 15:19:29
  • 我们在工程的pubspec.yaml中配置asset信息:
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # other module assets must declare here

  fonts:
    - family: iconfont
      fonts:
        - asset: uikit/assets/fonts/iconfont.ttf

  # To add assets to your application, add an assets section, like this:
  assets:
    - assets/config.properties

根据上面文件我们配置信息已经ok,之后我们就是需要在flutter中解析对应的信息,我们这里创建一个dart文件,在应用加载前预先执行解析:

class AppConfig 
  factory AppConfig() => _instance ?? (_instance = AppConfig._internal());

  static AppConfig _instance;

  AppConfig._internal();

  static const String online = 'ONLINE';

  static const String test = 'TEST';

  String evn;

  String versionName;

  String versionCode;

  /// build time
  String time;

  bool get isOnline 
    return evn == online;
  

  static bool get isInDebugMode 
    bool inDebugMode = false;
    assert(inDebugMode = true);
    return inDebugMode;
  

  Future init() async 
    Map<String, String> properties = Map();
    String value = await rootBundle.loadString("assets/config.properties");
    List<String> list = value?.split("\\n");
    list?.forEach((element) 
      if (element != null && element.contains("=")) 
        String key = element.substring(0, element.indexOf("="));
        String value = element.substring(element.indexOf("=") + 1);
        properties[key] = value;
      
    );
    parserProperties(properties);
    return Future.value();
  

  void parserProperties(Map<String, String> properties) 
    evn = properties['EVN'] ?? online;
    versionName = properties['versionName'] ?? "1.0.0";
    versionCode = properties['versionCode'] ?? "0";
    time = properties['time'] ?? TimeUtil.getTimeFormatMillisecond();
  

这里很多字段被删除了, 大家只需要关注整个流程就ok了, 同时这里的time字段配置文件中可以不预先赋值,这样在debug阶段中,每次编译都是当前生成的时间,但是在上线前打包时,我们可以采用脚本注入打包的时间,后面我们会给出样例。

Android端

上述我们已经创建了对应的配置文件,现在就需要在Android端同样解析对应的信息, 我们在应用对应的build.gradle中解析对应的信息,注入对应的信息:

def configProperties = new Properties()
def versionPropertiesFile = rootProject.file('../assets/config.properties')
if (versionPropertiesFile.exists()) 
    versionPropertiesFile.withReader('UTF-8') 
        reader -> configProperties.load(reader)
    


def flutterVersionCode = configProperties.getProperty('versionCode')
if (flutterVersionCode == null) 
    flutterVersionCode = '3'


def flutterVersionName = configProperties.getProperty('versionName')
if (flutterVersionName == null) 
    flutterVersionName = '2.0.0'


def evn = configProperties.getProperty("ENV")
if (evn == null) 
    evn = 'TEST'



android 
    compileSdkVersion 28

    sourceSets 
        main.java.srcDirs += 'src/main/kotlin'
    

    lintOptions 
        disable 'InvalidPackage'
    

    defaultConfig 
        applicationId "com.netease.yunxin.meeting"
        minSdkVersion 21
        targetSdkVersion 28
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        ndk 
            abiFilters 'armeabi-v7a', 'arm64-v8a'
        

        manifestPlaceholders = [COM_NETEASE_NIM_APP_KEY: evn == 'ONLINE' ? onlineKey : testKey]
    

IOS端

上面flutter,android已经公用了同一份信息,那ios怎么处理,这里我们知道ios在打包时候会执行build phases中的run script脚本,那我们可以在脚本的前面预先解析对应的信息,这里我们解析版本信息, 将信息写回Info.plist:

VERSION_NAME=$(cat ../assets/config.properties | grep versionName | head -1 | cut -d '=' -f 2)
VERSION_CODE=$(cat ../assets/config.properties | grep versionCode | head -1 | cut -d '=' -f 2)

/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION_NAME" "$PROJECT_DIR/$INFOPLIST_FILE"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $VERSION_CODE" "$PROJECT_DIR/$INFOPLIST_FILE"

/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build

这样我们就在三端中公用了同一份信息,也可以根据不同的端配置不同的信息,各端解析对应的文件,这样就避免了改丢或者改错的问题。

上面我们提到了脚本注入编译时间的问题,这里我们在上线打包时我们可以在脚本中注入打包时间, 我们创建一个build.sh脚本:

set -e
set +x

echo "build start"

project_path=$(dirname $(pwd))

echo $project_path

cd $project_path

rm -rf "$project_path/outputs"
mkdir -p "$project_path/outputs"

current_time=$(date "+%Y-%m-%d %H:%M:%S")

echo $current_time

sed -i "" '$d' $project_path/assets/config.properties

echo "time=$current_time" >> $project_path/assets/config.properties

sh $project_path/deploy/build_android.sh

cd $project_path

sh $project_path/deploy/build_ios.sh

cd $project_path

echo "build done"

set +e

上面我们注入了时间,首先删除了最后一行,同时追加了当时的编译时间

后记

Flutter开发中还有很多东西可以提升,后续有机会也会逐步写出来

以上是关于Flutter Android IOS 三端共用同一份配置文件的主要内容,如果未能解决你的问题,请参考以下文章

QCon2018Flutter & Dart三端一体化开发

weex源码浅析(Android部分)一

weex源码浅析(Android部分)一

你好,Flutter

单枪匹马撸个聊天室, 支持Web/Android/iOS三端

Flutter技术概览