关于udp的一些问题总结

 

socket 的端口号

客户端的 socket 很少调用 bind() 来指明 socket 的端口号。 相反通常是让操作系统自动分配一个端口号。

TCP客户端 socket 的端口号是在调用了 connect() 之后,系统 会自动分配端口号。

UDP客户端 socket 的端口号是在第一次调用 sendto() 之后, 系统会自动分配端口号。

如果 UDP 的端口是自动分配的话,那么系统不会再改变这个端 口号。如果 UDP 的IP 地址也是自动分配的话,那么每一次调用 sendto() 系统都可能会根据目的 IP 地址而改变源IP地址。

weak end system VS strong end system

网络接口会接收所有和本地 IP 地址一致的数据包,叫做 weak end system .

网络接口只会接收所有和本接口的 IP 地址一致的数据包,叫做 strong end system 。

不同点, 在一个 multihome 的主机有多个网络接口,那么 strong end system 的过滤检查会更强大一些。

UDP 发送数据的地址和接收数据的地址不一致的问题。

UDP 的客户端发送给服务器,如果服务器的用于接收的网络接口 有多个 IP 地址,那么服务器送响应的时候就会自动选择一个 primary IP 地址回送给客户端。primary IP 地址有可能和客户 端发送的 IP 地址不一样。

如果客户端根据接收的响应的 IP 地址来判断是否是服务器发送 的响应,那么就有可能出错。

解决办法,一个是改造客户端,不再用 IP 地址判断是否是一个 服务器发送的数据包,而是根据 DNS 得到的域名来判断。缺点 是系统一定要有域名服务器,而且查询域名会影响效率。 一个办法是服务器不是用 wildcard 来绑定 socket ,而是为每 一个 IP 地址绑定一个 socket 。 缺点是系统如果动态改变了 IP 地址就需要重新起动服务器,而且增加服务器必须 select() 检查所有的 socket 。

UDP ICMP

sendto() 成功返回后,意思是说主机的网络接口有足够的缓冲 队列空间,用以容纳下要发送的数据。

如果接收端回送 ICMP 消息,那么是 socket 不能够知道的,除 非 UDP socket 也调用了 connect() 变成了 connected UDP socket.

linux 的实现中,则会 unconnected socket 也会返回 ICMP 错 误,只要 SO_BSDCOMPAT socket option 没有置位。

ICMP 消息会在下一个 read() 中返回, 错误值是 ECONNREFUSED。

有些 System V 系统有 Bug ,不会为 connected socket 返回 ICMP 错误。

connected UDP socket VS unconnected UDP socket

调用了 connect() 函数的 UDP socket 就从 unconnected UDP socket 变成了 connected UDP socket 。

connect() 并不是真正建立连接,只是把 socket 和一个 peer name 联系在一起。

函数 connect() 函数的操作完全是本地操作, 不涉及网络。

系统会根据 connect() 函数指定的目的地址寻找网络接口,选 择该网络接口的 primary IP 地址作为本地地址。

不同点:

  1. connected socket 调用 send 而不是 sendto 。
  2. connected socket 调用 recv 而不是 recvfrom 。 只有目的地址 和 connect() 函数指定的地址一致才会被接收到。
  • 问题:如果系统也是根据 IP 地址来判断一个连接的话,那么也有可能出现 上面说的发送命令的目的地址和接收响应的源地址是不一样的问题。
  • 答案:只有改造服务器了。
  1. ICMP 错误会收到。
  2. 发送数据的时候,由于不用指定目的 IP 地址,在用户程序 和内核之间传递的数据少,所以效率稍微高一些。

connected UDP socket 是否可以再调用 connect()

和 TCP 不一样,connected socket 可以多次调用 connect()。

connect() 的目的地址中的 address family 如果是 AF_UNSPEC 那么 connected socket 会变成 unconnected socket 。

可能会返回 EAFNOSUPPORT ,但是没有关系。

 

 

 

 

WireShark如何抓取本地localhost的包

今天将自己的电脑既作为客户端又作为服务端进行一个程序的测试,想着用WireShark来抓包分析一下问题,但由于WireShark只能抓取经过电脑网卡的包,由于我是使用localhost或者127.0.0.1进行测试的,流量是不经过电脑网卡的,所以WireShark无法抓包,一番查找之下找到了解决方法。

1 . 以管理员身份打开命令提示符

2 . 输入 route add 本机ip mask 255.255.255.255 网关ip
如果不知道本机ip和网关ip,可以在命令行输入ipconfig查看
例如我的 : route add 192.168.0.106 mask 255.255.255.255 192.168.0.1

这句话的作用是将发给电脑的包转发给路由器,路由器再发给自己的电脑。。避免本地回环

3 . 将我们程序里面的localhost或者127.0.0.1替换成本机ip

4 . 使用WireShark即可抓到本地包

注:在测试完之后,使用route delete 本机ip mask 255.255.255.255 网关ip来删除我们上面的更改,不然我们本机的所有报文都会先经过网卡再回到本机,会比较消耗性能。

 

三、使用 RawCap

需要管理员权限运行 RawCap 。

进入终端(cmd),然后运行:

RawCap.exe 127.0.0.1 dumpfile.pcapRawCap.exe 本地IP dumpfile.pcap

抓好包后,按 Ctrl C,停止抓包。此时会在 RawCap 的同级目录下生成一个dumpfile.pcap文件。用 Wireshark 打开,就可以看到本地环回的数据包了。

四、使用 Npcap

Npcap 是对当前最流行的 WinPcap 工具包进行改进的一个项目。

安装前请先卸载 WinPcap(可以在Wireshark 的Help一栏查看是否在使用 Npcap) 。

安装时要勾选

Use DLT_NULL protocol sa Loopback ...

install npcap in winpcap api-compat mode(选这个,是要兼容 WinPcap)

npcap-0.78-r2

npcap-0.78-r2

安装完成启动 Wireshark, 可以看到在网络接口列表中,多了一项 Npcap Loopback adapter,这个就是来抓本地环回包的网络接口。

wireshark-npcap 捕获界面

wireshark-npcap 捕获界面

不需要钩子,使用 git push 部署网站,

参考资料

  1. Git 2.3 has been released
  2. Git 2.4 — atomic pushes, push to deploy, and more

正文

最近的工作又回到了微信公众号开发(创业团队摸石头,什么都得干。。。),在办公室的电脑上搞了台测试服务器,代码库放在办公室另一台电脑上。方便起见,打算给产品服务器做一个 push-to-deploy 。

