2016年9月26日星期一

Android项目中导入本地AAR

AAR文件是Android特有的压缩包,有别于JAR文件,AAR中打包了类和资源。

项目中导入本地AAR的方法,首先增加本地libs目录作为本地仓库:

repositories {
    flatDir {
        dirs 'libs'
    }
}

然后增加文件依赖,譬如libs/test.aar

dependencies {
    implementation(name:'test', ext: 'aar')
}

2016年9月25日星期日

使用SDKMAN软件管理开发工具SDK

SDKMAN是类UNIX上的开发工具SDK管理工具,可以方便的管理开发工具SDK的安装、卸载、版本切换等,官网:http://sdkman.io/

1. 安装

官方提供了SDKMAN的安装方式:
$ 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也是存放在这个目录

若要SDKMAN立即生效,可以重新打开新的终端,或者运行:

$ source "$HOME/.sdkman/bin/sdkman-init.sh"

2. 查看版本号

查看本地安装的SDKMAN的版本号:

$ sdk version
SDKMAN 5.7.3+337

3. 升级SDKMAN

更新SDKMAN:

$ sdk selfupdate

4. 管理SDK


1) 查看支持的sdk列表:


$ 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列表。

2) 查看单个工具


也可以查看某个具体工具的信息:

$ 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的版本

3) 安装:


$ sdk install gradle

也可以指定安装的版本号:

$ sdk install gradle 5.0

4) 使用指定版本:


$ sdk use gradle 5.0

使用use只能在当前shell环境有效

5) 设定默认版本


$ sdk default gradle 5.0

6) 查看当前版本


$ sdk current gradle

7) 卸载

需要指定卸载的版本号:

$ sdk uninstall gradle 5.0

2016年9月23日星期五

使用Gradle快速构建Android多渠道包

首先,多渠道的实现方式采用了美团多渠道打包的方式,参看http://tech.meituan.com/mt-apk-packaging.html

这种打包方式优点很明显:打包速度快的飞起,渠道越多优势越大;也有缺点,只适合多渠道包区别只有渠道号的不同,如果多个渠道需要自定义包名、应用名、资源、功能等的,还是需要使用多个productFlavor 的方式。
幸运的是公司的产品不需要这么麻烦,因此研究了下使用gradle实现美团多渠道打包的方法

基本流程


现将流程分解下:
  1. 调用assemble任务生成apk
  2. 将apk解压
  3. 在apk的META-INFO目录下创建空文件:channel_{channelName}
  4. 解压后的目录重新打包为apk

第一步,没什么好说的,标准as项目生成的gradle脚本就够用了

第二步,apk解压到本地,解压文件可以使用如下自定义task:
copy {
    from zipTree(srcFile)
    into file(upzipDir)
}
第三步,根据渠道号,在解压后的apk目录下的META-INFO文件夹下创建空文件

第四步,重新打包apk:
task("zip_" + variant.name + "_" + channelName, type: Zip)
    from upzipDir
    archiveName = targetArchiveName
    destinationDir targetout
}
基本流程如上,下面给出具体实现:

具体实现


首先自定义的构建输出目录在gradle.properties文件中配置:
# 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();
}

以上为整个过程,大家有更好的实现方案、更优雅的代码实现,欢迎留言共享,共同学习

参考:


gradle解压:http://mrhaki.blogspot.com/2012/06/gradle-goodness-unpacking-archive.html

json解析:https://dzone.com/articles/groovy-unmarshalling-json-to-a-specific-object
http://groovy-lang.org/json.html



2016年9月22日星期四

Gradle构建Android项目禁用指定variant

参考:http://stackoverflow.com/questions/21221868/exclude-specific-build-variants#22787971

使用Gradle构建Android项目时,会根据buildType和productFlavor组合成多个variant,每个variant都会执行一遍构建过程,但有时候某些组合是不需要的,譬如mock测试用的flavor并不需要打包release,我们可以使用variantFilter来指定过滤这个组合:

android {
    variantFilter { variant ->
        if (variant.buildType.name.equals("release") && variant.flavors.get(0).name.equals("mock")) {
            variant.setIgnore(true)
        }
    }
}

Android logcat

Log等级 Android log 等级在 android/log.h 中定义如下: typedef   enum   android_LogPriority {    /** For internal use only. */ ANDROID_LOG_UNKNOWN =...