正常的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进行原生交互一劳永逸。