所有自己搭建过 git 服务器的人应该都知道,服务器的 git 仓库一般都是 bare 仓库,没有工作目录。而如果不创建成 bare 仓库的话,对当前 branch 的 push 操作都会被拒绝。

以前做 push-to-deploy 的方式是使用 git 的钩子执行脚本,在收到 push 后临时设置仓库的工作目录,检出代码,然后再清除工作目录。虽然也没什么障碍,但总觉得有些不爽就是了。幸运的是,这种额外的配置在 git 2.3 以后就不再是必须的了。

Git 2.3 引入的新特性

Git 2.3 版本以后,如果你向服务器上有工作目录的仓库 push 改动的话,只要服务器的工作目录是干净的(没有未提交的变更),你 push 的改动就会直接体现在服务器的工作目录下。不需要编写钩子脚本,要实现 push-to-deploy 只需要在服务器的仓库改动一个设置就完事了:

$ git config receive.denyCurrentBranch updateInstead

话说这个配置的命令还真是有够直白,好像都没必要特意去记下它了。。。

配置流程

以上,猴子都能懂的 push-to-deploy 攻略总结如下:

  1. 服务器更新 Git 版本到 2.3 以上(建议 2.4 以上,后文解释)
  2. 服务器在选定的网站根目录新建 Git 仓库,不用 bare 。
  3. 服务器在新建的仓库下执行命令:$ git config receive.denyCurrentBranch updateInstead
  4. 客户端 Git 版本随意,在新仓库或原有仓库中新建一个名为 deploy 的 remote repo ,指向服务器仓库地址:git remote add deploy ssh://gaga@foo.bar/path/to/your/document/root
  5. 客户端向 deploy 分支 push 变更,服务器端工作目录随即改变。
  6. 完事

另外一些可能有用的东西

  1. Git 2.4 对这个特性又做了一些补充,加入了一个仅在该特性被触发时会执行的钩子push-to-checkout。对于一些代码更新后需要额外进行一些操作的项目,这是一个十分贴心的小功能。其他更新详见参考资料 2 。
  2. 这种方式会在项目根目录暴露包含项目所有历史的 .git 文件夹,如果项目对此比较敏感的话最好谨慎处理。

windows 安装mysql

出现dll没有

https://www.microsoft.com/zh-CN/download/details.aspx?id=40784

安装mysql

#进入bin目录

cd E:\mysql-5.7.12-winx64\bin   

#直接初始化mysql,生成data文件夹中的文件。

.\mysqld –initialize  自动生成带随机密码的root用户

.\mysqld –initialize-insecure  自动生成无密码的root用户

#指定配置文件和service名称

.\mysqld –install MySQL –defaults-file=D:\Server\mysql\my.ini

 #卸载mysql

.\mysqld –remove

命令:net start mysql          #启动服务器

命令: net stop mysql            #关闭服务器

 

如果发现命令执行后。mysql无法启动。。。注意。。所有的配置都是两个-。。。-   – install

Installing Jenkins OS X Homebrew

Homebrew is the easiest way to install and manage applications on OS X. Let\’s go through how we
install and configure Jenkins.

Installation

Follow the instructions at http://brew.sh/ if you have not had Homebrew installed. Then, let\’s proceed to install Jenkins


$ brew update && brew install jenkins

Jenkins is a Java web application. Download and install the latest Java Runtime Environment manually, or use Homebrew Cask to install it


$ brew cask install java

 

Starting Jenkins

After Jenkins is installed successfully, follow the instructions to start Jenkins on login


