需求背景

得益于360加固的强大且免费,以及在Linux平台下良好的易用性,在过去的三年多时间里,一直在使用Jenkins自动化构建应用的同时进行应用加固,导致产生了3000多条加固记录。官网平台每一页只显示5条,而且不支持批量删除。如果需要手动删除这些记录,除了需要耐心,还需要花费大量的时间。

注意事项
  1. 由于需要实现的功能单一,没有必要实现自动登陆的功能
  2. 使用之前需要先在网页端手动登陆后将cookie复制到脚本中
github地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/usr/bin/python
# -*- coding: UTF-8 -*-

import sys
import requests

del_app_url = 'http://jiagu.360.cn/web/apk/del'
app_list_url = 'http://jiagu.360.cn/web/apk/list'

total_count = 1
# 浏览器登录360加固平台,从控制台复制出cookies,填写到下面单引号中
cookies = dict(cookies_are='')


def del_app(app):
params = {'apkMd5': app['apkMd5']}
result = requests.get(del_app_url, cookies=cookies, params=params).json()
name = app['name'].encode("utf-8")
del_result = result['errMsg'] if result['errCode'] == 0 else result['data']
print ('删除%s加固记录%s' % (name, del_result.encode("utf-8")))


def get_app_list():
params = dict()
params['limit'] = 10
params['offset'] = 0
params['name'] = ''
params['fileName'] = ''
return requests.get(app_list_url, cookies=cookies, params=params).json()


def del_jiagu_history():
if not cookies['cookies_are']:
print '请先在浏览器中登陆后复制cookie到字典中'
sys.exit()

global total_count
while total_count > 0:
result = get_app_list()
if result['errCode'] == 0:
total_count = result['data']['count']
app_list = result['data']['list']
for app in app_list:
del_app(app)
else:
print result['errMsg']
print 'Deleting completed'


del_jiagu_history()

周六的早晨,刷到一条有趣的博文。原文如下:

有关生日悖论的词条

这是一个很有趣的一个问题,自己就用Kotlin模拟了一遍。

假设有10000000个班级,每个班级人数为23人:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.birthdayparadox

import java.util.*

class BirthdayParadox {
companion object {
private const val classNum = 10000000
private const val studentNum = 23

@JvmStatic
fun main(args: Array<String>) {
var equalTimes = 0
for (i in 0 until classNum) {
val randomArray =
createRandomArray(studentNum)
if (isSameElementInArray(randomArray)) {
equalTimes++
}
}
val percent = equalTimes * 1.0f / classNum * 100
println("学生人数为 $studentNum$classNum 个班级中生日相同的概率为:$percent%")
}

private fun createRandomArray(size: Int): IntArray {
val ia = IntArray(size)
for (i in 0 until size) {
ia[i] = Random().nextInt(365)
}
return ia
}

private fun isSameElementInArray(array: IntArray): Boolean {
if (array.isEmpty()) {
return false
}
for (i in array) {
if (array.indexOf(i) != array.lastIndexOf(i)) {
return true
}
}
return false
}
}
}

输出结果显示生日相同的概率为:50.724857%,验证了生日悖论的说法。

关于这个问题,我们可以在深入一点。比如班级中3位同学生日相同的概率是多少?4位呢?5位又是多少呢?

假设有10000000个班级,每个班级人数为55人:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.birthdayparadox

import java.util.*

