前篇福利-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…