前篇福利-Android增量编译3~5秒介绍了增量编译神器freeline的基本使用,这篇文章主要介绍freeline是如何实现快速增量编译的。javascript
首先看一下android打包流程图,图片来源Android开发学习笔记(二)——编译和运行原理
html
资源文件编译
aapt(Android Asset Package Tool)工具对app中的资源文件进行打包。其流程如图(图片来源)
java
apk文件生成与签名
apkbuild工具把编译后的资源文件和dex文件打包成为dex文件。jarsigner完成apk的签名,固然Android7.0以后能够经过apksigner工具进行签名。了解Android Studio 2.2中的APK打包中有介绍。python
Android增量编译分为代码增量和资源增量,资源增量是freeline的一个亮点,instant-run开启时其实在资源上并非增量的,而是把整个应用的资源打成资源包,推送至手机的。android
freeline 在实现上借鉴了buck,layoutCast的思想,把整个过程构建成多个任务,多任务并发,同时缓存各个阶段的生成文件,以达到快速构建的目的。git
先来看一张图(图片来源)
github
咱们在debug时可能会进行屡次代码修改,并运行程序看修改效果,也就是要进行屡次的增量编译,freeline对每次对编译过程进行了缓存。好比咱们进行了三次增量编译,freeline每次编译都是针对本次修改的文件,对比LayoutCast 和instant-run每次增量编译都是编译第一次全量编译以后的更改的文件,freeline速度快了不少,根据freeline官方给的数据,快了3~4倍,可是这样freeline进行增量编译时的复杂性增长了很多。
另外freeline增量编译后可调试,这点相对于instant-run 和LayoutCast来讲,优点很大。freeline官方介绍中提到的懒加载,我的认为只是锦上添花的做用,在实际中可能并无太大做用。android-studio
终于到了代码分析的环节,仍是先贴一下freeline的github地址:freeline,咱们看一下其源码有哪些内容缓存
android-studio-plugin是android中的freeline插件源码
databinding-cli顾名思义是对dababinding的支持
freeline_core是咱们今天分析的重点
gradle 是对gradle中freeline配置的支持
release-tools中是编译过程当中用到的工具,如aapt工具等
runtime是增量编译后客户端处理的逻辑
sample是给出的demo多线程
若是想编译调试freeline增量编译的源码,能够先clone下freeline的源码,而后导入sample工程,注意sample中其实就包含了freeline_core的源码,我这里用的ide是Pycharm。
freeline对于android的编译分为两个过程:全量编译和增量编译,咱们先来看全量编译。
代码入口固然是freeline.py,
if sys.version_info > (3, 0):
print 'Freeline only support Python 2.7+ now. Please use the correct version of Python for freeline.'
exit()
parser = get_parser()
args = parser.parse_args()
freeline = Freeline()
freeline.call(args=args)复制代码
首先判断是不是python2.7,freeline是基于python2.7的,而后对命令进行解析:
parser.add_argument('-v', '--version', action='store_true', help='show version')
parser.add_argument('-f', '--cleanBuild', action='store_true', help='force to execute a clean build')
parser.add_argument('-w', '--wait', action='store_true', help='make application wait for debugger')
parser.add_argument('-a', '--all', action='store_true',
help="together with '-f', freeline will force to clean build all projects.")
parser.add_argument('-c', '--clean', action='store_true', help='clean cache directory and workspace')
parser.add_argument('-d', '--debug', action='store_true', help='enable debug mode')
parser.add_argument('-i', '--init', action='store_true', help='init freeline project')复制代码
以后建立了Freeline对象
def __init__(self):
self.dispatcher = Dispatcher()
def call(self, args=None):
if 'init' in args and args.init:
print('init freeline project...')
init()
exit()
self.dispatcher.call_command(args)复制代码
freeline中建立了dispatcher,从名字能够就能够看出是进行命令分发的,就是在dispatcher中执行不一样的编译过程。在dispatcher执行call方法以前,init方法中执行了checkBeforeCleanBuild命令,完成了部分初始化任务。
if 'cleanBuild' in args and args.cleanBuild:
is_build_all_projects = args.all
wait_for_debugger = args.wait
self._setup_clean_build_command(is_build_all_projects, wait_for_debugger)
elif 'version' in args and args.version:
version()
elif 'clean' in args and args.clean:
self._command = CleanAllCacheCommand(self._config['build_cache_dir'])
else:
from freeline_build import FreelineBuildCommand
self._command = FreelineBuildCommand(self._config, task_engine=self._task_engine)复制代码
咱们重点关注最后一行,在这里建立了FreelineBuildCommand,接下来在这里进行全量编译和增量编译。首先须要判断时增量编译仍是全量编译,全量编译则执行CleanBuildCommand
,增量编译则执行IncrementalBuildCommand
if self._dispatch_policy.is_need_clean_build(self._config, file_changed_dict):
self._setup_clean_builder(file_changed_dict)
from build_commands import CleanBuildCommand
self._build_command = CleanBuildCommand(self._builder)
else:
# only flush changed list when your project need a incremental build.
Logger.debug('file changed list:')
Logger.debug(file_changed_dict)
self._setup_inc_builder(file_changed_dict)
from build_commands import IncrementalBuildCommand
self._build_command = IncrementalBuildCommand(self._builder)
self._build_command.execute()复制代码
咱们看一下is_need_clean_build
方法
def is_need_clean_build(self, config, file_changed_dict):
last_apk_build_time = file_changed_dict['build_info']['last_clean_build_time']
if last_apk_build_time == 0:
Logger.debug('final apk not found, need a clean build.')
return True
if file_changed_dict['build_info']['is_root_config_changed']:
Logger.debug('find root build.gradle changed, need a clean build.')
return True
file_count = 0
need_clean_build_projects = set()
for dir_name, bundle_dict in file_changed_dict['projects'].iteritems():
count = len(bundle_dict['src'])
Logger.debug('find {} has {} java files modified.'.format(dir_name, count))
file_count += count
if len(bundle_dict['config']) > 0 or len(bundle_dict['manifest']) > 0:
need_clean_build_projects.add(dir_name)
Logger.debug('find {} has build.gradle or manifest file modified.'.format(dir_name))
is_need_clean_build = file_count > 20 or len(need_clean_build_projects) > 0
if is_need_clean_build:
if file_count > 20:
Logger.debug(
'project has {}(>20) java files modified so that it need a clean build.'.format(file_count))
else:
Logger.debug('project need a clean build.')
else:
Logger.debug('project just need a incremental build.')
return is_need_clean_build复制代码
freelined的策略以下,若是有策略需求,能够经过更改这部分的代码来实现。
1.在git pull 或 一次性修改大量
2.没法依赖增量实现的修改:修改AndroidManifest.xml,更改第三方jar引用,依赖编译期切面,注解或其余代码预处理插件实现的功能等。
3.更换调试手机或同一调试手机安装了与开发环境不一致的安装包。
self.add_command(CheckBulidEnvironmentCommand(self._builder))
self.add_command(FindDependenciesOfTasksCommand(self._builder))
self.add_command(GenerateSortedBuildTasksCommand(self._builder))
self.add_command(UpdateApkCreatedTimeCommand(self._builder))
self.add_command(ExecuteCleanBuildCommand(self._builder))复制代码
能够看到,全量编译时实际时执行了如上几条command,咱们重点看一下GenerateSortedBuildTasksCommand
,这里建立了多条存在依赖关系的task,在task_engine启动按照依赖关系执行,其它command相似。
build_task.add_child_task(clean_all_cache_task)
build_task.add_child_task(install_task)
clean_all_cache_task.add_child_task(build_base_resource_task)
clean_all_cache_task.add_child_task(generate_project_info_task)
clean_all_cache_task.add_child_task(append_stat_task)
clean_all_cache_task.add_child_task(generate_apt_file_stat_task)
read_project_info_task.add_child_task(build_task)复制代码
最后在ExecuteCleanBuildCommand
中启动task_engine
self._task_engine.add_root_task(self._root_task)
self._task_engine.start()复制代码
增量编译与全量编译以前的步骤相同,在FreelineBuildCommand
中建立了IncrementalBuildCommand
self.add_command(CheckBulidEnvironmentCommand(self._builder))
self.add_command(GenerateSortedBuildTasksCommand(self._builder))
self.add_command(ExecuteIncrementalBuildCommand(self._builder))复制代码
建立了三个command,咱们重点看一下GenerateSortedBuildTasksCommand
这里比全量编译更复杂一些。def generate_sorted_build_tasks(self):
"""
sort build tasks according to the module's dependency
:return: None
"""
for module in self._all_modules:
task = android_tools.AndroidIncrementalBuildTask(module, self.__setup_inc_command(module))
self._tasks_dictionary[module] = task
for module in self._all_modules:
task = self._tasks_dictionary[module]
for dep in self._module_dependencies[module]:
task.add_parent_task(self._tasks_dictionary[dep])复制代码
能够看到首先遍历每一个module建立AndroidIncrementalBuildTask,以后遍历mudle建立任务依赖关系。建立AndroidIncrementalBuildTask时传入了GradleCompileCommand
self.add_command(GradleIncJavacCommand(self._module, self._invoker))
self.add_command(GradleIncDexCommand(self._module, self._invoker))复制代码
查看一下GradleIncJavacCommand
self._invoker.append_r_file()
self._invoker.fill_classpaths()
self._invoker.fill_extra_javac_args()
self._invoker.clean_dex_cache()
self._invoker.run_apt_only()
self._invoker.run_javac_task()
self._invoker.run_retrolambda()复制代码
执行了以上几个函数,具体的内容能够查看源码。
如下简单说一下task_engine时如何解决task的依赖关系,这里根据task中的 parent_task列表定义了每一个task的depth:
def calculate_task_depth(task):
depth = []
parent_task_queue = Queue.Queue()
parent_task_queue.put(task)
while not parent_task_queue.empty():
parent_task = parent_task_queue.get()
if parent_task.name not in depth:
depth.append(parent_task.name)
for parent in parent_task.parent_tasks:
if parent.name not in depth:
parent_task_queue.put(parent)
return len(depth)复制代码
在具体执行时根据depth对task进行了排序
depth_array.sort()
for depth in depth_array:
tasks = self.tasks_depth_dict[depth]
for task in tasks:
self.debug("depth: {}, task: {}".format(depth, task))
self.sorted_tasks.append(task)
self._logger.set_sorted_tasks(self.sorted_tasks)
for task in self.sorted_tasks:
self.pool.add_task(ExecutableTask(task, self))复制代码
而后每一个task执行时会判断parent是否执行完成
while not self.task.is_all_parent_finished():
# self.debug('{} waiting...'.format(self.task.name))
self.task.wait()复制代码
只有parent任务执行完成后,task才能够开始执行。
##总结
本文从增量编译的原理和代码角度简单分析了freeline的实现,其中原理部分主要参考了中文原理说明,代码部分主要分析了大致框架,没有深刻到每个细节,如freeline如何支持apt、lambda等,可能以后会再继续写文分析。
本人才疏学浅,若是有分析错误的地方,请指出。
##参考
github.com/alibaba/fre…
yq.aliyun.com/articles/59…
www.cnblogs.com/Pickuper/ar…
blog.csdn.net/luoshengyan…