转: 加快Android编译速度

转: http://timeszoro.xyz/2015/11/25/%E5%8A%A0%E5%BF%ABandroid%E7%BC%96%E8%AF%91%E9%80%9F%E5%BA%A6/

加快Android编译速度

对于Android开发者而言,随着工程不断的壮大,Android项目的编译时间也逐渐变长,即便是有时候添加一行代码也需要等待好久才能看见期待的效果。之前加快Android编译的工具相对较少,其中最具有代表性的开源项目当属FaceBook的Buck和 mmin18的LayoutCast,除此之外还有JRebel 和 Jimulabs。不过前两天google宣布推出Instant Run加快Android 编译速度,相信对其他的工具来说都是一次冲击,这也是写这篇文章的动机。

相对于Buck而言,LayoutCast显得更轻量一些,对项目的侵入性较弱。今年8月份的时候,花了一个星期左右的时间才完成公司的代码的适配,对于一些繁重的项目而言,Buck带来的好处是显而易见的,但是适配过程中的坑也是很多的。Instant Run 对项目的侵入性其实也是比较大的,但是这些都不需要用户去操作、配置,所以看起来和LayoutCast一样属于轻量型的。

时间去哪了?

Android程序编译大致过程如图所示,详细的过程可以参考gradle 中的tasks。


编译过程

那么为什么我们每次编译都需要等待那么久?事实上我们我们可以gradle中添加TaskExecutionListener来监听gradle脚本中每个task的执行时间。

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
class TimingsListener implements TaskExecutionListener, BuildListener {
private Clock clock
private timings = []
@Override
void beforeExecute(Task task) {
clock = new org.gradle.util.Clock()
}
@Override
void afterExecute(Task task, TaskState taskState) {
def ms = clock.timeInMs
timings.add([ms, task.path])
task.project.logger.warn "${task.path} took ${ms}ms"
}
@Override
void buildFinished(BuildResult result) {
println "Task timings:"
for (timing in timings) {
if (timing[0] >= 50) {
printf "%7sms %s\n", timing
}
}
}
@Override
void buildStarted(Gradle gradle) {}

@Override
void projectsEvaluated(Gradle gradle) {}

@Override
void projectsLoaded(Gradle gradle) {}

@Override
void settingsEvaluated(Settings settings) {}
}

gradle.addListener new TimingsListener()

执行脚本可以发现主要的费时在dex(包含preDex)以及install这两个步骤。BUCK和LayoutCast的主要工作也是集中于这些费时的步骤上面。

如何加快?

开发过程中对项目的改动一般分为Java文件的修改以及资源文件的修改,这些修改都会涉及到上述的几个费时步骤,这也就是为什么即便我们修改一行代码也需要编译很久。

1、Java文件修改

通常,修改的.java文件会先经过javac操作生成.class文件。而后与其他的.class文件经过dx生成.dex文件。经过dx的操作很费时,针对这种情况,BUCK、LayoutCast和Instant Run采用了两种方法来解决。

BUCK

BUCK建立了一套完善的依赖规则以及细化的缓存系统来缩减编译时间,并通过使用三方的dex merege工具将.dex文件合并的时间复杂度从O(N^2)降到O(NlgN)。


Buck Dex 过程

如图所示,当修改A.java文件时,只涉及到相应的dx操作以及dex merge操作(红色部分),这样就大大的缩减了dx的操作时间。BUCK在依赖规则上狠下功夫推出了ABI,更是进一步的减少了不必要的操作。

LayoutCast

LayoutCast的实现同很多插件的实现原理差不多,具体分析如下:

在ClassLoader查找类的时候会先去调用BaseDexClassLoader类中的findClass方法。

1
2
3
4
5
6
7
8
//----dalvik/system/BaseDexClassLoader.java  
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = pathList.findClass(name);
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}

随后在DexPathList类中根据dexElements来查找相应的class。

1
2
3
4
5
6
7
8
9
10
11
12
13
//----dalvik/system/DexPathList.java  
public Class findClass(String name) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext);
if (clazz != null) {
return clazz;
}
}
}
return null;
}

