Flutter工作生活Flutter学习

flutter多渠道打包

2019-07-04  本文已影响19人  移动端_小刚哥

正常的flutter打包是进入工程根目录执行flutter build ios或者flutter build apk,当然前提是已经根据flutter官网的教程进行配置,这里不多说
Android构建发布https://flutterchina.club/android-release/
iOS构建发布https://flutterchina.club/ios-release/

我们这里所说的多渠道打包其实还是Android原生的多渠道打包,没有实现执行命令flutter build apk就生成多个渠道包的操作。

说到安卓原生的多渠道打包应该分为两块来说,第一是打出渠道包,第二是能统计到各个渠道包的信息,那么我们首先进行第一步:打出渠道包

一、打渠道包

这一步很简单,只是在app的build.gradle中增加两处配置就可以了

//这里不知道具体有啥用,但是不写就报错
defaultConfig {

        ...

        flavorDimensions "versionCode"

       ...
    }
android {

  ...

    productFlavors {
        yingyongbao {}
        channel360 {}
        wandoujia {}
        xiaomi {}
        huawei {}
        baidu {}
        oppo {}
        vivo {}
        sanxing {}
        lianxiang {}
    }
    productFlavors.all { flavor ->
        flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
    }
    //    修改命名规则
    applicationVariants.all { variant ->
        variant.outputs.all {
            def formattedDate = new Date().format('yyyy_MM_dd_HH_mm_ss')
            outputFileName = rootProject.getName() + "-" + variant.flavorName + "-" + buildType.name + "-" + formattedDate + "-v" + defaultConfig.versionName + "-" + defaultConfig.versionCode + ".apk";
        }
    }

...

}

我这里遇到了问题,flutter项目增加了这些配置之后就不能直接连接Android手机连调了,会报以下错误,一直没有解决,如果有人解决烦劳相告🙏
但是以Android项目打开是可以连调的

The Gradle project does not define a task suitable for the requested build.
The android/app/build.gradle file defines product flavors: baidu, channel360,
huawei, lianxiang, oppo, sanxing, vivo, wandoujia, xiaomi, yingyongbao
You must specify a --flavor option to select one of them.
Gradle build aborted.

二、统计各个渠道包下载量等信息

统计我选择的友盟统计,flutter的第三方包选择的是flutter_umplus,地址:
https://pub.dev/packages/flutter_umplus

我们首先需要去友盟官网注册app信息,Android和iOS要注册两个,获取到AndroidKey和iOSKey然后在flutter项目中合适的地方初始化友盟

//集成友盟统计
    if(Platform.isAndroid){//Android平台
      FlutterUmplus.init(AndroidKey,channel:"Android的渠道名称",reportCrash: false,logEnable: true,encrypt: true);
    }else if(Platform.isIOS){//iOS平台
      FlutterUmplus.init(iOSKey,channel: "appstore",reportCrash: false,logEnable: true,encrypt: true);
    }

iOS只有AppStore一个渠道所以固定值appstore就可以了,Android项目我们怎么获取到当前渠道包的名称呢?很简单,在AndroidManifest.xml中添加meta-data原数据

<application>

        ...

        <!--友盟统计-->
        <meta-data android:value="${UMENG_CHANNEL_VALUE}"  android:name="UMENG_CHANNEL"  />

      ...

    </application>

这里的UMENG_CHANNEL_VALUE和build.gradle中的UMENG_CHANNEL_VALUE对应起来,在Android项目中可以读取这里的value值

public static String getChannel(Context context) {
        try {
            PackageManager pm = context.getPackageManager();
            ApplicationInfo appInfo = pm.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
            return appInfo.metaData.getString("UMENG_CHANNEL");
        } catch (PackageManager.NameNotFoundException ignored) {
        }
        return "";
    }

⚠️⚠️但是现在问题来了,我不知道flutter代码中怎么读取AndroidManifest.xml中的meta-data值,于是乎我就开始思考,想到了第一个☝️个方案

1. SharedPreferences

使用SharedPreferences存储数据,然后在flutter中使用shared_preferences中读取,于是我在MainActivity中读取数据并存储

//在flutter中不知道怎么获取manifest中的meta数据,所以在这里先获取了存起来,在flutter里边取出来用😂
    try {
      PackageManager pm = this.getPackageManager();
      ApplicationInfo appInfo = pm.getApplicationInfo(this.getPackageName(), PackageManager.GET_META_DATA);
      String str = appInfo.metaData.getString("UMENG_CHANNEL");

      SharedPreferences sharedPref = this.getApplication().getSharedPreferences("CONFIG_SETTING", Context.MODE_PRIVATE);
      SharedPreferences.Editor editor = sharedPref.edit();
      editor.putString("channelName",str);
      boolean isSucc = editor.commit();
      
      String theStr = sharedPref.getString("channelName","default");
      Log.d("str",theStr);
    } catch (PackageManager.NameNotFoundException ignored) {
    }
