模块化后的 Android App 自动构建(二)

1,698 阅读2分钟

接上篇 《模块化后的Android App自动构建(一)》。 
这一篇讲两方面,app的构建 以及分模块后遇见的问题和解决

目前,这两篇提到的脚本都在常态化使用中。如果有同学参考的过程中遇见什么问题,欢迎交流。

二、App的构建

准备条件见上一篇《模块化后的Android App自动构建(一)》

1、主模块的build.gradle配置

sdk 版本类型都统一使用gradle.properties配置的,与子模块保持统一


android {
   compileSdkVersion Integer.parseInt(System.properties['compileSdkVersion'])
    buildToolsVersion System.properties['buildToolsVersion']
 
    defaultConfig {
        minSdkVersion Integer.parseInt(System.properties['minSdkVersion'])
        targetSdkVersion Integer.parseInt(System.properties['targetSdkVersion'])
        ndk{
            abiFilters "x86"
            abiFilters "armeabi"
        }
    }
}


2、Jenkins的配置

在Jenkins上正常new item,基本信息配置没有什么特别,按照自己需要填写即可,三个注意点同上篇文章提到的,就是构建脚本不一样和构建结果处理不一样。

-1484111687387.png

-1484111696390.png

  • 构建脚本跟着三个参数,git地址写死在脚本里。

  • 参数一:${JOB_NAME} 项目名称

  • 参数二:分支名 "develop"

  • 参数三:build type "forTest"