其中dexElements代表着不同dex文件。

1
2
/** list of dex/resource (class path) elements */
private final Element[] dexElements;

也就是说,在ClassLoader加载类的时候会去按照dexElements中dex文件的顺序依次查找,如下图所示,在1.dex中查找到了A类,那么就不会再从后面的dex文件中继续查找了。


插入dex原理

LayoutCast就是利用这样的原理,将修改的Java文件生成dex文件,并将此dex文件利用反射的方式插入到dexElements数组的前面。当然,从Java到dex的过程需要额外的查找各种依赖包之类的工作,这部分工作在cast.py中实现。

这种方式的实现在ART下是没有问题的,但是在Dalvik中就会出现IllegalAccessError的问题

1
2
3
4
5
java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
dalvik.system.DexFile.defineClass(Native Method)
dalvik.system.DexFile.loadClassBinaryName(DexFile.java:211)
dalvik.system.DexPathList.findClass(DexPathList.java:315)
dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.j

具体的原因以及解决方案可以参考Bugly的文章

Install Run

Install Run 同样也是生成新的增量dex,但是新增dex中的类和原来的类名有区别。比如说,在修改Hello.java类之后,会生成包含Hello$overide类的dex文件。

那么,这个新增的dex文件中Hello$Override类是如何被调用的?

我们先看看原来的Hello.java文件经过Instant Run 编译前后的区别:

编译前的hello.java文件

1
2
3
public String name(String str) {
return str;
}

经过Instant Run之后的

1
2
3
4
5
---compiled  Hello.java
public String name(String str) {
IncrementalChange var2 = $change;
return var2 != null?(String)var2.access$dispatch("name.(Ljava/lang/String;)Ljava/lang/String;", new Object[]{this, str}):str;
}

可以看出,如果$change存在的话,就会调用$change中相应的函数,那么我们只需要通过反射将Hello.java中$change字段改为修改后的Hello$override的类就Ok了。
这也就是为什么Instant Run并不存在前面说到的IllegalAccessError的问题,并且支持不重启就能看见修改效果的原因。具体可以看看寒江不钓的博客

2、Res修改

Resource文件的修改会涉及到AAPT、ApkBuilder以及最后的Install操作。其中APPT的操作要求比较高,LayoutCast、Instant Run均没有在这部分进行优化,他们的主要工作在于后面的两个操作。其主要的思路在于将修改的后的资源利用aapt打包成新的.ap_文件,并通过反射的方式将原来的资源文件改为修改后的。

LayoutCast

LayoutCast主要做了两件事。

修改LayoutInflater服务

对于下面的用法我们并不陌生:

1
2
LayoutInflater layoutInflater = LayoutInflater.from(context);
View view = layoutInflater.inflate(resourceId, root);

其中LayoutInflater.from的实现是在Context的实现类ContextImp中获取LAYOUT_INFLATER_SERVICE系统服务

1
2
3
4
5
6
7
8
9
10
//----  android/view/LayoutInflater.java
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater)context.getSystemService(Context.
LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}

那么ContextImpl又是如何获取相应的服务的,查看ContextImpl类可以发现,

1
2
3
4
5
//---- android/app/ContextImpl.java
public Object getSystemService(String name) {
ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
return fetcher == null ? null : fetcher.getService(this);
}

可以发现调用getSystemService的过程是在SYSTEM_SERVICE_MAP的表中查找ServiceFetcher,并返回ServiceFetcher中的mCachedInstance。那么只需要将mCachedInstance替换为自定义的BootInflater并在BootInflater中完成Resource的Overrirde就可以了,如下图所示。


插入dex原理

修改Resource

我们知道Activity中的通过调用getResources()方法来访问资源,这实际上是调用ContextWrapper类中的getResource()方法

1
2
3
public Resources getResources(){
return mBase.getResources();
}

LayoutCast中就采用替换mBase为自定义的OverrideContext,并在其中将Resource返回为修改后的Resource。

Instant Run

