Android_Jenkins自動(dòng)打包(2021最新版本)_自動(dòng)上傳蒲公英并釘釘通知

一.本文目標(biāo)

Jenkins實(shí)現(xiàn)持續(xù)集成與自動(dòng)打包
自定義gradle打包腳本
自動(dòng)上傳蒲公英并釘釘群通知

二.Jenkins持續(xù)集成與自動(dòng)打包構(gòu)建

一切重復(fù)的工作皆可自動(dòng)化,大廠里面都有自動(dòng)化包構(gòu)建平臺(tái),大多是基于Jenkins持續(xù)集成方案來(lái)定制的.
Jenkins是一個(gè)開(kāi)源軟件項(xiàng)目,是基于Java開(kāi)發(fā)的一種持續(xù)集成工具,使軟件的持續(xù)集成變成可能Jenkins提供數(shù)百個(gè)插件來(lái)支持構(gòu)建,部署和自動(dòng)化任何項(xiàng)目

Jenkins軟件包安裝與服務(wù)管理

  • 安裝:brew install jenkins-lts
  • 開(kāi)啟服務(wù):brew services start jenkins-lts
  • 重啟服務(wù):brew services restart jenkins-lts
  • 升級(jí)服務(wù):brew upgrade jenkins-lts

安裝Homebrew

Mac用戶(hù)需要先安裝Homebrew才能安裝Jenkins,如果已安裝請(qǐng)?zhí)^(guò)

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

將以上命令粘貼至終端即可安裝Homebrew萝玷。

服務(wù)啟動(dòng),這個(gè)過(guò)程首次可能需要4-5分鐘,耐心等待

三.在瀏覽器打開(kāi)http://localhost:8080/

  • 需要安裝一些插件,選擇安裝推薦的插件這個(gè)選項(xiàng)(這個(gè)過(guò)程建議翻墻比較快),過(guò)程需要30分鐘左右
  • 賬戶(hù)創(chuàng)建,這里我們不創(chuàng)建新用戶(hù),點(diǎn)擊右下角的使用admin賬戶(hù)繼續(xù),而admin賬戶(hù)的密碼就存儲(chǔ)在上面的紅色字體文件中


  • 不用改實(shí)例配置,默認(rèn)就好



主頁(yè)面


四.插件安裝--->環(huán)境配置--->創(chuàng)建Job--->獲取蒲公英api_key--->獲取釘釘webhool--->編寫(xiě)gradle打包腳本

1.插件安裝

  • Multiple SCMs plugin -- 多倉(cāng)庫(kù)構(gòu)建
  • Groovy Postbuild -- groovy腳本
  • build user vars plugin -- 獲取當(dāng)前登錄用戶(hù)
  • Upload to pgyer -- apk上傳蒲公英
  • Locale plugin -- 本地語(yǔ)言

Manage Jenkins -->Manage Plugins 去進(jìn)行安裝上述插件,記得安裝完成后重啟服務(wù)

2.環(huán)境配置(jdk,git,gradle,android_sdk)

  • Manage Jenkins -->Global Tool Configuration

配置JDK

配置Git

配置gradle,建議和項(xiàng)目的gradle版本號(hào)保持一致

  • Manage Jenkins -->Configule System

配置android_sdk,下面的Locale記得寫(xiě)上zh_CN,這樣就支持中文了

3.創(chuàng)建Job

  • 新建Item
  • 配置項(xiàng)目
    主頁(yè)--->項(xiàng)目--->配置
    General的基本配置
    選中This project is parameterized,然后添加參數(shù)選擇Choice Parameter,按照下面的格式填寫(xiě)
  • 源碼管理配置
    需要添加項(xiàng)目在github或者gitlab的倉(cāng)庫(kù)url,然后添加私鑰,當(dāng)然也可以用賬號(hào)密碼,這里是私鑰演示

點(diǎn)擊添加按鈕,注冊(cè)憑證,可以直接選擇username-password
也可以選擇私鑰,Mac的私鑰是在 /Users/houyadong/.ssh/id_rsa 文件中

