正常的flutter打包是進(jìn)入工程根目錄執(zhí)行flutter build ios
或者flutter build apk
,當(dāng)然前提是已經(jīng)根據(jù)flutter官網(wǎng)的教程進(jìn)行配置,這里不多說(shuō)
Android構(gòu)建發(fā)布https://flutterchina.club/android-release/
iOS構(gòu)建發(fā)布https://flutterchina.club/ios-release/
我們這里所說(shuō)的多渠道打包其實(shí)還是Android原生的多渠道打包再菊,沒(méi)有實(shí)現(xiàn)執(zhí)行命令flutter build apk
就生成多個(gè)渠道包的操作业簿。
說(shuō)到安卓原生的多渠道打包應(yīng)該分為兩塊來(lái)說(shuō)披坏,第一是打出渠道包撼唾,第二是能統(tǒng)計(jì)到各個(gè)渠道包的信息,那么我們首先進(jìn)行第一步:打出渠道包
一音婶、打渠道包
這一步很簡(jiǎn)單慨畸,只是在app的build.gradle中增加兩處配置就可以了
//這里不知道具體有啥用,但是不寫(xiě)就報(bào)錯(cuò)
defaultConfig {
...
flavorDimensions "versionCode"
...
}
android {
...
productFlavors {
yingyongbao {}
channel360 {}
wandoujia {}
xiaomi {}
huawei {}
baidu {}
oppo {}
vivo {}
sanxing {}
lianxiang {}
}
productFlavors.all { flavor ->
flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
// 修改命名規(guī)則
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";
}
}
...
}
我這里遇到了問(wèn)題衣式,flutter項(xiàng)目增加了這些配置之后就不能直接連接Android手機(jī)連調(diào)了寸士,會(huì)報(bào)以下錯(cuò)誤,一直沒(méi)有解決碴卧,如果有人解決煩勞相告??
但是以Android項(xiàng)目打開(kāi)是可以連調(diào)的
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.
二弱卡、統(tǒng)計(jì)各個(gè)渠道包下載量等信息
統(tǒng)計(jì)我選擇的友盟統(tǒng)計(jì),flutter的第三方包選擇的是flutter_umplus
住册,地址:
https://pub.dev/packages/flutter_umplus
我們首先需要去友盟官網(wǎng)注冊(cè)app信息婶博,Android和iOS要注冊(cè)兩個(gè),獲取到AndroidKey和iOSKey然后在flutter項(xiàng)目中合適的地方初始化友盟
//集成友盟統(tǒng)計(jì)
if(Platform.isAndroid){//Android平臺(tái)
FlutterUmplus.init(AndroidKey,channel:"Android的渠道名稱(chēng)",reportCrash: false,logEnable: true,encrypt: true);
}else if(Platform.isIOS){//iOS平臺(tái)
FlutterUmplus.init(iOSKey,channel: "appstore",reportCrash: false,logEnable: true,encrypt: true);
}
iOS只有AppStore一個(gè)渠道所以固定值appstore就可以了荧飞,Android項(xiàng)目我們?cè)趺传@取到當(dāng)前渠道包的名稱(chēng)呢凡人?很簡(jiǎn)單,在AndroidManifest.xml中添加meta-data原數(shù)據(jù)
<application>
...
<!--友盟統(tǒng)計(jì)-->
<meta-data android:value="${UMENG_CHANNEL_VALUE}" android:name="UMENG_CHANNEL" />
...
</application>
這里的UMENG_CHANNEL_VALUE和build.gradle中的UMENG_CHANNEL_VALUE對(duì)應(yīng)起來(lái)叹阔,在Android項(xiàng)目中可以讀取這里的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 "";
}
????但是現(xiàn)在問(wèn)題來(lái)了划栓,我不知道flutter代碼中怎么讀取AndroidManifest.xml中的meta-data值,于是乎我就開(kāi)始思考条获,想到了第一個(gè)??個(gè)方案
1. SharedPreferences
使用SharedPreferences存儲(chǔ)數(shù)據(jù),然后在flutter中使用shared_preferences
中讀取蒋歌,于是我在MainActivity中讀取數(shù)據(jù)并存儲(chǔ)
//在flutter中不知道怎么獲取manifest中的meta數(shù)據(jù)帅掘,所以在這里先獲取了存起來(lái)委煤,在flutter里邊取出來(lái)用??
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("渠道名稱(chēng)"+channelName);
});
很不幸,讀取的時(shí)候一直是空修档,讀取不到碧绞,這里并不是說(shuō)這種方式不行,而是我不會(huì)用????吱窝,所以就暫時(shí)放棄這種方式了讥邻,待以后我對(duì)Android進(jìn)一步熟悉再來(lái)解決。這種方式不行并且現(xiàn)在還沒(méi)有找到有人封裝這種第三方的工具院峡,那么就需要自己動(dòng)手進(jìn)行原生交互了兴使,這里我偷了個(gè)懶,我沒(méi)有自己新建原生交互的plugin照激,而是修改了別人的代碼发魄,項(xiàng)目中用到了package_info
,但是看源碼只提供了四種屬性俩垃,沒(méi)有我們需要的励幼,不行就改,改到我們能用就好了口柳。
/// 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.擴(kuò)展package_info苹粟,增加channelName
package_info.dart
class PackageInfo {
PackageInfo({
this.appName,
this.packageName,
this.version,
this.buildNumber,
this.channelName, //自己新增的渠道名稱(chēng)
});
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"], //自己新增的渠道名稱(chēng)
));
}, 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;
///自己新增的渠道名稱(chēng)
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);
}
}
...
}
使用方法
//集成友盟統(tǒng)計(jì)
if(Platform.isAndroid){//Android平臺(tái)
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平臺(tái)
FlutterUmplus.init(ChannelClass.iosKey,channel: ChannelClass.appstore,reportCrash: false,logEnable: true,encrypt: true);
}
終于獲取到了渠道名稱(chēng)??????
我是修改了第三方的包才讀取到了Android原生的數(shù)據(jù),如果以后別的項(xiàng)目也有這種需要跃闹,那么代碼還要再改一次嵌削,后面還面臨著第三方包升級(jí)等問(wèn)題,最好還是自己寫(xiě)一個(gè)plugin進(jìn)行原生交互一勞永逸辣卒。