接上篇《模块化后的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里,里面需要配置跳转协议,如:

<activity
            android:name="com.showjoy.shop.module.detail.DetailActivity"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <data
                    android:host="page.sh"
                    android:path="/detail"
                    android:scheme="showjoyshop" />
            </intent-filter>
        </activity>

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

<activity
            android:name="com.showjoy.shop.module.detail.DetailActivity"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <data
                    android:host="page.sh"
                    android:path="/detail"
                    android:scheme="${SCHEME}" />
            </intent-filter>
        </activity>

这个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文件去掉以上配置。

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


标签: none

评论已关闭