Freeline适配kotlin-1

source增量逻辑梳理

Posted by Retrox on August 25, 2017

Freeline source文件的增量原理梳理

为什么要写这个呢?

因为我觉得…适配kotlin之前要弄通java的增量逻辑然后适配其实也不是很难

还有就是… source增量比资源增量好理解些(个人感觉)

先放个图

阿咏的图hhh

切入正题

官方已经很细致的讲述了freeline增量构建的原理,欢迎时刻回味官方介绍,贴个地址

官方原理介绍

这次我的分析作为一个第三方分析,主要来源于一个修轮子的人对于源码一层层的理解。因为要适配kotlin嘛,所以要先把java增量理顺。

所谓增量?

增量 ——> 对症下药

python freeline.py -f第一步跑项目,生成一些基本信息

在 app/build/freeline目录中我们可以发现这些文件

~/AndroidStudioProjects/One/app/build/freeline vlayout*
❯ ls
app                           freeline-backup               jar_dependencies.json         public_keeper.xml
freeline-assets               freeline_annotation_info.json project_info_cache.json       stat_cache.json
  • jar_dependencies.json : 记录项目所用第三方依赖jar包的地址,classpath会用到
  • project_info_cache.json : 记录项目相关的配置,不如说assets在哪啊,java环境变量啊什么的
  • stat_cache.json : 这就是本节重点了 记录了各个文件的状态,记录了什么状态呢???

让我们来看看啊~