因?yàn)橹黜?xiàng)目是ASProj,然后引用了i-ui項(xiàng)目的一些模塊,這里需要選擇Additional Behaviours添加Check out to a sub-directory,然后添加倉(cāng)庫(kù)的本地子目錄,如果項(xiàng)目是單工程結(jié)構(gòu),這一步可以跳過(guò)

  • 構(gòu)建觸發(fā)器
    可以執(zhí)行周期性的構(gòu)建項(xiàng)目,也可以當(dāng)有新代碼提交的時(shí)候去構(gòu)建,也可以定時(shí)構(gòu)建,這里面我們不需要這些
  • 構(gòu)建環(huán)境
    勾選圖中兩個(gè),在構(gòu)建的時(shí)候在控制臺(tái)中把時(shí)間戳打印上去,登錄的用戶(hù)信息設(shè)置到j(luò)enkins的環(huán)境變量里面
  • 構(gòu)建
    自定義的打包腳本任務(wù),打包之前先clean一遍,然后把堆棧信息給打印出來(lái)

點(diǎn)高級(jí),需要勾選圖中的pass all job parameters as Project properties,然后在輸入框中輸入圖中內(nèi)容
BUILD_TYPE:就是上面創(chuàng)建的debug包還是release包
BUILD_URL:指Jekins本次任務(wù)構(gòu)建的url
JOB_NAME:本次構(gòu)建的任務(wù)的名稱(chēng)
BRANCH_NAME:本次構(gòu)建的倉(cāng)庫(kù)的分支
BUILD_USER:發(fā)起本次構(gòu)建的當(dāng)前用戶(hù)

參數(shù)透?jìng)?這些key,還必須在項(xiàng)目根目錄下的gradle.properties中去同樣定義一份,值無(wú)所謂

4.獲取蒲公英的apiKey

注冊(cè)賬號(hào),然后API信息中獲取

5.釘釘自定義消息格式

  • 創(chuàng)建釘釘群

智能群助手,自定義,復(fù)制粘貼webhook

一定要定義關(guān)鍵詞,釘釘為了安全起見(jiàn),在往這個(gè)webHook發(fā)送消息的時(shí)候,如果這個(gè)消息中不包含設(shè)置的關(guān)鍵詞,釘釘群是收不到消息的.
所以一般設(shè)置關(guān)鍵詞為項(xiàng)目名稱(chēng)然后自定義腳本中 添加 項(xiàng)目名稱(chēng)到消息體中,這樣釘釘就能收到信息了

6.自定義打包腳本上傳apk到蒲公英與釘釘群通知

在項(xiàng)目根目錄下創(chuàng)建jenkins_package.gradle

import groovy.json.JsonSlurper
import java.text.SimpleDateFormat

task packageApk {
    println("BUILD_TYPE:" + BUILD_TYPE)
    File apkFileDir = null
    dependsOn("assemble" + BUILD_TYPE.capitalize())
   if(BUILD_TYPE.equals("release")){
        apkFileDir = new File(project.buildDir, "outputs/apk/release")
    }else{
        apkFileDir = new File(project.buildDir, "outputs/apk/debug")
    }
    doLast {
        def uploadFile = findApkFile(apkFileDir)
        println("uploadFile:" + uploadFile.name)
        uploadApk(uploadFile)
    }
}

def findApkFile(File apkFile) {
    println("apkFile:" + apkFile.name)
    if (apkFile.isDirectory()) {
        def files = apkFile.listFiles()
        for (int i = 0; i < files.length; i++) {
            def findFile = findApkFile(files[i])
            if (findFile != null) {
                return findFile
            }
        }
    } else if (apkFile.name.endsWith(".apk")) {
        return apkFile
    } else return null
}