class BirthdayParadox2 {
companion object {
private const val classNum = 10000000
private const val studentNum = 55

@JvmStatic
fun main(args: Array<String>) {
val map = mutableMapOf<Int, Int?>()

for (i in 0 until classNum) {
val randomArray =
createRandomArray(studentNum)
val sameCountSet =
getSameElementCountInArrayToSet(randomArray)
for (j in sameCountSet) {
for (m in 2 until studentNum) {
if (j == m) {
map[m] = if (map[m] == null) 1 else map[m]!!.plus(1)
}
}
}
}

println("学生人数为 $studentNum$classNum 个班级中")
for (entry in map) {
entry.value?.run {
val percent = entry.value!!.toFloat() / classNum * 100
println("有 ${entry.key} 人相同的生日概率是:$percent%")
}
}
}

private fun createRandomArray(size: Int): IntArray {
val intArray = IntArray(size)
for (i in 0 until size) {
intArray[i] = Random().nextInt(365)
}
return intArray
}

private fun getSameElementCountInArrayToSet(array: IntArray): Set<Int> {
val arrayToSet = array.toSet()
val frequencyList = mutableListOf<Int>()
for (i in arrayToSet) {
val frequency = Collections.frequency(array.toMutableList(), i)
frequencyList.add(frequency)
}
return frequencyList.toSet().filter { i -> i > 1 }.toSortedSet()
}
}
}

输出结果如下:

1
2
3
4
5
6
7
学生人数为 55 的 10000000 个班级中
有 2 人相同的生日概率是:98.19454%
有 3 人相同的生日概率是:15.88791%
有 4 人相同的生日概率是:0.60795003%
有 5 人相同的生日概率是:0.016490001%
有 6 人相同的生日概率是:4.3999997E-4%
有 7 人相同的生日概率是:2.0E-5%

GitHub地址

  • 录制人员:杨凡
  • 录制时间:2018年5月6日

歪脸网Jenkins持续开发平台搭建于2017年3月,至今经过多次优化,已经达到了生产环境的要求。本视频主要针对Jenkins持续开发平台下的Android动态打包、360加固、蒲公英上传分发做一个简单的介绍。


点击去Youtube中观看高清视频


简单介绍

蒲公英官方网址 https://www.pgyer.com/

蒲公英平台可以让开发者和企业将应用上传到网站,生成安装链接和二维码用户在手机上打开安装链接,或扫码二维码,即可开始安装!

因此,这款upload-pgyer的Jenkins插件可以让开发者将apk/ipa文件上传到蒲公英平台!并且这款插件可以将蒲公英平台返回的应用信息解析后注入到Jenkins的全局变量中,这样你就可以很方便的在其他构建步骤中使用这些返回的信息,你可以在Jenkins的job配置页面的构建构建后操作这两个操作中点击添加构建步骤选择upload to pgyer with apiVx

界面截图

参数介绍

需要填写的字段 字段的解释
pgyer uKey (必填) 用户Key,用来标识当前用户的身份,
对于同一个蒲公英的注册用户来说,这个值在固定的。
点击获取_ukey
pgyer api_key (必填) API Key,用来识别API调用者的身份,
如不特别说明,每个接口中都需要含有此参数。
对于同一个蒲公英的注册用户来说,这个值在固定的。
点击获取_api_key
scandir (必填) 需要上传的apk/ipa文件所在的文件夹或者父文件夹,
当前默认路径是${WORKSPACE},它代表了当前项目的绝对路径。
这个功能的实现使用了ant框架的DirectoryScanner类,点击查看DirectoryScanner类
这个字段就是DirectoryScanner类中的basedir方法的参数点击查看basedir方法
file wildcard (必填) 需要上传的apk/ipa文件的名字,支持通配符,
就像这样: */Test?/.apk,
这个功能的实现使用了ant框架的DirectoryScanner类,点击查看DirectoryScanner类
这个字段就是DirectoryScanner类中的includes方法的参数,点击查看includes方法
installType (选填) 应用安装方式,值为(1,2,3)。
1:公开,2:密码安装,3:邀请安装。
默认为1公开
password (选填) 设置App安装密码,如果不想设置密码,请传空字符串,或不传。
updateDescription (选填) 版本更新描述,请传空字符串,或不传。
qrcodePath (选填) 如果你需要下载蒲公英返回的二维码,那么这里填写二维码的存储路径,
如果你不需要下载,那么你不需要在这里填写任何内容。
envVarsPath (选填) 如果你想存储蒲公英返回的上传信息,那么这里填写保存信息的文件路径,
如果你不需要保存,那么你不需要在这里填写任何内容。

