渠道包与马甲包
目录:
一、马甲包
1.1 概念与关键词
1.2 主包与马甲包的区别
1.3 马甲包的作用
1.4 打法
1.5 用法
二、渠道包
2.1 概念与关键词
2.2 渠道包配置文件概览
2.3 渠道配置
2.4 Gradle相关配置
一、马甲包
参考资料:
马甲app怎么向主app导流?
App马甲包是什么?
Android如何优雅的写马甲包
《侵删》
“一个游戏三个包,死了一个还有俩”
故事可以从2012年底游戏应用集体下架风波讲起。
这场下架风波使得整个游戏行业人心惶惶,尤其人人游戏,企业开发者账号被封,旗下近20款游戏一夜之间全部下架,这是由于过度刷榜挑战苹果底线带来的恶果。从此人人游戏在iOS端走向衰弱。
自从苹果打击刷榜以来,马甲便成了游戏圈的标配。
1.1 概念与关键词
-
马甲包: APP主包的一种分身。
马甲包是利用各大市场规则漏洞,通过技术手段,多次上架同一款产品的方法。
-
主产品包: 与马甲包相对
1.2 马甲包 与 主产品包 的区别:
拥有同样的内容和功能,除了icon和应用名称不能完全一致,其他基本一致
-
应用名称不一样
-
关键词不一样
-
应用图标不一样
-
应用截图不一样
-
开屏图片最好不一样
-
其余的,比如主App的一些品牌因素,最好去掉
1.3 马甲包 的 作用:
graph TD
A[马甲包] -->导量
A -->覆盖更多关键词
A -->A/B测试
A -->刷榜
-
增加获取有效客户的渠道 - 导量
每一个马甲包都相当于额外开出的积攒流量的渠道。可以通过导流来积累流量
- 全部复制主App内容,共享后台,共享整个数据的情况无需导流
- 正常的导量形式:一般通过弹窗,广告,Push等引导用户到App Sture下载主App
-
增加关键词的覆盖量
单个App的关键词有100个字符的限制,多个App意味着可以覆盖到Nx100个字符的关键词
-
做A/B测试
在主App不方便测试,而马甲包不一样,它可以随意更改或替代,所以早期马甲包都是为了做测试用
-
刷榜
刷榜有被苹果下架的风险,但是刷榜能产生较高的性价比,可以用马甲包来刷榜,马甲包挂了就挂了。
1.4 打法
-
马甲采用主App的部分功能,不同功能性的马甲各自有自己的目标用户,再集体向主App导用户:喜马拉雅、玩图
-
隐藏主App,开发者账号不一样,资源允许的话,使用的后台也不一样
1.5 用法
-
build.gradle(app)
的android{}
中添加 产品风味:productFlavors
productFlavors {
/* 三种马甲包 马甲包可以多添加几种 */
ceshi {} //测试
official {}//正式
debug {} //演示
}
// 产品风味~~~
productFlavors.each { flavor ->
def props = new Properties()
/* 读取config文件夹中的配置文件 */
file("../config/${flavor.name}_config.properties").withInputStream {
props.load(new InputStreamReader(it, "GBK"))
}
def application_id = props.getProperty("APPLICATION_ID")
def app_name = props.getProperty("APP_NAME")
def server_url = props.getProperty("SERVER_URL")
def map_key = props.getProperty("MAP_KEY")
def umeng_key = props.getProperty("UMENG_APPKEY")
// 特调的applicationId
flavor.applicationId = application_id
// 清单文件占位符
flavor.manifestPlaceholders = [
APP_NAME : app_name,
MAP_KEY : map_key,
SERVER_URL : server_url,
UMENG_APPKEY: umeng_key
]
}
-
新建config目录,添加配置文件
ceshi_config . properties
official_config . properties
debug_config . properties
APPLICATION_ID =...
APP_NAME = ...
SERVER_URL =...
MAP_KEY=...
UMENG_APPKEY=...
<meta-data
android:name="SERVER_URL"
android:value="${SERVER_URL}" />
public static String getMetaDataInApp(@NonNull final String key) {
String value = "";
PackageManager pm = Utils.getApp().getPackageManager();
String packageName = Utils.getApp().getPackageName();
try {
ApplicationInfo ai = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
value = String.valueOf(ai.metaData.get(key));
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return value;
}
(类名).getMetaDataInApp("SERVER_URL");
二、渠道包:
2.1 涉及到的配置文件
-
渠道配置:
channels_config.gradle
-
全局应用配置:
config.gradle
-
app模块配置:
build.gradle(:app)
-
项目属性配置:
project_properties.gradle
2.2、马甲包依赖
classpath 'com.meituan.android.walle:plugin:1.1.7'
2.3 渠道配置:channels_config.gradle
`apply plugin: 'walle'
android {
defaultConfig {
buildConfigField "String", "CHANNEL_TYPE", "\"${rootProject.ext.channel}\""
}
signingConfigs {
company1 {
// 签名store文件路径
storeFile file(rootProject.ext.android.storeFile)
// 签名store文件的密码
storePassword rootProject.ext.android.storePassword
// 别名
keyAlias rootProject.ext.android.keyAlias
// 别名的密码
keyPassword rootProject.ext.android.keyPassword
}
company2 {
// 签名store文件路径
storeFile file(rootProject.ext.android.mtStoreFile)
// 签名store文件的密码
storePassword rootProject.ext.android.mtStorePassword
// 别名
keyAlias rootProject.ext.android.mtKeyAlias
// 别名的密码
keyPassword rootProject.ext.android.mtKeyPassword
}
}
}
2.4 app模块配置:build.gradle(:app)
// 在build.gradle文件中引入channels_config.gradle的配置
apply from: "channels_config.gradle"
// 引入上级目录下的buildSystem.gradle
apply from: "../buildSystem.gradle"
-
manifestPlaceHolders
配置的内容在AndroidManifest可以直接获取:
<meta-data
android:name="UMENG_CHANNEL"
android:value="${APP_CHANNEL_VALUE}" />
// build.gradle
android{
productFlavors {
xysp {
applicationId "com.mg.xyvideo"
versionCode rootProject.ext.android.versionCode
versionName rootProject.ext.android.versionName
// 看这边!!!看这边!!!
manifestPlaceholders = [
UMENG_APPKEY : "xxxxxxxxx3xxxxxxxxxxxxxx",
//友盟配置
APP_CHANNEL_VALUE : "xxxxxxxxxx"
]
}
}
}
def releaseTime() {
return new Date().format("yyyy-MM-dd_HH-mm-ss", TimeZone.getTimeZone("GMT+8"))
}
defaultConfig {
// company的维度优先于channel
flavorDimensions "company","channel"
}
productFlavors{
// 随便命名,建议根据该维度的具体信息进行命名
companyA{
dimension "company"
}
companyB{
dimension "company"
}
channelA{
dimension "channel"
}
channelB{
dimension "channel"
}
}
打开你的terminal,构建下
gradlew :app:assembleRelease
./gradlew :app:assembleRelease
好了现在打出了下面这些包
assembleCompanyAChannelA
assembleCompanyAChannelB
assembleCompanyBChannelA
assembleCompanyBChannelB
假设CompanyA和ChannelA配置了相同的属性,那么主维度的该属性会覆盖子维度的该属性
buildConfigField "String","FLAVOR_NAME","\"channelB\""
厉害的来了,在Java代码中可以这么取值:
有点意思
风赚网专注购买苹果开发者账号、购买苹果开发者个人账号、购买苹果开发者公司账号、购买苹果开发者企业账号,购买iOS开发者账号、iOS开发者账号购买、苹果开发者账号购买、苹果开发者账号个人购买、苹果开发者公司账号购买、苹果开发者企业账号购买、出售苹果开发者账号、出售苹果开发者个人账号、出售苹果开发者公司账号、出售苹果开发者企业账号,出售iOS开发者账号,超级签名、TF签名、企业签名、苹果TestFlight签名、苹果ios超级签名、苹果马甲包上架开发、苹果IOS应用商店代上架、苹果APP代上架、苹果马甲包上架、苹果马甲包现包、苹果马甲包购买出售、购买苹果马甲包、安卓马甲包上架、安卓谷歌马甲包上架开发、安卓谷歌APP代上架、安卓谷歌马甲包现包、谷歌马甲包上架、安卓谷歌马甲包购买出售、购买安卓谷歌马甲包、安卓IOS应用商店代上架、小米代上架、华为代上架、vivo代上架、oppo代上架、软件著作申请