文章目录

  • 一、不同 Android 系统创建 dex 数组源码对比
  • 二、不同 Android 系统创建 dex 数组源码对比
  • 三、 Android 5.1 及以下系统反射方法并创建 Element[] dexElements
  • 四、 Android 6.0 及以下系统反射方法并创建 Element[] dexElements
  • 五、 完整代码示例

参考博客 :

  • 【Android 安全】DEX 加密 ( 常用 Android 反编译工具 | apktool | dex2jar | enjarify | jd-gui | jadx )
  • 【Android 安全】DEX 加密 ( Proguard 简介 | Proguard 相关网址 | Proguard 混淆配置 )
  • 【Android 安全】DEX 加密 ( Proguard 简介 | 默认 ProGuard 分析 )
  • 【Android 安全】DEX 加密 ( Proguard keep 用法 | Proguard 默认混淆结果 | 保留类及成员混淆结果 | 保留注解以及被注解修饰的类/成员/方法 )
  • 【Android 安全】DEX 加密 ( Proguard 混淆 | 混淆后的报错信息 | Proguard 混淆映射文件 mapping.txt )
  • 【Android 安全】DEX 加密 ( Proguard 混淆 | 将混淆后的报错信息转为原始报错信息 | retrace.bat 命令执行目录 | 暴露更少信息 )
  • 【Android 安全】DEX 加密 ( DEX 加密原理 | DEX 加密简介 | APK 文件分析 | DEX 分割 )
  • 【Android 安全】DEX 加密 ( 多 DEX 加载 | 65535 方法数限制和 MultiDex 配置 | PathClassLoader 类加载源码分析 | DexPathList )
  • 【Android 安全】DEX 加密 ( 不同 Android 版本的 DEX 加载 | Android 8.0 版本 DEX 加载分析 | Android 5.0 版本 DEX 加载分析 )
  • 【Android 安全】DEX 加密 ( DEX 加密使用到的相关工具 | dx 工具 | zipalign 对齐工具 | apksigner 签名工具 )
  • 【Android 安全】DEX 加密 ( 支持多 DEX 的 Android 工程结构 )
  • 【Android 安全】DEX 加密 ( 代理 Application 开发 | multiple-dex-core 依赖库开发 | 配置元数据 | 获取 apk 文件并准备相关目录 )
  • 【Android 安全】DEX 加密 ( 代理 Application 开发 | 解压 apk 文件 | 判定是否是第一次启动 | 递归删除文件操作 | 解压 Zip 文件操作 )
  • 【Android 安全】DEX 加密 ( 代理 Application 开发 | 加载 dex 文件 | 反射获取系统的 Element[] dexElements )
  • 【Android 安全】DEX 加密 ( 代理 Application 开发 | 加载 dex 文件 | 使用反射获取方法创建本应用的 dexElements | 各版本创建 dex 数组源码对比 )

在 【Android 安全】DEX 加密 ( 支持多 DEX 的 Android 工程结构 ) 博客中介绍了 DEX 加密工程的基本结构 ,

app 是主应用 , 其 Module 类型是 “Phone & Tablet Module” ,

multiple-dex-core 是 Android 依赖库 , 其作用是解密并加载多 DEX 文件 , 其 Module 类型是 “Android Library” ,

multiple-dex-tools 是 Java 依赖库 , 其类型是 “Java or Kotlin Library” , 其作用是用于生成主 DEX ( 主 DEX 的作用就是用于解密与加载多 DEX ) , 并且还要为修改后的 APK 进行签名 ;

在 【Android 安全】DEX 加密 ( 代理 Application 开发 | multiple-dex-core 依赖库开发 | 配置元数据 | 获取 apk 文件并准备相关目录 ) 博客中讲解了 multiple-dex-core 依赖库开发 , 每次启动都要解密与加载 dex 文件 , 在该博客中讲解到了 获取 apk 文件 , 并准备解压目录 ;

在 【Android 安全】DEX 加密 ( 代理 Application 开发 | 解压 apk 文件 | 判定是否是第一次启动 | 递归删除文件操作 | 解压 Zip 文件操作 ) 博客中讲解了 apk 文件解压操作 ;

在 【Android 安全】DEX 加密 ( 代理 Application 开发 | 加载 dex 文件 | 反射获取系统的 Element[] dexElements )博客中讲解了 dex 文件加载第一阶段 , 获取系统中的 Element[] dexElements ;

本博客中主要讲解 dex 文件加载操作 第二阶段 , 创建本应用的 dex 文件数组 Element[] dexElements ;

