利用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-8import osimport shutilimport sysimport reimport hashlibdef 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.txtdef 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文件,并利用正则表达式匹配动态修改里面的渠道iddef 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

更多相关文章

  1. Android应用程序开发以及背后的设计思想深度剖析(1)
  2. Android(安卓)Studio JNI开发-1-引入第三方so文件
  3. Android(安卓)教程:如何刷入通讯基带
  4. 【Android(安卓)如何置底一个View(附 前置声明layout布局文件中的
  5. Android(安卓)应用瘦身,从 18MB 到 12.5MB
  6. Android虚拟机DVM和JAVA虚拟机JVM的区别总结
  7. Android中so使用知识和问题总结以及插件开发过程中加载so的方案
  8. Android下实现图片缓存的实例
  9. Android中AES加密解密。解决密文不唯一、解密不出明文问题

随机推荐

  1. 五大布局-----FrameLayout帧布局
  2. 简解selector的几个属性
  3. ArrayMap是如何提高内存的使用效率的?
  4. Android软键盘显示模式及打开和关闭方式(
  5. Android布局属性介绍
  6. 详解 Android(安卓)的 Activity 组件
  7. Android实现导航菜单左右滑动效果
  8. Android开发学习之Animation之Android帧
  9. Android的多语言文件转IOS多语言文件格式
  10. C#开发Android