Gradle中的文件操作

File遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//File traversal 闭包形式
task copys(type: Copy) {
def dir = new File('app/src/main')
dir.traverse(
//过滤条件
nameFilter: ~/.*\.jar|.*\.java|.*\.c/
) { file ->
println(file.getName())
from file.canonicalPath
//筛选
include '*Activity.java'
into 'app/libs'
}
}

除了使用闭包的形式来遍历指定文件夹外,还可以使用fileTree()来完成同样的操作:

1
2
3
4
5
6
7
8
task tree(type: Copy) {
def backUp = fileTree('app/build/intermediates/classes/debug') {
//筛选后的file全部注入到backUp形成一个集合
include '**/*.class'
exclude '**/R.class'
}
println(backUp.getFiles().size())
}

这样得到的就是filetree对象了,可以使用传统的for each来遍历其中的每一个file,值得一提的是,生成的fileTree依旧可以继续调用matching()来完成进一步的筛选,很有liquit的感觉哈。

1
2
3
4
backUp.matching {
include {'~/.*\\.jar|.*\\.java|.*\\.c/'}
exclude('**/*.class')
}

值得一提的是:在java中我们经常获取一个collection想要打印出集合内的信息,然而在gradle中生成的fileTree对象本身是不具备集合的系列功能的,不过我们可以通过files()来实现把fileTree”转换成”具备collection特性的对象,files(backUp).files->即可获得到同时具备backup的内容又同时具备collection接口的对象了(Set)

###expand

此方法可用作于模版文件拓展,常用在于配置文件的生成:
有配置文件如下:

1
# # Application configuration file # hostname: ${databaseHostname} appVersion: ${version} locale: en_us initialConnections: 10 transferThrottle: 5400 queueTimeout: 30000 buildNumber: ${buildNumber} buildDate: ${date.format("yyyyMMdd'T'HHmmssZ")}

其中携带$符号的参数都是会随着项目的发布动态改变的,我们可以编写一个如下的task

1
2
3
4
5
6
7
8
9
10
11
task autoGenConfig(type: Copy){
from 'sourceDir'//模版配置文件的路径
include 'templateConfig.properties'//模板配置文件的文件名
into 'build/config'
expand([
databaseHostname: 'aaron.db.info',
version: 1.5,
buildNumber: (int)(Math.random()*1000),
date: new Date()
])
}

只要运行上述的task,最终能生成如下的配置文件:

1
# # Application configuration file # hostname: aaron.db.info appVersion: 1.5 locale: en_us initialConnections: 10 transferThrottle: 5400 queueTimeout: 30000 buildNumber: 77 buildDate 20120105T162959-0700

可见expand()函数对于项目版本升级的管理来说提供了很大的便利。

###文本操作利器Filter

相比于expand()函数提供对文本的轻量级的”置换”功能,filter()函数提供了更为强大的功能,遍历指定文本的每一行进行修改,例如需要为每一行文本添加行号的要求:

1
2
3
4
5
6
7
8
9
10
11
task addLineNum(type: Copy){
int num = 0
into 'targetDir'
from 'sourceDir'
include 'target.md'
rename { it - '.md' + '.java' }//修改后的文件名后缀给覆盖掉
filter{ line->
num++
String.valueOf(num) + line
}
}

###class文件打包jar

1
2
3
4
5
6
7
8
9
10
task buildJar(type: Jar, dependsOn: ['build']) { //build后才有class文件
archiveName = 'my-lib.jar'
from('app/build/intermediates/classes/debug') //class文件的路径
from(project.zipTree("libs/xxx-x.x.x.jar"))//这个同样是把第三方jar文件作为source
destinationDir = file('app/libs') //destinationDir is file not string
// destinationDir = 'app/build/libs' // error
//排除指定class
exclude('com/cool/boy/ugrowthview/BuildConfig.class')
include('com/cool/boy/ugrowthview/*.class')
}