一、不同 Android 系统创建 dex 数组源码对比


Android4.4(KitKatAPI19)\rm Android \ 4.4 \ ( KitKat \ API \ 19 )Android 4.4 (KitKat API 19) 系统中创建 Element[] dexElements 的方法 :

/*package*/ final class DexPathList {private Element[] dexElements;public DexPathList(ClassLoader definingContext, String dexPath,String libraryPath, File optimizedDirectory) {this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions);}/*** 创建一个数组 ,  每个数组元素都是 dex 路径名称 .*/private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,ArrayList<IOException> suppressedExceptions) {}
}

参考 : 4.4.4_r1/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

Android4.4W(KitKatWatchAPI20)\rm Android \ 4.4W \ ( KitKat Watch \ API \ 20 )Android 4.4W (KitKatWatch API 20) 系统中创建 Element[] dexElements 的方法 :

/*package*/ final class DexPathList {private Element[] dexElements;public DexPathList(ClassLoader definingContext, String dexPath,String libraryPath, File optimizedDirectory) {this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions);}/*** 创建一个数组 ,  每个数组元素都是 dex 路径名称 .*/private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,ArrayList<IOException> suppressedExceptions){}
}

参考 : 4.4w_r1/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

Android5.0(LollipopAPI21)\rm Android \ 5.0 \ ( Lollipop \ API \ 21 )Android 5.0 (Lollipop API 21) 系统中创建 Element[] dexElements 的方法 :

/*package*/ final class DexPathList {private Element[] dexElements;public DexPathList(ClassLoader definingContext, String dexPath,String libraryPath, File optimizedDirectory) {this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions);}/*** 创建一个数组 ,  每个数组元素都是 dex 路径名称 .*/private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,ArrayList<IOException> suppressedExceptions){}
}

参考 : 5.0.1_r1/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

Android5.1(LollipopAPI22)\rm Android \ 5.1 \ ( Lollipop \ API \ 22 )Android 5.1 (Lollipop API 22) 系统中创建 Element[] dexElements 的方法 :

/*package*/ final class DexPathList {private Element[] dexElements;public DexPathList(ClassLoader definingContext, String dexPath,String libraryPath, File optimizedDirectory) {this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions);}/*** 创建一个数组 ,  每个数组元素都是 dex 路径名称 .*/private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,ArrayList<IOException> suppressedExceptions){}
}

参考 : 5.1.0_r3/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

Android6.0(MarshmallowAPI23)\rm Android \ 6.0 \ (Marshmallow \ API \ 23 )Android 6.0 (Marshmallow API 23) 系统中创建 Element[] dexElements 的方法 :