python ${JENKINS_HOME}/workspace/publish/jenkins/shop/build.py ${JOB_NAME}  "develop" "forTest"
  • 构建结果需要提取apk和mapping文件,这里.html不是必须的,只是因为我自己加了findbugs的执行,这是执行结果。 
    outputs/apk/*.apk, outputs/mapping/**/mapping.txt,outputs/*.html

3、构建脚本

这里的脚本,区分了测试包和release发布包,因为发布包需要打多个渠道包,下面主要贴出release包的脚本。主要思路同library的打包:

  • 新建路径sourcecode,把project 根目录下的相关配置复制过来

  • 再pull 主模块的代码,然后gradle 构建

  • 然后通过将以渠道名命名的空文件写入META-INF来打多个渠道包。

  • 最后把构建完成的结果文件(*.apk,mapping.txt)copy到需要的路径


#coding:utf-8
 
#针对不同APP 进行配置
 
GIT_REPO = "git@git.showjoy.net:shopandroid/shopandroid.git"
 
 
 
# 以下配置无需修改
OUTPUTS_PATH = "/var/lib/jenkins/workspace/"
 
GRADL_HOME = "/root/gradle-2.10/bin/"
 
SOURCE_CODE = "sourcecode"
 
BUILD_TYPE = "release"
 
DECODE_PATH = "decode"
 
OUTPUTS_PATH_TMP = "outputs"
 
CONFIG_PATH = "../../shop_gradle_config"
 
 
 
#把某一目录下的所有文件复制到指定目录中 
def copyFiles(sourceDir,  targetDir): 
     for file in os.listdir(sourceDir): 
         sourceFile = os.path.join(sourceDir,  file) 
         targetFile = os.path.join(targetDir,  file) 
         if os.path.isfile(sourceFile): 
             if not os.path.exists(targetDir): 
                 os.makedirs(targetDir) 
             if not os.path.exists(targetFile) or(os.path.exists(targetFile) and (os.path.getsize(targetFile) != os.path.getsize(sourceFile))): 
                     open(targetFile, "wb").write(open(sourceFile, "rb").read()) 
         if os.path.isdir(sourceFile): 
             First_Directory = False 
             copyFiles(sourceFile, targetFile) 
 
def chdir(dir):
    os.chdir(dir)
    print ('当前目录:' + os.getcwd())
 
def getVersionName(path):
    import re
    versionName = ""
    f = open(path)
    for line in f:
        searchObj = re.search( r'versionName "(.*)"', line, re.M|re.I)
        if searchObj:
            versionName = searchObj.group(1) 
            break
    return versionName
 
def timeCost(startTime):
    cost_time  = (time.time() - startTime)
    minutes = cost_time/60
    seconds = cost_time - 60 * minutes
    print ('当前用时 : %d mins %d secs' %(minutes, seconds))
 
 
if __name__ == "__main__":
 
    import shutil
    import sys
    import os
    content = "n"
 
    print sys.path[0]
    chdir(sys.path[0])
 
    import time    
    start_time = time.time()
    print ('start time %f' %start_time)
 
    # 开始读取参数
    if len(sys.argv) < 3:
        print "构建脚本 参数不够2个,第一个是脚本路径,第二个是项目名称,第三是构建分支"
        exit()
 
    if len(sys.argv) >= 4:
        BUILD_TYPE = sys.argv[3]
        pass
 
    OUTPUTS_PATH = OUTPUTS_PATH + sys.argv[1] + "/outputs"
 
    if os.path.exists(OUTPUTS_PATH):
        shutil.rmtree(OUTPUTS_PATH)
 
# git 地址
    branch = sys.argv[2]
    module = GIT_REPO
    print ('代码地址:' + module + ",分支:" + branch)
 
# 获取项目名称
    import re
    MODULE_NAME = re.search(r'/(.*)\.git', GIT_REPO).group(1)
    print "项目名称:" + MODULE_NAME
 
# clone代码
 
    if os.path.exists(SOURCE_CODE):
        shutil.rmtree(SOURCE_CODE)
    os.mkdir(SOURCE_CODE)
    chdir(SOURCE_CODE)
    print ('start to clone codes')
 
    os.system("/usr/bin/git clone -b " + branch + " " + module)
 
# 读取版本号
    versionName = getVersionName(MODULE_NAME + "/publish.gradle")
    print ("版本号为:" + versionName)
 
# 复制gradle配置文件
    chdir(os.path.dirname(os.getcwd()))
    print ('copy files from gradle_config to ' + SOURCE_CODE)
    copyFiles(CONFIG_PATH, SOURCE_CODE)
 
 
# 进入sourcecode folder
    chdir(SOURCE_CODE)
 
 
# 写入setting.gradle
 
    setting_file = open('settings.gradle', "wb")
    setting_file.write("include ':" + MODULE_NAME + "'")
    setting_file.write('\n')
    setting_file.close()
 
    if os.path.exists("outputs"):
        shutil.rmtree("outputs")
    os.mkdir("outputs") 
 
# 开始编译
    print ('start to build apk')
 
    os.system(GRADL_HOME + "gradle clean assemble" + BUILD_TYPE)
 
# 开始打不同的渠道包
    chdir(os.path.dirname(os.getcwd()))
    market = "channels.txt"
 
    if os.path.exists(OUTPUTS_PATH_TMP):
        shutil.rmtree(OUTPUTS_PATH_TMP)
    os.mkdir(OUTPUTS_PATH_TMP) 
 
    OUTPUTS_PATH_TMP = OUTPUTS_PATH_TMP + "/" + versionName
 
    copyFiles(SOURCE_CODE + "/" + MODULE_NAME + "/build/outputs", OUTPUTS_PATH_TMP)
 
    apk_file = OUTPUTS_PATH_TMP + "/apk/" + MODULE_NAME + "-release.apk"
 
    from shutil import copyfile
    print ("read channel from " + market)
    import zipfile
    f = open(market)
    for line in f:
        channel = line
        channel = channel.strip()
        if len(channel) == 0:
            pass
        print ('channel:' + channel + ", version: " + versionName)
 
        new_apk_aligned_name = OUTPUTS_PATH_TMP + "/" + MODULE_NAME + "-" + channel + "-release-" + versionName + ".apk"
        copyfile(apk_file, new_apk_aligned_name)
 
        zipped = zipfile.ZipFile(new_apk_aligned_name, 'a', zipfile.ZIP_DEFLATED)
        empty_channel_file = "META-INF/channel_{channel}".format(channel=channel)
        zipped.write(SOURCE_CODE + "/channel_empty_file.txt", empty_channel_file)
        zipped.close()
 
        timeCost(start_time)
 
    copyFiles(OUTPUTS_PATH_TMP, OUTPUTS_PATH)
 
    shutil.rmtree(SOURCE_CODE)
 
    end_time = time.time()
    print ('end time %f' %end_time)
 
    timeCost(start_time)
 
    print ('打包完成,发布之前请先完成自测')
    

三、分模块后构建遇到的问题和解决

1、子模块在多个app共用,统一跳转协议scheme的设置

拆成多个module后,相应的activity也会写到子module的AndroidManifest.xml里,里面需要配置跳转协议,如:



            
                
 
                
                
 
                
            
        

这里 scheme会写死成 “showjoyshop”。如果这个模块只被一个应用依赖,那并没有什么问题,而如果这个模块被多个应用依赖,那 scheme就需要改成对应应用的 scheme。所以就做了如下了优化:



            
                
 
                
                
 
                
            
        

这个 scheme的值会通过gradle来配置:


android {
    ......
    defaultConfig {
        manifestPlaceholders = [SCHEME: System.properties['scheme']]
    }
}

可是这个配置不能写在子模块的build.gradle里,如果写在子模块的build.gradle那构建出来的aar还是会被替换成当前project的scheme,还是无法被其他App依赖。

所以需要将这个配置放置到主模块的build.gradle里,只有当app 构建的时候,才去替换合并后Androidmanifest.xml里的 scheme

这样在本地开发的时候,还有可能遇到这样的一个问题,这个问题的出现是在只在setting.gradle里  include了module,但是没有在主模块里 compile,就会出现:

-1484113698353.png所以,就需要在本地开发环境里gradle.properties里配置参数libraryBaseGradle指定的的gradle文件里加上scheme的设置:

android {
    ......
    defaultConfig {
        manifestPlaceholders = [SCHEME: System.properties['scheme']]
    }
}

而在构建主机上的gradle.properties里配置参数libraryBaseGradle指定的的gradle文件去掉以上配置。

如此便完美解决了该问题。

赏一个 您的支持是对我最大的鼓励。加班再忙,也要熬夜继续码字分享! 码字辛苦 赏个五毛 支持: 微信支付 支付宝

好人一生平安 谢谢!

微信支付 支付宝

用 [微信] 扫描二维码打赏

用 [支付宝] 扫描二维码打赏