Android Studio Gradle多渠道打包

 原理使用Android Studio打一次渠道包,用反编译工具反编译后,修改渠道信息重新编译

准备文件

分渠道配置文件:channel.txt ↓

# 多渠道配置里“统计平台”、“市场名称”、“渠道编号”分别代表什么意思?
# 统计平台:即android name,应用中集成的数据分析sdk的公司名称,例:umeng_channel(下拉列表里提供了若干选项);
# 市场名称:各大安卓应用分发市场(下拉列表里提供了Top20的市场供选择),以帮助开发者区分不同渠道包特征上传相对应市场;
# 渠道编号:即android value,一般填写相关channel id。用户可自行定义区分各大市场的关键字,尽量避免使用特殊字符。
BaiduMobAd_CHANNEL yingyonghui yingyonghui
BaiduMobAd_CHANNEL oppo oppo
BaiduMobAd_CHANNEL 360 360
BaiduMobAd_CHANNEL baidu baidu
BaiduMobAd_CHANNEL xiaomi xiaomi
BaiduMobAd_CHANNEL huawei huawei
BaiduMobAd_CHANNEL lianxiang lianxiang
BaiduMobAd_CHANNEL yingyongbao yingyongbao
BaiduMobAd_CHANNEL aliyun aliyun
BaiduMobAd_CHANNEL sanxing sanxing
BaiduMobAd_CHANNEL vivo vivo
BaiduMobAd_CHANNEL honor honor

(反编译+对齐+签名)文件:↓

// 可以在Android SDK目录里面找到D:\Android\sdk\build-tools\30.0.3\lib

apksigner.jar

// Mac就找【zipalign】,windows就找【zipalign.exe】

zipalign

zipalign.exe

// 官网:下载Apktool官网

apktool_2.9.3.jar

app build.gradle文件中这样配置

 下面需要自己自行调整

要打多渠道包时点击运行那个绿色的小三角

核心文件:release.gradle