/*package*/ final class DexPathList {private Element[] dexElements;public DexPathList(ClassLoader definingContext, String dexPath,String libraryPath, File optimizedDirectory) {// save dexPath for BaseDexClassLoaderthis.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions);}/*** 创建一个数组 ,  每个数组元素都是 dex 路径名称 .*/private static Element[] makePathElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions) {}
}

参考 : 6.0.1_r16/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

Android7.0(NougatAPI24)\rm Android \ 7.0 \ ( Nougat \ API \ 24 )Android 7.0 (Nougat API 24) 系统中创建 Element[] dexElements 的方法 :

/*package*/ final class DexPathList {private Element[] dexElements;public DexPathList(ClassLoader definingContext, String dexPath,String librarySearchPath, File optimizedDirectory) {// save dexPath for BaseDexClassLoaderthis.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions, definingContext);}private static Element[] makeDexElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions,ClassLoader loader) {}/*** Makes an array of directory/zip path elements, one per element of the given array.*/private static Element[] makePathElements(List<File> files,List<IOException> suppressedExceptions,ClassLoader loader) {return makeElements(files, null, suppressedExceptions, true, loader);}/** TODO (dimitry): Revert after apps stops relying on the existence of this* method (see http://b/21957414 and http://b/26317852 for details)*/private static Element[] makePathElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions) {return makeElements(files, optimizedDirectory, suppressedExceptions, false, null);}
}

参考 : 7.0.0_r31/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

Android7.1(NougatAPI25)\rm Android \ 7.1 \ ( Nougat \ API \ 25 )Android 7.1 (Nougat API 25) 系统中创建 Element[] dexElements 的方法 :

/*package*/ final class DexPathList {private Element[] dexElements;public DexPathList(ClassLoader definingContext, String dexPath,String librarySearchPath, File optimizedDirectory) {// save dexPath for BaseDexClassLoaderthis.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions, definingContext);}private static Element[] makeDexElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions,ClassLoader loader) {}/*** Makes an array of directory/zip path elements, one per element of the given array.*/private static Element[] makePathElements(List<File> files,List<IOException> suppressedExceptions,ClassLoader loader) {return makeElements(files, null, suppressedExceptions, true, loader);}/** TODO (dimitry): Revert after apps stops relying on the existence of this* method (see http://b/21957414 and http://b/26317852 for details)*/private static Element[] makePathElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions) {return makeElements(files, optimizedDirectory, suppressedExceptions, false, null);}
}

参考 : 7.1.1_r28/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

Android8.0(OreoAPI25)\rm Android \ 8.0 \ ( Oreo \ API \ 25 )Android 8.0 (Oreo API 25) 系统中创建 Element[] dexElements 的方法 :

/*package*/ final class DexPathList {private Element[] dexElements;public DexPathList(ClassLoader definingContext, String dexPath,String librarySearchPath, File optimizedDirectory) {// save dexPath for BaseDexClassLoaderthis.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions, definingContext);}private static Element[] makeDexElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions,ClassLoader loader) {}/** TODO (dimitry): Revert after apps stops relying on the existence of this* method (see http://b/21957414 and http://b/26317852 for details)*/@SuppressWarnings("unused")private static Element[] makePathElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions) {return makeDexElements(files, optimizedDirectory, suppressedExceptions, null);}/*** Makes an array of directory/zip path elements for the native library search path, one per* element of the given array.*/private static NativeLibraryElement[] makePathElements(List<File> files) {}
}

参考 : 8.0.0_r4/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

Android9.0(PieAPI28)\rm Android \ 9.0 \ ( Pie \ API \ 28 )Android 9.0 (Pie API 28) 系统中创建 Element[] dexElements 的方法 :

/*package*/ final class DexPathList {private Element[] dexElements;public DexPathList(ClassLoader definingContext, String dexPath,String librarySearchPath, File optimizedDirectory) {// save dexPath for BaseDexClassLoaderthis.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions, definingContext, isTrusted);}/*** Makes an array of dex/resource path elements, one per element of* the given array.*/private static Element[] makeDexElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions, ClassLoader loader) {return makeDexElements(files, optimizedDirectory, suppressedExceptions, loader, false);}private static Element[] makeDexElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {}/** TODO (dimitry): Revert after apps stops relying on the existence of this* method (see http://b/21957414 and http://b/26317852 for details)*/@SuppressWarnings("unused")private static Element[] makePathElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions) {return makeDexElements(files, optimizedDirectory, suppressedExceptions, null);}/*** Makes an array of directory/zip path elements for the native library search path, one per* element of the given array.*/private static NativeLibraryElement[] makePathElements(List<File> files) {}
}

参考 : 9.0.0_r8/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

Android10.0(QAPI29)\rm Android \ 10.0 \ ( Q \ API \ 29 )Android 10.0 (Q API 29) 系统中创建 Element[] dexElements 的方法 :

/*package*/ final class DexPathList {private Element[] dexElements;DexPathList(ClassLoader definingContext, String dexPath,String librarySearchPath, File optimizedDirectory, boolean isTrusted) {// save dexPath for BaseDexClassLoaderthis.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions, definingContext, isTrusted);}/*** Makes an array of dex/resource path elements, one per element of* the given array.*/@UnsupportedAppUsageprivate static Element[] makeDexElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions, ClassLoader loader) {return makeDexElements(files, optimizedDirectory, suppressedExceptions, loader, false);}private static Element[] makeDexElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {}/** TODO (dimitry): Revert after apps stops relying on the existence of this* method (see http://b/21957414 and http://b/26317852 for details)*/@UnsupportedAppUsage@SuppressWarnings("unused")private static Element[] makePathElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions) {return makeDexElements(files, optimizedDirectory, suppressedExceptions, null);}/*** Makes an array of directory/zip path elements for the native library search path, one per* element of the given array.*/@UnsupportedAppUsageprivate static NativeLibraryElement[] makePathElements(List<File> files) {}
}

参考 : 10.0.0_r6/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

二、不同 Android 系统创建 dex 数组源码对比


Android5.0、5.1\rm Android \ 5.0 、 5.1Android 5.0、5.1 系统需要反射如下 makeDexElements 方法 ;

    private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,ArrayList<IOException> suppressedExceptions){}

Android6.0、7.1、7.1、8.0、9.0、10\rm Android \ 6.0 、 7.1、7.1、8.0、9.0、10Android 6.0、7.1、7.1、8.0、9.0、10 系统需要反射如下 makePathElements 方法 ;

    @SuppressWarnings("unused")private static Element[] makePathElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions) {return makeDexElements(files, optimizedDirectory, suppressedExceptions, null);}

三、 Android 5.1 及以下系统反射方法并创建 Element[] dexElements


Android 5.1 及以下系统反射方法并创建 Element[] dexElements :

        if(Build.VERSION.SDK_INT <=Build.VERSION_CODES.LOLLIPOP_MR1){ // 5.0, 5.1  makeDexElements// 反射 5.0, 5.1, 6.0 版本的 DexPathList 中的 makeDexElements 方法makeDexElements = reflexMethod(pathList, "makeDexElements",ArrayList::class.java, File::class.java, ArrayList::class.java)var suppressedExceptions: ArrayList<IOException> = ArrayList<IOException>()addElements = makeDexElements.invoke(pathList, dexFiles,optimizedDirectory,suppressedExceptions) as Array<Any>}

四、 Android 6.0 及以下系统反射方法并创建 Element[] dexElements


Android 6.0 及以下系统反射方法并创建 Element[] dexElements :

        }else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){   // 7.0 以上版本 makePathElements// 反射 7.0 以上版本的 DexPathList 中的 makeDexElements 方法makeDexElements = reflexMethod(pathList, "makePathElements",ArrayList::class.java, File::class.java, ArrayList::class.java)var suppressedExceptions: ArrayList<IOException> = ArrayList<IOException>()addElements = makeDexElements.invoke(pathList, dexFiles,optimizedDirectory,suppressedExceptions) as Array<Any>}

五、 完整代码示例


        /*2 . 在本应用中创建 Element[] dexElements 数组 , 用于存放解密后的 dex 文件不同的 Android 版本中 , 创建 Element[] dexElements 数组的方法不同 , 这里需要做兼容*/var makeDexElements: Methodvar addElements : Array<Any>if(Build.VERSION.SDK_INT <=Build.VERSION_CODES.LOLLIPOP_MR1){ // 5.0, 5.1  makeDexElements// 反射 5.0, 5.1, 6.0 版本的 DexPathList 中的 makeDexElements 方法makeDexElements = reflexMethod(pathList, "makeDexElements",ArrayList::class.java, File::class.java, ArrayList::class.java)var suppressedExceptions: ArrayList<IOException> = ArrayList<IOException>()addElements = makeDexElements.invoke(pathList, dexFiles,optimizedDirectory,suppressedExceptions) as Array<Any>}else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){   // 7.0 以上版本 makePathElements// 反射 7.0 以上版本的 DexPathList 中的 makeDexElements 方法makeDexElements = reflexMethod(pathList, "makePathElements",ArrayList::class.java, File::class.java, ArrayList::class.java)var suppressedExceptions: ArrayList<IOException> = ArrayList<IOException>()addElements = makeDexElements.invoke(pathList, dexFiles,optimizedDirectory,suppressedExceptions) as Array<Any>}

【Android 安全】DEX 加密 ( 代理 Application 开发 | 加载 dex 文件 | 使用反射获取方法创建本应用的 dexElements | 各版本创建 dex 数组源码对比 )相关推荐

  1. 【Android 安全】DEX 加密 ( 代理 Application 开发 | 加载 dex 文件 | 将系统的 dexElements 与 应用的 dexElements 合并 | 替换操作 )

    文章目录 一.将系统的 dexElements 与 应用的 dexElements 合并 二.Element[] dexElements 替换操作 三.完整 dex 加载源码 参考博客 : [Andr ...

  2. 【Android 安全】DEX 加密 ( 代理 Application 开发 | 加载 dex 文件 | 反射获取系统的 Element[] dexElements )

    文章目录 一.dex 文件准备 二.加载 dex 文件流程 三.Element[] dexElements 分析 四.反射获取系统的 Element[] dexElements 参考博客 : [And ...

  3. 【Android 安全】DEX 加密 ( 代理 Application 开发 | 解压 apk 文件 | 判定是否是第一次启动 | 递归删除文件操作 | 解压 Zip 文件操作 )

    文章目录 一.判定是否是第一次启动 二.递归删除文件操作 三.解压 Zip 文件操作 四.解压操作相关代码 参考博客 : [Android 安全]DEX 加密 ( 常用 Android 反编译工具 | ...

  4. 【Android 安全】DEX 加密 ( 代理 Application 开发 | 项目中配置 OpenSSL 开源库 | 使用 OpenSSL 开源库解密 dex 文件 )

    文章目录 一.项目中配置 OpenSSL 开源库 二.OpenSSL 开源库解密参考代码 三.解密 dex 文件的 Java 代码 四.解密 dex 文件的 Jni 代码 参考博客 : [Androi ...

  5. 【Android 安全】DEX 加密 ( 代理 Application 开发 | 交叉编译 OpenSSL 开源库 )

    文章目录 一.OpenSSL 开源库简介 二.OpenSSL 源码及环境变量脚本下载 三.修改环境变量脚本 四.OpenSSL 交叉编译 五.OpenSSL 交叉编译相关资源下载 参考博客 : [An ...

  6. 【Android 安全】DEX 加密 ( 代理 Application 开发 | multiple-dex-core 依赖库开发 | 配置元数据 | 获取 apk 文件并准备相关目录 )

    文章目录 一.multiple-dex-core 依赖库作用 二.配置目录元数据 三.multiple-dex-core 代理 Application 四.获取 apk 文件并准备相关目录 五.相关代 ...

  7. 【Android 安全】DEX 加密 ( Java 工具开发 | 解压 apk 文件 | 加密生成 dex 文件 | 打包未签名 apk 文件 | 文件解压缩相关代码 )

    文章目录 一.解压 apk 文件 二.加密生成 dex 文件 三.打包未签名 apk 文件 四.完整代码示例 五.文件解压缩相关代码 六.执行结果 参考博客 : [Android 安全]DEX 加密 ...

  8. 异步加载js文件并执行js方法:实现异步处理网页的复杂效果

    异步加载js文件并执行js方法:实现异步处理网页的复杂效果 有这么一个场景,当你的网页页面效果过多就会造成了打开页面的速度变得缓慢,长时间处于加载的状态,这样的效果通常会让用户感到不友好,通常的处理方 ...

  9. HTML5动态加载资源方式,动态加载JavaScript文件的两种方法

    这篇文章主要为大家详细介绍了动态加载JavaScript文件的两种方法,感兴趣的小伙伴们可以参考一下 第一种便是利用ajax方式,把script文件代码从背景加载到前台,而后对加载到的内容经过eval ...

最新文章

  1. 基于癌症基因组学数据的miRNA 功能模块识别算法研究
  2. STM32使用GPIO_WriteBit()函数使LED灯闪烁
  3. linux基础篇-10,权限管理chown chgrp chmod umask
  4. 卡尔蔡司携手神策数据,赋能近视防控数字化
  5. mysql hadoop架构,Debezium实现Mysql到Elasticsearch高效实时同步
  6. 【机器视觉】 continue算子
  7. Use function as controller
  8. 总谐波失真计算公式_新能源汽车技术|车用轮毂电机转矩谐波协同控制策略
  9. 使用BaaS更快地构建Xamarin应用程序
  10. dnf最新地图编号2020_2020手游崛起端游没落?网易新端游好玩刺激能搬砖,网友:妙...
  11. CUDA算法——Stream and Event
  12. Go 性能优化技巧 4/10
  13. Vc++安装包_Visual C++ 6.0中文版安装包下载及win11安装教程
  14. 【微信小程序】表单验证WxValidate.js使用
  15. 个人计算机培训校本研修总结,个人研修总结
  16. 虚幻4_添加武器插槽到骨骼
  17. 深度学习与自然语言处理教程(8) - NLP中的卷积神经网络(NLP通关指南·完结)
  18. 现在流行的网络直播都需要什么设备?
  19. 计算机大作业说明文档,计算机大作业.doc
  20. 微信小程序盲盒系统源码 附带教程

热门文章

  1. 监控录像帮忙找回医院被偷的女婴
  2. [首次分析]PHP写框架
  3. pycharm Python解释器的配置--可以指定批处理文件为解释器
  4. 多用户商城系统 KgMall2.1公布
  5. Css2.0+Css3.0+jQuery手册 chm
  6. 算法学习:manacher
  7. python文档的数据读取,把读取数据写入到新的表里
  8. 找不到请求的 .Net Framework 数据提供程序。可能没有安装
  9. 《快活帮》第九次团队作业:Beta冲刺与验收准备
  10. [luogu1131][bzoj1060][ZJOI2007]时态同步【树形DP】