- 利用zip文件中的comment的字段,例如VasDolly
后面在解析源码时,会详细说明方式2。
自Android N之后,Google建议使用V2来做签名,因为这样更加安全(对整个apk文件进行hash校验,无法修改apk信息),安装速度也更加高效(无需解析校验单个文件,v1需要单个文件校验hash)。
美团对此动作非常快,立马推出了:
其原理是利用v2的方式在做签名时,在apk中插入了一个签名块(安装时校验apk的hash不包含此块),该快中允许插入一些key-value对,于是将签名插在该区域。
当然,腾讯的VasDolly采取的也是相同的方案。
本文,为VasDolly的源码解析,即会详细分析:
- 针对v1签名方式,利用zip的comment区域
- 针对v2签名方式,利用apk中的签名块中插入key-value
本文不涉及v1,v2具体的签名方式,以及安装时的校验流程,这些内容在:
一文中,说的非常详细。
本文重点是源码的解析。
二、接入VasDolly
其实,接入非常简单,而且readme写的非常详细。
但是为了文章的完整性,简单陈述一下。
根目录build.gradle
buildscript {
dependencies {
classpath ‘com.leon.channel:plugin:1.1.7’
}
}
app的build.gradle
apply plugin: ‘channel’
android {
signingConfigs {
release {
storeFile file(RELEASE_STORE_FILE)
storePassword RELEASE_STORE_PASSWORD
keyAlias RELEASE_KEY_ALIAS
keyPassword RELEASE_KEY_PASSWORD
v1SigningEnabled true
v2SigningEnabled false
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled false
proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’
}
}
channel{
//指定渠道文件
channelFile = file(“/Users/zhanghongyang01/git-repo/learn/VasDollyTest/channel.txt”)
//多渠道包的输出目录,默认为new File(project.buildDir,“channel”)
baseOutputDir = new File(project.buildDir,“channel”)
//多渠道包的命名规则,默认为:
a
p
p
N
a
m
e
−
{appName}-
appName−{versionName}-
v
e
r
s
i
o
n
C
o
d
e
−
{versionCode}-
versionCode−{flavorName}-
b
u
i
l
d
T
y
p
e
a
p
k
N
a
m
e
F
o
r
m
a
t
=
′
{buildType} apkNameFormat ='
buildTypeapkNameFormat=′{appName}-
v
e
r
s
i
o
n
N
a
m
e
−
{versionName}-
versionName−{versionCode}-
f
l
a
v
o
r
N
a
m
e
−
{flavorName}-
flavorName−{buildType}’
//快速模式:生成渠道包时不进行校验(速度可以提升10倍以上)
isFastMode = true
}
}
dependencies {
api ‘com.leon.channel:helper:1.1.7’
}
首先要apply plugin,然后在android的闭包下写入channel相关信息。
channel中需要制定一个channel.txt文件,其中每行代码一个渠道:
c1
c2
c3
dependencies中的依赖主要是为了获取渠道号的辅助类,毕竟你写入渠道信息的地方这么奇怪,肯定要提供API进行读取渠道号。
注意:我们在signingConfigs的release中配置的是:v1SigningEnabled=true
和v2SigningEnabled=false
,先看V1方式的快速渠道包。
在Terminal面板执行./gradlew channelRelease
执行完成后,即可在app/build/channel/release
下看到:
release
├── app-1.0-1-c1-release.apk
├── app-1.0-1-c2-release.apk
└── app-1.0-1-c3-release.apk
注意:本文主要用于讲解源码,如果只需接入,尽可能查看github文档。
三、V1的渠道读取与写入
首先我们需要知道对于V1的签名,渠道信息写在哪?
这里直接白话说明一下,我们的apk实际上就是普通的zip,在一个zip文件的最后允许写入N个字符的注释,我们关注的zip末尾两个部分:
2字节的的注释长度+N个字节的注释。
那么,我们只要把签名内容作为注释写入,再修改2字节的注释长度即可。
现在需要考虑的是我们怎么知道一个apk有没有写入这个渠道信息呢,需要有一个判断的标准:
这时候,魔数这个概念产生了,我们可以在文件文件末尾写入一个特殊的字符串,当我们读取文件末尾为这个特殊的字符串,即可认为该apk写入了渠道信息。
很多文件类型起始部分都包含特性的魔数用于区分文件类型。
最终的渠道信息为:
渠道字符串+渠道字符串长度+魔数
3.1 读取
有了上面的分析,读取就简单了:
- 拿到本地的apk文件
- 读取固定字节与预定义魔数做比对
- 然后再往前读取两个字节为渠道信息长度
- 再根据这个长度往前读取对应字节,即可取出渠道信息。
在看源码之前,我们也可以使用二进制编辑器打开打包好的Apk,看末尾的几个字节,如图:
咱们逆着看:
- 首先读取8个字节,对应一个特殊字符串“ltlovezh”
- 往前两个字节为02 00,对应渠道信息长度,实际值为2.
- 再往前读取2个字节为63 31,对照ASCII表,即可知为c1
这样我们就读取除了渠道信息为:c1。
这么看代码也不复杂,最后看一眼代码吧:
代码中通过ChannelReaderUtil.getChannel获取渠道信息:
public static String getChannel(Context context) {
if (mChannelCache == null) {
String channel = getChannelByV2(context);
if (channel == null) {
channel = getChannelByV1(context);
}
mChannelCache = channel;
}
return mChannelCache;
}
我们只看v1,根据调用流程,最终会到:
V1SchemeUtil.readChannel方法:
public static String readChannel(File file) throws Exception {
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(file, “r”);
long index = raf.length();
byte[] buffer = new byte[ChannelConstants.V1_MAGIC.length];
index -= ChannelConstants.V1_MAGIC.length;
raf.seek(index);
raf.readFully(buffer);
// whether magic bytes matched
if (isV1MagicMatch(buffer)) {
index -= ChannelConstants.SHORT_LENGTH;
raf.seek(index);
// read channel length field
int length = readShort(raf);
if (length > 0) {
index -= length;
raf.seek(index);
// read channel bytes
byte[] bytesComment = new byte[length];
raf.readFully(bytesComment);
return new String(bytesComment, ChannelConstants.CONTENT_CHARSET);
} else {
throw new Exception(“zip channel info not found”);
}
} else {
throw new Exception(“zip v1 magic not found”);
}
} finally {
if (raf != null) {
raf.close();
}
}
}
使用了RandomAccessFile,可以很方便的使用seek指定到具体的字节处。注意第一次seek的目标是length - magic.length
,即对应我们的读取魔数,读取到比对是否相同。
如果相同,再往前读取SHORT_LENGTH = 2
个字节,读取为short类型,即为渠道信息所占据的字节数。
再往前对去对应的长度,转化为String,即为渠道信息,与我们前面的分析一模一样。
ok,读取始终是简单的。
后面还要看如何写入以及如何自动化。
3.2 写入v1渠道信息
写入渠道信息,先思考下,有个apk,需要写入渠道信息,需要几步:
- 找到合适的写入位置
- 写入渠道信息、写入长度、写入魔数
好像唯一的难点就是找到合适的位置。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
结语
网上高级工程师面试相关文章鱼龙混杂,要么一堆内容,要么内容质量太浅, 鉴于此我整理了上述安卓开发高级工程师面试题以及答案。希望帮助大家顺利进阶为高级工程师。
目前我就职于某大厂安卓高级工程师职位,在当下大环境下也想为安卓工程师出一份力,通过我的技术经验整理了面试经常问的题,答案部分是一篇文章或者几篇文章,都是我认真看过并且觉得不错才整理出来。
大家知道高级工程师不会像刚入门那样被问的问题一句话两句话就能表述清楚,所以我通过过滤好文章来帮助大家理解。
现在都说互联网寒冬,其实只要自身技术能力够强,咱们就不怕!我这边专门针对Android开发工程师整理了一套【Android进阶学习视频】、【全套Android面试秘籍】、【Android知识点PDF】。
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算
子,让我们一起学习成长!**](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算