运行截图

当你的应用上传成功后,在Jenkins中你就能看到上面图片中的信息。同时,你就可以在其他构建步骤中使用蒲公英返回来的信息,例如我的经验:

蒲公英APIV1 返回字段说明

可以使用的环境变量 作用或解释
appKey App Key
appType 应用类型(1:iOS; 2:Android)
appIsLastest 是否是最新版(1:是; 2:否)
appFileSize App 文件大小
appName 应用名称
appVersion 版本号
appVersionNo 适用于Android的版本编号,iOS始终为0
appBuildVersion 蒲公英生成的用于区分历史版本的build号
appIdentifier 应用程序包名,iOS为BundleId,Android为包名
appIcon 应用的Icon图标key,访问地址为 http://o1wh05aeh.qnssl.com/image/view/app_icons/[appIcon]
appDescription 应用介绍
appUpdateDescription 应用更新说明
appScreenshots 应用截图的key,获取地址为 http://o1whyeemo.qnssl.com/image/view/app_screenshots/[appScreenshots]
appShortcutUrl 应用短链接
appCreated 应用上传时间
appUpdated 应用更新时间
appQRCodeURL 应用二维码地址
appPgyerURL 应用主页地址
appBuildURL 本次上传的应用主页

蒲公英APIV2 返回字段说明

可以使用的环境变量 作用或解释
buildKey Build Key是唯一标识应用的索引ID
buildType 应用类型(1:iOS; 2:Android)
buildIsFirst 是否是第一个App(1:是; 2:否)
buildIsLastest 是否是最新版(1:是; 2:否)
buildFileSize App 文件大小
buildName 应用名称
buildVersion 版本号, 默认为1.0 (是应用向用户宣传时候用到的标识,例如:1.1、8.2.1等。)
buildVersionNo 上传包的版本编号,默认为1 (即编译的版本号,一般来说,编译一次会变动一次这个版本号, 在 Android 上叫 Version Code。对于 iOS 来说,是字符串类型;对于 Android 来说是一个整数。例如:1001,28等。)
buildBuildVersion 蒲公英生成的用于区分历史版本的build号
buildIdentifier 应用程序包名,iOS为BundleId,Android为包名
buildIcon 应用的Icon图标key,访问地址为 https://www.pgyer.com/image/view/app_icons/[应用的Icon图标key]
buildDescription 应用介绍
buildUpdateDescription 应用更新说明
buildScreenShots 应用截图的key,获取地址为 https://www.pgyer.com/image/view/app_screenshots/[应用截图的key]
buildShortcutUrl 应用短链接
buildCreated 应用上传时间
buildUpdated 应用更新时间
buildQRCodeURL 应用二维码地址
appPgyerURL 应用主页地址
appBuildURL 本次上传的应用主页

Change Log

版本 1.31(2018-05-07)

  • 升级Gson 2.8.4
  • 优化上传日志
  • 更换readme.md图片地址

版本 1.30(2018-04-16)

  • 移除Jsoup,并引入okhttp作为网络库
  • 增加文件上传进度
  • 增加蒲公英APIV2

A simple introduction

中文文档

Pgyer’s official website is https://www.pgyer.com/

Pgyer can upload the application to the site, generate installation link and qr code user to open the installation link, or scan code qr code, can start installation.

So this plugin can be uploaded to the pgyer platform!And it can put the fields returned by pgyer into an environment variable, which you can use in other build steps, You can select upload to pgyer by adding build steps or adding post-build steps.

Screenshot

Introduction to parameters

