当你完成一个游戏或者应用apk开发以后,你要上传到多个平台,此时就要打许多的渠道包,如果一个个打渠道包容易出错,主要是也很麻烦,每次打渠道包其实只需要改动androidmainfest.xml里的部分代码。
比如一般apk包都会接入友盟统计,
<meta-data android:name="UMENG_CHANNEL" android:value="my_channel"/>
我们只需修改my_channel这个值,然后打包就可以了。
然而这就够了吗,你用eclipse打出来的包并不安全,很容易就会被人反编译,然后做简单的修改进行二次打包。于是需要对apk包进行加密。
1.利用google提供的反编译工具apktool对母包反编译
2.利用python动态(正则匹配)修改里面的androidmainfest.xml文件
3.加密反编译目录里的classes.dex和XXXX.RSA文件。
4.再利用apktool回编译成未签名包。
5.使用jarsigner命令对该包签名。
所需工具下载地址:http://pan.baidu.com/s/1hq8kVys
1.首先你得先装python2.7.x
1.首先你得先装python2.7.x
1.首先你得先装python2.7.x
因为很重要,所以要说三遍。
2.其他环境准备,java和android-sdk(既然走到多渠道打包这一步了,这个应该都有的吧。)
3.下载apktool.jar。(这个工具我一会的附件里有)
4.这些东西如何下载和安装我就不说了。
0.下载并解压apkPackage.zip
1.打开apktool目录
2.打开config.txt文件
3.dx_root修改为android SDK中dx路径的位置(以dx结尾)
4.修改store为你自己签名文件位置的绝对路径,并修改对应的alias和password
注:务必使用和你的apk包使用的相同的签名,否则会安装失败
5.打开partten.txt文件
6.逐行增加要修改的渠道ID对应的字串(即android:name=”xxx”,中的xxx,默认即为UMENG_CHANNEL,注:一行只写一个)
7.打开channel.txt文件
8.逐行增加你的渠道ID(即android:value=”yyy”,中的yyy)
注:一行只写一个渠道ID
9.回到上层目录。
10.把你的apk包拖到apkPackage.bat上,过一会会在该目录下生成bin文件夹,里面就是所有的渠道包。
# coding=utf-8
import os
import shutil
import sys
import re
import hashlib
def os_is_win32():
return sys.platform == 'win32'
def add_path_prefix(path_str):
if not os_is_win32():
return path_str
if path_str.startswith("\\\\?\\"):
return path_str
ret = os.path.abspath(path_str)
ret = ret.replace("/", "\\")
return ret
#读取channel.txt
def readChannelfile(filename):
f = file(filename)
while True:
line = f.readline().strip('\n')
if len(line) == 0:
break
else:
channelList.append(line);
f.close()
#读取config.txt,配置签名文件相关信息
def readKeyfile(filename):
f = file(filename)
while True:
line = f.readline().strip('\n')
if len(line) == 0:
break
else:
keyList.append(line);
f.close()
#读取parttern.txt,做正则匹配的准备
def readRegfile(filename):
f = file(filename)
while True:
line = f.readline().strip('\n')
if len(line) == 0:
break
else:
regexList.append(line);
f.close()
def backUpManifest():
if os.path.exists('AndroidManifest.xml'):
os.remove('AndroidManifest.xml')
manifestPath = 'temp/AndroidManifest.xml'
shutil.copyfile(manifestPath, 'AndroidManifest.xml')
#加密dex文件
def encryptDex():
dxPath = keyList[4].split('=')[-1]
if os.path.exists('classes.dex'):
os.remove('classes.dex')
dexPath = 'temp/classes.dex'
shutil.copyfile(dexPath, 'classes.dex')
cmdDex2jar = dxPath + r' --dex --output=dex.jar classes.dex'
os.system(cmdDex2jar)
cmdEnJar = 'Encrypt dex.jar _rf.dat E'
os.system(cmdEnJar)
os.remove('dex.jar')
rtFile = '_rf.dat'
md5M = hashlib.md5()
md5M.update(rtFile)
md5RtFile = md5M.hexdigest()
os.rename(rtFile,md5RtFile)
#shutil.copyfile('sign.bin','temp/assets/sign.bin')
shutil.copyfile(md5RtFile,'temp/assets/' + md5RtFile)
# shutil.copyfile('_rf.dat','temp/assets/_rf.dat')
os.remove(dexPath)
os.remove('classes.dex')
# os.remove('_rf.dat')
#加密RSA签名文件
def encryptSign():
signDir = 'temp/original/META-INF'
signPath = ''
signName = ''
for item in os.listdir(signDir):
if item.find('RSA') > 0:
signPath = signDir + '/' + item
signName = item
print(signPath)
break
# shutil.copyfile(signPath, signName)
cmdReadSign = 'java -jar GetSignInfo.jar ' + signPath
os.system(cmdReadSign)
cmdEnSign = 'Encrypt tmpsign.bin sign.bin E'
os.system(cmdEnSign)
os.remove('tmpsign.bin')
binFile = 'sign.bin'
md5M = hashlib.md5()
md5M.update(binFile)
md5BinFile = md5M.hexdigest()
os.rename(binFile,md5BinFile)
#shutil.copyfile('sign.bin','temp/assets/sign.bin')
shutil.copyfile(md5BinFile,'temp/assets/' + md5BinFile)
# os.remove('sign.bin')
#加密dex和签名,如果不需要则可以注释这一步
def beforeModify():
#复制libs到temp/libs
libsPath = 'libs/armeabi/libexecmain.so'
libPath = 'lib/armeabi/libexecmain.so'
shutil.copyfile('tocopy/'+libsPath, 'temp/' + libPath)
#加密dex和sign
encryptDex()
encryptSign()
#复制dex到temp
if os.path.exists('temp/classes.dex'):
os.remove('temp/classes.dex')
manifestPath = 'tocopy/classes.dex'
shutil.copyfile(manifestPath, 'temp/classes.dex')
#主要部分,读取AndroidManifest.xml文件,并利用正则表达式匹配动态修改里面的渠道id
def modifyChannel(value):
tempXML = ''
flagApp = False
f = file('AndroidManifest.xml')
appb = -1
appe = -1
appP = ''
to_run_act = '\r\t\t<meta-data android:name=\"TO_RUN_ACTIVITY\" android:value=\"android.app.Application\"/>\n'
for line in f:
# if line.find('channel_value') > 0:
# line = line.replace('channel_value', value)
tempXML += line
# 加密后需改动启动包名为com.shell.AppApplication
if not flagApp:
if appb < 0:
appb = tempXML.find('<application')
if appb > 0:
appe = tempXML.find('>',appb)
if appe > 0:
flagApp = True
tempApp = tempXML[appb:appe] + '>'
namePos = tempApp.find('android:name')
if namePos > 0:
nameB = tempApp.find('\"',namePos)
if nameB > 0:
nameE = tempApp.find('\"',nameB+1)
if nameE > 0:
appP = tempApp[nameB+1:nameE]
tempApp = tempApp.replace(appP,'com.shell.AppApplication')
to_run_act = to_run_act.replace('android.app.Application',appP)
else:
tempApp = tempApp.replace('>',' android:name=\"com.shell.AppApplication\">')
tempXML = tempXML[0:appb] + tempApp + to_run_act
f.close()
# 两个可能需要正则匹配的字串
patten = r'<meta-data android:name="%s" android:value="%s"/>'
pattend = r'<meta-data android:value="%s" android:name="%s"/>'
# 正则替换为渠道id
for regx in regexList:
tempXML = re.sub(patten % (regx,".*"),patten % (regx,value),tempXML)
tempXML = re.sub(pattend % (".*",regx),pattend % (value,regx),tempXML)
output = open('temp/AndroidManifest.xml', 'w')
output.write(tempXML)
output.close()
#重新打包apk文件并签名
unsignApk = r'%s/%s_%s_unsigned.apk'% (output_apk_dir,easyName, value)
cmdPack = r'java -jar apktool.jar b temp -o %s'% (unsignApk)
os.system(cmdPack)
print("unsigned apk build ok")
signedjar = r'%s/%s_%s.apk'% (output_apk_dir,easyName, value)
unsignedjar = r'%s/%s_%s_unsigned.apk'% (output_apk_dir,easyName, value)
cmd_sign = r'jarsigner -verbose -keystore %s -storepass %s -keypass %s -digestalg SHA1 -sigalg MD5withRSA -tsa https://timestamp.geotrust.com/tsa -signedjar %s %s %s'% (keystore, storepass, alianpass,signedjar, unsignedjar, alianame)
os.system(cmd_sign)
os.remove(unsignedjar);
channelList = []
keyList = []
regexList = []
apkName = sys.argv[1]
rtFile = '_rf.dat'
md5M = hashlib.md5()
md5M.update(rtFile)
md5RtFile = md5M.hexdigest()
binFile = 'sign.bin'
md5M = hashlib.md5()
md5M.update(binFile)
md5BinFile = md5M.hexdigest()
print(apkName)
easyName = os.path.basename(apkName).split('.apk')[0]
print(easyName)
readKeyfile('config.txt')
keystore=keyList[0].split('=')[-1]
storepass=keyList[1].split('=')[-1]
alianame=keyList[2].split('=')[-1]
alianpass=keyList[3].split('=')[-1]
output_apk_dir="../bin"
if os.path.exists(output_apk_dir):
shutil.rmtree(output_apk_dir)
readChannelfile('channel.txt')
print '-------------------- start --------------------'
#反编译apk到temp目录
cmdExtract = r'java -jar apktool.jar d -f -s %s -o temp'% (apkName)
os.system(cmdExtract)
backUpManifest()
beforeModify()
readRegfile('parttern.txt')
for channel in channelList:
modifyChannel(channel)
if os.path.exists('temp'):
shutil.rmtree('temp')
if os.path.exists('AndroidManifest.xml'):
os.remove('AndroidManifest.xml')
if os.path.exists(md5RtFile):
os.remove(md5RtFile)
if os.path.exists(md5BinFile):
os.remove(md5BinFile)
print '-------------------- Done --------------------'
如果不需要加密,可以注释beforeModify()。
写在最后,如何对apk包加密是个需要深入研究的问题,这里就不贴源代码了。
如有疑问欢迎加Q:546551349交流,你猜我猜不猜。
当然也可以加群交流:296733909