import java.nio.file.Files
import java.util.regex.Matcher
import java.util.regex.Pattern
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStreamext {SIGN_JAR = rootDir.getPath() + "/tools/apksigner.jar"ZIPALIGN = rootDir.getPath() + "/tools/zipalign"APKTOOL_JAR = rootDir.getPath() + "/tools/apktool_2.9.3.jar"storeFile = rootDir.getPath() + "/app.keystore" //密钥路径storePassword = "dmx1234567890" //密钥密码keyAlias = "Alias" //密钥别名keyPassword = "dmx1234567890"//别名密码// 反编译目录unApkPath = buildDir.getPath() + '/outputs/release/unapk'// 渠道Apk输出路径channel_apks_path = buildDir.getPath() + '/outputs/release/channels/'// 保存渠道配置CHANNEL_CONFIG = rootDir.getPath() + "/channel.txt"
}
/*** Apk渠道类*/
class ApkChannel {/*** 统计平台,即 android name*/String name;/*** 市场名称,即应用分渠道包时,会加上这个名称 列:app_1.0.0_5_{market}_sign.apk*/String market;/*** 统计渠道,即 android value*/String value;public ApkChannel(String name, String market, String value) {this.name = name;this.market = market;this.value = value;}}static def isWindows() {return org.gradle.internal.os.OperatingSystem.current().isWindows()
}static def isMacOsX() {return org.gradle.internal.os.OperatingSystem.current().isMacOsX()
}
// 这个在MacOS上有时候会删除不到,会导致编译出来的包存在.DS_Store文件,懂的可以自己研究处理一下
def deleteDS_Store() {if (isMacOsX()) {exec {commandLine "/bin/sh", "-c", "find ${unApkPath} -name '.DS_Store' -depth -exec rm {} +"}}
}static def String readXml(File xmlFile) {String result = nulltry (InputStream stream = new FileInputStream(xmlFile)) {// 创建byte数组byte[] buffer = new byte[stream.available()]// 将文件中的数据读到byte数组中stream.read(buffer)result = new String(buffer, "UTF-8")} catch (Exception e) {throw new Exception(e)}return result
}static def boolean writeXml(File xmlFile, String xmlString) {boolean isWriteXml = false// 写入文件try (BufferedWriter writer = new BufferedWriter(new FileWriter(xmlFile))) {writer.write(xmlString);isWriteXml = true} catch (IOException e) {throw new Exception(e)}return isWriteXml
}/*** 根据统计平台名称匹配,是否存在该统计平台的 meta-data* @param xmlString* @param channelName* @return*/
private static boolean isExistsMetaData(String xmlString, String channelName) {String metaDataReg = "\\<meta-data android:name=\\\"" + channelName + "\\\" android:value=\".*?\"/\\>"Pattern pattern = Pattern.compile(metaDataReg)return pattern.matcher(xmlString).find()
}
/*** 替换指定的统计平台的 meta data* @param xmlString* @param channelName* @param metaData*/
private static String replaceMetaData(String xmlString, String channelName, String metaData) {String metaDataReg = "\\<meta-data android:name=\\\"" + channelName + "\\\" android:value=\".*?\"/\\>"Pattern pattern = Pattern.compile(metaDataReg)Matcher matcher = pattern.matcher(xmlString)if (matcher.find()) {return xmlString.replace(matcher.group(), metaData)}return xmlString
}
/*** 生成 meta data* @param channelName* @param channelValue* @return*/
private static String generateMetaData(String channelName, String channelValue) {return String.format("<meta-data android:name=\"%s\" android:value=\"%s\"/>", channelName, channelValue)
}def List<ApkChannel> initChannels() {List<ApkChannel> channels = new ArrayList<ApkChannel>()println("并初始化channel.txt文件...")File channelFile = new File(CHANNEL_CONFIG)if (!channelFile.exists()) {throw new FileNotFoundException(channelFile.getPath() + "文件不存在!")}try (BufferedReader reader = new BufferedReader(new FileReader(channelFile))) {String linewhile ((line = reader.readLine()) != null) {// 处理每一行数据if (line.startsWith("#")) {println(line.replace("# ", ""))} else {String[] arr = line.split(" ")channels.add(new ApkChannel(arr[0], arr[1], arr[2]))}}println("初始化成功,渠道数:" + channels.size())} catch (Exception e) {e.printStackTrace()}return channels
}/*** 反编译Apk* @param inApkFile 要反编译的Apk文件* @return 返回反编译后的文件目录*/
def File decompileApk(File inApkFile) {println "*************** apktool decompile start ***************"File outDecompileFile = new File(unApkPath, inApkFile.name.replace(".apk", ""))if (outDecompileFile.exists()) {if (!delete(outDecompileFile)) {throw new RuntimeException("delete apktoolOutputDir failure!")}}if (!outDecompileFile.mkdirs()) {throw new RuntimeException("make apktoolOutputDir failure!")}println("apktool decompile out file: " + outDecompileFile)// 解压APK命令String unApkCommand = String.format("java -jar %s d -o %s %s -f -s",APKTOOL_JAR,outDecompileFile.getPath(),inApkFile.getPath())exec {if (isWindows()) {commandLine "powershell", unApkCommand} else if (isMacOsX()) {commandLine "/bin/sh", "-c", unApkCommand} else {throw new RuntimeException("Please confirm your platform command line!")}}deleteDS_Store()println "*************** apktool decompile finish ***************"return outDecompileFile
}
/*** 编译Apk* @param inDecompileApkFileDir 输入反编译后的文件目录* @param outCompileApkFileDir 输出编译Apk文件存储目录* @param outFileName 编译后的Apk文件名* @return*/
def File compileApk(File inDecompileApkFileDir, File outCompileApkFileDir, String outFileName) {println "*************** apktool compile start ***************"if (!inDecompileApkFileDir.exists()) {throw new FileNotFoundException("no " + inDecompileApkFileDir.getPath() + " has found!")}if (!outCompileApkFileDir.exists()) {outCompileApkFileDir.mkdirs()}File outCompileApkFile = new File(outCompileApkFileDir, outFileName)String buildApkCommand = String.format("java -jar %s b %s -o %s",APKTOOL_JAR,inDecompileApkFileDir.getPath(),outCompileApkFile.getPath())exec {if (isWindows()) {commandLine "powershell", buildApkCommand} else if (isMacOsX()) {commandLine "/bin/sh", "-c", buildApkCommand} else {throw new RuntimeException("Please confirm your platform command line!")}}println "*************** apktool compile finish ***************"return outCompileApkFile
}/*** 对齐Apk* @param inApkFile 要对齐的Apk文件* @return 返回对齐后的Apk文件*/
def File zipalignApk(File inApkFile) {println "*************** zipalign optimize start ***************"String zipalignApkFilename = inApkFile.name.replace(".apk", "_unsigned.apk")File outZipalignApkFile = new File(inApkFile.getParent(), zipalignApkFilename)exec {if (isWindows()) {// zipalign.exe -p -f -v 4 infile.apk outfile.apkString alignApkCommand = String.format("%s.exe -p -f -v 4 %s %s",ZIPALIGN,inApkFile.getPath(),outZipalignApkFile.getPath())commandLine "powershell", alignApkCommand} else if (isMacOsX()) {// zipalign -p -f -v 4 infile.apk outfile.apkString alignApkCommand = String.format("%s -p -f -v 4 %s %s",ZIPALIGN,inApkFile.getPath(),outZipalignApkFile.getPath())commandLine "/bin/sh", "-c", alignApkCommand} else {throw new RuntimeException("Please confirm your platform command line!")}}println "*************** zipalign optimize finish ***************"return outZipalignApkFile
}
/*** 签名Apk* @param inApkFile 要签名的Apk文件* @return 返回签名后的Apk文件*/
def File signerApk(File inApkFile) {println "*************** start apksigner ***************"File outSignerApkFile = new File(inApkFile.getPath().replace("_unsigned.apk", "_signed.apk"))String apksignerCommand = String.format("java -jar %s sign -verbose --ks %s --v1-signing-enabled true --v2-signing-enabled true --v3-signing-enabled false --ks-key-alias %s --ks-pass pass:%s --key-pass pass:%s --out %s %s",SIGN_JAR,storeFile,keyAlias,storePassword,keyPassword,outSignerApkFile.getPath(),inApkFile.getPath())exec {if (isWindows()) {commandLine "powershell", apksignerCommand} else if (isMacOsX()) {commandLine "/bin/sh", "-c", apksignerCommand} else {throw new RuntimeException("Please confirm your platform command line!")}}println "*************** finish apksigner ***************"return outSignerApkFile
}private def processingChannel(File decompileApkFile, String channelMarket) {// 删除恶心的.DS_StoredeleteDS_Store()// 编译File compileApkFile = compileApk(decompileApkFile, new File(channel_apks_path), decompileApkFile.name + "_" + channelMarket + ".apk")// 对齐File zipalignApkFile = zipalignApk(compileApkFile)if (zipalignApkFile.exists()) {delete(compileApkFile)}// 签名File signerApkFile = signerApk(zipalignApkFile)if (signerApkFile.exists()) {delete(zipalignApkFile)}
}task assemblePackageChannel() {dependsOn('assembleAdRelease')doLast {if (isMacOsX()) {exec { commandLine "/bin/sh", "-c", "chmod +x " + SIGN_JAR }exec { commandLine "/bin/sh", "-c", "chmod +x " + ZIPALIGN }exec { commandLine "/bin/sh", "-c", "chmod +x " + APKTOOL_JAR }}List<ApkChannel> channels = initChannels()if (channels.size() <= 0) {throw new Exception("没有渠道信息!")}FilenameFilter filter = new FilenameFilter() {@Overrideboolean accept(File dir, String name) {return name.endsWith(".apk")}}// 获得release包地址,暂时固定死String sourceApkDir = buildDir.getPath() + "/outputs/apk/ad/release"File inSourceApkFile = new File(sourceApkDir).listFiles(filter).first()if (inSourceApkFile == null || !inSourceApkFile.exists()) {throw new FileNotFoundException("no apk files has found!")}File decompileApkFile = decompileApk(inSourceApkFile)// 检测AndroidManifest.xml是否存在File xmlFile = new File(decompileApkFile, "AndroidManifest.xml")if (!inSourceApkFile.exists()) {throw new FileNotFoundException("no AndroidManifest.xml files has found!")}String xmlString = readXml(xmlFile)if (xmlString == null || xmlString.length() <= 0) {throw new NullPointerException("read AndroidManifest.xml is null.")}// 多渠道处理for (ApkChannel channel : channels) {// 检测是否存在多个平台if (channel.name.split("\\|").length > 1) {String[] channelNames = channel.name.split("\\|")String[] channelValues = channel.value.split("\\|")for (int i = 0; i < channelNames.length; i++) {String channelName = channelNames[i]String channelValue = channelValues[i]if (isExistsMetaData(xmlString, channelName)) {// 替换渠道信息xmlString = replaceMetaData(xmlString, channelName, generateMetaData(channelName, channelValue))// 写入渠道信息writeXml(xmlFile, xmlString)processingChannel(decompileApkFile, channel.market)}}} else {if (isExistsMetaData(xmlString, channel.name)) {// 替换渠道信息xmlString = replaceMetaData(xmlString, channel.name, generateMetaData(channel.name, channel.value))// 写入渠道信息writeXml(xmlFile, xmlString)processingChannel(decompileApkFile, channel.market)}}}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/395679.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AT32F421驱动BLDC 配合上位机控制与调参

AT32F421驱动BLDC 配合上位机控制与调参 &#x1f527;AT32 电机控制与调参上位机软件&#xff1a;ArteryMotorMonitor&#xff1a;https://www.arterytek.com/cn/support/motor_control.jsp?index0&#x1f33f;测试电机参数&#xff1a;2204-12N14P&#xff0c;无感BLDC&…

LVS(Linux Virtual Server)

简介 LVS&#xff08;Linux Virtual Server&#xff09;是一个高性能的开源负载均衡解决方案&#xff0c;它通过在Linux内核中实现IPVS&#xff08;IP Virtual Server&#xff09;模块来提供负载均衡功能。LVS能够将外部请求根据特定的算法分发到后端的多个服务器上&#xff0c…

百度文心一言API调用,千帆大模型获取API Key和API Secret图解

百度文心一言大模型调用教程&#xff0c;获取文心一言API Key和API Secret的方法&#xff0c;码笔记mabiji.com告诉大家在百度智能云的千帆大模型平台创建应用&#xff0c;即可获取文心一言的API Key和API Secret&#xff0c;详细流程如下&#xff1a; 1、在百度智能云的千帆大…

gitlab项目添加新成员

gitlab项目添加新成员 1、进入项目&#xff0c;找到settings----->点击Members 2、手动输入要添加成员的账号或者搜索&#xff0c;最后点击Add to project就可以了 choose a role permission 是为要添加的成员选择角色权限 补充&#xff1a; ‌Maintainer&#xff1a;拥…

同态加密和SEAL库的介绍(八)性能

本篇会对比三种加密方案&#xff0c;同时每种方案配置三种参数。即九种情况下的各个操作的性能差异&#xff0c;为大家选择合适的方案和合适的参数提供参考。表格中所有时长的单位均为微妙&#xff0c;即 。 当然数据量比较大&#xff0c;为了方便大家查找&#xff0c…

[BSidesCF 2019]Kookie1

打开题目&#xff0c;看到 根据提示&#xff0c;账号&#xff1a;cookie。密码&#xff1a;monster。试一下登录&#xff0c;登陆成功 抓包看看信息 根据提示&#xff0c; 看一下返回包 账号要加username要改成admin&#xff0c;改一下试试 构造cookie 直接得到flag flag{c…

Redis远程字典服务器(3)——常用数据结构和单线程模型

目录 一&#xff0c;常用数据结构 1.0 前言 1.1 string 1.2 hash 1.3 list 1.4 set 1.5 zset 1.6 演示 二&#xff0c;关于单线程模型 2.1 关于Redis的单线程 2.2 Redis为什么快 一&#xff0c;常用数据结构 1.0 前言 Redis是采用键值对的方式来存储数据的&#…

探索算法系列 - 前缀和算法

目录 一维前缀和&#xff08;原题链接&#xff09; 二维前缀和&#xff08;原题链接&#xff09; 寻找数组的中心下标&#xff08;原题链接&#xff09; 除自身以外数组的乘积&#xff08;原题链接&#xff09; 和为 K 的子数组&#xff08;原题链接&#xff09; 和可被 …

Java—抽象类和接口 (´▽`)ノ

目录&#xff1a; 一、抽象类&#xff1a; 1、概念: 在面向对象中&#xff0c;所有对象都是由类来创建的&#xff0c;但是呢&#xff0c;并不是所有的类都用来创建类&#xff0c;如果一个类不能描述一个具体的对象&#xff0c;那么这个类就是抽象类。 可以看到&#xff0c;我们…

kotlin简介

Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言&#xff0c;被称之为 Android 世界的Swift&#xff0c;由 JetBrains 设计开发并开源。 Kotlin 可以编译成Java字节码&#xff0c;也可以编译成 JavaScript&#xff0c;方便在没有 JVM 的设备上运行。 在Google I/O 2017…

嵌入式软件中状态机的几种操作

嵌入式软件中状态机的几种操作 1、状态机的术语 现态&#xff1a;是指当前所处的状态。 条件&#xff1a;又称为“事件”&#xff0c;当一个条件被满足&#xff0c;将会触发一个动作&#xff0c;或者执行一次状态的迁移。 动作&#xff1a;条件满足后执行的动作。动作执行完…

Animate软件基本概念:遮罩层和引导层

这里简单讲一下Animate软件中关于遮罩层和引导层的基本概念。 FlashASer&#xff1a;AdobeAnimate2021软件零基础入门教程https://zhuanlan.zhihu.com/p/633230084 FlashASer&#xff1a;实用的各种Adobe Animate软件教程https://zhuanlan.zhihu.com/p/675680471 FlashASer&…

Hadoop集群安装配置,spark集群安装配置

前提&#xff1a;准备3台linux服务器&#xff0c;并保证其网络可进行相互通信 假设三台机器IP分别为&#xff1a; 192.168.88.101 192.168.88.102 192.168.88.103一.配置三台服务器之间SSH免密登录,关闭防火墙&#xff0c;时间同步 三台服务器均执行以下1,2,3,4,5,6,8命令 1…

【MongoDB 】MongoDB 介绍及应用,设计到4个案例

MongoDB 介绍概述 基础概念 MongoDB 是非关系型数据库&#xff0c;也就是nosql&#xff0c;存储json数据格式会非常灵活&#xff0c;要比数据库mysql/MariaDB更好&#xff0c;同时也能为mysql/MariaDB分摊一部分的流量压力。 对于经常读写的数据他会存入内存&#xff0c;如此…

视频剪辑SDK,人脸美化与滤镜特效,焕发直播新活力

在数字化浪潮席卷全球的今天&#xff0c;直播已成为连接品牌与消费者、创作者与观众的重要桥梁。为了在激烈的直播竞争中脱颖而出&#xff0c;提供高质量、富有创意的直播内容成为关键。美摄科技&#xff0c;作为视频处理技术的先行者&#xff0c;携手其强大的视频剪辑SDK解决方…

2024年计算机类学术会议有哪些

随着科技的飞速发展&#xff0c;计算机科学与技术领域正以前所未有的速度进步&#xff0c;各类学术会议成为了交流最新研究成果、探讨前沿技术趋势的重要平台。2024年&#xff0c;全球范围内将举办多场计算机类学术会议&#xff0c;这些会议不仅汇聚了顶尖的专家学者&#xff0…

一六三、go多版本安装与切换

如何安装多版本 官方安装方法 参考链接 当已安装某个版本后&#xff0c;执行如下命令 go install golang.org/dl/go1.19.7latest go1.19.7 download执行第一行命令后&#xff0c;会在GOPATH中新增go1.19.7。执行第二行命令后&#xff0c;会在sdk目录下载完整的包。 通过sm…

制造知识普及(十)-- 常见的工业软件介绍

「 1. ERP」 企业资源计划&#xff08;enterprise resource planning&#xff0c;ERP&#xff09;是制造企业的核心管理软件。ERP系统的基本思想是以销定产&#xff0c;协同管控企业的产、供、销、人、财、物等资源&#xff0c;帮助企业按照销售订单&#xff0c;基于产品的制造…

gitlab-runner /var/run/docker.sock connect permission denied

usermod -aG docker gitlab-runner sudo service docker restart参考&#xff1a;https://gitlab.com/gitlab-org/gitlab-runner/-/issues/3492

如何在 Android 手机/平板电脑上恢复误删除的 DCIM 文件夹

DCIM 文件夹是智能手机和平板电脑上最重要的文件夹之一。许多人报告说他们在 Android 设备上遇到了 DCIM 文件夹为空的问题。实际上&#xff0c;这种情况大多数情况下都会发生&#xff0c;当您意外从 Android 设备中删除 DCIM 文件夹或因病毒攻击、应用程序问题和意外格式化等原…