自动化打包资源混淆集成python实践----打包

泪湿孤枕 提交于 2019-12-26 09:30:33

1、自动化打包方案

  1)友盟多渠道多渠道打包

    2)gradle productFlavors系统的条件编译

    3)美团打包

    4)APK文件注释写入渠道号

2、各打包方案简介

       1)友盟多渠道多渠道打包(window 绿色版,gradle版本 现在过时)

        

        原理:拆包分解apk,修改AndroidManifest.xml二进制文件后,再重新打包

        耗时:较短(多渠道打包时,避免了多次dex过程,aapt过程)

        渠道号保存方式:保存在AndroidManifest.xml 文 件 meta 数据中

        不足:较难保证AndroidManifest.xml二进制修改正确,可能会出现兼容性问题;多渠道apk 命名不能预定,需要额外处理重命名apk文件。

     

 

      2).gradle productFlavors系统的条件编译

  

      原理: 每个渠道包,都走完整个打包(appt、混淆、dex,签名)流程

      耗时:长(跟渠道包量成正比)

      渠道号保存方式:保存在AndroidManifest.xml 文 件 meta 数据中

 

     3)美团方案

     

     原理:在APK文件的META-INF目里增加渠道文件

     耗时:快(多个渠道包,只有一次完整打包流程)

     渠道号保存方式:mtchannel_yybcpd文件名中

     

    4)APK文件注释写入渠道号

     

    

 原理:Android 使用的apk包的压缩方式是zip,与zip有相同的文件结构,正确写入 File Comment 部分,不破坏压缩包、不用重新打包前提下,写入自己想要的数据。

 耗时:快

 渠道号保存方式:保存在zip 注释中。

 

3、android 打包流程和签名机制:

  1)android 打包流程

 

   ps:代码混淆在dx 生成dex 之前进行。

    

  2)签名机制

    (1)生成MANIFEST.MF文件 遍历apk包中的所有文件(entry),对非文件夹非签名 文件的文件,逐个生成SHA1的数字签名信息,再用 Base64进行编码

    (2)生成CERT.SF文件 对生成的Manifest,使用SHA1-RSA算法,用私钥进 行签名

    (3)生成CERT.RSA文件 对CERT.SF文件做签名,内容存档(公钥、所采用的加 密算法)到中CERT.RSA

     ps:从第一步规则源码,/build/tools/signapk/SignApk.java ,关键源码代码如下:

     

    从源码中,可以看出空文件夹免签名,并看不出来美团方案怎样规避签名机制。

    从apk安装过程签名校验关键类:PackageParser.java   collectCertificates()方法:

   

 

  从上图可以看空文件夹和META-INFO文件夹中的文件、AndroidManifest.xml免签名检验的,美团就是通过在META-INFO文件夹

      新增渠道号文件,实现多渠道打包,当然不需要进行zipalign操作(通过zipalign -c -v 4 application.apk检验包是否已经对齐)。 

 

4、python实现方案3和方案4(python2.7 windos环境):

  1)思路:1.执行gradle 打包命令

     2.监听gradle 打包进程,打包结束后

      方案3:拷贝apk,对apk进行zipfile 文件操作,在META-INFO文件夹中新增多渠道文件。

      方案4:拷贝apk,对apk添加zip comment.

    

 

  2)python代码实现:

         (1)执行gradle 打包命令     

  cmd = 'gradle assembleInnerRelease ' #假设 builde.gradle productFlavors 中配置inner
  pro = subprocess.Popen(cmd,shell = True)

  (2)监听进程(做了简单的封装)

   

#监听进程 用来监听gradle 打包进程,后文微信资源混淆进程等
def func_listen_process(Process,listener):
    if listener == None or not isinstance(listener,ProcessListener):
        print 'func_listen_process: ' + process + ' listener is null or not processListener interface'
        return
    
    listener.start()
    
    while (True):   
        time.sleep(1)  
        retCode = subprocess.Popen.poll(pro)   
        if retCode is not None:
            listener.end()
            break
        else:
            listener.doing()