Instant Run 对资源文件的处理和LayoutCast基本类似,但是在细节的处理上有所不同,比如Instant Run 通过对ActivityThread类中的mPackagesmResourcePackages的修改来改变LoadedApkmResDir的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
for (String fieldName : new String[] { "mPackages", "mResourcePackages" })
{
Field field = activityThread.getDeclaredField(fieldName);
field.setAccessible(true);
Object value = field.get(currentActivityThread);
for (Map.Entry<String, WeakReference<?>> entry : ((Map)value).entrySet())
{
Object loadedApk = ((WeakReference)entry.getValue()).get();
if (loadedApk != null) {
if (mApplication.get(loadedApk) == bootstrap)
{
if (externalResourceFile != null) {
mResDir.set(loadedApk, externalResourceFile);
}
if ((realApplication != null) && (mLoadedApk != null)) {
mLoadedApk.set(realApplication, loadedApk);
}
}
}
}
}

资源文件修改的处理相对于Java文件的处理较为复杂,这中间涉及到aapt、attribute唯一性 、ID值一致等问题都增加了资源文件处理的难度。

总结

总的来说,每种方法都有自己的特色,BUCK依赖于自己强大的缓存和依赖管理系统。而LayoutCast和Instant Run相对而言采用了更灵巧的方法。相对而言,Instant Run 凭借着天然的优势(和升级后的gradle结合),可以胜LayoutCast一筹,但是LayoutCast这种想法的提出还是很赞的。目前增量的编译集中在Java文件的修改,对于Res的修改暂时好像还不支持,这在后续应该会有提升吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/543916.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ipv6寻址_什么是IPV4寻址?

ipv6寻址IPV4寻址简介 (Introduction to IPV4 Addressing ) Internet protocol version 4 in any network, is a standard protocol for assigning a logical address (IP address) to hosts. You are currently using the same protocol. This protocol is capable of providi…

1593: [Usaco2008 Feb]Hotel 旅馆

1593: [Usaco2008 Feb]Hotel 旅馆 Time Limit: 10 Sec Memory Limit: 64 MBSubmit: 489 Solved: 272[Submit][Status][Discuss]Description 奶牛们最近的旅游计划&#xff0c;是到苏必利尔湖畔&#xff0c;享受那里的湖光山色&#xff0c;以及明媚的阳光。作为整个旅游的策划…

最长递增子序列 子串_最长递增子序列

最长递增子序列 子串Description: 描述&#xff1a; This is one of the most popular dynamic programming problems often used as building block to solve other problems. 这是最流行的动态编程问题之一&#xff0c;通常用作解决其他问题的基础。 Problem statement: 问…

beta版本项目冲刺

项目冲刺第一天项目冲刺第二天项目冲刺第三天项目冲刺第四天项目冲刺第五天项目冲刺第六天项目冲刺第七天转载于:https://www.cnblogs.com/malinlin/p/5006041.html

mkdir 函数_PHP mkdir()函数与示例

mkdir 函数PHP mkdir()函数 (PHP mkdir() function) The full form of mkdir is "Make Directory", the function mkdir() is used to create a directory. mkdir的完整格式为“ Make Directory” &#xff0c; 函数mkdir()用于创建目录。 Syntax: 句法&#xff1a…

WPF自定义控件与样式(5)-Calendar/DatePicker日期控件自定义样式及扩展

原文:WPF自定义控件与样式(5)-Calendar/DatePicker日期控件自定义样式及扩展一&#xff0e;前言 申明&#xff1a;WPF自定义控件与样式是一个系列文章&#xff0c;前后是有些关联的&#xff0c;但大多是按照由简到繁的顺序逐步发布的等&#xff0c;若有不明白的地方可以参考本…

Cookie介绍及使用

Cookie学习: 作用:解决了发送的不同请求的数据共享问题 使用: Cookie的创建和存储//创建Cookie对象Cookie cnew Cookie("mouse","");//设置cookie(可选)//设置有效期c.setMaxAge(int seconds);//设置有效路径c.setPath(String uri);//响应Cookie信息给客…

acm模式_ACM的完整形式是什么?

acm模式ACM&#xff1a;计算机协会 (ACM: Association for Computing Machinery) ACM is an abbreviation of the "Association for Computing Machinery (ACM)". ACM是“计算机协会(ACM)”的缩写 。 It is an international academic association or scholarly soc…