{
  "app": {
    
    "/Users/retrox/AndroidStudioProjects/One/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png": {
      "md5": "4ad08251ddecfdd6e2a2c53e79b8d8de",
      "mtime": 1491468325.0
    },
    
 "/Users/retrox/AndroidStudioProjects/One/app/src/main/res/layout/activity_vlayout_main.xml": {
      "md5": "27764e0bfc8b1eceb487270b5f81077f",
      "mtime": 1491671113.0
    },
    "/Users/retrox/AndroidStudioProjects/One/app/src/main/java/com/twtstudio/one/view/InfoView.java": {
      "md5": "ca64e944f5f77b3d9d963e6b5f7a5b1b",
      "mtime": 1491468325.0
    },
    
    "/Users/retrox/AndroidStudioProjects/One/app/src/main/res/values/dimens.xml": {
      "md5": "77708536bdf75969da02b4a5687d1a9b",
      "mtime": 1491468325.0
    },
    "/Users/retrox/AndroidStudioProjects/One/app/src/main/res/mipmap-xxhdpi/ic_launcher.png": {
      "md5": "40d69d20dd057b4a331d03f2f3f2667e",
      "mtime": 1491468325.0
    },
    "/Users/retrox/AndroidStudioProjects/One/app/src/main/AndroidManifest.xml": {
      "md5": "58017fd1d190500d73de049906a5c5c6",
      "mtime": 1491663464.0
    },
    "/Users/retrox/AndroidStudioProjects/One/app/src/main/res/values/strings.xml": {
      "md5": "5a7370677c86e0d1f9172ad12d9b07c8",
      "mtime": 1491468325.0
    },
    "/Users/retrox/AndroidStudioProjects/One/app/src/main/res/layout/info_fragment.xml": {
      "md5": "66fa79125bcec1303742ef02a1e00b91",
      "mtime": 1491468325.0
    },
    "/Users/retrox/AndroidStudioProjects/One/app/src/main/res/layout/item.xml": {
      "md5": "d9ac17df542927edd7e87d686827a68e",
      "mtime": 1491661862.0
    },
    "/Users/retrox/AndroidStudioProjects/One/app/src/main/java/com/twtstudio/one/OneApp.java": {
      "md5": "2540855b975841251027d551e06e6c39",
      "mtime": 1491468325.0
    },
    "/Users/retrox/AndroidStudioProjects/One/app/src/main/res/mipmap-mdpi/ic_launcher.png": {
      "md5": "5ff8945dee0f56d079db8ce6691aaaeb",
      "mtime": 1491468325.0
    },
    "/Users/retrox/AndroidStudioProjects/One/app/build.gradle": {
      "md5": "5ca79f9228d06c1548bded1b926b62b4",
      "mtime": 1503676918.0
    },
    "/Users/retrox/AndroidStudioProjects/One/app/libs/Depth_lib_android.jar": {
      "md5": "725d9cbc54865c1fe5ccfcec13255753",
      "mtime": 1491468325.0
    },
    "/Users/retrox/AndroidStudioProjects/One/app/src/main/res/layout/activity_welcome.xml": {
      "md5": "7bc85b0c5224fef021a037f4b057cffb",
      "mtime": 1491663032.0
    },
    "/Users/retrox/AndroidStudioProjects/One/app/src/main/res/layout/footer.xml": {
      "md5": "55dcb32dc6a806382ddfc05505e7c42c",
      "mtime": 1491468325.0
    },
    "/Users/retrox/AndroidStudioProjects/One/app/src/main/res/layout/activity_content.xml": {
      "md5": "70bcbcd6fe9ee665e4e83ee172936246",
      "mtime": 1491468325.0
    },
    "/Users/retrox/AndroidStudioProjects/One/app/src/main/res/layout/activity_main.xml": {
      "md5": "f5b8721e3a23069a6f9f945cdf8551ac",
      "mtime": 1491536877.0
    },
    "/Users/retrox/AndroidStudioProjects/One/app/src/main/res/mipmap-xhdpi/ic_launcher.png": {
      "md5": "93fd2eadf162fd41695cab262540184f",
      "mtime": 1491468325.0
    },
    "/Users/retrox/AndroidStudioProjects/One/app/src/main/res/values/styles.xml": {
      "md5": "ef65245d9b65dc4e791395cc5351de59",
      "mtime": 1491468325.0
    },
    "/Users/retrox/AndroidStudioProjects/One/app/src/main/res/mipmap-hdpi/ic_launcher.png": {
      "md5": "357539403cded61d96161198a0773ffc",
      "mtime": 1491468325.0
    },
    "/Users/retrox/AndroidStudioProjects/One/app/src/main/res/drawable/one_pic.jpeg": {
      "md5": "d4adae21678a36795006a1d9d6ad2fb3",
      "mtime": 1491468325.0
    },
    "/Users/retrox/AndroidStudioProjects/One/app/src/main/res/values-w820dp/dimens.xml": {
      "md5": "f06a662d076312e2271749c093cf40d3",
      "mtime": 1491468325.0
    },
    "/Users/retrox/AndroidStudioProjects/One/app/src/main/java/com/twtstudio/one/presenter/InfoBeanPresenter.java": {
      "md5": "d89984c2adf024bcd39104bdec13d131",
      "mtime": 1491527284.0
    },
    "/Users/retrox/AndroidStudioProjects/One/app/src/main/res/values/colors.xml": {
      "md5": "5891b38465c5cfd7020d91693388aea6",
      "mtime": 1491468325.0
    },
    "/Users/retrox/AndroidStudioProjects/One/app/src/main/res/layout/item_simple_title.xml": {
      "md5": "18b47cab0db0c34cdde68598d2c41552",
      "mtime": 1491671160.0
    }
  },
  "/Users/retrox/AndroidStudioProjects/One/settings.gradle": {
    "md5": "a7fe1ac39169b5e5285a5e53662f251b"
  },
  "/Users/retrox/AndroidStudioProjects/One/build.gradle": {
    "md5": "413db5cee66d5c922059d2ab9b0f9163"
  }
}

所以看得出来他是扫描了项目代码,资源相关目录的文件,并且标注了修改日期和md5值

写这篇博客的时候我已经适配了kotlin…所以我故意把json里面的的kt文件一个个删掉…

宝宝心里苦啊

继续说,其实看到这个json,心里还是有一点小头绪的,那我们去看代码吧!

    def __check_changes(self, module_name, fpath, module_cache):
        if not fpath:
            return False

        if fpath not in module_cache:
            self.debug('find new file {}'.format(fpath))
            return True

        stat = module_cache[fpath]
        mtime = os.path.getmtime(fpath)
        if mtime > stat['mtime']:
            self.debug('find {} has modification.'.format(fpath))
            stat['mtime'] = mtime
            self._stat_cache[module_name][fpath] = stat

            md5 = get_md5(fpath)
            if md5 != stat['md5']:
                self.debug('find {} has modification.'.format(fpath))
                stat['md5'] = md5
                self._stat_cache[module_name][fpath] = stat
                return True
        return False

这就是检查文件变化的代码(json缓存层的代码我就不贴了 大家意会 也可以看代码)

  • 如果在json cache文件里找不到的话 直接返回true

  • 如果找到了,那就检查修改时间 ,如果修改时间有变化 然后就判断md5 最后综合得出结论

个人感觉:用修改时间比较来避免大量的md5

运算然后用md5校验最大限度的减小不必要增量