#抽象类加抽象方法就等于面向对象编程中的接口  
from abc import ABCMeta,abstractmethod 
#定义接口
class ProcessListener:
    __metaclass__ = ABCMeta #指定这是一个抽象类
    @abstractmethod  #抽象方法
    def start(self):
        pass
    @abstractmethod  #抽象方法
    def doing(self):
        pass
    @abstractmethod  #抽象方法
    def end(self):
        pass

 

   (3)监听gradlle打包进程

#监听gradle 打包进程
func_listen_process(pro,GradlePackProcessListener())
#gradle打包进程监听器        
class GradlePackProcessListener(ProcessListener):
    curTime = 0
    apkCount = 0
    
    def __init__(self):
        return
    def start(self):
        self.curTime = time.time()
    def doing(self):
        return
    def end(self):
     apkCount = func_channelsReleasePack(workSpace)
        self.curTime = time.time() - self.curTime;
        print "\nandroid channels pack cost total time :    " + str(int(self.curTime)) +'s , ' + str(apkCount) + ' apks'

#多渠道打正式包 channelsFile.txt 每行一个渠道号
def func_channelsReleasePack(workSpace):
    apkPath = workSpace + '/build/outputs/apk'
    channelsFile = open(workSpace + '/channelsFile.txt', "r")
    apks = []
    countApk = 0
    global version
    
    
    print u"\nstart channle release pack\n"
    
    while True:  
        channelName = channelsFile.readline() 
        channelName = channelName.strip('\n').strip('\t')
                                
        if channelName:
            channelApkName = apkPath + '/' + apkPrefix + '_' + channelName + '_' + version +'.apk'
            shutil.copyfile(apkPath+'/' + apkPrefix + '_inner_' + ''+ version +'.apk',channelApkName)
            apks.append(apkPrefix + '_' + channelName + '_' + version +'.apk')
            zipped = zipfile.ZipFile(channelApkName, 'a', zipfile.ZIP_DEFLATED)                    
            empty_channel_file = "META-INF/channel_{channel}".format(channel = channelName)
            newChannelFile = open(apkPath+'/channel_'+ channelName, 'w')
            newChannelFile.close()
            zipped.write(apkPath+'/channel_'+ channelName,empty_channel_file)
            zipped.close();
            os.remove(apkPath+'/channel_'+ channelName)
            print '---> /' + apkPrefix +'_' + channelName + '_' + version +'.apk'
            
            countApk = countApk + 1;
            
        else:
            channelsFile.close()
            #删除出多余的文件
            func_delFiles(apkPath,apks)
            break
    print u"\nend channle release pack"
    #func_zipalignApks(apks)
    return countApk;

 

 (4)获取应用获取渠道号(这里只给出方案3渠道号获取方法,方案四渠道号获取方法见一种为 Apk 动态写入信息的方案):

 

/**
     * 获取友盟渠道
     * #app安装后,拷贝保存在/data/app/目录下
     * @return
     */
    public static String getChannel(Context mContext) {
        ApplicationInfo appinfo = mContext.getApplicationInfo();
        String sourceDir = appinfo.sourceDir;
        String ret = "";
        ZipFile zipfile = null;
        try {
            Log.d("sihaixuan", "app source dir : " + sourceDir);
            zipfile = new ZipFile(sourceDir);
            Enumeration<?> entries = zipfile.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = ((ZipEntry) entries.nextElement());
                String entryName = entry.getName();
                Log.d("sihaixuan",entryName + "");
                if (entryName.contains("channel")) {
                    ret = entryName;

                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (zipfile != null) {
                try {
                    zipfile.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        String[] split = ret.split("_");
        if (split != null && split.length >= 2) {
            return ret.substring(split[0].length() + 1);

        } else {
            return "";
        }
    }

 

(5)打包测试

 

 

 5、总结

  

 ps:gradle 定义 task 也可以实现以上方案,新一代Android渠道打包工具:1000个渠道包只需要5秒实现了apk 注释添加渠道号

github:AndroidPackCi

参考资料:

   团Android自动化之旅—生成渠道包

   新一代Android渠道打包工具:1000个渠道包只需要5秒

         友盟渠道打包工具

        Android签名机制之---签名过程详解

       Android源码查看网址http://androidxref.com/

      一种为 Apk 动态写入信息的方案

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!