gradle编译过程中有类似如下几个task
:app:generateXXXXResValues UP-TO-DATE
:app:generateXXXXResources
:app:mergeXXXXResources UP-TO-DATE
:app:processXXXXManifest UP-TO-DATE
:app:processXXXXResources
:app:generateXXXXSources
有关processXXXXResources的详解请阅读《gradle编译打包过程 之 ProcessAndroidResources的源码分析》
project.afterEvaluate {
def processResSet = project.tasks.findAll{
boolean isProcessResourcesTask = false
android.applicationVariants.all { variant ->
if(it.name == 'process' + variant.getName() + 'Resources'){
isProcessResourcesTask = true
}
}
return isProcessResourcesTask
}
for(def processRes in processResSet){
processRes.doLast{
int newPkgId = 0x6D
//gradle 3.0.0
File[] fileList = getResPackageOutputFolder().listFiles()
for(def i = 0; i < fileList.length; i++){
if(fileList[i].isFile() && fileList[i].path.endsWith(".ap_")){
dealApFile(fileList[i], newPkgId, android.defaultConfig.applicationId)
}
}
String newPkgIdStr = "0x" + Integer.toHexString(newPkgId)
replaceResIdInJavaDir(getSourceOutputDir(), newPkgIdStr)
replaceResIdInRText(getTextSymbolOutputFile(), newPkgIdStr)
// //gradle 2.2.3
// dealApFile(packageOutputFile, newPkgId, android.defaultConfig.applicationId)
// replaceResIdInJava(textSymbolOutputDir, sourceOutputDir, android.defaultConfig.applicationId, newPkgId)
// String newPkgIdStr = "0x" + Integer.toHexString(newPkgId)
// replaceResIdInJavaDir(sourceOutputDir, newPkgIdStr)
// replaceResIdInRText(textSymbolOutputDir + File.separator + "R.txt", newPkgIdStr)
}
}
}
先根据variant找到processXXXXResources这类task,然后遍历执行doLast,这样doLast中的语句块就会在资源编译完成后立刻执行。至于语句块中的代码我们后面一点点分析。
build/intermediates/symbols/[productFlavors]/[buildType]/R.txt (这个貌似与kotlin有关)
build/generated/source/r/[productFlavors]/[buildType]/[packageName]/R.java
sourceOutputDir是build/generated/source/r/[productFlavors]/[buildType]/
(注意,上面是基于gradle2.3.3版本,gradle3.0.0版本ProcessAndroidResources代码变动很大,需要使用一个函数来获取,而且获取的路径也有所不同,所以doLast代码块中处理有不同)
def replaceResIdInRText(File textSymbolOutputFile, String newPkgIdStr){
println textSymbolOutputFile.path
def list1 = []
textSymbolOutputFile.withReader('UTF-8') { reader ->
reader.eachLine {
if (it.contains('0x7f')) {
it = it.replace('0x7f', newPkgIdStr)
}
list1.add(it + "\n")
}
}
textSymbolOutputFile.withWriter('UTF-8') { writer ->
list1.each {
writer.write(it)
}
}
}
def replaceResIdInJavaDir(File srcFile, String newPkgIdStr){
if(srcFile.isFile()){
if(srcFile.name.equals("R.java")){
def list = []
file(srcFile).withReader('UTF-8') { reader ->
reader.eachLine {
if (it.contains('0x7f')) {
it = it.replace('0x7f', newPkgIdStr)
}
list.add(it + "\n")
}
}
file(srcFile).withWriter('UTF-8') { writer ->
list.each {
writer.write(it)
}
}
}
}
else{
def fileList = srcFile.listFiles()
for(def i = 0; i < fileList.length; i++){
replaceResIdInJavaDir(fileList[i], newPkgIdStr)
}
}
}
代码比较简单,就是将文件里的0x7f都替换成新的pkgId。然后在doLast中执行这两个函数,见前面代码(注意不同gradle版本代码有点不同)。
341.file=/Users/bennu/TestApp/app/build/intermediates/res/resources-debug.ap_/resources.arsc
54.base=/Users/bennu/TestApp/app/build/intermediates/res/resources-debug.ap_
76.set=ANDROID_RESOURCE
327.set=ANDROID_RESOURCE
357.base=/Users/bennu/TestApp/app/build/intermediates/res/resources-debug.ap_
374.file=/Users/bennu/TestApp/app/build/intermediates/res/resources-debug.ap_/res/drawable-xhdpi-v4/abc_ic_star_half_black_16dp.png
def unZip(File src, String savepath)throws IOException
{
def count = -1;
def index = -1;
def flag = false;
def file1 = null;
def is = null;
def fos = null;
def bos = null;
ZipFile zipFile = new ZipFile(src);
Enumeration> entries = zipFile.entries();
while(entries.hasMoreElements())
{
def buf = new byte[2048];
ZipEntry entry = (ZipEntry)entries.nextElement();
def filename = entry.getName();
filename = savepath + filename;
File file2=file(filename.substring(0, filename.lastIndexOf('/')));
if(!file2.exists()){
file2.mkdirs()
}
if(!filename.endsWith("/")){
file1 = file(filename);
file1.createNewFile();
is = zipFile.getInputStream(entry);
fos = new FileOutputStream(file1);
bos = new BufferedOutputStream(fos, 2048);
while((count = is.read(buf)) > -1)
{
bos.write(buf, 0, count );
}
bos.flush();
fos.close();
is.close();
}
}
zipFile.close();
}
def zipFolder(String srcPath, String savePath)throws IOException
{
def saveFile = file(savePath)
saveFile.delete()
saveFile.createNewFile()
def outStream = new ZipOutputStream(new FileOutputStream(saveFile))
def srcFile = file(srcPath)
zipFile(srcFile.getAbsolutePath() + File.separator, "", outStream)
outStream.finish()
outStream.close()
}
def zipFile(String folderPath, String fileString, ZipOutputStream out)throws IOException
{
File srcFile = file(folderPath + fileString)
if(srcFile.isFile()){
def zipEntry = new ZipEntry(fileString)
def inputStream = new FileInputStream(srcFile)
out.putNextEntry(zipEntry)
def len
def buf = new byte[2048]
while((len = inputStream.read(buf)) != -1){
out.write(buf, 0, len)
}
out.closeEntry()
}
else{
def fileList = srcFile.list()
if(fileList.length <= 0){
def zipEntry = new ZipEntry(fileString + File.separator)
out.putNextEntry(zipEntry)
out.closeEntry()
}
for(def i = 0; i < fileList.length; i++){
zipFile(folderPath, fileString.equals("") ? fileList[i] : fileString + File.separator + fileList[i], out)
}
}
}
这部分不是重点,不细说了,注意压缩的时候不能带着根目录。
def dealApFile(File packageOutputFile, int newPkgId, String pkgName){
int prefixIndex = packageOutputFile.path.lastIndexOf(".")
String unzipPath = packageOutputFile.path.substring(0, prefixIndex) + File.separator
unZip(packageOutputFile, unzipPath)
//TODO 这里处理二进制文件,下面会讲
replaceResIdInResDir(unzipPath, newPkgId)
replaceResIdInArsc(file(unzipPath + 'resources.arsc'), newPkgId, pkgName)
zipFolder(unzipPath, packageOutputFile.path)
//file(unzipPath).deleteDir() //如果需要可以在处理后删除解压后的文件
}
解压后的目录保持与ap_文件同名,防止出现混乱。
def replaceResIdInArsc(File resFile, int newPkgId, String pkgName) throws Exception
{
def buf = resFile.bytes
for(def i = 0; i + 15 < buf.length; ){
if(buf[i] == 0x00 && buf[i+1] == 0x02 && buf[i+8] == 0x7F && buf[i+9] == 0x00 && buf[i+10] == 0x00 && buf[i+11] == 0x00){
buf[i+8] = newPkgId
break
}
i=i+4
}
def outStream = new FileOutputStream(resFile)
outStream.write(buf, 0, buf.length)
outStream.flush()
outStream.close()
}
代码很简单,就不细说了。
def replaceResIdInResDir(String resPath, int newPkgId) throws Exception
{
File resFile = file(resPath)
if(resFile.isFile()){
if(resPath.endsWith(".xml")){
replaceResIdInXml(resFile, newPkgId)
}
}
else{
def fileList = resFile.list()
if(fileList == null || fileList.length <= 0){
return
}
for(def i = 0; i < fileList.length; i++){
replaceResIdInResDir(resPath + File.separator + fileList[i], newPkgId)
}
}
}
def replaceResIdInXml(File resFile, int newPkgId) throws Exception
{
def buf = resFile.bytes
for(def i = 0; i + 7 < buf.length; i=i+4){
if(buf[i] == 0x08 && buf[i+1] == 0x00 && buf[i+2] == 0x00 && (buf[i+3] == 0x01 || buf[i+3] == 0x02)){
if(buf[i+7] == 0x7f){
buf[i+7] = newPkgId
//println resFile.name + "," + (i+7)
}
}
}
def outStream = new FileOutputStream(resFile)
outStream.write(buf, 0, buf.length)
outStream.flush()
outStream.close()
}
然后在之前的dealApFile函数中执行即可。
这样修改后,我们的App终于正常运行起来了,但是还是有一点小问题,样式不对了,即在AndroidManifest.xml为Application设置的theme失效了。
观察日志发现这样一条信息def replaceResIdInArsc(File resFile, int newPkgId, String pkgName) throws Exception
{
def buf = resFile.bytes
for(def i = 0; i + 15 < buf.length; ){
if(buf[i] == 0x00 && buf[i+1] == 0x02 && buf[i+8] == 0x7F && buf[i+9] == 0x00 && buf[i+10] == 0x00 && buf[i+11] == 0x00){
buf[i+8] = newPkgId
i += headSize
continue
}
if(buf[i] == 0x01 && buf[i+1] == 0x02 && buf[i+9] == 0x00 && buf[i+10] == 0x00 && buf[i+11] == 0x00){
int offsetStart = i + ((buf[i+3]&0xFF) << 8) + (buf[i+2]&0xFF)
int offsetSize = ((buf[i+15]&0xFF) << 24) + ((buf[i+14]&0xFF) << 16) + ((buf[i+13]&0xFF) << 8) + (buf[i+12]&0xFF)
int dataStart = offsetStart + offsetSize * 4
int dataEnd = i + ((buf[i+7]&0xFF) << 24) + ((buf[i+6]&0xFF) << 16) + ((buf[i+5]&0xFF) << 8) + (buf[i+4]&0xFF) - 1
//println "chuck start " + i + " offsetStart " + offsetStart + " offsetSize " + offsetSize + " dataStart " + dataStart + " dataEnd " + dataEnd
if(offsetStart < dataStart && dataStart < dataEnd && dataEnd < buf.length){
//println "chuck start " + i
replaceResIdInArscConfigList(buf, offsetStart, offsetSize, dataStart, dataEnd, newPkgId)
i = dataEnd + 1
continue
}
}
i=i+4
}
def outStream = new FileOutputStream(resFile)
outStream.write(buf, 0, buf.length)
outStream.flush()
outStream.close()
}
(注意,这个函数依然需要补充,后面会讲)
首先找到ConfigList的header,以RES_TABLE_TYPE_TYPE开头,考虑字序即0102,然后2byte是头大小,再4byte是块大小,然后就是resType,resType后三个byte是固定的0,所以我们找这样的数据:def replaceResIdInArscConfigList(byte[] buf, int offsetStart, int offsetSize, int dataStart, int dataEnd, int newPkgId) throws Exception
{
//println "offsetStart " + offsetStart + " offsetSize " + offsetSize + " dataStart " + dataStart + " dataEnd " + dataEnd
if(offsetSize == 1){
replaceResIdInArscEntry(buf, dataStart, dataEnd, newPkgId)
}
else{
int lastoffset = dataStart
for(def i = offsetStart + 4; i + 3 < dataStart; i=i+4){
if(buf[i] == -1 && buf[i+1] == -1 && buf[i+2] == -1 && buf[i+3] == -1){
continue
}
int offset = dataStart + ((buf[i+3]&0xFF) << 24) + ((buf[i+2]&0xFF) << 16) + ((buf[i+1]&0xFF) << 8) + (buf[i]&0xFF)
replaceResIdInArscEntry(buf, lastoffset, offset, newPkgId)
lastoffset = offset
}
replaceResIdInArscEntry(buf, lastoffset, dataEnd, newPkgId)
}
}
def replaceResIdInArscEntry(byte[] buf, int entryStart, int entryEnd, int newPkgId){
//println "entryStart " + entryStart + " entryEnd " + entryEnd
if(buf[entryStart] == 0x08 && buf[entryStart+1] == 0x00 && buf[entryStart+2] == 0x00 && buf[entryStart+3] == 0x00){
if(entryStart+15 > entryEnd){
return
}
if(buf[entryStart+8] == 0x08 && buf[entryStart+9] == 0x00 && buf[entryStart+10] == 0x00 && buf[entryStart+11] == 0x01 && buf[entryStart+15] == 0x7F){
buf[entryStart+15] = newPkgId
//println entryStart+15
}
}
if(buf[entryStart] == 0x10 && buf[entryStart+1] == 0x00 && buf[entryStart+2] == 0x01 && buf[entryStart+3] == 0x00){
if(entryStart+15 > entryEnd){
return
}
if(buf[entryStart+11] == 0x7F){
buf[entryStart+11] = newPkgId
//println entryStart+11
}
int size = ((buf[entryStart+15]&0xFF) << 24) + ((buf[entryStart+14]&0xFF) << 16) + ((buf[entryStart+13]&0xFF) << 8) + (buf[entryStart+12]&0xFF)
for(def i = 0; i < size; i++){
if(buf[entryStart+19+i*12] == 0x7F){
buf[entryStart+19+i*12] = newPkgId
//println entryStart+19+i*12
}
if(buf[entryStart+20+i*12] == 0x08 && buf[entryStart+21+i*12] == 0x00 && buf[entryStart+22+i*12] == 0x00 && (buf[entryStart+23+i*12] == 0x01 || buf[entryStart+23+i*12] == 0x02) && buf[entryStart+27+i*12] == 0x7F){
buf[entryStart+27+i*12] = newPkgId
//println entryStart+27+i*12
}
}
}
}
如果以08000000开始则是非bag,以10000000开始则是bag,分别处理。
W/ResourceType: Failed resolving bag parent id 0x7d090062W/ResourceType: Attempt to retrieve bag 0x7d090114 which is invalid or in a cycle.
def getDynamicRef(String pkgName ,int newPkgId){
int typeLength = 2
int headSizeLength = 2
int totalSizeLength = 4
int countLength = 4
int pkgIdLength = 4
def pkgbyte = pkgName.bytes
int pkgLength = pkgbyte.length * 2
if(pkgLength % 4 != 0){
pkgLength += 2
}
if(pkgLength < 256){
pkgLength = 256
}
def pkgBuf = new byte[typeLength + headSizeLength + totalSizeLength + countLength + pkgIdLength + pkgLength]
pkgBuf[0]=0x03
pkgBuf[1]=0x02
pkgBuf[typeLength]=0x0c
pkgBuf[typeLength + 1]=0x00
pkgBuf[typeLength + headSizeLength] = pkgBuf.length & 0x000000ff
pkgBuf[typeLength + headSizeLength + 1] = (pkgBuf.length & 0x0000ff00) >> 8
pkgBuf[typeLength + headSizeLength + 2] = (pkgBuf.length & 0x00ff0000) >> 16
pkgBuf[typeLength + headSizeLength + 3] = (pkgBuf.length & 0xff000000) >> 24
pkgBuf[typeLength + headSizeLength + totalSizeLength]=0x01
pkgBuf[typeLength + headSizeLength + totalSizeLength + countLength] = newPkgId
for(int i = 0; i < pkgbyte.length; i++){
pkgBuf[typeLength + headSizeLength + totalSizeLength + countLength + pkgIdLength + i * 2] = pkgbyte[i]
}
return pkgBuf
}
根据
dynamicRefTable结构,这里我们只加入一组packageId和packageName即可。然后需要修改之前的replaceResIdInArsc函数,补充相关代码,最终这个函数代码如下:
def replaceResIdInArsc(File resFile, int newPkgId, String pkgName) throws Exception
{
def buf = resFile.bytes
def dynamicRefBytes = getDynamicRef(pkgName, newPkgId)
int size = buf.length + dynamicRefBytes.length
buf[4] = size & 0x000000ff
buf[5] = (size & 0x0000ff00) >> 8
buf[6] = (size & 0x00ff0000) >> 16
buf[7] = (size & 0xff000000) >> 24
for(def i = 0; i + 15 < buf.length; ){
if(buf[i] == 0x00 && buf[i+1] == 0x02 && buf[i+8] == 0x7F && buf[i+9] == 0x00 && buf[i+10] == 0x00 && buf[i+11] == 0x00){
//println "packagePosition:" + i
int headSize = ((buf[i+3]&0xFF) << 8) + (buf[i+2]&0xFF)
int pkgSize = ((buf[i+7]&0xFF) << 24) + ((buf[i+6]&0xFF) << 16) + ((buf[i+5]&0xFF) << 8) + (buf[i+4]&0xFF) + dynamicRefBytes.length
buf[i+4] = pkgSize & 0x000000ff
buf[i+5] = (pkgSize & 0x0000ff00) >> 8
buf[i+6] = (pkgSize & 0x00ff0000) >> 16
buf[i+7] = (pkgSize & 0xff000000) >> 24
buf[i+8] = newPkgId
i += headSize
continue
}
if(buf[i] == 0x01 && buf[i+1] == 0x02 && buf[i+9] == 0x00 && buf[i+10] == 0x00 && buf[i+11] == 0x00){
int offsetStart = i + ((buf[i+3]&0xFF) << 8) + (buf[i+2]&0xFF)
int offsetSize = ((buf[i+15]&0xFF) << 24) + ((buf[i+14]&0xFF) << 16) + ((buf[i+13]&0xFF) << 8) + (buf[i+12]&0xFF)
int dataStart = offsetStart + offsetSize * 4
int dataEnd = i + ((buf[i+7]&0xFF) << 24) + ((buf[i+6]&0xFF) << 16) + ((buf[i+5]&0xFF) << 8) + (buf[i+4]&0xFF) - 1
//println "chuck start " + i + " offsetStart " + offsetStart + " offsetSize " + offsetSize + " dataStart " + dataStart + " dataEnd " + dataEnd
if(offsetStart < dataStart && dataStart < dataEnd && dataEnd < buf.length){
//println "chuck start " + i
replaceResIdInArscConfigList(buf, offsetStart, offsetSize, dataStart, dataEnd, newPkgId)
i = dataEnd + 1
continue
}
}
i=i+4
}
def outStream = new FileOutputStream(resFile)
outStream.write(buf, 0, buf.length)
outStream.write(dynamicRefBytes)
outStream.flush()
outStream.close()
}
先创建出dynamicRefTable结构的数据,然后将文件大小增加并重新写回;