def uploadApk(File uploadApkFile) {
    // 查找上傳的 apk 文件, 這里需要換成自己 apk 路徑
    println("uploadApk:" + uploadApkFile.absolutePath + "--" + uploadApkFile.exists())
    if (uploadApkFile == null || !uploadApkFile.exists()) {
        throw new RuntimeException("apk file not exists!")
    }
    println "*************** upload start ***************"

    String BOUNDARY = UUID.randomUUID().toString(); // 邊界標(biāo)識(shí) 隨機(jī)生成
    String PREFIX = "--", LINE_END = "\r\n";
    String CONTENT_TYPE = "multipart/form-data"; // 內(nèi)容類(lèi)型

    try {
        URL url = new URL("https://www.pgyer.com/apiv2/app/upload");
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setReadTimeout(30000);
        conn.setConnectTimeout(30000);
        conn.setDoInput(true); // 允許輸入流
        conn.setDoOutput(true); // 允許輸出流
        conn.setUseCaches(false); // 不允許使用緩存
        conn.setRequestMethod("POST"); // 請(qǐng)求方式
        conn.setRequestProperty("Charset", "UTF-8"); // 設(shè)置編碼
        conn.setRequestProperty("connection", "keep-alive");
        conn.setRequestProperty("Content-Type", CONTENT_TYPE + ";boundary=" + BOUNDARY);
        DataOutputStream dos = new DataOutputStream(conn.getOutputStream());

        StringBuffer sb = new StringBuffer();
        sb.append(PREFIX).append(BOUNDARY).append(LINE_END);//分界符
        sb.append("Content-Disposition: form-data; name=\"" + "_api_key" + "\"" + LINE_END);
        sb.append("Content-Type: text/plain; charset=UTF-8" + LINE_END);
        //sb.append("Content-Transfer-Encoding: 8bit" + LINE_END);
        sb.append(LINE_END);
        sb.append("*********************************************");//替換成你再蒲公英上申請(qǐng)的apiKey
        sb.append(LINE_END);//換行!


        if (uploadApkFile != null) {
            /**
             * 當(dāng)文件不為空,把文件包裝并且上傳
             */
            sb.append(PREFIX);
            sb.append(BOUNDARY);
            sb.append(LINE_END);
            /**
             * 這里重點(diǎn)注意: name里面的值為服務(wù)器端需要key 只有這個(gè)key 才可以得到對(duì)應(yīng)的文件
             * filename是文件的名字,包含后綴名的 比如:abc.png
             */
            sb.append("Content-Disposition: form-data; name=\"file\"; filename=\"" + uploadApkFile.getName() + "\"" + LINE_END);
            sb.append("Content-Type: application/octet-stream; charset=UTF-8" + LINE_END);
            sb.append(LINE_END);
            dos.write(sb.toString().getBytes())

            InputStream is = new FileInputStream(uploadApkFile)
            byte[] bytes = new byte[1024];
            int len = 0;
            while ((len = is.read(bytes)) != -1) {
                dos.write(bytes, 0, len);
            }
            is.close();
            dos.write(LINE_END.getBytes());
            byte[] end_data = (PREFIX + BOUNDARY + PREFIX + LINE_END).getBytes();

            dos.write(end_data);
            dos.flush();
            /**
             * 獲取響應(yīng)碼 200=成功 當(dāng)響應(yīng)成功硼被,獲取響應(yīng)的流
             */
            int res = conn.getResponseCode();
            if (res == 200) {
                println("Upload request success");
                BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()))
                StringBuffer ret = new StringBuffer();
                String line
                while ((line = br.readLine()) != null) {
                    ret.append(line)
                }
                String result = ret.toString();
                println("Upload result : " + result);

                def resp = new JsonSlurper().parseText(result)
                println result
                println "*************** upload finish ***************"
                sendMsgToDing(resp.data)
            } else {
                //發(fā)送釘釘 消息--構(gòu)建失敗
            }
        }
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

