利用python对android批量多渠道打包

利用python对android批量多渠道打包

当你完成一个游戏或者应用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文件夹,里面就是所有的渠道包。

python代码展示

# 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

你可能感兴趣的:(android,加密,批量打包,多渠道打包,防二次打包)