对于gradle的文件系统来说有一个Lazy Files的概念,例如通过fileTree()的形式来获取一个FileRoot方便后续的遍历,正常的思路是只要调用了这个方法就立马对path里面的file进行收集从而形成一个immutable的list对象,而在Gradle中,却因为整个构建过程分为了三个阶段如果一个path下的文件是必须经历了Execution阶段后才能操作的话就麻烦了,所以引入了lazy file的概念,尤其是在操作.class文件的时候,必须要经历build结束后才能有class文件。所以在gradle中的文件集合中的实际内容只有在构建执行到某一个特别的时间点后才会被填充的。

ps: 对于Gradle构建过程来说,分为了三个阶段:

* Initialization: 此阶段确定哪些项目会加入到构建队列中,并且为每一个项目创建一个Project对象出来.
* Configuration: 根据提供的构建脚本生成对应的task对象,引入了configuration on demand的概念(选择性的生成task对象,对于多项目构建系统中减少编译脚本时间是蛮有用的,通过给项目解藕实现的)
* Execution: 根据configuration生成的task来确定需要用到哪些task,然后去执行选中的task,具体被选中的task是根据执行command line时传入的参数来确认的。

使用ThreadLocal storage来避免lock带来的性能损耗问题

在jvm的垃圾回收章节中有讲到一个问题,就是关于young-generation中对新生对象的内存分配的优化涉及到的两个技术点,一个是Bump-the-pointer,此技术追踪了当前young-generation区的edan区域最新分配内存的对象,此对象放置在edan区域的top位置,最容易被访问到,然后当再次需要分配内存给新的对象的时候,都会检测edan区剩余的内存空间是否足以分配给new出来的对象,如果空间足够的话那么新生成的对象再次取代上一个new出来的对象的top位置(应该是一个链表的结构).不够的话自然触发minor gc了。

以上看起来很不错,但是前提是在单线程中很好用;万一是多线程环境呢?第一反应肯定是在alloc内存之前加锁呗…然而这样带来的问题就是性能低下,毕竟new那么频繁,对性能的影响肯定是极大的。

所以对应的技术就是Thread-local allocation buffers,避免加锁也能实现内存的高效分配。

参考

其中有一个example code如下:

1
2
3
4
5
6
7
8
9
10
int count=0;
#pragma omp parallel shared(count)
{
. . .
if (event_happened) {
#pragma omp atomic
count++;
}
. . .
}

这段代码必须要保证在多线程环境下对count的自加行为是同步发生的,所以解决的思路是:让每一个线程在自己的内存空间中对这个count进行自加,然后再统一sum各个线程中的count值,最终得到的value就是正确的结果。据此改善后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int count=0;
int tcount=0;//线程独立内存空间中的count值,其特点就是只能被当前线程内部的所有函数操作,但是其他的线程无法操作。
#pragma omp threadprivate(tcount)
omp_set_dynamic(0);
#pragma omp parallel
{
. . .
if (event_happened) {
tcount++;
}
. . .
}
#pragma omp parallel shared(count)
{
#pragma omp atomic
count += tcount;//最终的sum操作
}

那么实现一个thread-local storage的原理是怎样的呢?
通常的做法是建立一个全局表,这张表里记录着threadID->thread-data-addr,也就是这张表需要记录的就是每一个thread的id映射到各自的数据内存地址即可。

具体实现,参考java层实现的thread-local,在此博文中有讲到,可供参考ThreadLocal的工作原理

—–update
threadID->thread-data是最早的设计,目前已经使用一个Values类来描述存储数据结构,实质上是一个Object[],并且是一个比较有意思的数组,体现在threadLocal对象作为key,独立变量参数作为value;可是这个数据结构不就是一个Obejct[]数组吗?哪来的key->value映射呢?(肯定需要一个map吧),然而设计的是这个obj[]的偶数index存放的是threadlcoal对象而相邻的奇数index存放的就是对应的value.

Android WebView开发tips

  • webView 优化的tips(native端):

    • 修改加载资源顺序
    • webView缓存优化(缓存累积占用手机内存问题)
    • 开启硬件加速设置开关(提升页面绘制效率,测试html中video/canvas标签)
    • webView内存泄露的问题
    • Js与native端的两种交互方式实现的原理解析
    • 打包时混淆代码需要注意的地方

Read More