$ ln -sfv /usr/local/opt/jenkins/*.plist ~/Library/LaunchAgents

If you want to configure Jenkins to launch on system startup, for all users on OS X, then copy the plist file to the system Launchd location instead


$ sudo cp -fv /usr/local/opt/jenkins/*.plist /Library/LaunchDaemons
$ sudo chown `whoami` /Library/LaunchDaemons/homebrew.mxcl.jenkins.plist

You can always start Jenkins manually with


$ /usr/local/bin/jenkins

or if you have set up your PATH correctly when installing Homebrew, simply


$ jenkins

 

Restarting Jenkins

If you have an older version of Jenkins and you are upgrading it, then you can restart it this way:


$ launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.jenkins.plist
$ launchctl load ~/Library/LaunchAgents/homebrew.mxcl.jenkins.plist

 

Jenkins Options

The default Jenkins startup command in ~/Library/LaunchAgents/homebrew.mxcl.jenkins.plist is very simple.


      /usr/bin/java
      -Dmail.smtp.starttls.enable=true
      -jar
      /usr/local/opt/jenkins/libexec/jenkins.war
      --httpListenAddress=127.0.0.1
      --httpPort=8080

 

Launchd starts a java command, listening on only the loopback network interface and using the port 8080 and listen to SMTP connection over TLS.

Network Port

8080 is a common network port for web development. Tomcat defaults to 8080. On my machine, I run Gitbucket on 8080. Let\’s change it to “8181”

--httpPort=8080

 

Java Options

If you have any additional Java runtime options, add them to the configuration file. For example, we want to set:
  • Initial and maximum VM size to 1G
  • Use CMS garbage collector for more responsive system
  • Garbage collect PermGen classes – more details here
  • Limit PermGen size

    -Xms1G
    -Xmx1G

    -XX: UseConcMarkSweepGC
    -XX: CMSClassUnloadingEnabled
    -XX:MaxPermSize=256m

More information for Hotspot Java VM options.

 Jenkins Plugins

A fresh Jenkins installation is ready to be used, especially if you only need to manage Java projects. There are many Jenkins plugins that can make Jenkins much more powerful. To access Plugin Manager, go to JenkinsDashboardPlugin Manager or the url http://jenkins:8181/pluginManager/available.

The easiest way to install a plugin is to view the list of all published plugins in the Plugin Manager, click and Install and restart Jenkins. To install a plugin manually, copy the plugin-name.hpi file to Jenkin\’s directory. In the Homebrew installation, that will be $HOME/.jenkins/plugins. Restart Jenkins and the plugin will be installed.

These are the plugins I have installed:

Jenkins configuration

Besides the launchd configuration file explained above, all other Jenkins configuration is stored in $HOME/.jenkins. The main configuration file is config.xml. You really should not edit the configuration files by hand. Use the Jenkins web interface to make changes http://jenkins:8181/manage

Upgrading Jenkins

As a continuous integration server, Jenkins itself is frequently updated. You can check if a new version of Jenkins is available

$ brew update && brew outdated jenkins

Upgrading jenkins is as easy as

 


$ brew upgrade jenkins

Homebrew\’s default behaviour is to keep older versions. If you don\’t need the older versions of Jenkins anymore, you can remove them

$ brew cleanup jenkins

Preserve Configuration And Plugins

You don\’t want to set up Jenkins all over again on every upgrade. The good news is, Jenkins server files and your configuration are in separate locations. Your new Jenkins installation will automatically pick up your existing configurations and plugins at “$HOME/.jenkins”

启动执行脚本目录所在:

安装包所在:

服务项所在:

可以通过命令开始停止:

// 方法一:
sudo launchctl load /Library/LaunchDaemons/org.jenkins-ci.plist 启动  
   
sudo launchctl unload /Library/LaunchDaemons/org.jenkins-ci.plist 停止

如果要修改端口,比如7070,可在第8步重启jenkins前执行以下命令修改端口参数

defaults write /Library/Preferences/org.jenkins-ci httpPort 7070

Jenkins默认安装目录:

/users/share/  

或者更改目录:

cd 到 /Library/LaunchDaemons 编辑 org.jenkins-ci.plist  更改jenkinshome和username
重启Jenkins即可 

然后链接 launchd 配置文件

// 方法二(1): 
$ln -sfv /usr/local/opt/jenkins/*.plist ~/Library/LaunchAgents

可以更改此 plist 来进行一些自定义的配置,详细列表可以参考https://wiki.jenkins-ci.org/display/JENKINS/Starting%20and%20Accessing%20Jenkins

如果要其他机器也可以访问,把 plist 里的--httpListenAddress=127.0.0.1删掉即可

修改完后,在终端执行

// 方法二(2)
$launchctl load ~/Library/LaunchAgents/homebrew.mxcl.jenkins.plist

即可启动 Jenkins

接着用浏览器访问localhost:8080(默认配置),就可以看到 Jenkins 的 web 界面了

# 这种方式安装的Jenkins默认目录是/usr/local/Cellar/jenkins/1.651/libexec/....
#所以想让其他局域网用户访问则需要修改/etc/apache2/httpd.conf的ServerRoot 路径  
改为/usr/local/Cellar/ 即可 

进入 系统管理-启用安全-访问控制-Jenkins专有用户数据库-安全矩阵 添加一个用户:

保存之后会在Jenkins安装目录下生成config.xml文件.

<useSecurity>trueuseSecurity>  这个节点表示使用安全管理,也就是需要用户登录才能操作  

用刚才添加的用户进行注册,不使用密码登录可以

<useSecurity>falseuseSecurity>   

即可.


Jenkins修改workspace和build目录

Globally Changing the workspace location for all Jobs

In order of changing the workspace for a single job, there is another option in the Jenkins system settings that allows for changing the workspace for every job.
Navigate to Jenkins->Manage Jenkins->Configure System and click on the AdvancedButton on the right hand side.


Voilà! This opens up the following options section
Now you can change your workspace and build directory to any other location on your machine. Jenkins provides 3 predefined variables that can be used to specify the new location:

  • ${JENKINS_HOME} — Jenkins home directory
  • ${ITEM_ROOTDIR} — Root directory of a job for which the workspace is allocated
  • ${ITEM_FULLNAME} — ‘/’-separated job name, like “foo/bar”

The default value for the “Workspace Root Directory” is ${ITEM_ROOTDIR}/workspace and for the “Build Record Root Directory” it’s ${ITEM_ROOTDIR}/builds.

环境变量配置:

展示二维码图片

二维码图片的URL链接有了,那要怎样才能将二维码图片展示在Jenkins项目的历史构建列表中呢?

这里需要用到另外一个插件,description setter plugin。安装该插件后,在【Post-build Actions】栏目中会多出description setter功能,可以实现构建完成后设置当次build的描述信息。这个描述信息不仅会显示在build页面中,同时也会显示在历史构建列表中。

有了这个前提,要将二维码图片展示在历史构建列表中貌似就可以实现了,能直观想到的方式就是采用HTMLimg标签,将<img src='qr_code_url'>写入到build描述信息中。

这个方法的思路是正确的,不过这么做以后并不会实现我们预期的效果。

这是因为Jenkins出于安全的考虑,所有描述信息的Markup Formatter默认都是采用Plain text模式,在这种模式下是不会对build描述信息中的HTML编码进行解析的。

要改变也很容易,Manage Jenkins -> Configure Global Security,将Markup Formatter的设置更改为Safe HTML即可。

更改配置后,我们就可以在build描述信息中采用HTMLimg标签插入图片了。

另外还需要补充一个点。如果是使用蒲公英(pyger)平台,会发现每次上传安装包后返回的二维码图片是一个短链接,神奇的是这个短连接居然是固定的(对同一个账号而言)。这个短连接总是指向最近生成的二维码图片,但是对于二维码图片的唯一URL地址,平台并没有在响应中进行返回。在这种情况下,我们每次构建完成后保存二维码图片的URL链接就没有意义了。

应对的做法是,每次上传完安装包后,通过返回的二维码图片短链接将二维码图片下载并保存到本地,然后在build描述信息中引用该图片在Jenkins中的地址即可。

收集编译成果物(Artifacts)

每次完成构建后,编译生成的文件较多,但是并不是所有的文件都是我们需要的。

通常情况下,我们可能只需要其中的部分文件,例如.ipa/.app/.plist/.apk等,这时我们可以将这部分文件单独收集起来,并在构建页面中展示出来,以便在需要时进行下载。

要实现这样一个功能,需要在【Post-build Actions】栏目中新增Archive the artifacts,然后在Files to archive中通过正则表达式指定成果物文件的路径。

设置完毕后,每次构建完成后,Jenkins会在Console Output中采用设定的正则表达式进行搜索匹配,如果能成功匹配到文件,则会将文件收集起来。

gradle 执行另一个项目的命令

$gradle build 打全渠道即所有flavor;且含所有buildTypes
$gradle assemble[flavor][buildType]
如:gradle assembleFlavor1Release 如:gradle assembleFlavor2Debug
gradle本身支持命令缩写, 如:gradle assFlavor1R

 

https://github.com/openbakery/gradle-xcodePlugin

1.Adding dependencies to a task

There are several ways you can define the dependencies of a task. In Section 16.5, “Task dependencies”you were introduced to defining dependencies using task names. Task names can refer to tasks in the same project as the task, or to tasks in other projects. To refer to a task in another project, you prefix the name of the task with the path of the project it belongs to. The following is an example which adds a dependency from projectA:taskX to projectB:taskY:

Example 19.11. Adding dependency on task from another project

build.gradle

project('projectA') {
    task taskX(dependsOn: ':projectB:taskY') {
        doLast {
            println 'taskX'
        }
    }
}

project('projectB') {
    task taskY {
        doLast {
            println 'taskY'
        }
    }
}

Output of gradle -q taskX

> gradle -q taskX
taskY
taskX

使用project()和task.execute()

task test2() {
    doLast{
        println "aaaa";
    }
}

task test1() {
    doLast{
        test2.execute()
        project("app").test3.execute();
        project("app").tasks["test3"].execute();
        project("app").tasks.test3.execute();
    }
}

//最后执行的某个任务..一般是清理项目

build.finalizedBy('buildsomethingElse')

2.Running another Gradle build from a build

You can use the GradleBuild task. You can use either of the dir or buildFile properties to specify which build to execute, and the tasks property to specify which tasks to execute.

https://docs.gradle.org/current/userguide/organizing_build_logic.html#sec:external_build

Example 43.6. Running another build from a build

build.gradle

task build(type: GradleBuild) {
    buildFile = 'other.gradle'
    tasks = ['hello']
}

other.gradle

task hello {
    doLast {
        println "hello from the other build."
    }
}

Output of gradle -q build

> gradle -q build
hello from the other build.

3.使用命令行选择build.gradle

https://docs.gradle.org/current/userguide/tutorial_gradle_command_line.html#sec:selecting_build

When you run the gradle command, it looks for a build file in the current directory. You can use the -b option to select another build file. If you use -b option then settings.gradle file is not used. Example:

Example 4.5. Selecting the project using a build file

subdir/myproject.gradle

task hello {
    doLast {
        println "using build file '$buildFile.name' in '$buildFile.parentFile.name'."
    }
}

Output of gradle -q -b subdir/myproject.gradle hello

> gradle -q -b subdir/myproject.gradle hello
using build file 'myproject.gradle' in 'subdir'.

Alternatively, you can use the -p option to specify the project directory to use. For multi-project builds you should use -p option instead of -b option.

Example 4.6. Selecting the project using project directory

Output of gradle -q -p subdir hello

> gradle -q -p subdir hello
using build file 'build.gradle' in 'subdir'.

Android常用的Gradle配置和加速编译

Why Gradle

Gradle makes the impossible possible, the possible easy and the easy elegant.

在Android开发中经常会用Gradle来构建项目,Gradle能很方便的项目的版本集成和打包,虽Gradle官方已经给出很详细的文档了,但还是有必要抽离出一些常用的配置。

整个Android项目的编译依赖于Gradle的编译,虽然前几天发布了Android 2.0 stable,拥有了Instant Run强大的功能,但是有“改一行布局代码Run一次”习惯的同学有必要知道如何加速Gradle的编译。

主要介绍一下:

  • 如何配置Gradle
  • 如何加速Gradle的编译
  • 一些常用的项目构建知识

How Gradle

gradle.properties文件适合配置IDE的属性,当然也适合配置你在项目的关键/敏感参数,因为它将运行在Incubating parallel mode(

孵化并行模式,应该解释为运行时候嵌入在项目当中),也是属于默认的gitignore,这样你的敏感信息(key账号密码,appkey等)就不会被push到git了,你需要注意的是 属性中有中文的话,记得转成unicode来显示,不然可能引发一些莫名的错误

类似于这样,你可以把你的签名keystore的信息,服务端的endpoint,第三方服务的appkey,ide的配置信息放在这。

  KEY_ALIAS=yat3s
  KEYSTORE_PASSWORD=123456
  KEY_PASSWORD=123456
  umeng_appkey_product=adcdefghijk
  umeng_appkey_dev=adcdefghijk
  deepshare_appid=adcdefghijk
  bugly_appid_product=adcdefghijk
  bugly_appid_dev=adcdefghijk
  rong_appkey=adcdefghijk
  endpoint_product=http://api.yat3s.com
  endpoint_dev=http://api.yat3s.com:9000
  app_name=\u006f\u0070\u0065\u006e\u5f00\u8154`

那么配置好这些信息如何在Gradle和Java中使用呢?

其实Groovy语言和Java很接近也很好用,往下看↓

buildTypes

  buildTypes {
   release {
       minifyEnabled true
       shrinkResources true
       signingConfig signingConfigs.release
       proguardFiles getDefaultProguardFile(\'proguard-android.txt\'), \'proguard-rules.pro\'
       buildConfigField "String", "ENDPOINT", "\"${endpoint_product}\""
       resValue "string", "umeng_appkey", "${umeng_appkey_product}"
       resValue "string", "deepshare_appid", "${deepshare_appid}"
       resValue "string", "bugly_appid", "${bugly_appid_product}"
       resValue "string", "rong_appkey", "${rong_appkey}"
       resValue "string", "channel", "product"
       resValue "string", "op_app_name", "${app_name}"
   }
   debug {
       signingConfig signingConfigs.release
       proguardFiles getDefaultProguardFile(\'proguard-android.txt\'), \'proguard-rules.pro\'
       buildConfigField "String", "ENDPOINT", "\"${endpoint_product}\""
       resValue "string", "umeng_appkey", "${umeng_appkey_dev}"
       resValue "string", "deepshare_appid", "${deepshare_appid}"
       resValue "string", "bugly_appid", "${bugly_appid_dev}"
       resValue "string", "rong_appkey", "${rong_appkey}"
       resValue "string", "channel", "dev"
       resValue "string", "op_app_name", "dev_${app_name}"
     }
   }
  • 引用gradle.properties 只需要加 ${key}
  • 定义在 buildConfigField “String”, “ENDPOINT”,”\”${endpoint_product}\””,在Java享用时候只需要读取BuildConfig.ENDPOINT即可
  • 定义在 resValue “string”, “umeng_appkey”, “${umeng_appkey_product}” , 在xml中享用只需要R.string.umeng_appkey即可

记住几个点:

  • 在你确保你minifyEnabled(混淆)没问题的时候debug模式尽量别开minifyEnabled和shrinkResources,这样会大大降低编译速度
  • 如果真要开混淆,把生成mapping关掉
  • 尽量把一些第三方key分成staging和product,这样容错性高,避免你推送给你的用户一个”Test”
  • 定义buildConfigField时候注意双引号的问题 “\”${endpoint_product}\””
  • 如果不方便管理签名可debug和release同一个签名。 如果需要同时安装debug和release包只需要修改applicationId即可

defaultConfig

这里尽量新建一个Conifg.gradle文件来统一管理这些基本配置(在project下右键new file即可) ,

Config.gradle文件如下

ext {
android = [compileSdkVersion: 23,
           buildToolsVersion: "23.0.1",
           applicationId    : "com.yat3s.d3v",
           minSdkVersion    : 15,
           targetSdkVersion : 23,
           versionCode      : 36,
           versionName      : "1.3.1"]
}
dependencies = [
               design              : "com.android.support:design:23.3.0",
               nineoldandroids     : "com.nineoldandroids:library:2.4.0",
               retrofit            : "com.squareup.retrofit:retrofit:2.0.0",
               rxandroid           : "io.reactivex:rxandroid:1.0.0",
               okhttp-urlconnection: "com.squareup.okhttp:okhttp-urlconnection:2.0.0",
               okhttp              : "com.squareup.okhttp:okhttp:2.0.0",
               butterknife         : "com.jakewharton:butterknife:7.0.1"]

然后在项目的build.gradle文件下添加 apply from: “config.gradle”

  apply from: "config.gradle"
  buildscript {
      repositories {
        jcenter()
      }
      dependencies {
        classpath \'com.android.tools.build:gradle:2.0.0\'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
      }
    }       
    allprojects {
      repositories {
        jcenter()
      }
    }       ``

然后你使用起来就简单多了

def config = rootProject.ext.android // 配置
def dep = rootProject.ext.dependencies // 依赖
useLibrary \'org.apache.http.legacy\' // 如果你是api23的话,在用到apache的http库时候,记得加上这个,不然混淆时候因为urlConnection的升级导致异常
compileSdkVersion config.compileSdkVersion
buildToolsVersion config.buildToolsVersion
defaultConfig {
  applicationId config.applicationId
  minSdkVersion config.minSdkVersion
  targetSdkVersion config.targetSdkVersion
  versionCode config.versionCode
  versionName config.versionName
  manifestPlaceholders = [UMENG_CHANNEL_VALUE: "open"]
}

dependencies {
  compile fileTree(dir: \'libs\', include: [\'*.jar\'])
  compile dep.design
  compile dep.retrofit
  compile dep.okhttp
}

那么这样用的好处是什么呢?

  • 如果你有多个Module都需要依赖design包的话,design包一升级导致了多个module需要改版本,你就可能同时引入了多个依赖导致apk增大
  • 统一管理versionCode和versionName
  • 看起来是不是更简洁

signingConfigs

def keystore = file(\'keystore/yat3s.jks\')
signingConfigs {
    release {
        keyAlias KEY_ALIAS
        keyPassword KEY_PASSWORD
        storePassword KEYSTORE_PASSWORD
        storeFile keystore
    }
}
  • 在非资源定义(下面有提到的resValue为资源定义,其他的地方则为非资源定义)下引用gradle.properties可直接输入properties的key
  • 如果你要公开你的源码记得把你的keystore ignore掉,私有库无视该项

packagingOptions

packagingOptions {
  exclude \'META-INF/LICENSE\'
  exclude(\'META-INF/LICENSE.txt\')
  exclude(\'META-INF/NOTICE.txt\')
  exclude \'META-INF/NOTICE\'
  exclude \'META-INF/DEPENDENCIES\'
  // umeng推送的jar包含有的okio库跟okhttp的okio库冲突
  exclude \'META-INF/maven/com.squareup.okio/okio/pom.xml\'
  exclude \'META-INF/maven/com.squareup.okio/okio/pom.properties\'
}
  • 主要处理第三方库在导入时候的一些声明文件冲突,Option this

compileOptions {

compileOptions {
  sourceCompatibility JavaVersion.VERSION_1_7
  targetCompatibility JavaVersion.VERSION_1_7
}
  • 个人觉得如果你不用lambda表达式的话,可以用java1.7,因为1.8还不是完美的支持Android

lintOptions

 lintOptions {
    disable "InvalidPackage"
    disable "MissingTranslation" // 禁用中英文string.xml的强制lint
    lintConfig file("lint.xml")
}

打包管理

def getDate() {
    return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC")) // 注意时区
}     

// 获取当前git的Revision
def getRevision() {
    return ext.hash = \'git rev-parse --short HEAD\'.execute().text.trim()
}
productFlavors {
   rc_open {}
   rc_360 {}
   rc_yingyongbao {}
   rc_baidu {}
   rc_91 {}
   rc_wandoujia {}
   rc_anzhuo {}
   rc_xiaomi {}
   rc_meizu {}
   rc_oppo {}
   rc_huawei {}
   rc_weibo {}
   rc_dev {}
   }
productFlavors.all { flavor ->
    // 这里只是方便友盟统计每个渠道的数据
    flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
 }

  // 修改打包后APK的文件名
  applicationVariants.all { variant ->
   variant.outputs.each { output ->
       def oldFile = output.outputFile
       if (variant.buildType.name.equals(\'release\')) {
           // 输出apk名称为yat3s_v1.0_2016-04-12_yingyongbao_a23f2e1.apk
           def releaseApkName = \'yat3s-v\'   defaultConfig.versionName   \'_\'   getDate()   \'_\'   variant.productFlavors[0].name   "_"   getRevision()   \'.apk\'
           output.outputFile = new File(oldFile.parent, releaseApkName)
       }
       if (variant.buildType.name.equals(\'debug\')) {
          // Do nothing
       }
   }
 }
  • 多渠道打包用的是Gradle的productFlavors
  • git的Revision 方便你管理你的release和tag
  • 打包的时候可以用Jenkins来自动Build你的包

Speed Gradle

https://developer.android.com/studio/build/optimize-your-build.html

我们都知道编译项目时候是依赖gradle的,gradle的构建速度决定了你的工作效率,上面零散的提到几点,下面总结一下:

  • 第一个大招 升级Android studio 2.0 并且使用最新版的gradle 2.0 使用Instant Run
  • 在你确保你的混淆没问题时候在debug模式下关闭所有混淆minifyEnabled false, shrinkResources false
  • 尽可能的让你的所有module的依赖库版本一致。
  • 在允许的情况下,在android studio的配置中,开启offline模式,在构建项目时候在命令后面加上–daemon –parallel –offline即可
  • 在添加依赖的时候尽量明确版本号,省去gradle查找最新版的时间,不要compile \’com.facebook.fresco:fresco:latest\’ 或compile \’com.facebook.fresco:fresco:1. \’,
  • 开启守护线程并行编译,在gradle.properties中添加
    org.gradle.parallel=true
    org.gradle.daemon=true
    org.gradle.jvmargs=-Xms256m -Xmx1024m
  • 在gradle中def方法的时候尽量在debug情况下减少耗时操作或者不操作,比如:
    def getRevision() {
      if (!System.getenv(\'CI_BUILD\')) {
        return 0
      }
      return ext.hash = \'git rev-parse --short HEAD\'.execute().text.trim()
    }

GRADLE自定义你的BUILDCONFIG

在Android开发中,我们使用android.util.Log来打印日志,方便我们的开发调试。但是这些代码不想在发布后执行,我们并不想在软件发布后调试日志被其他开发者看到,现在我的方法是设置一个全局变量,标记软件为Debug模式还是Release模式。来看下代码:

public class Log {
    private static final boolean DEBUG = true;

    public static void i(String tag, String msg) {
        if (DEBUG)
            android.util.Log.i(tag, msg);
    }

    public static void e(String tag, String msg) {
        if (DEBUG)
            android.util.Log.e(tag, msg);
    }

    public static void d(String tag, String msg) {
        if (DEBUG)
            android.util.Log.d(tag, msg);
    }

    public static void v(String tag, String msg) {
        if (DEBUG)
            android.util.Log.v(tag, msg);
    }

    public static void w(String tag, String msg) {
        if (DEBUG)
            android.util.Log.w(tag, msg);
    }
}

这样打包发布之前只要改下DEBUG=false就行了,但是每次在发布之前都要手动去改这个变量,不是很方便,而且不排除开发者忘记改的情况。那么有没有更好更方便的做法呢?

首先在Gradle脚本中默认的debug和release两种模式BuildCondig.DEBUG字段分别为true和false,而且不可更改。该字段编译后自动生成,在Studio中生成的目录在 app/build/source/BuildConfig/Build Varients/package name/BuildConfig 文件下。我们以9GAG为例来看下release模式下该文件的内容:

public final class BuildConfig {
  public static final boolean DEBUG = false;
  public static final String APPLICATION_ID = "com.storm.9gag";
  public static final String BUILD_TYPE = "release";
  public static final String FLAVOR = "wandoujia";
  public static final int VERSION_CODE = 1;
  public static final String VERSION_NAME = "1.0";
  // Fields from build type: release
  public static final boolean LOG_DEBUG = false;
}

自定义BuildConfig字段

大家看到上述内容的时候发现莫名的有个LOG_DEBUG字段,这个完全是我自定义的一个字段,我来用它控制Log的输出,而没有选择用默认的DEBUG字段。举例一个场景,我们在App开发用到的api环境假设可能会有测试、正式环境,我们不可能所有的控制都通过DEBUG字段来控制,而且有时候环境复杂可能还会有两个以上的环境,这个时候就用到了Gradle提供了自定义BuildConfig字段,我们在程序中通过这个字段就可以配置我们不同的开发环境。

语法很简单:

buildConfigField "boolean", "API_ENV", "true"

上述语法就定义了一个boolean类型的API_ENV字段,值为true,之后我们就可以在程序中使用BuildConfig.API_ENV字段来判断我们所处的api环境。例如:

public class BooheeClient {
    public static final boolean DEBUG = BuildConfig.API_ENV;

    public static String getHost {
        if (DEBUG) {
            return "your qa host";
        }
        return "your production host";
    }
}

不仅如此,如果遇到复杂的环境,你也可能自定义一个String类型的字段,这种方式免去了发布之前手动更改环境的麻烦,减少出错的可能性,只需要在Gradle配置好debug、release等模式下的环境就好了,打包的之后毫无顾虑。

使用方法很简单,大家如果有问题或者疑问可以直接博客留言。

 

利用Jenkins玩转Android自动打包发包

请尊重原创,转载请注明出处:http://blog.csdn.net/mabeijianxi/article/details/52680283

先看一眼效果图:

功能描述:

可以选择不同的环境与不同的渠道,可以输入显示在App上的版本号,打包完成后可自动上传并且生成安装二维码

总体步骤可为:

  1. 下载新版Jenkins挂载到Tomcat
  2. 编写Python脚本拉取蒲公英上Apk二维码(可选)
  3. 配置项目build.gradle里面的脚本
  4. 安装Jenkins里面需要用到的一些插件并且配置Jenkins参数

脚本配置地址:https://github.com/mabeijianxi/android-automation

正式开撸

一、下载新版Jenkins挂载到Tomcat:

Jenkins下载适合的版本后点击安装,之后在Tomcat的webapps目录下新建一个Jenkins目录,再把刚安装好的Jenkins目录打开找到war目录,拷贝目录下全部数据到webapps下新建的Jenkins目录中。还有种启动方式是通过命令启动jenkins.war就行了,“`java -jar jenkins_located_path/jenkins.war“`。我们可以先为工作空间配置个环境变量。


我使用的是第一种方式,再启动Tomcat后访问http://localhost:8080/jenkins就会进入引导进入页面,如果有选择安装插件界面进去后点击安装就行,一会儿就能进入主界面。

 

二、安装Jenkins里面需要用到的一些插件:

这里也没什么技术含量,系统管理->插件管理->管理插件->可选插件: 勾选如下插件:

  1. Branch API Plugin
  2. build timeout plugin
  3. build-name-setter
  4. Credentials Binding Plugin
  5. description setter plugin
  6. Dynamic Parameter Plug-in
  7. Environment Injector Plugin
  8. fir-plugin(可选)
  9. Git plugin(可选)
  10. GIT server Plugin(可选)
  11. Gradle Plugin
  12. Pipeline: Basic Steps
  13. Pipeline: Build Step
  14. Pipeline: Input Step
  15. Pipeline: Nodes and Processes
  16. Pipeline: Stage Step
  17. Post-Build Script Plug-in
  18. SSH Slaves plugin
  19. Subversion Release Manager plugin(可选)
  20. Timestamper
  21. Workspace Cleanup Plugin
  22. Subversion Plug-in(可选)

由于有些插件之间有依赖关系所以没全部列出来,如过有我问题可以对比我的截图。

三、配置build.gradle:

上面都是枯燥的准备工作,好玩的才刚刚开始,下面的参数传入很有意思,也是我踩了很多坑以后想到的。 进入Android Studio->打开主Module的build.gradle:

  1. 配置签名信息,不然打的包就没意义了
  2. 以参数化构建你想动态写入的数据。我自己需要动态写入的参数有versionName、打包时间戳、是来自Jenkjins运行还是我们本地打包的标识符,这里很重要,如果是来自Jenkins打包那么我们的生成路径肯定是服务器上的路径,否则是我们本地电脑上的路径。把这三个参数与其在本地的默认值定义在gradle.properties中,然后在build.gradle变能引用。
  3. 动态修改生成的Apk路径与名称,因为如果是Jenkins打包发包那么名称必须是Jenkins传递过来的,不能写死,且唯一,不然没法上传

废话不多说,程序员就哪需要看这么多文字,直接干代码(一下是项目中部分代码):

gradle.build:

def getDate() {
    def date = new Date()
    def formattedDate = date.format(\'yyyyMMddHHmm\')
    return formattedDate
}
def verCode = 14
android {
    compileSdkVersion 22
    buildToolsVersion "23.0.3"
    signingConfigs {
        signingConfig {
            keyAlias \'xxx\'
            keyPassword \'xxx\'
            storeFile file(\'xxx\')
            storePassword \'xxx\'
        }
    }

    defaultConfig {
        applicationId "com.henanjianye.soon.communityo2o"
        minSdkVersion 14
        targetSdkVersion 22
        multiDexEnabled true
        versionCode verCode
        versionName APP_VERSION
        resValue("string", \'app_version\', APP_VERSION)
        buildConfigField "boolean", "LEO_DEBUG", "true"
        buildConfigField \'String\', \'API_SERVER_URL\', RELEASE_API_SERVER_URL
        buildConfigField \'String\', \'API_SERVER_URL_MALL\', RELEASE_API_SERVER_URL_MALL
        signingConfig signingConfigs.signingConfig
    }
    buildTypes {
        release {
            buildConfigField \'String\', \'API_SERVER_URL\', RELEASE_API_SERVER_URL
            buildConfigField \'String\', \'API_SERVER_URL_MALL\', RELEASE_API_SERVER_URL_MALL
            buildConfigField \'String\', \'IM_SERVER_HOST\', RELEASE_IM_SERVER_HOST
            buildConfigField \'int\', \'IM_SERVER_PORT\', RELEASE_IM_SERVER_PORT
            buildConfigField "boolean", "LEO_DEBUG", RELEASE_LEO_DEBUG
            minifyEnabled true
            zipAlignEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile(\'proguard-android.txt\'), \'proguard-rules.pro\'
        }
        debug {
            buildConfigField \'String\', \'API_SERVER_URL\', RELEASE_API_SERVER_URL
            buildConfigField \'String\', \'API_SERVER_URL_MALL\', RELEASE_API_SERVER_URL_MALL
            buildConfigField \'String\', \'IM_SERVER_HOST\', RELEASE_IM_SERVER_HOST
            buildConfigField \'int\', \'IM_SERVER_PORT\', RELEASE_IM_SERVER_PORT
            buildConfigField "boolean", "LEO_DEBUG", RELEASE_LEO_DEBUG
        }
    }
    dexOptions {
        javaMaxHeapSize "2g"
    }


    //渠道Flavors,我这里写了一些常用的
    productFlavors {
        commonsoon {
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "commonsoon"]
        }
        zhushou91 {
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "zhushou91"]
        }
    }
    allprojects {
        repositories {
            mavenCentral()
            maven { url "https://jitpack.io" }
        }
    }
    //修改生成的apk名字
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            def newName
            def timeNow
            def oldFile = output.outputFile
            def outDirectory = oldFile.parent

            if ("true".equals(IS_JENKINS)) {
                timeNow = JENKINS_TIME
                outDirectory = \'G:/Tomcat/webapps/jenkins/apk\'
                newName = \'yj-android-v\'  
                        APP_VERSION   \'-\'   variant.productFlavors[0].name   timeNow   \'-\'   variant.buildType.name   \'.apk\'
            } else {
                timeNow = getDate()
                if (variant.buildType.name.equals(\'debug\')) {
                    newName = "yj-android-v${APP_VERSION}-debug.apk"
                } else {
                    newName = \'yj-android-v\'  
                            APP_VERSION   \'-\'   variant.productFlavors[0].name   timeNow   \'-\'   variant.buildType.name   \'.apk\'
                }
            }

            output.outputFile = new File(outDirectory, newName)

        }
    }
}


gradle.properties:


RELEASE_API_SERVER_URL="xxx"
RELEASE_API_SERVER_URL_MALL="xxx"
RELEASE_IM_SERVER_HOST="xxx"
RELEASE_IM_SERVER_PORT=5222
RELEASE_LEO_DEBUG=false

INSIDE_TEST_API_SERVER_URL="xxx"
INSIDE_TEST_API_SERVER_URL_MALL="xxx"
INSIDE_TEST_IM_SERVER_HOST="xxx"
INSIDE_TEST_IM_SERVER_PORT=5222
INSIDE_TEST_LEO_DEBUG=true

APP_VERSION=2.4.0
IS_JENKINS=false
JENKINS_TIME=\'\'

四、配置Jenkins参数

回到主界面->系统管理->Global Tool Configuration: 配置好JDK与Gradle。由于我本地已安装好JDK与Gradle所以只需为其指定路径即可。


然后回到主界面->新建->构建一个自由风格的项目->ok:

  1. 勾选上参数化构建过程,先点击Choice可为其配置可选参数。我的项目需要配置的可选参数有API环境、打包渠道、是否来自Jenkins打包的标识。
  2. 点击String Parameter,让使用者可以自定义显示在App上的版本号,方便测试。可以再添加一个可输入的标签,最后把这个标签加到构建页的构建名称中。
  3. 点击Dynamic Parameter,注入Groovy脚本,主要是生成时间戳。
  4. 如果是SVN用户可选择List SubVersion tags,然后写地址帐号密码等,这个选项可以让你选择打包SVN上不同版本的代码。如果是Git用户可参照http://birdinroom.blog.51cto.com/7740375/1404930 上面的配置来拉取不同的版本。
  5. 勾选源码管理中自己所用的管理工具Git或者Subversion,填写好相关信息,当然我们的地址是动态的,所以需要引用上一步所选择的版本,如图。(如果是Linux后面得加上/,不然拉取的其目录下的内容不包含TAG_SVN这个目录)
  6. 在构建环境中勾选上Set Build Name,主要是动态生成每次显示在构建页上的名称方便查看。
  7. 在构建栏里面选择Invoke Gradle Script->选择配置好的Gradle Version->在Tasks中输入Gradle命令(没了解过的建议先看下Gradle的基本命令),我们先执行一个clean,然后开始编译,这时候就可以用引用上面配置的一些参数了,这里可以用-P命令把参数传入,也可以更方便的把下面的Pass jod parameters as Gradle properties勾选上,其实内部也是用-Pkey=Value的命令->在Root Build script中动态指定你项目的目录,为什么是动态的呢?这是因为我们上面提供了可选版本的功能,所以拉取的每个版本所放目录是不一样的,想要编译相应的版本就得动态指定编译目录。
  8. 上面的操作如果成功那么其实这时候构建已经可以在build.gradle所配置的输出路径中找到相应的Apk了,但是这还不够酷,我想把生成的Apk放到托管平台,一般用fir.im与蒲公英。如果选择fir.im那么本步骤略过,我个人推荐蒲公英,因为其可用命令行上传比较方(装)便(B)。下面是蒲公英上传的步骤。
    1)简单的实现方式:参照官方文档https://www.pgyer.com/doc/view/jenkins 。这种方式可以可以简单的通过命令行上传,方便使用,且后续可以动态拿到Apk的下载连接,与最新版本的二维码。但是想要拿到每个版本的二维码确实不行的。其实上传成功后返回的json数据中Apk的下载地址与二维码地址是写死的一个短网址,后续说明解决办法。这里按照官方的步骤,如果是Linux 那么在增加构建步骤中选择Execute Shell,而Windows环境的需要先下载curl工具,然后选择Execute Windows batch command。get地址与使用方法都在里面http://www.2cto.com/os/201205/131164.html

    需要注意这里的上传文件的名称也是动态引用的方式,因为每次生成的名称是唯一的。如果是Shell那么引用方式是${NAME},而batch引用方式是%NAME%。
    2)比较装逼的方式:我不只想要每个版本的下载长链接,我还想要每个版本二维码的长链接。但是就算通过浏览器打开最新上传的Apk地址,里面的二维码也只有短链接,不管是Apk的短链接还是二维码的它们其实是写死的,永远指向最新版的地址,这就有个问题,当在Jenkins中打开非最新版本的Apk地址或者二维码图片地址那么都会指向最新的,这就比较蛋疼了。经过仔细观察发现蒲公英Apk长链接生成的规则其实是固定字符串 appKey。Apk在返回的json串中可以找到。于是可以利用python脚本来执行上传,然后正则配置到AppKey,然后再拼上静态字符串就拿到了本次存放Apk的长链接。当然如果只是为了Apk安装长链接用第一种方式即可。现在需要的是二维码的长链接,用Apk的长链接发起Http请求然后就可以拿到页面数据,通过正则匹配可以匹配到里面二维码的img标签地址,但是是短地址,蒲公英只有非最新Apk才会在这个页面的img标签中返回长地址。于是我打了一个包名与签名同上传Apk相同的Apk,里面没东西,大小只有几十K。在第一个Apk上传完成后马上上传这个小的Apk,这样再去拉取页面里面的img标签就可以得到二维码的长网址了。具体实现看下面python代码。

    
    #coding: utf-8
    import os
    import re
    import requests
    import sys

    if len(sys.argv) == 2: mainpath = sys.argv[-1] else: exit()

    temppath = r\’G:\mydownload\GETUI_ANDROID_SDK\GETUI_ANDROID_SDK\Demo\PushDemo\yijia\temp.apk\’

    cmd = \’curl -F “file=@{path}” -F “uKey=xxx” -F “_api_key=xxx” http://www.pgyer.com/apiv1/app/upload\’ content = os.popen(cmd.format(path=mainpath)).read() print(content)

    match = re.search(\'”appKey”:”(\w )”\’, content)

    if match: appkey = match.group(1)

    content = os.popen(cmd.format(path=temppath)).read()
    
    url = \'http://static.pgyer.com/\'   appkey
    html = requests.get(url).text
    
    match = re.search(\'http://static.pgyer.com/app/qrcodeHistory/\w \', html)
    if match:
        print(\'appKey#{soonAppkey}#appKeysoon#{soon}#soon\'.format(soonAppkey=appkey,soon=match.group()))
    else:
        print(\'no qrcode\')
    

    命令中执行如图代码:

  9. 经过上面的操作后大业马上就成了,接下来就是收集成果的时候了,在增加构建后操作步骤中选择Set build description,在Regular expression中填写正则,然后Description中可以引用,这里去匹配的是构建日志中的内容,Description的内容将显示到构建页面。我们这里如果需要插入下载链接或者二维码的话那么就需要用到Html标签,这时候需要去先设置下。步骤:系统管理->Configure Global Security-> Markup Formatter->Safe HTML。如果是选择上面的简单实现方式那么Regular expression的正则可以用:”appKey”:”(.*)”,”userKey”,如果用我的pyhton脚本上传,那么正则可以是:appKey#(.*)#appKeysoon#(.*)#soon,然后在Description中通过\1或者\2引用即可。

四、打包喝咖啡

到这里那么最感动的时刻来了,轻轻的点击下保存按钮,这时候你会来到构建界面,轻击 Build with Parameters,就会出现这个界面


配置好参数,按下时代性的构建。从这一刻起谁向你要包你可以点根烟淡淡的说道:妈的,撸着呢,一边自己打去。我反正是受够了要包岁月。

注意事项:

1、参数传递的时候Jenkins里面的参数名称需要与build.gradle里面的一致,不然没什么卵用。如:


2、在Linux下需要注意一些编译工具的权限问题,如aapt。
3、在服务器上的SDK最好与你本地的一致,不然指不定编译的时候会少东西。如果服务器的环境与你本地环境不同就不能直接copy过去了,最好的办法是下载一个服务器环境的SDKManager或者直接用Android Studio,然后对照你本地SDK里面的数据挨个下载,完了再copy到服务器。