然后我们再回去看增量文件的扫描方法

# scan src
            src_dirs = self._config['project_source_sets'][module_name]['main_src_directory']
            for src_dir in src_dirs:
                if os.path.exists(src_dir):
                    for dirpath, dirnames, files in os.walk(src_dir):
                        if re.findall(r'[/\\+]androidTest[/\\+]', dirpath) or '/.' in dirpath:
                            continue
                        for fn in files:
                            if fn.endswith('java'):
                                if fn.endswith('package-info.java') or fn.endswith('BuildConfig.java'):
                                    continue
                                fpath = os.path.join(dirpath, fn)
                                if self.__check_changes(module_name, fpath, module_cache):
                                    self._changed_files[module_name]['src'].append(fpath)
                            # 添加kotlin的增量检查
#                            elif fn.endswith('kt'):
#                               fpath = os.path.join(dirpath, fn)
#                              if self.__check_changes(module_name, fpath, module_cache):
#                                 self._changed_files[module_name]['kotlin'].append(fpath)

就是对相应的文件夹遍历而已 然后判断修改 如果修改就把它的路基保存下来 秋后问斩~

我注释掉了自己加的kotlin判断部分 当然你也可以推断出 之前的代码对于kotlin文件是不作处理的

对! 直接跳过了……

self._changed_files[module_name]['src'].append(fpath)这里放着修改过的文件,然后对它们做什么操作呢

当然是javac编译他们了啊!

    # 删减了一些处理兼容性 apt什么的代码 为了方便阅读
    def _generate_java_compile_args(self, extra_javac_args_enabled=False):
        javacargs = [self._javac]
        arguments = ['-encoding', 'UTF-8', '-g']
        if not self._is_retrolambda_enabled:
            arguments.extend(['-target', '1.7', '-source', '1.7'])

        arguments.append('-cp')
        arguments.append(os.pathsep.join(self._classpaths))

        # 睁大眼睛
        for fpath in self._changed_files['src']:
            arguments.append(fpath)
    
        arguments.append('-d')
        arguments.append(self._finder.get_patch_classes_cache_dir())

        javacargs.extend(arguments)
        return javacargs

随意感受 java增量过程

class GradleIncJavacCommand(IncJavacCommand):
    def __init__(self, module_name, invoker):
        IncJavacCommand.__init__(self, module_name, invoker)

    def execute(self):
        self._invoker.check_r_md5()  # check if R.java has changed
        # self._invoker.check_other_modules_resources()
        should_run_javac_task = self._invoker.check_javac_task()
        if not should_run_javac_task:
            self.debug('no need to execute')
            return

        self.debug('start to execute javac command...')
        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() # 在这个方法里面调用了_generate_java_compile_args
        self._invoker.run_retrolambda()

然后呢? 打包增量Dex推送到手机

官方文档对原理有解析 就不再细说 (很多热修复框架也在说这个)

追查到freeline源码

class GradleIncDexCommand(IncDexCommand):
    def __init__(self, module_name, invoker):
        IncDexCommand.__init__(self, module_name, invoker)

    def execute(self):
        should_run_dex_task = self._invoker.check_dex_task() 
        if not should_run_dex_task:
            self.debug('no need to execute')
            return

        self.debug('start to execute dex command...')
        self._invoker.run_dex_task()

  # 根据字节码打包成dex  
  def run_dex_task(self):
        patch_classes_cache_dir = self._finder.get_patch_classes_cache_dir()
        # dex_path = self._finder.get_dst_dex_path()
        dex_path = self._finder.get_patch_dex_dir()
        add_path = None
        if is_windows_system():
            add_path = str(os.path.abspath(os.path.join(self._javac, os.pardir)))
            dex_args = [self._dx, '--dex', '--multi-dex', '--output=' + dex_path, patch_classes_cache_dir]
        else:
            dex_args = [self._dx, '--dex', '--no-optimize', '--force-jumbo', '--multi-dex', '--output=' + dex_path,
                        patch_classes_cache_dir]

        self.debug('dex exec: ' + ' '.join(dex_args))
        output, err, code = cexec(dex_args, add_path=add_path)

        if code != 0:
            raise FreelineException('incremental dex compile failed.', '{}\n{}'.format(output, err))
        else:
            mark_restart_flag(self._cache_dir)

这部分就是检查是否需要进行dex操作然后…

生成增量的部分dex然后插到DebugApp的上面 实现dex的热更新

设置一些标志位什么的(用来表示要不要重启app 要不要重启activity什么的)