field explanation
pgyer uKey (Required) User Key, used to identify the current user’s identity,
for the same pgyer registered users, the value of the fixed!
Click to get pgyer uKey
pgyer api_key (Required) API Key, used to identify the identity of the API caller,
if not specified, each interface needs to contain this parameter.
For the same pgyer registered users, this value is fixed.
Click to get pgyer api_key
scandir (Required) need to upload ipa or apk file base dir path!
The default is ${WORKSPACE}, It means the path of the current project!
It is using ant’s DirectoryScanner class, click to see DirectoryScanner class
It is equivalent to the parameters of the basedir method in the DirectoryScanner class! click to see basedir method
file wildcard (Required) need to upload ipa or apk file name, Support wildcards,
like this: /Test?/*.apk
It is using ant’s DirectoryScanner class, click to see DirectoryScanner class
It is equivalent to the parameters of the includes method in the DirectoryScanner class!** click to see includes method
installType (Optional) application installation, the value is (1,2,3).
1: public,
2: password installed,
3: invitation to install.
The default is 1 public!
password (Optional) set the App installation password, if you do not want to set the password, please pass empty string, or not pass.
updateDescription (Optional) version update description, please pass empty string, or not pass.
qrcodePath (Optional) If you need to download the qrcode, please enter the save path of the qrcode!otherwise, not download!
envVarsPath (Optional) if you need to save info, please enter save file path! otherwise, not save!

Running log

When it runs successfully, you can use the environment variables that are used! for example:

Pgyer apiV1 returns a description of the field

environment variables explanation
appKey App Key
appType Application type (1:iOS; 2: Android)
appIsLastest Is it the latest version (1: yes; 2: no)
appFileSize App file size
appName App Name
appVersion App Version
appVersionNo For Android version Numbers, iOS is always 0
appBuildVersion pgyer builds build Numbers that distinguish historical versions
appIdentifier Application package name, iOS for BundleId, Android for package name
appIcon Application the icon of the key, get the address http://o1wh05aeh.qnssl.com/image/view/app_icons/[appIcon]
appDescription Introduction to the Application
appUpdateDescription Application update description
appScreenshots Application the screenshot of the key, get the address http://o1whyeemo.qnssl.com/image/view/app_screenshots/[appScreenshots]
appShortcutUrl Application short links
appCreated Application upload time
appUpdated Application update time
appQRCodeURL Application the qr code address
appPgyerURL Application pgyer url
appBuildURL Application build pgyer url

Pgyer apiV2 returns a description of the field

environment variables explanation
buildKey Build Key is the only index ID that identifies the application
buildType Application Type(1:iOS; 2:Android)
buildIsFirst Is it the first App? (1: Yes; 2: No;)
buildIsLastest Is it the newest? (1: Yes; 2: No;)
buildFileSize The size of App
buildName App Name
buildVersion The default is 1.0 (is the logo that to advertise the application, for example: 1.1, 8.2.1, etc.).
buildVersionNo The version number of the uploaded package, the default is 1 (that is, the compiled version number, in general, the compiler will change once the version number. For iOS, is a string type; for Android it is an integer. For example: 1001 , 28 etc.)
buildBuildVersion pgyer builds build Numbers that distinguish historical versions
buildIdentifier Application package name, iOS for BundleId, Android for package name
buildIcon Icon Key of application, URL is https://www.pgyer.com/image/view/app_icons/[Icon Key of application]
buildDescription Application Description
buildUpdateDescription Application Update Description
buildScreenShots Application screenshots key, the address is https://www.pgyer.com/image/view/app_screenshots/[Application screenshots key]
buildShortcutUrl App Download Url
buildCreated App Upload time
buildUpdated App Update time
buildQRCodeURL App QR code Url
appPgyerURL Application pgyer url
appBuildURL Application build pgyer url

Change Log

Version 1.31(2018-05-07)

  • Upgrade gson 2.8.4
  • Optimize upload log
  • Change the document image address.

Version 1.30(2018-04-16)

  • Remove jsoup, and import okhttp that be used network
  • Add upload file progress
  • Add pgyer api v2

须知2333
  • 在移动平台使用Jenkins持续集成,极大的方便了开发人员和测试人员之间的工作配合。是极大的,是极大的,是极大的。
  • 蒲公英作为一个优秀的集应用分发和测试的平台,方便了在开发阶段和测试阶段应用的分发繁琐问题,并且保证了测试包的统一性和安全性。
  • But,目前蒲公英官方没有提供Jenkins相关插件!!!蒲公英官方提供的开发API中也只是简单的用crul做了一个示例!如果官方提供的方法,那么提取二维码,获取应用短连接等其他功能的实现就相对来说比较麻烦。
  • So,如果定制化较强,那么上传后蒲公英返回的结果是很重要的。我们可以根据返回的信息获取二维码链接,应用短连接,版本信息等如下信息:
  • 例如,我们可以在Jenkins中使用Description Setter Plugin插件将应用二维码显示到构建历史栏目中,这样测试人员用手机扫码下载即可测试最新的开发任务。如下图:
  • So,既然官方不提供上传插件,那我们就自己动手吧!动手吧!动手吧!
定制需求
  • 控制台可以输出帮助信息
  • 实现APP上传到蒲公英平台,这是最基本的
  • 将二维码下载到本地
  • 上传文件路径要支持路径通配
  • 将蒲公英返回的信息保存到本地,方便以后扩展和查看上传详情
  • 友好的日志提示
  • 全面的异常处理
实现需求

选择开源框架Jsoup,Gson。Jsoup虽然最擅长的是解析Html但是内部封装了网络传输,可以方便的提交Post参数和文本;Gson可以快速解析蒲公英返回的信息!

  • 帮助信息实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    private static void printHelpInfo() {
    System.out.println("参数说明请参考:https://www.pgyer.com/doc/api#uploadApp");
    System.out.println("java -jar <uKey> <_api_key> <file> <qrcode> [installType] [password]");
    System.out.println(" uKey: (必填) 用户Key");
    System.out.println(" _api_key: (必填) API Key");
    System.out.println(" file: (必填) 需要上传的ipa或者apk文件");
    System.out.println(" qrcode: (必填) 上传到蒲公英后二维码图片存储的路径,绝对路径");
    System.out.println(" installType: (选填) 应用安装方式,值为(1,2,3)。1:公开,2:密码安装,3:邀请安装。默认为1公开");
    System.out.println(" password: (选填) 设置App安装密码,如果不想设置密码,请传空字符串,或不传\n");
    }
  • 上传文件到蒲公英实现代码片段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Document doc = Jsoup.connect(UPLOAD_URL)
    .ignoreContentType(true)
    .data("uKey", uKey)
    .data("_api_key", _api_key)
    .data("file", uploadFile.getName(), is)
    .data("installType", installType)
    .data("password", password)
    .timeout(1000 * 60 * 10)
    .post();
  • 下载二维码实现方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public static File download(String urlString, String savePath, String fileName) {
    try {
    File sf = new File(savePath);
    if (!sf.exists()) sf.mkdirs();
    String filePath = savePath + File.separator + fileName;

    URL url = new URL(urlString);
    URLConnection con = url.openConnection();
    con.setConnectTimeout(60 * 1000);
    InputStream is = con.getInputStream();

    byte[] bs = new byte[1024 * 8];
    int len;

    OutputStream os = new FileOutputStream(filePath);
    while ((len = is.read(bs)) != -1) {
    os.write(bs, 0, len);
    }
    os.close();
    is.close();
    return new File(filePath);
    } catch (Exception e) {
    // e.printStackTrace();
    System.out.println("图片下载失败:" + e.getMessage() + "\n");
    return null;
    }
    }
  • 文件上传文件路径通配找出具体的上传文件实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    private static String findFile(String file) {
    if (StringUtil.isBlank(file)) return null;
    if (!file.contains("*")) return file;

    String dirPath = file.substring(0, file.lastIndexOf("/"));
    String[] keys = file.substring(file.lastIndexOf("/") + 1).split("\\*");
    String[] files = new File(dirPath).list(new FilenameFilter() {
    @Override
    public boolean accept(File dir, String name) {
    boolean ok = true;
    for (String key : keys) {
    ok &= key.equals("") || name.contains(key);
    }
    return ok;
    }
    });
    return files == null || files.length == 0 ? null : files[0];
    }
  • 以上是一些重要步骤的实现,其他实现请查阅代码

后续优化事项
  • 上传文件进度
  • 下载二维码进度
Talk is cheap, show me the code

Jenkins简单介绍
  • Jenkins官网,其安装配置过程在此不介绍
  • Jenkins是基于Java开发的一种持续集成工具,用户监控持续重复的工作
  • 持续的软件版本发布/测试项目
  • 监控外部调用执行的工作
360jiagu简单介绍
  • 360加固保官网
  • 免费!免费!!免费!!!
  • 移动市场如火如荼,应用安全也是每个公司非常重视的问题
  • 360作为国内甚至国际上优秀的互联网安全公司出品了自己的Android应用加固服务
  • 盗版监测,崩溃日志分析,应用升级,数据分析,消息推送也是加固保附带的功能

官网上加固助手只提供了PC和Mac版本,如果需要Linux版本的,请联系客服
我自己就是通过邮件的方式索取了Linux版本的加固助手,感谢360

fireline(火线)简单介绍
  • fireline官网
  • 提供静态代码检查相关功能,同样也是360旗下的产品
  • APP安全检查,代码规范检查,内存泄露检查,日志输出检查,空指针检查,多线程检查
  • 类似的产品还有FindBugs, Checkstyle, error-prone, PMD
蒲公英简单介绍
在Jenkins中使用360加固
  • 在Jenkins中打开项目设置
  • 构建操作中点击新增构建步骤,选择Execute shell
  • 在Command中输入以下内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    # Java环境变量,请替换为本机实际路径
    JAVA_HOME="/opt/jdk/bin"

    # 加固保环境变量,请替换为本机实际路径
    JIAGU_HOME="/home/dafan/mount1/360jiagu/jiagu/jiagu"

    # 登录360所需要的账户名和密码,请替换为自己的账号密码
    USER="360账号"
    PSWD="360密码"

    # 签名相关信息,请替换为实际签名的信息
    release_keyAlias="签名别名"
    release_keyPassword="文件密码"
    release_storeFile="签名文件路径"
    release_storePassword="签名密码"

    # 登录360
    $JAVA_HOME/java -jar $JIAGU_HOME/jiagu.jar -login $USER $PSWD

    # 配置增强服务,可选:-update -x86 -carshlog,可多选,如下配置:
    $JAVA_HOME/java -jar $JIAGU_HOME/jiagu.jar -config -x86 -crashlog

    # 配置签名信息,用于加完完成后的自动重签名
    $JAVA_HOME/java -jar $JIAGU_HOME/jiagu.jar -importsign $release_storeFile $release_storePassword $release_keyAlias $release_keyPassword

    # 加固,支持通配符
    $JAVA_HOME/java -jar $JIAGU_HOME/jiagu.jar -jiagu ${PWD}/app/build/outputs/apk/*_release.apk ${PWD}/app/build/outputs/apk/ -autosign
    • 需要申请注册360账号
    • 加固过程会破坏应用原有签名,所以加固完成后需要重新签名
    • 注意替换上述脚本中的相关账号、密码和相关环境变量路径
将加固后的应用上传到蒲公英平台
  • 蒲公英官方提供了API上传的接口,却没有提供Jenkins插件
  • 使用官方提供的curl方法上传后获取应用二维码地址等相关信息是比较麻烦的
  • 鉴于此,我自己用Java写了一个上传框架,详细使用方法请点击upload-pgyer
  • 在上述Command的最后面接着输入以下内如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
      # 上传到蒲公英的环境变量,请替换为本机真实路径
    PGYER_HOME="/home/dafan/mount1/pgyer"

    #上传到蒲公英平台,请替换蒲公英key和二维码下载路径
    $JAVA_HOME/java -jar $PGYER_HOME/upload-pgyer.jar \
    蒲公英uKey 蒲公英_api_key \
    ${PWD}/app/build/outputs/apk/*_release_*_jiagu_sign.apk \
    ${JENKINS_HOME}/jobs/${JOB_BASE_NAME}/builds/${BUILD_NUMBER}/archive/app/build/outputs/apk/qrcode_release_${branch}_${BUILD_NUMBER}.png \
    2 \
    ms666666
    • 注意替换你自己的相关路径
    • UploadPgyer.jar 目前支持单个*的文件路径通配
总结

此文只是针对Jenkins平台上Android使用加固和上传到蒲公英并将二维码下载到本地提供了些许思路,实践过程中根据不同的环境具体有不同的配置方法,请大家灵活运用。在具体的实践过程中,如果有不明白的地方可以微信扫描下方二维码加我好友,大家一起商量讨论。

使用前须知:
  • Glide是一个专注于平滑滚动的图片加载和缓存库。
  • 项目中如果用到Glide去加载和缓存图片,那么下载这些图片又应该怎么处理呢?
  • 一种解决就是使用本库所提供的使用GlideDown去下载图片
  • 所以本库依赖于Glide 2.8版本
  • 好处是如果有缓存,则优先使用缓存来下载图片,避免重复的网络请求
  • 关于本库使用到的一下权限,需要开发者在6.0+的手机上自行动态申请

    1
    2
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
除了下载图片还能做什么?
  • 提供4中方式添加存储路径和图片的网络地址
  • 如果不想提供下载图片后保存的文件名,可以只提供下载存储的文件夹路径
  • 可以设置下载后的图片显示在系统相册中
  • 可以下载过程中取消下载任务
  • 下载多图,如果其中一张下载设置,可以设置是否中断后续下载任务
  • 设置是否提供下载过程中的日志显示
  • 目标文件已存在的话,可以设置是否覆盖目标文件
  • 提供图片下载进度对话框,实时显示下载进度
  • 提供必要的回调接口,开发者可以自定义处理各种事件
怎么引入到自己项目中:
  • 主项目build.gradle中的repositories节点下添加如下代码

    1
    maven { url "http://nexus.iamfan.cn/repository/android/" }
  • 在需要引入的moudle中的build.grade中的dependencies节点下添加如下代码

    1
    compile('ren.helloworld:glidedown:1.14@aar') { transitive = true }
提供的方法及解释:
  • GlideDown.class类所提供的 :
方法名 解释
down(Map<String,String>map) 添加存储路径和图片网络地址的Map<savePath,downUrl>集合
down(String saveDirPath,List<String> downUrls) 添加存储图片的文件夹路径和图片网络地址集合List<downUrls>
down(List<String> savePaths, List<String> downUrls) 添加存储路径集合和图片网络地址集合
down(String savePath, String downUrl) 添加存储路径和图片网络地址
showDialog(boolean isShowDialgo) 下载过程中是否显示进度条对话框,默认为false
showDialog(String dialogTitle) 下载过程中显示进度条对话框,并设置对话框的标题
cancelButton(boolean cancelButton) 下载过程中是否显示取消下载的按钮,默认为false
scanImage(boolean isScanImage) 下载成功的图片是否需要显示在系统相册中,默认为true
failureblock(boolean isFailureBlock) 下载多图时,如果其中一张下载失败,是否中断后面的下载,默认为true
overwrite(boolean isOverwrite) 如果需要下载的图片已经存在,是否继续下载并覆盖原文件,默认为false
debug(boolean isDebug) 下载过程中是否输出日志,默认为false
enqueue(Listener listener) 执行下载,监听可以为null
  • GlideDown.Listener.class所提供的 :
方法名 解释
onBefore() 主线程 非必
开始下载之前会调用此方法
onProgress(int i) 主线程 非必
下载第i张图片时会调用此方法
onSuccessOne(int i, File file) 主线程 非必
第i张图片下载成功后会调用此方法,并回调下载成功后的文件
onSuccess(LinkedList<String> strings) 主线程 必须
所有的图片下载完成后会调用此方法
onFailure(int i, String errmsg) 主线程 必须
第i张图片下载失败后会调用的方法,并回调失败的原因
onAfter() 主线程 非必
图片无论是下载成功还是失败,都会在最后调用的方法
关于混淆
  • 本库已经内置了混淆规则,混淆规则如下,如果需要额外的混淆规则,请在app moudle的混淆规则中添加。

    1
    2
    3
    4
    5
    -keep public class * implements com.bumptech.glide.module.GlideModule
    -keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
    **[] $VALUES;
    public *;
    }
关于测试部分
  • app moudle中的MainActivity类中已有简单的测试代码。代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
       private void down() {
    String dirPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/GlideDown";
    String ss_p = dirPath + "/ss.jpg";
    String ss_d = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1488440549345&di=38b952767d660a9a1128a16b6f54c830&imgtype=0&src=http%3A%2F%2Fwapfile.desktx.com%2Fpc%2F170104%2Fbigpic%2F5864a73ded7df.jpg";

    List<String> sl = new ArrayList<>();
    sl.add("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1488440549345&di=74e26d9ab9ad9559502d711c7f19d9cd&imgtype=0&src=http%3A%2F%2Fimg.pconline.com.cn%2Fimages%2Fupload%2Fupc%2Ftx%2Fwallpaper%2F1212%2F13%2Fc0%2F16574646_1355387800466_800x600.jpg");
    sl.add("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1488440549344&di=5ff0a690713e5289b22590ed0002114e&imgtype=0&src=http%3A%2F%2Fuploads.xuexila.com%2Fallimg%2F1702%2F966-1F21Q01S7.jpg");

    List<String> ll_p = new ArrayList<>();
    ll_p.add(dirPath + "/ll_1.jpg");
    ll_p.add(dirPath + "/ll_2.jpg");
    List<String> ll_d = new ArrayList<>();
    ll_d.add("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1488440549344&di=fe6284a102e54e68e822940df637f23f&imgtype=0&src=http%3A%2F%2Fb.zol-img.com.cn%2Fdesk%2Fbizhi%2Fimage%2F4%2F960x600%2F1383276504143.jpg");
    ll_d.add("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1488440549344&di=c0d400610b65cde4874fd1acb83c3ec5&imgtype=0&src=http%3A%2F%2Fuploads.xuexila.com%2Fallimg%2F1701%2F966-1F116153608.jpg");

    Map<String, String> m = new HashMap<>();
    m.put(dirPath + "/m_1.jpg", "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1488440549341&di=e477965bba15a857f287b427d0946d98&imgtype=0&src=http%3A%2F%2Fb.zol-img.com.cn%2Fdesk%2Fbizhi%2Fimage%2F3%2F960x600%2F1380159960698.jpg");
    m.put(dirPath + "/m_2.jpg", "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1488440549340&di=1a984087d6a92b06e930467d43f4692b&imgtype=0&src=http%3A%2F%2Fwapfile.desktx.com%2Fpc%2F170104%2Fbigpic%2F5864a739c0c0d.jpg");

    new GlideDown(this)
    .down(ss_p, ss_d)
    .down(dirPath, sl)
    .down(ll_p, ll_d)
    .down(m)
    .showDialog(true)
    .skipDiskCache(true)
    .skipMemoryCache(true)
    .scanImage(true)
    .failureblock(false)
    .overwrite(true)
    .debug(true)
    .cancelButton(true)
    .enqueue(new GlideDown.Listener() {
    @Override
    public void onSuccess(LinkedList<String> linkedList) {

    }

    @Override
    public void onFailure(int i, String s) {

    }
    });
    }