def sendMsgToDing(def data) {
    def conn = new URL("*********************************************").openConnection()//替換成自己的釘釘webHook的url
    conn.setRequestMethod('POST')
    conn.setRequestProperty("Connection", "Keep-Alive")
    conn.setRequestProperty("Content-type", "application/json;charset=UTF-8")
    conn.setConnectTimeout(30000)
    conn.setReadTimeout(30000)
    conn.setDoInput(true)
    conn.setDoOutput(true)
    def dos = new DataOutputStream(conn.getOutputStream())

    def downloadUrl = "https://www.pgyer.com/" + data.buildShortcutUrl
    def qrCodeUrl = "![](" + data.buildQRCodeURL + ")"
    def detailLink = "[項(xiàng)目地址](${BUILD_URL})"

    def _title = "### 【${JOB_NAME}】構(gòu)建成功"
    def _content = new StringBuffer()
    _content.append("\n\n### ${JOB_NAME}構(gòu)建成功")
    _content.append("\n\n構(gòu)建版本:${BRANCH_NAME}")
    _content.append("\n\n構(gòu)建類(lèi)型:${BUILD_TYPE}")
    _content.append("\n\n下載地址:" + downloadUrl)
    _content.append("\n\n" + qrCodeUrl)
    _content.append("\n\n構(gòu)建用戶(hù):${BUILD_USER}")
    _content.append("\n\n構(gòu)建時(shí)間:" + getNowTime())
    _content.append("\n\n查看詳情:" + detailLink)
    def json = new groovy.json.JsonBuilder()
    json {
        msgtype "markdown"
        markdown {
            title _title
            text _content.toString()
        }
        at {
            atMobiles([])
            isAtAll false
        }
    }

    println(json)
    dos.writeBytes(json.toString())
    def input = new BufferedReader(new InputStreamReader(conn.getInputStream()))
    String line = ""
    String result = ""
    while ((line = input.readLine()) != null) {
        result += line
    }
    dos.flush()
    dos.close()
    input.close()
    conn.connect()
    println(result)

    println("*************** 釘釘消息已發(fā)送 ***************")
}

//獲取當(dāng)前時(shí)間
def getNowTime() {
    def str = "";
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    Calendar lastDate = Calendar.getInstance();
    str = sdf.format(lastDate.getTime());
    return str;
}

在app中的build.gradle中添加

apply from: '../jenkins_package.gradle'

五.Jenkins執(zhí)行打包

項(xiàng)目 -->Build with parameters --> 選擇debug或release -- >開(kāi)始構(gòu)建

可以在控制臺(tái)看打包輸出

打包成功并且上傳到了蒲公英

發(fā)送釘釘群消息

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市喇澡,隨后出現(xiàn)的幾起案子腕侄,更是在濱河造成了極大的恐慌,老刑警劉巖订雾,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肢预,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡洼哎,警方通過(guò)查閱死者的電腦和手機(jī)烫映,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)沼本,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人锭沟,你說(shuō)我怎么就攤上這事抽兆。” “怎么了族淮?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵辫红,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我瞧筛,道長(zhǎng)厉熟,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任较幌,我火速辦了婚禮揍瑟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘乍炉。我一直安慰自己绢片,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布岛琼。 她就那樣靜靜地躺著底循,像睡著了一般。 火紅的嫁衣襯著肌膚如雪槐瑞。 梳的紋絲不亂的頭發(fā)上熙涤,一...
    開(kāi)封第一講書(shū)人閱讀 51,287評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音困檩,去河邊找鬼祠挫。 笑死,一個(gè)胖子當(dāng)著我的面吹牛悼沿,可吹牛的內(nèi)容都是我干的等舔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼糟趾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼慌植!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起义郑,我...
    開(kāi)封第一講書(shū)人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蝶柿,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后非驮,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體只锭,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年院尔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蜻展。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡邀摆,死狀恐怖纵顾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情栋盹,我是刑警寧澤施逾,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站例获,受9級(jí)特大地震影響汉额,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜榨汤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一蠕搜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧收壕,春花似錦妓灌、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至圃验,卻和暖如春掉伏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背澳窑。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工斧散, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人照捡。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓颅湘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親栗精。 傳聞我的和親對(duì)象是個(gè)殘疾皇子闯参,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容