项目中导入本地AAR的方法,首先增加本地libs目录作为本地仓库:
repositories {
flatDir {
dirs 'libs'
}
}
然后增加文件依赖,譬如libs/test.aar
dependencies {
implementation(name:'test', ext: 'aar')
}
repositories {
flatDir {
dirs 'libs'
}
}
然后增加文件依赖,譬如libs/test.aar
dependencies {
implementation(name:'test', ext: 'aar')
}
$ curl -s "https://get.sdkman.io" | bash遗憾的是由于众所周知的国内网络环境问题导致资源始终下载不下来。强制终止安装后,还引发了一个问题:由于部分本地文件已经创建,重新尝试安装时会误判SDKMAN已经安装,但是却无法运行SDKMAN:
......
Looking for a previous installation of SDKMAN...
SDKMAN found.
======================================================================================================
You already have SDKMAN installed.
SDKMAN was found at:
/Users/daemon/.sdkman
Please consider running the following if you need to upgrade.
$ sdk selfupdate force
======================================================================================================
因此再次尝试安装,需要首先删除本地已有的文件和目录:
$ cd ~ $ rm -r .sdkman由于无法正常安装,因此需要使用代理:
$ export http_proxy="http://127.0.0.1:8087/" $ export https_proxy="http://127.0.0.1:8087/" $ curl -s "https://get.sdkman.io" | bash $ unset http_proxy $ unset https_proxy好了,这下安装好了,所有的文件都在 $HOME/.sdkman 目录下,后面安装的开发工具SDK也是存放在这个目录
$ source "$HOME/.sdkman/bin/sdkman-init.sh"
$ sdk version
SDKMAN 5.7.3+337
$ sdk selfupdate
$ sdk list
================================================================================
Available Candidates
================================================================================
q-quit /-search down
j-down ?-search up
k-up h-help
--------------------------------------------------------------------------------
Ant (1.10.1) https://ant.apache.org/
......
可以查看SDKMAN支持管理的所有开发工具SDK列表。
$ sdk list gradle
================================================================================
Available Gradle Versions
================================================================================
5.0 4.4 2.14.1 1.11
5.0-rc-5 4.3.1 2.14 1.10
5.0-rc-4 4.3 2.13 1.9
5.0-rc-3 4.2.1 2.12 1.8
5.0-rc-2 4.2 2.11 1.7
5.0-rc-1 4.1 2.10 1.6
4.10.3 4.0.2 2.9 1.5
4.10.2 4.0.1 2.8 1.4
4.10.1 4.0 2.7 1.3
4.10 3.5.1 2.6 1.2
4.9 3.5 2.5 1.1
4.8.1 3.4.1 2.4 1.0
4.8 3.4 2.3 0.9.2
4.7 3.3 2.2.1 0.9.1
4.6 3.2.1 2.2 0.9
4.5.1 3.2 2.1 0.8
4.5 3.1 2.0 0.7
4.4.1 3.0 1.12
================================================================================
+ - local version
* - installed
> - currently in use
================================================================================
可以看到所有gradle的版本
$ sdk install gradle
也可以指定安装的版本号:
$ sdk install gradle 5.0
$ sdk use gradle 5.0
使用use只能在当前shell环境有效
$ sdk default gradle 5.0
$ sdk current gradle
$ sdk uninstall gradle 5.0
copy {
from zipTree(srcFile)
into file(upzipDir)
}
第三步,根据渠道号,在解压后的apk目录下的META-INFO文件夹下创建空文件task("zip_" + variant.name + "_" + channelName, type: Zip)
from upzipDir
archiveName = targetArchiveName
destinationDir targetout
}
基本流程如上,下面给出具体实现:# app/gradle.properties targetout=targetout然后渠道号采用读取json配置文件的方法,配置文件放在app项目根目录下,名称为,channels.json,示例如下:
{
"channels": [
{
"channelName": "xiaomi"
},
{
"channelName": "yingyongbao"
},
{
"channelName": "taobao"
}
]
}
下面是主要的脚本文件:
apply plugin: 'com.android.application'
android {
.......
buildType {
debug {
minifyEnabled false
}
release {
minifyEnabled true
zipAlignEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
// 自定义的构建结果输出目录,构建前删除目录并重建
File target = new File(rootDir, project.targetout);
target.deleteDir()
target.mkdir()
// 读取json配置文件,转换为Channels类,类定义在脚本末尾
def channelsMap = new groovy.json.JsonSlurper().parse(file("channels.json"))
Channels channels = new Channels(channelsMap)
applicationVariants.all { variant ->
variant.assemble << {
variant.outputs.each { output ->
// 默认构建apk文件输出的路径,解压目录放在同级目录下
File oriFile = output.outputFile
String oriPath = oriFile.getAbsolutePath();
if (oriPath.endsWith(".apk")) {
oriPath = oriPath.substring(0, oriPath.length() - 4)
}
File upzipDir = new File(oriPath);
// 解压apk文件
copy {
from zipTree(oriFile)
into file(upzipDir)
}
// 挨个处理多渠道
channels.channels.each { channel ->
// 读取渠道名称
def channelName = channel.channelName
// 组装渠道文件名称,并创建空文件
def channelFileName = "channel_" + channelName
def dir = new File(upzipDir, "META-INF")
File channelFile = new File(dir, channelFileName)
channelFile.createNewFile()
// 定义重新压缩的apk的文件名称
String targetArchiveName = applicationId + "-v" + versionName + "-" + channelName + "-" + variant.buildType.name + ".apk"
// 重新压缩渠道包
task("zip_" + variant.name + "_" + channelName, type: Zip) {
from oriPath
archiveName = targetArchiveName
destinationDir target
}.execute()
// 删除之前创建的渠道空文件,便于下一个渠道包的生成
channelFile.delete()
}
}
}
}
}
// 单个渠道属性定义
class Channel {
String channelName;
}
// 渠道列表属性定义
class Channels {
List<channel> channels;
@Override
String toString() {
return "channels:" + channels
}
}
以上为多渠道打包脚本的实现,下面就是渠道号的获取了
try {
final ZipFile z = new ZipFile(context.getApplicationInfo().sourceDir);
Enumeration e = z.entries();
while (e.hasMoreElements()) {
final ZipEntry entry = e.nextElement();
if (entry.getName().startsWith("META-INF/channel_")) {
channelId = entry.getName().split("_")[1];
Log.e("tag", "渠道号: " + channelId);
break;
}
}
} catch (IOException e) {
e.printStack();
}
android {
variantFilter { variant ->
if (variant.buildType.name.equals("release") && variant.flavors.get(0).name.equals("mock")) {
variant.setIgnore(true)
}
}
}
getopt()是一个专门设计来减轻命令行处理负担的库函数。| POSIX表示可移植操作系统接口: Portable Operating System Interface,电气和电子工程师协会(Institute of Electrical and Electronics Engineers,IEEE)最初开发 POSIX 标准,是为了提高 UNIX 环境下应用程序的可移植性。然而,POSIX 并不局限于 UNIX。许多其它的操作系统,例如 DEC OpenVMS 和 Microsoft Windows NT,都支持 POSIX 标准。 |
| int main(int argc, char *argv[]) { …… } | int main(int argc, char **argv) { …… } |
main() 时,已经对命令行进行了处理。argc 参数包含参数的计数值,而 argv 包含指向这些参数的指针数组。argv[0]是程序名。| #include <stdio.h> int main(int argc, char **argv) { int i, nflg; nflg = 0; if(argc > 1 && argv[1][0] == '-' && argv[1][1] == 'n'){ nflg++; argc--; argv++; } for(i=1; i<argc; i++){ fputs(argv[i], stdout); if(i < argc-1) putchar(' '); } if(nflg == 0) putchar('\n'); return 0; } |
| #include <unistd.h> int getopt(int argc, char * const argv[], const char *optstring); extern char *optarg; extern int optind, opterr, optopt; |
argc)、指向这些参数的数组 (argv) 和选项字串 (optstring) 后,getopt() 将返回第一个选项,并设置一些全局变量。使用相同的参数再次调用该函数时,它将返回下一个选项,并设置相应的全局变量。如果不再有可识别的选项,将返回 -1,此任务就完成了。getopt() 所设置的全局变量包括:char *optarg——当前选项参数字串(如果有)。int optind——argv的当前索引值。当getopt()在while循环中使用时,循环结束后,剩下的字串视为操作数,在argv[optind]至argv[argc-1]中可以找到。int optopt——当发现无效选项字符之时,getopt()函数或返回'?'字符,或返回':'字符,并且optopt包含了所发现的无效选项字符。| #include <stdio.h> #include <unistd.h> int main (int argc, char **argv) { int oc; /*选项字符 */ char *b_opt_arg; /*选项参数字串 */ while((oc = getopt(argc, argv, "ngl:")) != -1) { switch(oc) { case 'n': printf("My name is Lyong.\n"); break; case 'g': printf("Her name is Xxiong.\n"); break; case 'l': b_opt_arg = optarg; printf("Our love is %s\n", optarg); break; } } return 0; } |
| $ ./opt_parse_demo -n My name is Lyong. $ ./opt_parse_demo -g Her name is Xxiong. $ ./opt_parse_demo -l forever Our love is forever $ ./opt_parse_demo -ngl forever My name is Lyong. Her name is Xxiong. Our love is forever |
| #include <stdio.h> #include <unistd.h> int main (int argc, char **argv) { int oc; /*选项字符 */ char *b_opt_arg; /*选项参数字串 */ while((oc = getopt(argc, argv, "ngl:")) != -1) { switch(oc) { case 'n': printf("My name is Lyong.\n"); break; case 'g': printf("Her name is Xxiong.\n"); break; case 'l': b_opt_arg = optarg; printf("Our love is %s\n", optarg); break; case '?': printf("arguments error!\n"); break; } } return 0; } |
| $ ./opt_parse_demo -l ./opt_parse_demo: option requires an argument -- l arguments error! |
| #include <stdio.h> #include <unistd.h> int main (int argc, char **argv) { int oc; /*选项字符 */ char ec; /*无效的选项字符*/ char *b_opt_arg; /*选项参数字串 */ while((oc = getopt(argc, argv, ":ngl:")) != -1) { switch(oc) { case 'n': printf("My name is Lyong.\n"); break; case 'g': printf("Her name is Xxiong.\n"); break; case 'l': b_opt_arg = optarg; printf("Our love is %s\n", optarg); break; case '?': ec = (char)optopt; printf("无效的选项字符 \' %c \'!\n", ec); break; case ':': printf("缺少选项参数!\n"); break; } } return 0; } |
| $ ./opt_parse_demo -a 无效的选项字符 ' a '! $ ./opt_parse_demo -l 缺少选项参数! |
| $ ./opt_parse_demo -l forever a b c d -g -n Our love is forever Her name is Xxiong. My name is Lyong. |
getopt_long() 是同时支持长选项和短选项的 getopt() 版本。下面是它们的声明:| #include <getopt.h> int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex); int getopt_long_only(int argc, char * const argv[],const char *optstring,const struct option *longopts, int *longindex); |
getopt_long()的前三个参数与上 面的getopt()相同,第4个参数是指向option结构的数组,option结构被称为“长选项表”。longindex参数如果没有设置为 NULL,那么它就指向一个变量,这个变量会被赋值为寻找到的长选项在longopts中的索引值,这可以用于错误诊断。option结构在getopt.h中的声明如下:| struct option{ const char *name; int has_arg; int *flag; int val; }; |
| 符号常量 | 数值 | 含义 |
| no_argument | 0 | 选项没有参数 |
| required_argument | 1 | 选项需要参数 |
| optional_argument | 2 | 选项参数可选 |
| int do_name, do_gf_name, do_love; /*标记变量*/ char *b_opt_arg; while((c = getopt(argc, argv, ":ngl:")) != -1) { switch (c){ case 'n': do_name = 1; case 'g': do_gf_name = 1; break; break; case 'l': b_opt_arg = optarg; …… } } |
| #include <stdio.h> #include <getopt.h> int do_name, do_gf_name; char *l_opt_arg; struct option longopts[] = { { "name", no_argument, &do_name, 1 }, { "gf_name", no_argument, &do_gf_name, 1 }, { "love", required_argument, NULL, 'l' }, { 0, 0, 0, 0}, }; int main(int argc, char *argv[]) { int c; while((c = getopt_long(argc, argv, ":l:", longopts, NULL)) != -1){ switch (c){ case 'l': l_opt_arg = optarg; printf("Our love is %s!\n", l_opt_arg); break; case 0: printf("getopt_long()设置变量 : do_name = %d\n", do_name); printf("getopt_long()设置变量 : do_gf_name = %d\n", do_gf_name); break; } } return 0; } |
| 如果这个指针为NULL,那么 getopt_long()返回该结构val字段中的数值。如果该指针不为NULL,getopt_long()会使得它所指向的变量中填入val字段中 的数值,并且getopt_long()返回0。如果flag不是NULL,但未发现长选项,那么它所指向的变量的数值不变。 |
| $ ./long_opt_demo --name getopt_long()设置变量 : do_name = 1 getopt_long()设置变量 : do_gf_name = 0 $ ./long_opt_demo --gf_name getopt_long()设置变量 : do_name = 0 getopt_long()设置变量 : do_gf_name = 1 $ ./long_opt_demo --love forever Our love is forever! $ ./long_opt_demo -l forever Our love is forever! |
| 返回值 | 含 义 |
| 0 | getopt_long()设置一个标志,它的值与option结构中的val字段的值一样 |
| 1 | 每碰到一个命令行参数,optarg都会记录它 |
| '?' | 无效选项 |
| ':' | 缺少选项参数 |
| 'x' | 选项字符'x' |
| -1 | 选项解析结束 |
| #include <stdio.h> #include <getopt.h> int do_name, do_gf_name; char *l_opt_arg; struct option longopts[] = { { "name", no_argument, NULL, 'n' }, { "gf_name", no_argument, NULL, 'g' }, { "love", required_argument, NULL, 'l' }, { 0, 0, 0, 0}, }; int main(int argc, char *argv[]) { int c; while((c = getopt_long(argc, argv, ":l:", longopts, NULL)) != -1){ switch (c){ case 'n': printf("My name is LYR.\n"); break; case 'g': printf("Her name is BX.\n"); break; case 'l': l_opt_arg = optarg; printf("Our love is %s!\n", l_opt_arg); break; } } return 0; } |
| $ ./long_opt_demo --name --gf_name --love forever My name is LYR. Her name is BX. Our love is forever! $ ./long_opt_demo -ng -l forever My name is LYR. Her name is BX. Our love is forever! |
getopt() 函数是一个标准库调用,可允许您使用直接的 while/switch 语句方便地逐个处理命令行参数和检测选项(带或不带附加的参数)。与其类似的 getopt_long() 允许在几乎不进行额外工作的情况下处理更具描述性的长选项,这非常受开发人员的欢迎。Log等级 Android log 等级在 android/log.h 中定义如下: typedef enum android_LogPriority { /** For internal use only. */ ANDROID_LOG_UNKNOWN =...