c语言:用%f输出实数,只能得到6位小数及求float型数据的有效位数

1.用%f输出实数&#xff0c;只能得到6位小数。程序&#xff1a;#include<stdio.h>int main(){double a 1.0;printf("%f\n",a/3);return 0;}结果&#xff1a;0.333333请按任意键继续. . .2.float型数据的有效位数。程序&#xff1a;#include<stdio.h>int…

二分法查找算法

二分法查找索引值 二分法查找算法步骤&#xff1a;(前提&#xff1a;查询数组为一组有序数)1、定义低位和高位指针low&#xff0c;high&#xff1b;2、通过判断low和high的所指的数值中间值mid来判断关键值是在高位段还是低位段。例题解析&#xff1a; 查找5的索引值 sum {1,2…

bba70_BBA的完整形式是什么?

bba70BBA&#xff1a;工商管理学士 (BBA: Bachelor of Business Administration) BBA is an abbreviation of Bachelor of Business Administration also spelled as B.B.A. In the field of Business Administration, it is an undergraduate degree program. This is a degre…

Qt和纹理

2019独角兽企业重金招聘Python工程师标准>>> test 转载于:https://my.oschina.net/assange/blog/537631

计算机图形学图形旋转_计算机图形学翻译

计算机图形学图形旋转计算机图形学| 翻译 (Computer Graphics | Translations) Transformation techniques mean to modify the current shape or object in a particular manner. Changing of an object after creation, in terms of position or even size is known as trans…

AP 1532E register   Cisco 2504 AP注册WLC

客户的环境是&#xff1a;WLC是 2504 的AP的型号是 1532E的首先要是版本匹配&#xff0c;那么我们就要查一个兼容性列表&#xff0c;请看附件同时&#xff0c;我们要把WLC的版本升级到AIR-CT2500-K9-8-1-111-0.aes 这个版本&#xff1b;同时由于瘦AP 1532E的版本是 Cisco IOS S…

Python 位运算、判断、循环

位运算 1、原码、反码和补码 计算机内部使用补码来表示 2、按位运算实现快速计算 (1) 通过^(异或)快速交换两个整数。 a^b b^a a^b (2) 通过a&(-a)快速获取a的最后为1 位置的整数。 00 00 01 01 -> 5 & 11 11 10 11 -> -5 - - - 00 00 00 01-> 14、利用位运…

dbms数据库管理系统_数据库管理系统(DBMS)中的视图

dbms数据库管理系统DBMS College professor once realized that students feel sad when they see their friends marks higher than them and it creates a negative impact on them. It gave the Professor an idea to create a view table in his student academic result d…

C#中IDisposable 回收非托管资源

C#中IDisposable 更多2014/9/7 来源&#xff1a;C#学习浏览量&#xff1a;4185学习标签&#xff1a; IDisposable本文导读&#xff1a;C#中IDisposable接口的主要用途是释放非托管资源。当不再使用托管对象时&#xff0c;垃圾回收器会自动释放分配给该对象的内存。但无法预测进…

css导航栏_使用CSS的导航栏

css导航栏CSS | 导航栏 (CSS | Navigation Bar) Developing websites is great but developing a user-friendly website is even greater. So how does one design a user-friendly website? What tools to use? Well, there are many tools to mention which are quite hel…

Python 集合、序列基础知识

集合 Python 中set与dict类似&#xff0c;也是一组key的集合&#xff0c;但不存储value。由于key不能重复&#xff0c;所以&#xff0c;在set中&#xff0c;没有重复的key。 key为不可变类型&#xff0c;即可哈希的值。 num {} print(type(num)) # <class dict> num …

Java代理系列-静态代理

2019独角兽企业重金招聘Python工程师标准>>> 代理模式可以做很多事&#xff0c;像hibernate&#xff0c;spring都使用了代理模式。 spring的aop就是用代理做的。 本系列分为4章&#xff0c;静态代理&#xff0c;动态代理热身&#xff0c;动态代理&#xff0c;cglib代…