SharedPreferences.getInstance().then((sp){
    String channelName = sp.getString("channelName");
    print("渠道名称"+channelName);
  });

很不幸,读取的时候一直是空,读取不到,这里并不是说这种方式不行,而是我不会用😂😂,所以就暂时放弃这种方式了,待以后我对Android进一步熟悉再来解决。这种方式不行并且现在还没有找到有人封装这种第三方的工具,那么就需要自己动手进行原生交互了,这里我偷了个懒,我没有自己新建原生交互的plugin,而是修改了别人的代码,项目中用到了package_info,但是看源码只提供了四种属性,没有我们需要的,不行就改,改到我们能用就好了。

/// The app name. `CFBundleDisplayName` on iOS, `application/label` on Android.
  final String appName;

  /// The package name. `bundleIdentifier` on iOS, `getPackageName` on Android.
  final String packageName;

  /// The package version. `CFBundleShortVersionString` on iOS, `versionName` on Android.
  final String version;

  /// The build number. `CFBundleVersion` on iOS, `versionCode` on Android.
  final String buildNumber;
2.扩展package_info,增加channelName

package_info.dart

class PackageInfo {
  PackageInfo({
    this.appName,
    this.packageName,
    this.version,
    this.buildNumber,
    this.channelName, //自己新增的渠道名称
  });

  static Future<PackageInfo> _fromPlatform;

  /// Retrieves package information from the platform.
  /// The result is cached.
  static Future<PackageInfo> fromPlatform() async {
    if (_fromPlatform == null) {
      final Completer<PackageInfo> completer = Completer<PackageInfo>();

      // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter.
      // https://github.com/flutter/flutter/issues/26431
      // ignore: strong_mode_implicit_dynamic_method
      _kChannel.invokeMethod('getAll').then((dynamic result) {
        final Map<dynamic, dynamic> map = result;

        completer.complete(PackageInfo(
          appName: map["appName"],
          packageName: map["packageName"],
          version: map["version"],
          buildNumber: map["buildNumber"],
          channelName: map["channelName"], //自己新增的渠道名称
        ));
      }, onError: completer.completeError);

      _fromPlatform = completer.future;
    }
    return _fromPlatform;
  }

  /// The app name. `CFBundleDisplayName` on iOS, `application/label` on Android.
  final String appName;

  /// The package name. `bundleIdentifier` on iOS, `getPackageName` on Android.
  final String packageName;

  /// The package version. `CFBundleShortVersionString` on iOS, `versionName` on Android.
  final String version;

  /// The build number. `CFBundleVersion` on iOS, `versionCode` on Android.
  final String buildNumber;

  ///自己新增的渠道名称
  final String channelName;
}

PackageInfoPlugin

/** PackageInfoPlugin */
public class PackageInfoPlugin implements MethodCallHandler {
  ...
  @Override
  public void onMethodCall(MethodCall call, Result result) {
    try {
      Context context = mRegistrar.context();
      if (call.method.equals("getAll")) {
        PackageManager pm = context.getPackageManager();
        PackageInfo info = pm.getPackageInfo(context.getPackageName(), 0);

        //获取渠道名使用
        ApplicationInfo appInfo = pm.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);

        Map<String, String> map = new HashMap<String, String>();
        map.put("appName", info.applicationInfo.loadLabel(pm).toString());
        map.put("packageName", context.getPackageName());
        map.put("version", info.versionName);
        map.put("buildNumber", String.valueOf(getLongVersionCode(info)));
        map.put("channelName", String.valueOf(appInfo.metaData.getString("UMENG_CHANNEL")));

        result.success(map);
      } else {
        result.notImplemented();
      }
    } catch (PackageManager.NameNotFoundException ex) {
      result.error("Name not found", ex.getMessage(), null);
    }
  }
...
}

使用方法

//集成友盟统计
    if(Platform.isAndroid){//Android平台
      PackageInfo.fromPlatform().then((package){
        String channelName = package.channelName;
        print("渠道名"+channelName);
        FlutterUmplus.init(ChannelClass.androidKey,channel: channelName,reportCrash: false,logEnable: true,encrypt: true);
      });
    }else if(Platform.isIOS){//iOS平台
      FlutterUmplus.init(ChannelClass.iosKey,channel: ChannelClass.appstore,reportCrash: false,logEnable: true,encrypt: true);
    }

终于获取到了渠道名称👏👏👏

我是修改了第三方的包才读取到了Android原生的数据,如果以后别的项目也有这种需要,那么代码还要再改一次,后面还面临着第三方包升级等问题,最好还是自己写一个plugin进行原生交互一劳永逸。

参考文章
https://blog.csdn.net/cs_lwb/article/details/82813909

上一篇下一篇

猜你喜欢

热点阅读