flutter多渠道打包

作者: 移动端_小刚哥 | 来源:发表于2019-07-04 13:39 被阅读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

    相关文章

      网友评论

        本文标题:flutter多渠道打包

        本文链接:https://www.haomeiwen.com/subject/sywlhctx.html