├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── CMakeLists.txt ├── build.gradle ├── maindex-keep.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── bytedance │ │ └── app │ │ └── boost_multidex │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── cpp │ │ └── native-lib.cpp │ ├── java │ │ └── com │ │ │ └── bytedance │ │ │ └── app │ │ │ └── boost_multidex │ │ │ ├── MainActivity.java │ │ │ └── MainApplication.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── bytedance │ └── app │ └── boost_multidex │ └── ExampleUnitTest.java ├── boost_multidex ├── .gitignore ├── CMakeLists.txt ├── build.gradle ├── gradle.properties ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── bytedance │ │ └── boost_multidex │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── cpp │ │ └── boost_multidex.cpp │ ├── java │ │ └── com │ │ │ └── bytedance │ │ │ └── boost_multidex │ │ │ ├── BoostMultiDex.java │ │ │ ├── BoostMultiDexApplication.java │ │ │ ├── BoostNative.java │ │ │ ├── Constants.java │ │ │ ├── DexHolder.java │ │ │ ├── DexInstallProcessor.java │ │ │ ├── DexLoader.java │ │ │ ├── Locker.java │ │ │ ├── Monitor.java │ │ │ ├── OptimizeService.java │ │ │ ├── Result.java │ │ │ └── Utility.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── bytedance │ └── boost_multidex │ └── ExampleUnitTest.java ├── build.gradle ├── docs └── bmd-logo.png ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .idea 4 | local.properties 5 | .DS_Store 6 | build 7 | captures 8 | .externalNativeBuild 9 | .cxx 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | ---------------------------------------------------------------------------------- 204 | 205 | Other dependencies: 206 | 207 | Android Open Source Project 4.4.4_r1 208 | Copyright (C) 2005-2015 The Android Open Source Project 209 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](docs/bmd-logo.png) 2 | 3 | [![GitHub license](https://img.shields.io/badge/license-Apache%202-blue)](https://github.com/bytedance/ByteX/blob/master/LICENSE) 4 | 5 | 6 | **BoostMultiDex**是一个用于Android低版本设备(4.X及以下,SDK < 21)快速加载多DEX的解决方案,由抖音/Tiktok Android技术团队出品。 7 | 8 | 相比于Android官方原始MultiDex方案,它能够减少80%以上的黑屏等待时间,挽救低版本Android用户的升级安装体验。并且,不同于目前业界所有优化方案,BoostMultiDex方案是从Android Dalvik虚拟机底层机制入手,从根本上解决了安装APK后首次执行MultiDex耗时过长问题。 9 | 10 | ## 背景 11 | 12 | 我们知道,Android低版本(4.X及以下,SDK < 21)的设备,采用的Java运行环境是Dalvik虚拟机。它相比于高版本,最大的问题就是在安装或者升级更新之后,首次冷启动的耗时漫长。这常常需要花费几十秒甚至几分钟,用户不得不面对一片黑屏,熬过这段时间才能正常使用APP。 13 | 14 | 这是非常影响用户的使用体验的。尤其在海外,像东南亚以及拉美等地区,还存有着很大量的低端机。4.X以下低版本用户虽然比较少,但对于抖音及Tiktok这样有着亿级规模的用户的APP,即使占比10%,数目也有上千万。因此如果想要打通下沉市场,这部分用户的使用和升级体验是绝对无法忽视的。 15 | 16 | 这个问题的根本原因就在于,安装或者升级后首次MultiDex花费的时间过于漫长。为了解决这个问题,我们挖掘了Dalvik虚拟机的底层系统机制,对DEX相关处理逻辑进行了重新设计,最终推出了BoostMultiDex方案,挽救低版本Android用户的升级安装体验。 17 | 18 | ## 技术要点 19 | 20 | BoostMultiDex方案的技术实现要点如下: 21 | 22 | 1. 利用系统隐藏函数,直接加载原始DEX字节码,避免ODEX耗时 23 | 2. 多级加载,在DEX字节码、DEX文件、ODEX文件中选取最合适的产物启动APP 24 | 3. 单独进程做OPT,并实现合理的中断及恢复机制 25 | 26 | 更重要的是,BoostMultiDex已经在抖音/TikTok亿级全球用户上验证通过,可以说涵盖了各个国家、各种复杂情况的Android机型,目前业界其他大型APP都很难涉及到如此广泛的规模。由此,我们也解决了各种奇怪的兼容性问题,最大程度上确保了技术方案的稳定性。 27 | 28 | ## 快速接入 29 | 30 | build.gradle的dependencies中添加依赖: 31 | 32 | ```gradle 33 | dependencies { 34 | ... ... 35 | // For specific version number, please refer to app demo 36 | implementation 'com.bytedance.boost_multidex:boost_multidex:${ARTIFACT_VERSION}' 37 | } 38 | ``` 39 | 40 | 与官方MultiDex类似,在Application.attachBaseContext的最前面进行初始化即可: 41 | 42 | ```java 43 | public class YourApplication extends Application { 44 | 45 | @Override 46 | protected void attachBaseContext(Context base) { 47 | super.attachBaseContext(base); 48 | 49 | BoostMultiDex.install(base); 50 | 51 | ... ... 52 | } 53 | ``` 54 | 55 | ## 编译构建 56 | 57 | 如果想自行编译打包,需要使用[R16B版本的NDK](https://developer.android.com/ndk/downloads/older_releases)以支持armeabi架构,如果不需要,可以直接在boost_multidex/build.gradle中去掉此依赖。 58 | 59 | 执行以下命令即可构建本地aar包: 60 | 61 | ```gralde 62 | ./gradlew :boost_multidex:assembleRelease 63 | ``` 64 | 65 | 产物为`boost_multidex/build/outputs/aar/boost_multidex-release.aar` 66 | 67 | ## 性能对比 68 | 69 | | Android版本 | 厂商 | 机型 | 原始MultiDex耗时(s) | BoostMultiDex耗时(s) | 70 | | :------: | :------: | :------: | :------: | :------: | 71 | | 4.4.2 | LG | LGMS323 | 33.545 | 5.014 | 72 | | 4.4.4 | MOTO | G | 45.691 | 6.719 | 73 | | 4.3 | Samsung | GT-N7100 | 24.186 | 3.660 | 74 | | 4.3.0 | Samsung | SGH-T999 | 30.331 | 3.791 | 75 | | 4.2.2 | HUAWEI | Hol-T00 | 崩溃 | 3.724 | 76 | | 4.2.1 | HUAWEI | G610-U00 | 36.465 | 4.981 | 77 | | 4.1.2 | Samsung | I9100 | 30.962 | 5.345 | 78 | 79 | 以上是在抖音上测得的实际数据,APK中共有6个Secondary DEX,显而易见,BoostMultiDex方案相比官方MultiDex方案,其耗时有着本质上的优化,基本都只到原先的11%~17%之间。 **也就是说BoostMultiDex减少了原先过程80%以上的耗时。** 另外我们看到,其中有一个机型,在官方MultiDex下是直接崩溃,无法启动的。使用BoostMultiDex也将使得这些机型可以焕发新生。 80 | 81 | ## 详细原理 82 | 83 | 请参考本项目[Wiki](https://github.com/bytedance/BoostMultiDex/wiki/Technical-Article) 84 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about using CMake with Android Studio, read the 2 | # documentation: https://d.android.com/studio/projects/add-native-code.html 3 | 4 | # Sets the minimum version of CMake required to build the native library. 5 | 6 | cmake_minimum_required(VERSION 3.4.1) 7 | 8 | # Creates and names a library, sets it as either STATIC 9 | # or SHARED, and provides the relative paths to its source code. 10 | # You can define multiple libraries, and CMake builds them for you. 11 | # Gradle automatically packages shared libraries with your APK. 12 | 13 | add_library( # Sets the name of the library. 14 | native-lib 15 | 16 | # Sets the library as a shared library. 17 | SHARED 18 | 19 | # Provides a relative path to your source file(s). 20 | src/main/cpp/native-lib.cpp) 21 | 22 | # Searches for a specified prebuilt library and stores the path as a 23 | # variable. Because CMake includes system libraries in the search path by 24 | # default, you only need to specify the name of the public NDK library 25 | # you want to add. CMake verifies that the library exists before 26 | # completing its build. 27 | 28 | find_library( # Sets the name of the path variable. 29 | log-lib 30 | 31 | # Specifies the name of the NDK library that 32 | # you want CMake to locate. 33 | log) 34 | 35 | # Specifies libraries CMake should link to your target library. You 36 | # can link multiple libraries, such as libraries you define in this 37 | # build script, prebuilt third-party libraries, or system libraries. 38 | 39 | target_link_libraries( # Specifies the target library. 40 | native-lib 41 | 42 | # Links the target library to the log library 43 | # included in the NDK. 44 | ${log-lib}) -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "com.bytedance.app.boost_multidex" 7 | minSdkVersion 16 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | 13 | multiDexEnabled true 14 | multiDexKeepProguard file('maindex-keep.pro') 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | externalNativeBuild { 23 | cmake { 24 | path "CMakeLists.txt" 25 | } 26 | } 27 | } 28 | 29 | dependencies { 30 | implementation fileTree(dir: 'libs', include: ['*.jar']) 31 | implementation 'com.android.support:appcompat-v7:28.0.0' 32 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 33 | 34 | implementation 'com.bytedance.boost_multidex:boost_multidex:1.0.1' 35 | 36 | implementation "org.scala-lang:scala-library:2.11.7" 37 | implementation "com.facebook.fresco:fresco:1.0.1" 38 | implementation "com.facebook.fresco:animated-webp:1.0.1" 39 | implementation "com.facebook.fresco:imagepipeline-okhttp3:1.0.1" 40 | implementation "io.reactivex.rxjava2:rxjava:2.2.3" 41 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' 42 | implementation 'com.squareup.retrofit2:retrofit:2.3.0' 43 | implementation 'com.squareup.retrofit2:converter-gson:2.3.0' 44 | implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' 45 | implementation "com.squareup.okhttp:okhttp:2.7.5" 46 | implementation "com.facebook.soloader:soloader:0.1.0" 47 | implementation 'org.greenrobot:eventbus:3.1.1' 48 | implementation 'com.github.promeg:tinypinyin:2.0.3' 49 | implementation 'com.squareup.okhttp3:okhttp:3.4.1' 50 | implementation 'com.squareup.okio:okio:1.5.0' 51 | implementation 'com.google.code.gson:gson:2.2.4' 52 | implementation 'com.github.bumptech.glide:glide:4.4.0' 53 | } 54 | -------------------------------------------------------------------------------- /app/maindex-keep.pro: -------------------------------------------------------------------------------- 1 | -keep class com.bytedance.app.boost_multidex.MainApplication -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/bytedance/app/boost_multidex/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.bytedance.app.boost_multidex; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.bytedance.app.boost_multidex", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/cpp/native-lib.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | extern "C" JNIEXPORT jstring JNICALL 6 | Java_com_bytedance_app_boost_1multidex_MainActivity_stringFromJNI( 7 | JNIEnv *env, 8 | jobject /* this */) { 9 | std::string hello = "Hello BoostMultiDex!"; 10 | // abort(); 11 | return env->NewStringUTF(hello.c_str()); 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/bytedance/app/boost_multidex/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.bytedance.app.boost_multidex; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.util.Log; 6 | import android.widget.TextView; 7 | import scala.math.BigDecimal; 8 | 9 | public class MainActivity extends Activity { 10 | static { 11 | System.loadLibrary("native-lib"); 12 | } 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.activity_main); 18 | 19 | TextView tv = findViewById(R.id.sample_text); 20 | tv.setText(stringFromJNI()); 21 | 22 | try { 23 | Object object = Class.class.getDeclaredMethod("getDex").invoke(BigDecimal.class); 24 | Log.d("MainActivity", "dex bytes is " + object); 25 | } catch (Throwable tr) { 26 | tr.printStackTrace(); 27 | } 28 | } 29 | 30 | /** 31 | * A native method that is implemented by the 'native-lib' native library, 32 | * which is packaged with this application. 33 | */ 34 | public native String stringFromJNI(); 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/bytedance/app/boost_multidex/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.bytedance.app.boost_multidex; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.support.multidex.MultiDex; 6 | import android.util.Log; 7 | 8 | import com.bytedance.boost_multidex.BoostMultiDex; 9 | import com.bytedance.boost_multidex.Result; 10 | 11 | /** 12 | * Created by Xiaolin(xiaolin.gan@bytedance.com) on 2019/2/22. 13 | */ 14 | 15 | public class MainApplication extends Application { 16 | @Override 17 | protected void attachBaseContext(Context base) { 18 | super.attachBaseContext(base); 19 | 20 | boolean useBoostMultiDex = true; 21 | 22 | long start = System.currentTimeMillis(); 23 | if (useBoostMultiDex) { 24 | Result result = BoostMultiDex.install(this); 25 | if (result != null && result.fatalThrowable != null) { 26 | Log.e("BMD", "exception occored " + result.fatalThrowable); 27 | } 28 | } else { 29 | MultiDex.install(this); 30 | } 31 | 32 | Log.i("BMD", "multidex cost time " + (System.currentTimeMillis() - start) + " ms"); 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedance/BoostMultiDex/29d4110f28a4bc3571ad336fa2c27c941d185143/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedance/BoostMultiDex/29d4110f28a4bc3571ad336fa2c27c941d185143/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedance/BoostMultiDex/29d4110f28a4bc3571ad336fa2c27c941d185143/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedance/BoostMultiDex/29d4110f28a4bc3571ad336fa2c27c941d185143/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedance/BoostMultiDex/29d4110f28a4bc3571ad336fa2c27c941d185143/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedance/BoostMultiDex/29d4110f28a4bc3571ad336fa2c27c941d185143/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedance/BoostMultiDex/29d4110f28a4bc3571ad336fa2c27c941d185143/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedance/BoostMultiDex/29d4110f28a4bc3571ad336fa2c27c941d185143/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedance/BoostMultiDex/29d4110f28a4bc3571ad336fa2c27c941d185143/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedance/BoostMultiDex/29d4110f28a4bc3571ad336fa2c27c941d185143/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | BoostMultiDex 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/bytedance/app/boost_multidex/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.bytedance.app.boost_multidex; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /boost_multidex/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /boost_multidex/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about using CMake with Android Studio, read the 2 | # documentation: https://d.android.com/studio/projects/add-native-code.html 3 | 4 | # Sets the minimum version of CMake required to build the native library. 5 | 6 | cmake_minimum_required(VERSION 3.4.1) 7 | 8 | add_library( boost_multidex 9 | SHARED 10 | src/main/cpp/boost_multidex.cpp) 11 | 12 | target_link_libraries( boost_multidex 13 | log 14 | z ) -------------------------------------------------------------------------------- /boost_multidex/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.novoda.bintray-release' 3 | 4 | android { 5 | compileSdkVersion 28 6 | ndkVersion "16.1.4479499" 7 | 8 | defaultConfig { 9 | minSdkVersion 14 10 | targetSdkVersion 28 11 | // versionCode 1 12 | versionName ARTIFACT_VERSION 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | consumerProguardFiles "proguard-rules.pro" 17 | 18 | externalNativeBuild { 19 | cmake { 20 | cppFlags "-std=c++14", "-fno-exceptions", "-O2" 21 | abiFilters "armeabi", "armeabi-v7a", "x86" 22 | } 23 | } 24 | } 25 | 26 | buildTypes { 27 | release { 28 | minifyEnabled true 29 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 30 | } 31 | } 32 | 33 | externalNativeBuild { 34 | cmake { 35 | path "CMakeLists.txt" 36 | } 37 | } 38 | } 39 | 40 | dependencies { 41 | implementation fileTree(dir: 'libs', include: ['*.jar']) 42 | 43 | testImplementation 'junit:junit:4.12' 44 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 45 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 46 | } 47 | 48 | publish { 49 | userOrg = ORG_NAME 50 | groupId = GROUP_ID 51 | artifactId = ARTIFACT_ID 52 | publishVersion = ARTIFACT_VERSION 53 | desc = DESC 54 | website = WEBSITE 55 | } 56 | -------------------------------------------------------------------------------- /boost_multidex/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | #aar发布相关 15 | ORG_NAME=galaxy-leo-open-source 16 | GROUP_ID=com.bytedance.boost_multidex 17 | ARTIFACT_ID=boost_multidex 18 | ARTIFACT_VERSION=1.0.1 19 | WEBSITE=https://github.com/bytedance/BoostMultiDex 20 | DESC=boost multidex 21 | # ./gradlew clean build bintrayUpload -PbintrayUser=BINTRAY_USERNAME -PbintrayKey=BINTRAY_KEY -PdryRun=false 22 | -------------------------------------------------------------------------------- /boost_multidex/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keep class com.bytedance.boost_multidex.** {*;} 24 | -keep class com.bytedance.boost_multidex.BoostMultiDex {*;} 25 | -keep class com.bytedance.boost_multidex.Result {*;} 26 | -keepclasseswithmembernames class * { 27 | native ; 28 | } -------------------------------------------------------------------------------- /boost_multidex/src/androidTest/java/com/bytedance/boost_multidex/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.bytedance.boost_multidex; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.bytedance.boost_multidex.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /boost_multidex/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /boost_multidex/src/main/cpp/boost_multidex.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Xiaolin(xiaolin.gan@bytedance.com) on 2019/3/5. 3 | // 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #define LOG_TAG "BOOST_MULTIDEX.NATIVE" 22 | 23 | static constexpr char cond = false; 24 | 25 | #define ALOGF(format, ...) __android_log_assert(&cond, LOG_TAG, format, ##__VA_ARGS__) 26 | 27 | #define ALOGE(format, ...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, format, ##__VA_ARGS__) 28 | #define ALOGW(format, ...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, format, ##__VA_ARGS__) 29 | #define ALOGI(format, ...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, format, ##__VA_ARGS__) 30 | 31 | #ifndef NDEBUG 32 | #define NDEBUG // remove log 33 | #endif 34 | 35 | #ifdef NDEBUG 36 | //#define ALOGD(format, ...) 37 | //#define ALOGV(format, ...) 38 | #define ALOGD(format, ...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, format, ##__VA_ARGS__) 39 | #define ALOGV(format, ...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, format, ##__VA_ARGS__) 40 | #else 41 | #define ALOGD(format, ...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, format, ##__VA_ARGS__) 42 | #define ALOGV(format, ...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, format, ##__VA_ARGS__) 43 | #endif 44 | 45 | struct Object { 46 | void* clazz; 47 | uint32_t lock; 48 | }; 49 | 50 | struct ArrayObject : Object { 51 | uint32_t length; 52 | uint64_t contents[1]; 53 | }; 54 | 55 | using func_openDexFileBytes = void (*)(const uint32_t* args, int32_t* pResult); 56 | static func_openDexFileBytes openDexFileBytes; // Dalvik_dalvik_system_DexFile_openDexFile_bytearray 57 | 58 | using func_openDexFileNative = void (*)(const uint32_t* args, int32_t* pResult); 59 | static func_openDexFileNative openDexFileNative; 60 | 61 | using func_dvmRawDexFileOpen = int (*)(const char* fileName, const char* odexOutputName, void* ppRawDexFile, bool isBootstrap); 62 | static func_dvmRawDexFileOpen dvmRawDexFileOpen; 63 | 64 | struct MemMapping { 65 | void* addr; /* start of data */ 66 | size_t length; /* length of data */ 67 | void* baseAddr; /* page-aligned base address */ 68 | size_t baseLength; /* length of mapping */ 69 | }; 70 | 71 | struct DvmDex { 72 | void* unusedPtr[7]; 73 | bool isMappedReadOnly; 74 | MemMapping memMap; 75 | jobject dex_object; // use this 76 | jobject dex_object_htc; // htc use this 77 | }; 78 | 79 | struct RawDexFile { 80 | char* cacheFileName; 81 | DvmDex* pDvmDex; 82 | }; 83 | 84 | struct DexOrJar { 85 | char* fileName; 86 | bool isDex; 87 | bool okayToFree; 88 | RawDexFile* pRawDexFile; 89 | void* pJarFile; 90 | uint8_t* pDexMemory; // malloc()ed memory, if any 91 | }; 92 | 93 | static jclass sDexFileClazz; 94 | static jfieldID sCookieField; 95 | static jfieldID sFileNameField; 96 | static jfieldID sGuardField; 97 | 98 | static jclass sCloseGuardClazz; 99 | static jmethodID sGuardGetMethod; 100 | 101 | static jmethodID sOpenDexFileMethod; 102 | 103 | static jclass sDexClazz; 104 | static jmethodID sDexConstructor; 105 | 106 | static bool sIsSpecHtc; 107 | 108 | bool sIsSetHandler; 109 | 110 | bool sSigFlag; 111 | 112 | static sigjmp_buf sSigJmpBuf; 113 | 114 | static struct sigaction OldSignalAction; 115 | 116 | #define CHECK_EXCEPTION \ 117 | do { \ 118 | if (env->ExceptionCheck() == JNI_TRUE) { \ 119 | return JNI_FALSE; \ 120 | } \ 121 | } while(false) 122 | 123 | #define CHECK_EXCEPTION_AND_EXE_ABORT(MSG, CALL_FUNC) \ 124 | do { \ 125 | if (env->ExceptionCheck() == JNI_TRUE) { \ 126 | CALL_FUNC; \ 127 | ALOGE(MSG); \ 128 | return nullptr; \ 129 | } \ 130 | } while(false) 131 | 132 | #define CHECK_EXCEPTION_AND_ABORT(MSG) CHECK_EXCEPTION_AND_EXE_ABORT(MSG, ) 133 | 134 | class ScopedSetSigFlag { 135 | public: 136 | ScopedSetSigFlag() { 137 | sSigFlag = true; 138 | } 139 | 140 | ~ScopedSetSigFlag() { 141 | sSigFlag = false; 142 | } 143 | }; 144 | 145 | static void* MapFile(const char* file_path, uint32_t *out_file_size) { 146 | int fd = TEMP_FAILURE_RETRY(open(file_path, O_RDONLY, S_IRUSR)); 147 | if (fd == -1) { 148 | ALOGE("fail to open %s", file_path); 149 | return nullptr; 150 | } 151 | 152 | uint32_t file_size = static_cast(lseek(fd, 0, SEEK_END)); 153 | 154 | ALOGV("mapping file size is %zu", file_size); 155 | 156 | void *ptr = mmap(nullptr, file_size, PROT_READ, MAP_SHARED, fd, 0); 157 | TEMP_FAILURE_RETRY(close(fd)); 158 | 159 | if (ptr == MAP_FAILED) { 160 | ALOGE("fail to map file %s", file_path); 161 | return nullptr; 162 | } 163 | 164 | *out_file_size = file_size; 165 | return ptr; 166 | } 167 | 168 | static int64_t ObtainCheckSum(const char *file_path) { 169 | uint32_t file_size = 0; 170 | void *ptr = MapFile(file_path, &file_size); 171 | if (ptr == nullptr) { 172 | return 0; 173 | } 174 | 175 | int64_t result = adler32(0, static_cast(ptr), static_cast(file_size)); 176 | 177 | munmap(ptr, file_size); 178 | 179 | return result; 180 | } 181 | 182 | static func_openDexFileBytes findOpenDexFileFunc(JNINativeMethod *func, const char *name, 183 | const char *signature = "([B)I") { 184 | size_t len_name = strlen(name); 185 | while (func->name != nullptr) { 186 | if ((strncmp(name, func->name, len_name) == 0) 187 | && (strncmp(signature, func->signature, len_name) == 0)) { 188 | return reinterpret_cast(func->fnPtr); 189 | } 190 | func++; 191 | } 192 | return nullptr; 193 | } 194 | 195 | static bool CheckIsSpecHtc() { 196 | const char htc[] = "htc"; 197 | size_t len = strlen(htc); 198 | 199 | char brand[PROP_NAME_MAX]; 200 | __system_property_get("ro.product.brand", brand); 201 | if (strncasecmp(htc, brand, len) == 0) { 202 | return true; 203 | } 204 | __system_property_get("ro.product.manufacturer", brand); 205 | if (strncasecmp(htc, brand, len) == 0) { 206 | return true; 207 | } 208 | return false; 209 | } 210 | 211 | static void OpenDexHandler(int) { 212 | if(sSigFlag) { 213 | siglongjmp(sSigJmpBuf, 1); 214 | } else { 215 | sigaction(SIGSEGV, &OldSignalAction, nullptr); 216 | } 217 | } 218 | 219 | static bool SetSignalHandler() { 220 | struct sigaction action{}; 221 | 222 | if(sigemptyset(&action.sa_mask) != 0) { 223 | ALOGE("fail set empty mask of action"); 224 | return false; 225 | } 226 | action.sa_handler = OpenDexHandler; 227 | 228 | if(sigaction(SIGSEGV, &action, &OldSignalAction) != 0) { 229 | ALOGE("fail set action, err=%s", strerror(errno)); 230 | return false; 231 | } 232 | 233 | ALOGI("set action successfully"); 234 | return true; 235 | } 236 | 237 | extern "C" JNIEXPORT jlong JNICALL 238 | Java_com_bytedance_boost_1multidex_BoostNative_obtainCheckSum(JNIEnv *env, jclass, jstring filePath) { 239 | const char *file_path = env->GetStringUTFChars(filePath, nullptr); 240 | 241 | jlong result = ObtainCheckSum(file_path); 242 | 243 | env->ReleaseStringUTFChars(filePath, file_path); 244 | 245 | return result; 246 | } 247 | 248 | extern "C" JNIEXPORT jobject JNICALL 249 | Java_com_bytedance_boost_1multidex_BoostNative_loadDirectDex(JNIEnv *env, jclass, 250 | jstring jFilePath, 251 | jbyteArray jFileContents) { 252 | if (sigsetjmp(sSigJmpBuf, 1) != 0) { 253 | ALOGE("recover and skip crash"); 254 | return nullptr; 255 | } 256 | 257 | ScopedSetSigFlag scoped; 258 | 259 | // if jFilePath is null, the byte array is from a dex in zip. 260 | // do not support when both jFilePath and jFileContents are empty. 261 | int32_t cookie; 262 | if (sOpenDexFileMethod != nullptr) { 263 | if (jFileContents == nullptr) { 264 | uint32_t file_size = 0; 265 | const char *file_path = env->GetStringUTFChars(jFilePath, nullptr); 266 | void *ptr = MapFile(file_path, &file_size); 267 | env->ReleaseStringUTFChars(jFilePath, file_path); 268 | if (ptr == nullptr) { 269 | ALOGE("fail to map file"); 270 | return nullptr; 271 | } 272 | 273 | jFileContents = env->NewByteArray(file_size); 274 | CHECK_EXCEPTION_AND_EXE_ABORT("fail to new bytes", munmap(ptr, file_size)); 275 | 276 | env->SetByteArrayRegion(jFileContents, 0, file_size, static_cast(ptr)); 277 | 278 | munmap(ptr, file_size); 279 | CHECK_EXCEPTION_AND_ABORT("fail to set bytes"); 280 | } 281 | cookie = env->CallStaticIntMethod(sDexFileClazz, sOpenDexFileMethod, jFileContents); 282 | CHECK_EXCEPTION_AND_ABORT("fail to call open dex file bytes method"); 283 | } else { 284 | uint32_t args[1]; 285 | ArrayObject *array_object_ptr; 286 | uint32_t length; 287 | if (jFileContents == nullptr) { 288 | uint32_t file_size = 0; 289 | const char *file_path = env->GetStringUTFChars(jFilePath, nullptr); 290 | void *ptr = MapFile(file_path, &file_size); 291 | if (ptr == nullptr) { 292 | ALOGE("fail to map dex file"); 293 | return nullptr; 294 | } 295 | env->ReleaseStringUTFChars(jFilePath, file_path); 296 | 297 | length = sizeof(ArrayObject) - sizeof(ArrayObject::contents) + file_size; 298 | 299 | array_object_ptr = static_cast(malloc(sizeof(ArrayObject) - sizeof(ArrayObject::contents) + length)); 300 | if (array_object_ptr == nullptr) { 301 | ALOGE("fail to alloc array object"); 302 | munmap(ptr, file_size); 303 | return nullptr; 304 | } 305 | array_object_ptr->length = file_size; 306 | memcpy(array_object_ptr->contents, ptr, length); 307 | 308 | munmap(ptr, file_size); 309 | } else { 310 | uint32_t jarray_length = static_cast(env->GetArrayLength(jFileContents)); 311 | uint8_t * jarray_ptr = static_cast(env->GetPrimitiveArrayCritical(jFileContents, nullptr)); 312 | length = sizeof(ArrayObject) - sizeof(ArrayObject::contents) + jarray_length; 313 | 314 | array_object_ptr = static_cast(malloc(sizeof(ArrayObject) - sizeof(ArrayObject::contents) + length)); 315 | if (array_object_ptr == nullptr) { 316 | ALOGE("fail to alloc array object for jFileContents"); 317 | return nullptr; 318 | } 319 | array_object_ptr->length = jarray_length; 320 | memcpy(array_object_ptr->contents, jarray_ptr, length); 321 | 322 | env->ReleasePrimitiveArrayCritical(jFileContents, jarray_ptr, 0); 323 | } 324 | 325 | args[0] = reinterpret_cast(array_object_ptr); 326 | openDexFileBytes(args, &cookie); 327 | 328 | CHECK_EXCEPTION_AND_ABORT("fail to open dex file bytes"); 329 | 330 | if (sDexClazz != nullptr && sDexConstructor != nullptr) { 331 | DexOrJar* dexOrJar = reinterpret_cast(cookie); 332 | if (jFileContents == nullptr) { 333 | jFileContents = env->NewByteArray(length); 334 | CHECK_EXCEPTION_AND_ABORT("fail to new array of file bytes"); 335 | env->SetByteArrayRegion(jFileContents, 0, length, 336 | reinterpret_cast(array_object_ptr->contents)); 337 | CHECK_EXCEPTION_AND_ABORT("fail to set array of file bytes"); 338 | } 339 | 340 | jobject dex_object = env->NewGlobalRef( 341 | env->NewObject(sDexClazz, sDexConstructor, jFileContents)); 342 | if (!sIsSpecHtc) { 343 | dexOrJar->pRawDexFile->pDvmDex->dex_object = dex_object; 344 | } else { 345 | dexOrJar->pRawDexFile->pDvmDex->dex_object = dex_object; 346 | dexOrJar->pRawDexFile->pDvmDex->dex_object_htc = dex_object; 347 | } 348 | } 349 | 350 | free(array_object_ptr); 351 | } 352 | 353 | jobject dex_file = env->AllocObject(sDexFileClazz); 354 | env->SetIntField(dex_file, sCookieField, cookie); // set mCookie 355 | env->SetObjectField(dex_file, sFileNameField, jFilePath); // set mFileName 356 | env->SetObjectField(dex_file, sGuardField, env->CallStaticObjectMethod(sCloseGuardClazz, sGuardGetMethod)); // set guard 357 | 358 | return dex_file; 359 | } 360 | 361 | extern "C" 362 | JNIEXPORT void JNICALL 363 | Java_com_bytedance_boost_1multidex_BoostNative_recoverAction(JNIEnv *, jclass) { 364 | if (sIsSetHandler) { 365 | sigaction(SIGSEGV, &OldSignalAction, nullptr); 366 | } 367 | } 368 | 369 | extern "C" 370 | JNIEXPORT jboolean JNICALL 371 | Java_com_bytedance_boost_1multidex_BoostNative_makeOptDexFile(JNIEnv *env, jclass, 372 | jstring jFilePath, 373 | jstring jOptFilePath) { 374 | if (dvmRawDexFileOpen == nullptr) { 375 | return JNI_FALSE; 376 | } 377 | 378 | if (sigsetjmp(sSigJmpBuf, 1) != 0) { 379 | ALOGE("recover and skip crash"); 380 | return JNI_FALSE; 381 | } 382 | 383 | ScopedSetSigFlag scoped; 384 | 385 | const char *file_path = env->GetStringUTFChars(jFilePath, nullptr); 386 | const char *opt_file_path = env->GetStringUTFChars(jOptFilePath, nullptr); 387 | 388 | void *arg; 389 | int result = dvmRawDexFileOpen(file_path, opt_file_path, &arg, false); 390 | 391 | env->ReleaseStringUTFChars(jFilePath, file_path); 392 | env->ReleaseStringUTFChars(jOptFilePath, opt_file_path); 393 | 394 | return (result != -1) ? JNI_TRUE : JNI_FALSE; 395 | } 396 | 397 | extern "C" JNIEXPORT jboolean JNICALL 398 | Java_com_bytedance_boost_1multidex_BoostNative_initialize(JNIEnv *env, jclass, jint sdkVersion, jclass runtimeExceptionClass) { 399 | jclass clazz = env->FindClass("dalvik/system/DexFile"); CHECK_EXCEPTION; 400 | sDexFileClazz = static_cast(env->NewGlobalRef(clazz)); CHECK_EXCEPTION; 401 | sCookieField = env->GetFieldID(sDexFileClazz, "mCookie", "I"); CHECK_EXCEPTION; 402 | sFileNameField = env->GetFieldID(sDexFileClazz, "mFileName", "Ljava/lang/String;"); CHECK_EXCEPTION; 403 | sGuardField = env->GetFieldID(sDexFileClazz, "guard", "Ldalvik/system/CloseGuard;"); CHECK_EXCEPTION; 404 | 405 | clazz = env->FindClass("dalvik/system/CloseGuard"); CHECK_EXCEPTION; 406 | sCloseGuardClazz = static_cast(env->NewGlobalRef(clazz)); CHECK_EXCEPTION; 407 | 408 | sGuardGetMethod = env->GetStaticMethodID(sCloseGuardClazz, "get", "()Ldalvik/system/CloseGuard;"); CHECK_EXCEPTION; 409 | 410 | const char* dvm = "libdvm.so"; 411 | void* handler = dlopen(dvm, RTLD_NOW); 412 | if (handler == nullptr) { 413 | env->ThrowNew(runtimeExceptionClass, "Fail to find dvm"); 414 | return JNI_FALSE; 415 | } 416 | 417 | dvmRawDexFileOpen = (func_dvmRawDexFileOpen) dlsym(handler, "_Z17dvmRawDexFileOpenPKcS0_PP10RawDexFileb"); 418 | if (dvmRawDexFileOpen == nullptr) { 419 | ALOGE("fail to get dvm func"); 420 | } 421 | 422 | if (sdkVersion < 19) { 423 | sOpenDexFileMethod = env->GetStaticMethodID(sDexFileClazz, "openDexFile", "([B)I"); 424 | env->ExceptionClear(); 425 | } else { 426 | // SDK = 19 427 | clazz = env->FindClass("com/android/dex/Dex"); CHECK_EXCEPTION; 428 | sDexClazz = static_cast(env->NewGlobalRef(clazz)); CHECK_EXCEPTION; 429 | sDexConstructor = env->GetMethodID(sDexClazz, "", "([B)V"); CHECK_EXCEPTION; 430 | sIsSpecHtc = CheckIsSpecHtc(); 431 | } 432 | 433 | if (sOpenDexFileMethod == nullptr) { 434 | auto* natives_DexFile = (JNINativeMethod*) dlsym(handler, "dvm_dalvik_system_DexFile"); 435 | if (natives_DexFile == nullptr) { 436 | env->ThrowNew(runtimeExceptionClass, "Fail to find DexFile symbols"); 437 | return JNI_FALSE; 438 | } 439 | 440 | openDexFileBytes = findOpenDexFileFunc(natives_DexFile, "openDexFile", "([B)I"); 441 | if (openDexFileBytes == nullptr) { 442 | return JNI_FALSE; 443 | } 444 | } 445 | 446 | sIsSetHandler = SetSignalHandler(); 447 | if (!sIsSetHandler) { 448 | ALOGE("fail to set signal handler"); 449 | return JNI_FALSE; 450 | } 451 | 452 | return JNI_TRUE; 453 | } 454 | 455 | extern "C" JNIEXPORT JNICALL jint JNI_OnLoad(JavaVM *vm, void *) { 456 | JNIEnv *env; 457 | 458 | if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { 459 | return JNI_ERR; 460 | } 461 | 462 | jclass clazz = env->FindClass("com/bytedance/boost_multidex/BoostNative"); 463 | 464 | // speed up first invocation of native methods 465 | static JNINativeMethod native_methods[] = { 466 | {"obtainCheckSum", 467 | "(Ljava/lang/String;)J", 468 | (void *) Java_com_bytedance_boost_1multidex_BoostNative_obtainCheckSum}, 469 | }; 470 | 471 | if (env->RegisterNatives(clazz, native_methods, 472 | sizeof(native_methods) / sizeof(native_methods[0])) != JNI_OK) { 473 | return JNI_ERR; 474 | } 475 | 476 | return JNI_VERSION_1_6; 477 | } 478 | -------------------------------------------------------------------------------- /boost_multidex/src/main/java/com/bytedance/boost_multidex/BoostMultiDex.java: -------------------------------------------------------------------------------- 1 | package com.bytedance.boost_multidex; 2 | 3 | import android.content.Context; 4 | import android.content.pm.ApplicationInfo; 5 | import android.os.Build; 6 | 7 | import java.io.File; 8 | import java.util.StringTokenizer; 9 | 10 | /** 11 | * Created by Xiaolin(xiaolin.gan@bytedance.com) on 2019/3/25. 12 | */ 13 | public class BoostMultiDex { 14 | public static Result install(Context context) { 15 | return install(context, null); 16 | } 17 | 18 | public static Result install(Context context, Monitor monitor) { 19 | Monitor.init(monitor); 20 | 21 | monitor = Monitor.get(); 22 | 23 | monitor.logInfo("BoostMultiDex is installing, version is " + BuildConfig.VERSION_NAME); 24 | 25 | if (isVMCapable(System.getProperty("java.vm.version"))) { 26 | monitor.logInfo("BoostMultiDex support library is disabled for VM capable"); 27 | return null; 28 | } 29 | 30 | if (Build.VERSION.SDK_INT < Constants.MIN_SDK_VERSION) { 31 | monitor.logInfo("BoostMultiDex installation failed. SDK " + Build.VERSION.SDK_INT 32 | + " is unsupported. Min SDK version is " + Constants.MIN_SDK_VERSION + "."); 33 | return null; 34 | } 35 | 36 | Result result = Result.get(); 37 | try { 38 | ApplicationInfo applicationInfo = context.getApplicationInfo(); 39 | if (applicationInfo == null) { 40 | throw new RuntimeException("ApplicationInfo is NULL."); 41 | } 42 | 43 | File sourceDir = new File(applicationInfo.sourceDir); 44 | 45 | String processName = monitor.getProcessName(); 46 | if (processName == null) { 47 | processName = Utility.getCurProcessName(context); 48 | } 49 | if (Utility.isOptimizeProcess(processName)) { 50 | // Force use dex bytes in opt process. 51 | // But a better way is avoid calling install(), then go to opt service directly. 52 | new DexInstallProcessor().doInstallationInOptProcess(context, sourceDir); 53 | return null; 54 | } else { 55 | new DexInstallProcessor().doInstallation(context, sourceDir, result); 56 | } 57 | 58 | } catch (Throwable e) { 59 | monitor.logError("BoostMultiDex installation failure", e); 60 | result.setFatalThrowable(e); 61 | } 62 | monitor.logInfo("install done"); 63 | 64 | return result; 65 | } 66 | 67 | public static boolean isOptimizeProcess(String processName) { 68 | return Utility.isOptimizeProcess(processName); 69 | } 70 | 71 | /** 72 | * Identifies if the current VM has a native support for multidex, meaning there is no need for 73 | * additional installation by this library. 74 | * @return true if the VM handles multidex 75 | */ 76 | private static boolean isVMCapable(String versionString) { 77 | boolean isCapable = false; 78 | if (versionString != null) { 79 | StringTokenizer tokenizer = new StringTokenizer(versionString, "."); 80 | String majorToken = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null; 81 | String minorToken = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null; 82 | if (majorToken != null && minorToken != null) { 83 | try { 84 | int major = Integer.parseInt(majorToken); 85 | int minor = Integer.parseInt(minorToken); 86 | isCapable = (major > Constants.VM_WITH_MULTIDEX_VERSION_MAJOR) 87 | || ((major == Constants.VM_WITH_MULTIDEX_VERSION_MAJOR) 88 | && (minor >= Constants.VM_WITH_MULTIDEX_VERSION_MINOR)); 89 | } catch (NumberFormatException e) { 90 | // let isCapable be false 91 | } 92 | } 93 | } 94 | Monitor.get().logInfo("VM with version " + versionString + 95 | (isCapable ? 96 | " has support" : 97 | " does not have support")); 98 | return isCapable; 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /boost_multidex/src/main/java/com/bytedance/boost_multidex/BoostMultiDexApplication.java: -------------------------------------------------------------------------------- 1 | package com.bytedance.boost_multidex; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | public class BoostMultiDexApplication extends Application { 7 | @Override 8 | protected void attachBaseContext(Context base) { 9 | super.attachBaseContext(base); 10 | 11 | if (!BoostMultiDex.isOptimizeProcess(Utility.getCurProcessName(base))) { 12 | return; 13 | } 14 | 15 | BoostMultiDex.install(base); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /boost_multidex/src/main/java/com/bytedance/boost_multidex/BoostNative.java: -------------------------------------------------------------------------------- 1 | package com.bytedance.boost_multidex; 2 | 3 | import android.os.Build; 4 | 5 | import java.io.File; 6 | import java.lang.reflect.Method; 7 | 8 | /** 9 | * Created by Xiaolin(xiaolin.gan@bytedance.com) on 2019/3/5. 10 | */ 11 | final class BoostNative { 12 | private static volatile boolean alreadyInit; 13 | 14 | private static boolean supportFastLoadDex; 15 | 16 | static { 17 | Monitor.get().loadLibrary("boost_multidex"); 18 | } 19 | 20 | private static void checkSupportFastLoad(Result result) { 21 | try { 22 | Method getPropertyMethod = Class.forName("android.os.SystemProperties") 23 | .getDeclaredMethod("get", String.class, String.class); 24 | if (Build.VERSION.SDK_INT >= 19) { 25 | String vmLibName = (String) getPropertyMethod.invoke(null, "persist.sys.dalvik.vm.lib", null); 26 | result.vmLibName = vmLibName; 27 | Monitor.get().logInfo("VM lib is " + vmLibName); 28 | if ("libart.so".equals(vmLibName)) { 29 | Monitor.get().logWarning("VM lib is art, skip!"); 30 | return; 31 | } 32 | } 33 | 34 | String yunosVersion = (String) getPropertyMethod.invoke(null, "ro.yunos.version", null); 35 | if (yunosVersion != null && !yunosVersion.isEmpty() || new File(Constants.LIB_YUNOS_PATH).exists()) { 36 | result.isYunOS = true; 37 | Monitor.get().logWarning("Yun os is " + yunosVersion + ", skip boost!"); 38 | return; 39 | } 40 | 41 | supportFastLoadDex = initialize(Build.VERSION.SDK_INT, RuntimeException.class); 42 | 43 | result.supportFastLoadDex = supportFastLoadDex; 44 | } catch (Throwable tr) { 45 | result.addUnFatalThrowable(tr); 46 | Monitor.get().logWarning("Fail to init", tr); 47 | } 48 | } 49 | 50 | static synchronized boolean isSupportFastLoad() { 51 | if (!alreadyInit) { 52 | checkSupportFastLoad(Result.get()); 53 | alreadyInit = true; 54 | } 55 | 56 | return supportFastLoadDex; 57 | } 58 | 59 | static native Object loadDirectDex(String fileName, byte[] fileContents); 60 | 61 | static native long obtainCheckSum(String path); 62 | 63 | static native void recoverAction(); 64 | 65 | static native boolean makeOptDexFile(String filePath, String optFilePath); 66 | 67 | private static native boolean initialize(int sdkVersion, Class runtimeExceptionClass); 68 | } 69 | -------------------------------------------------------------------------------- /boost_multidex/src/main/java/com/bytedance/boost_multidex/Constants.java: -------------------------------------------------------------------------------- 1 | package com.bytedance.boost_multidex; 2 | 3 | /** 4 | * Created by Xiaolin(xiaolin.gan@bytedance.com) on 2019/3/3. 5 | */ 6 | interface Constants { 7 | String TAG = "BoostMultiDex"; 8 | 9 | String CODE_CACHE_SECONDARY_FOLDER_NAME = "code_cache/secondary-dexes"; 10 | 11 | String BOOST_MULTIDEX_DIR_NAME = "boost_multidex"; 12 | 13 | String DEX_DIR_NAME = "dex_cache"; 14 | 15 | String ODEX_DIR_NAME = "odex_cache"; 16 | 17 | String ZIP_DIR_NAME = "zip_cache"; 18 | 19 | int MIN_SDK_VERSION = 14; 20 | 21 | int VM_WITH_MULTIDEX_VERSION_MAJOR = 2; 22 | 23 | int VM_WITH_MULTIDEX_VERSION_MINOR = 1; 24 | 25 | long SPACE_THRESHOLD = 150_000_000L; 26 | 27 | long SPACE_MIN_THRESHOLD = 20_000_000L; 28 | 29 | long MEM_THRESHOLD = 128_000_000L; 30 | 31 | /** 32 | * We look for additional dex files named {@code classes2.dex}, 33 | * {@code classes3.dex}, etc. 34 | */ 35 | 36 | String DEX_PREFIX = "classes"; 37 | String DEX_SUFFIX = ".dex"; 38 | String ZIP_SUFFIX = ".zip"; 39 | String ODEX_SUFFIX = ".odex"; 40 | 41 | String EXTRACTED_NAME_EXT = ".classes"; 42 | String EXTRACTED_SUFFIX = ".dex"; 43 | int MAX_EXTRACT_ATTEMPTS = 3; 44 | int EXTRACTED_SUFFIX_LENGTH = EXTRACTED_SUFFIX.length(); 45 | 46 | String PREFS_FILE = "boost_multidex.records"; 47 | String KEY_TIME_STAMP = "timestamp"; 48 | String KEY_CRC = "crc"; 49 | String KEY_DEX_NUMBER = "dex.number"; 50 | String KEY_DEX_CHECKSUM = "dex.checksum."; 51 | String KEY_DEX_TIME = "dex.time."; 52 | String KEY_ODEX_CHECKSUM = "odex.checksum."; 53 | String KEY_ODEX_TIME = "odex.time."; 54 | String KEY_DEX_OBJ_TYPE = "dex.obj.type"; 55 | 56 | /** 57 | * Size of reading buffers. 58 | */ 59 | int BUFFER_SIZE = 0x2000; 60 | /* Keep value away from 0 because it is a too probable time stamp value */ 61 | long NO_VALUE = -1L; 62 | 63 | String LOCK_PREPARE_FILENAME = "boost_multidex.prepare.lock"; 64 | String LOCK_INSTALL_FILENAME = "boost_multidex.install.lock"; 65 | 66 | /* redefine those constant here because of bug 13721174 preventing to compile using the 67 | * constants defined in ZipFile */ 68 | int ENDHDR = 22; 69 | int ENDSIG = 0x6054b50; 70 | 71 | int LOAD_TYPE_APK_BUF = 0; 72 | int LOAD_TYPE_DEX_BUF = 1; 73 | int LOAD_TYPE_DEX_OPT = 2; 74 | int LOAD_TYPE_ZIP_OPT = 3; 75 | int LOAD_TYPE_INVALID = 9; 76 | 77 | String LIB_YUNOS_PATH = "/system/lib/libvmkid_lemur.so"; 78 | } 79 | -------------------------------------------------------------------------------- /boost_multidex/src/main/java/com/bytedance/boost_multidex/DexHolder.java: -------------------------------------------------------------------------------- 1 | package com.bytedance.boost_multidex; 2 | 3 | import android.content.SharedPreferences; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.util.zip.ZipEntry; 8 | import java.util.zip.ZipFile; 9 | 10 | import dalvik.system.DexFile; 11 | 12 | /** 13 | * Created by Xiaolin(xiaolin.gan@bytedance.com) on 2019/3/25. 14 | */ 15 | abstract class DexHolder { 16 | File mFile; 17 | 18 | abstract Object toDexFile(); 19 | 20 | protected Object toDexListElement(DexLoader.ElementConstructor elementConstructor) throws Exception { 21 | Object dexFile = toDexFile(); 22 | return dexFile == null ? null : elementConstructor.newInstance(mFile, dexFile); 23 | } 24 | 25 | abstract DexHolder toFasterHolder(SharedPreferences preferences); 26 | 27 | abstract StoreInfo getInfo(); 28 | 29 | 30 | private static void putTypeInfo(SharedPreferences.Editor editor, int secondaryNumber, int type) { 31 | editor.putInt(Constants.KEY_DEX_OBJ_TYPE + secondaryNumber, type); 32 | } 33 | 34 | private static void putZipOptInfo(SharedPreferences.Editor editor, int secondaryNumber, File zipFile) throws IOException { 35 | String keyCheckSum = Constants.KEY_DEX_CHECKSUM; 36 | 37 | long checkSum = Utility.doZipCheckSum(zipFile); 38 | editor.putLong(keyCheckSum + secondaryNumber, checkSum); 39 | 40 | String keyTime = Constants.KEY_DEX_TIME; 41 | long time = zipFile.lastModified(); 42 | editor.putLong(keyTime + secondaryNumber, time); 43 | 44 | Monitor.get().logInfo("Put z key " + (keyCheckSum + keyTime + secondaryNumber) 45 | + " checksum=" + checkSum + ", time=" + time); 46 | } 47 | 48 | private static void putDexFileInfo(SharedPreferences.Editor editor, int secondaryNumber, File file) throws IOException { 49 | String keyCheckSum = Constants.KEY_DEX_CHECKSUM; 50 | 51 | long checkSum = Utility.doFileCheckSum(file); 52 | editor.putLong(keyCheckSum + secondaryNumber, checkSum); 53 | 54 | String keyTime = Constants.KEY_DEX_TIME; 55 | long time = file.lastModified(); 56 | editor.putLong(keyTime + secondaryNumber, time); 57 | 58 | Monitor.get().logInfo("Put f key " + (keyCheckSum + keyTime + secondaryNumber) 59 | + " checksum=" + checkSum + ", time=" + time); 60 | } 61 | 62 | private static void putDexOptInfo(SharedPreferences.Editor editor, int secondaryNumber, File optFile) throws IOException { 63 | String keyCheckSum = Constants.KEY_ODEX_CHECKSUM; 64 | 65 | long checkSum = optFile.length(); 66 | editor.putLong(keyCheckSum + secondaryNumber, checkSum); 67 | 68 | String keyTime = Constants.KEY_ODEX_TIME; 69 | long time = optFile.lastModified(); 70 | editor.putLong(keyTime + secondaryNumber, time); 71 | 72 | Monitor.get().logInfo("Put o key " + (keyCheckSum + keyTime + secondaryNumber) 73 | + " checksum=" + checkSum + ", time=" + time); 74 | } 75 | 76 | static DexHolder obtainValidDexBuffer(SharedPreferences preferences, int secondaryNumber, File validDexFile, File optDexFile) 77 | throws IOException { 78 | SharedPreferences.Editor editor = preferences.edit(); 79 | putTypeInfo(editor, secondaryNumber, Constants.LOAD_TYPE_DEX_BUF); 80 | putDexFileInfo(editor, secondaryNumber, validDexFile); 81 | editor.commit(); 82 | 83 | return new DexHolder.DexBuffer(secondaryNumber, validDexFile, optDexFile); 84 | } 85 | 86 | static DexHolder obtainValidForceDexOpt(SharedPreferences preferences, int secondaryNumber, File dexFile, File optDexFile, 87 | ZipFile apkZipFile, ZipEntry dexFileEntry) throws IOException { 88 | File validDexFile = Utility.obtainEntryFileInZip(apkZipFile, dexFileEntry, dexFile); 89 | SharedPreferences.Editor editor = preferences.edit(); 90 | putTypeInfo(editor, secondaryNumber, Constants.LOAD_TYPE_DEX_OPT); 91 | putDexFileInfo(editor, secondaryNumber, validDexFile); 92 | editor.commit(); 93 | return new DexHolder.DexOpt(secondaryNumber, validDexFile, optDexFile, true); 94 | } 95 | 96 | static DexHolder obtainValidDexOpt(SharedPreferences preferences, int secondaryNumber, File validDexFile, File optDexFile) throws IOException { 97 | SharedPreferences.Editor editor = preferences.edit(); 98 | putTypeInfo(editor, secondaryNumber, Constants.LOAD_TYPE_DEX_OPT); 99 | putDexOptInfo(editor, secondaryNumber, optDexFile); 100 | editor.commit(); 101 | return new DexHolder.DexOpt(secondaryNumber, validDexFile, optDexFile, false); 102 | } 103 | 104 | static DexHolder.ZipOpt obtainValidZipDex(SharedPreferences preferences, int secondaryNumber, File validZipFile, File validZipOptFile, ZipFile apkZipFile, ZipEntry dexFileEntry) throws IOException { 105 | Utility.obtainZipForEntryFileInZip(apkZipFile, dexFileEntry, validZipFile); 106 | SharedPreferences.Editor editor = preferences.edit(); 107 | putTypeInfo(editor, secondaryNumber, Constants.LOAD_TYPE_ZIP_OPT); 108 | putZipOptInfo(editor, secondaryNumber, validZipFile); 109 | editor.commit(); 110 | return new DexHolder.ZipOpt(secondaryNumber, validZipFile, validZipOptFile); 111 | } 112 | 113 | static class ZipOpt extends DexHolder { 114 | private int mIndex; 115 | private File mOptFile; 116 | 117 | ZipOpt(int index, File file, File optFile) { 118 | this.mIndex = index; 119 | this.mFile = file; 120 | this.mOptFile = optFile; 121 | } 122 | 123 | @Override 124 | public Object toDexFile() { 125 | try { 126 | return DexFile.loadDex(mFile.getPath(), mOptFile.getPath(), 0); 127 | } catch (IOException e) { 128 | Monitor.get().logError("Fail to load dex file"); 129 | throw new RuntimeException(e); 130 | } 131 | } 132 | 133 | @Override 134 | public DexHolder toFasterHolder(SharedPreferences preferences) { 135 | return null; 136 | } 137 | 138 | @Override 139 | public StoreInfo getInfo() { 140 | return null; 141 | } 142 | 143 | @Override 144 | public String toString() { 145 | return super.toString() + ", index: " + mIndex 146 | + ", [file: " + mFile.getPath() + ", size: " + mFile.length() 147 | + "], [opt file: " + mOptFile + ", size: " + mOptFile.length() + "]"; 148 | } 149 | } 150 | 151 | static class DexOpt extends DexHolder { 152 | private int mIndex; 153 | private File mOptFile; 154 | private boolean mForceOpt; 155 | 156 | DexOpt(int index, File file, File optFile, boolean forceOpt) { 157 | this.mIndex = index; 158 | this.mFile = file; 159 | this.mOptFile = optFile; 160 | this.mForceOpt = forceOpt; 161 | } 162 | 163 | @Override 164 | public Object toDexFile() { 165 | try { 166 | return DexFile.loadDex(mFile.getPath(), mOptFile.getPath(), 0); 167 | } catch (IOException e1) { 168 | Monitor.get().logError("Fail to load dex file first time", e1); 169 | try { 170 | if (mForceOpt) { 171 | return DexFile.loadDex(mFile.getPath(), mOptFile.getPath(), 0); 172 | } else { 173 | return BoostNative.loadDirectDex(mFile.getPath(), null); 174 | } 175 | } catch (IOException e2) { 176 | Monitor.get().logError("Fail to load dex file", e2); 177 | throw new RuntimeException(e2); 178 | } 179 | } 180 | } 181 | 182 | @Override 183 | public DexHolder toFasterHolder(SharedPreferences preferences) { 184 | return null; 185 | } 186 | 187 | @Override 188 | public StoreInfo getInfo() { 189 | return new StoreInfo(Constants.LOAD_TYPE_DEX_OPT, mIndex, mOptFile); 190 | } 191 | 192 | @Override 193 | public String toString() { 194 | return super.toString() + ", index: " + mIndex 195 | + ", [file: " + mFile.getPath() + ", size: " + mFile.length() 196 | + "], [opt file: " + mOptFile + ", size: " + mOptFile.length() 197 | + "], force: " + mForceOpt ; 198 | } 199 | } 200 | 201 | static class DexBuffer extends DexHolder { 202 | private int mIndex; 203 | private File mOptFile; 204 | 205 | DexBuffer(int index, File file, File optFile) { 206 | this.mIndex = index; 207 | this.mFile = file; 208 | this.mOptFile = optFile; 209 | } 210 | 211 | @Override 212 | public Object toDexFile() { 213 | try { 214 | return BoostNative.loadDirectDex(mFile.getPath(), null); 215 | } catch (Exception e) { 216 | Monitor.get().logError("Fail to create DexFile: " + toString(), e); 217 | Result.get().unFatalThrowable.add(e); 218 | return null; 219 | } 220 | } 221 | 222 | @Override 223 | public DexHolder toFasterHolder(SharedPreferences preferences) { 224 | try { 225 | if (!BoostNative.isSupportFastLoad() || !BoostNative.makeOptDexFile(mFile.getPath(), mOptFile.getPath())) { 226 | Monitor.get().logWarning("Opt dex in origin way"); 227 | DexFile.loadDex(mFile.getPath(), mOptFile.getPath(), 0).close(); 228 | } 229 | return obtainValidDexOpt(preferences, mIndex, mFile, mOptFile); 230 | } catch (IOException e) { 231 | Monitor.get().logError("Fail to opt dex finally", e); 232 | return null; 233 | } 234 | } 235 | 236 | @Override 237 | public StoreInfo getInfo() { 238 | return new StoreInfo(Constants.LOAD_TYPE_DEX_BUF, mIndex, mFile); 239 | } 240 | 241 | @Override 242 | public String toString() { 243 | return super.toString() + ", index: " + mIndex 244 | + ", [file: " + mFile.getPath() + ", size: " + mFile.length() 245 | + "], [opt file: " + mOptFile + ", size: " + mOptFile.length() 246 | + "]"; 247 | } 248 | } 249 | 250 | static class ApkBuffer extends DexHolder { 251 | private int mIndex; 252 | private byte[] mBytes; 253 | private File mOptFile; 254 | 255 | ApkBuffer(int index, byte[] bytes, File file, File optFile) { 256 | this.mIndex = index; 257 | this.mBytes = bytes; 258 | this.mFile = file; 259 | this.mOptFile = optFile; 260 | } 261 | 262 | @Override 263 | public Object toDexFile() { 264 | try { 265 | return BoostNative.loadDirectDex(null, mBytes); 266 | } catch (Exception e) { 267 | Monitor.get().logError("Fail to create DexFile: " + toString(), e); 268 | Result.get().unFatalThrowable.add(e); 269 | return null; 270 | } 271 | } 272 | 273 | @Override 274 | public Object toDexListElement(DexLoader.ElementConstructor elementConstructor) throws Exception { 275 | Object dexFile = toDexFile(); 276 | if (dexFile == null) { 277 | return null; 278 | } 279 | return elementConstructor.newInstance(null, dexFile); 280 | } 281 | 282 | @Override 283 | public DexHolder toFasterHolder(SharedPreferences preferences) { 284 | if (Utility.storeBytesToFile(mBytes, mFile)) { 285 | try { 286 | return DexHolder.obtainValidDexBuffer(preferences, mIndex, mFile, mOptFile); 287 | } catch (IOException e) { 288 | Monitor.get().logError("fail to get dex buffer", e); 289 | return null; 290 | } 291 | } else { 292 | return null; 293 | } 294 | } 295 | 296 | @Override 297 | public StoreInfo getInfo() { 298 | return null; 299 | } 300 | 301 | @Override 302 | public String toString() { 303 | return super.toString() + ", index: " + mIndex 304 | + ", [file: " + mFile.getPath() + ", size: " + mFile.length() 305 | + "], [opt file: " + mOptFile + ", size: " + mOptFile.length() 306 | + "], bytes len: " + (mBytes == null ? null : mBytes.length); 307 | } 308 | } 309 | 310 | class StoreInfo { 311 | File file; 312 | int type; 313 | int index; 314 | 315 | StoreInfo(int type, int index, File file) { 316 | this.type = type; 317 | this.index = index; 318 | this.file = file; 319 | } 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /boost_multidex/src/main/java/com/bytedance/boost_multidex/DexInstallProcessor.java: -------------------------------------------------------------------------------- 1 | package com.bytedance.boost_multidex; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.os.Build; 7 | import android.os.Environment; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.Random; 14 | import java.util.zip.ZipEntry; 15 | import java.util.zip.ZipFile; 16 | 17 | /** 18 | * Created by Xiaolin(xiaolin.gan@bytedance.com) on 2019/3/26. 19 | */ 20 | class DexInstallProcessor { 21 | private SharedPreferences mPreferences; 22 | private boolean mDoCheckSum; 23 | 24 | DexInstallProcessor() { 25 | Random random = new Random(); 26 | mDoCheckSum = random.nextInt(3) == 0; 27 | Monitor.get().logInfo("Do checksum " + mDoCheckSum); 28 | } 29 | 30 | void doInstallation(final Context mainContext, File sourceApk, Result result) throws Exception { 31 | File filesDir = mainContext.getFilesDir(); 32 | if (!filesDir.exists()) { 33 | Utility.mkdirChecked(filesDir); 34 | } 35 | Utility.clearDirFiles(new File(filesDir.getParent(), Constants.CODE_CACHE_SECONDARY_FOLDER_NAME)); 36 | 37 | File rootDir = Utility.ensureDirCreated(filesDir, Constants.BOOST_MULTIDEX_DIR_NAME); 38 | File dexDir = Utility.ensureDirCreated(rootDir, Constants.DEX_DIR_NAME); 39 | File optDexDir = Utility.ensureDirCreated(rootDir, Constants.ODEX_DIR_NAME); 40 | File zipDir = Utility.ensureDirCreated(rootDir, Constants.ZIP_DIR_NAME); 41 | 42 | result.setDirs(filesDir, rootDir, dexDir, optDexDir, zipDir); 43 | 44 | Locker prepareLocker = new Locker(new File(rootDir, Constants.LOCK_PREPARE_FILENAME)); 45 | prepareLocker.lock(); 46 | 47 | Locker locker = new Locker(new File(rootDir, Constants.LOCK_INSTALL_FILENAME)); 48 | 49 | locker.lock(); 50 | prepareLocker.close(); 51 | 52 | List dexHolderList; 53 | try { 54 | mPreferences = mainContext.getSharedPreferences(Constants.PREFS_FILE, Context.MODE_PRIVATE); 55 | 56 | result.freeSpaceBefore = Environment.getDataDirectory().getFreeSpace(); 57 | 58 | dexHolderList = obtainDexObjectList(sourceApk, rootDir, dexDir, optDexDir, zipDir, result); 59 | 60 | installSecondaryDexes(mainContext.getClassLoader(), dexHolderList); 61 | // Some IOException causes may be fixed by a clean extraction. 62 | } catch (Throwable e) { 63 | Monitor.get().logWarning("Failed to install extracted secondary dex files", e); 64 | throw e; 65 | } finally { 66 | locker.close(); 67 | } 68 | 69 | long freeSpaceAfter = Environment.getDataDirectory().getFreeSpace(); 70 | result.freeSpaceAfter = freeSpaceAfter; 71 | if (freeSpaceAfter < Constants.SPACE_MIN_THRESHOLD) { 72 | Monitor.get().logWarning("Free space is too small: " + freeSpaceAfter 73 | + ", compare to " + Constants.SPACE_MIN_THRESHOLD); 74 | } else { 75 | for (final DexHolder dexHolder : dexHolderList) { 76 | if (!(dexHolder instanceof DexHolder.ZipOpt || dexHolder instanceof DexHolder.DexOpt)) { 77 | Monitor.get().doAfterInstall(new Runnable() { 78 | @Override 79 | public void run() { 80 | OptimizeService.startOptimizeService(mainContext); 81 | } 82 | }); 83 | return; 84 | } 85 | } 86 | } 87 | } 88 | 89 | void doInstallationInOptProcess(Context context, File apkFile) throws Exception { 90 | if (!BoostNative.isSupportFastLoad()) { 91 | Monitor.get().logError("Fast load is not supported!"); 92 | return; 93 | } 94 | 95 | int secondaryNumber = 2; 96 | 97 | final ZipFile apkZipFile = new ZipFile(apkFile); 98 | ZipEntry dexEntry; 99 | 100 | List dexHolderList = new ArrayList<>(); 101 | while ((dexEntry = apkZipFile.getEntry(Constants.DEX_PREFIX + secondaryNumber + Constants.DEX_SUFFIX)) != null) { 102 | byte[] bytes = obtainEntryBytesInApk(apkZipFile, dexEntry); 103 | dexHolderList.add(new DexHolder.ApkBuffer(secondaryNumber, bytes, null, null)); 104 | secondaryNumber++; 105 | } 106 | 107 | DexLoader.create(Build.VERSION.SDK_INT).install(context.getClassLoader(), dexHolderList); 108 | apkZipFile.close(); 109 | 110 | try { 111 | BoostNative.recoverAction(); 112 | } catch (UnsatisfiedLinkError ignored) { 113 | } 114 | } 115 | 116 | private void installSecondaryDexes(ClassLoader loader, List dexHolderList) throws Exception { 117 | DexLoader.create(Build.VERSION.SDK_INT).install(loader, dexHolderList, mPreferences); 118 | try { 119 | BoostNative.recoverAction(); 120 | } catch (UnsatisfiedLinkError ignored) { 121 | } 122 | Monitor.get().logDebug("After install all, sp value is " + mPreferences.getAll()); 123 | } 124 | 125 | @SuppressLint("ApplySharedPref") 126 | private List obtainDexObjectList(File apkFile, File rootDir, File dexDir, File odexDir, File zipDir, Result result) throws IOException { 127 | long archiveCheckSum = Utility.doZipCheckSum(apkFile); 128 | long archiveTimeStamp = apkFile.lastModified(); 129 | 130 | String keyApkTime = Constants.KEY_TIME_STAMP; 131 | String keyApkCrc = Constants.KEY_CRC; 132 | String keyApkDexNum = Constants.KEY_DEX_NUMBER; 133 | 134 | boolean isModified = (mPreferences.getLong(keyApkTime, Constants.NO_VALUE) != archiveTimeStamp) 135 | || (mPreferences.getLong(keyApkCrc, Constants.NO_VALUE) != archiveCheckSum); 136 | 137 | result.modified = isModified; 138 | 139 | List dexHolderList = new ArrayList<>(); 140 | if (isModified) { 141 | Utility.clearDirFiles(dexDir); 142 | Utility.clearDirFiles(odexDir); 143 | Utility.clearDirFiles(zipDir); 144 | 145 | SharedPreferences.Editor edit = mPreferences.edit(); 146 | edit.clear(); 147 | edit.commit(); 148 | 149 | int secondaryNumber = 2; 150 | 151 | final ZipFile apkZipFile = new ZipFile(apkFile); 152 | ZipEntry dexEntry; 153 | 154 | while ((dexEntry = apkZipFile.getEntry(Constants.DEX_PREFIX + secondaryNumber + Constants.DEX_SUFFIX)) != null) { 155 | File dexFile = new File(dexDir, secondaryNumber + Constants.DEX_SUFFIX); 156 | File optDexFile = new File(odexDir, secondaryNumber + Constants.ODEX_SUFFIX); 157 | if (BoostNative.isSupportFastLoad()) { 158 | // all in apk dex bytes 159 | if (Utility.isBetterUseApkBuf()) { 160 | byte[] bytes = obtainEntryBytesInApk(apkZipFile, dexEntry); 161 | dexHolderList.add(new DexHolder.ApkBuffer(secondaryNumber, bytes, dexFile, optDexFile)); 162 | } else { 163 | File validDexFile = obtainEntryFileInApk(apkZipFile, dexEntry, dexFile); 164 | dexHolderList.add(DexHolder.obtainValidDexBuffer(mPreferences, secondaryNumber, validDexFile, optDexFile)); 165 | } 166 | } else { 167 | // all dex or zip 168 | if (Environment.getDataDirectory().getFreeSpace() > Constants.SPACE_THRESHOLD) { 169 | dexHolderList.add(DexHolder.obtainValidForceDexOpt(mPreferences, secondaryNumber, dexFile, optDexFile, apkZipFile, dexEntry)); 170 | } else { 171 | File zipFile = new File(zipDir, secondaryNumber + Constants.ZIP_SUFFIX); 172 | File zipOptFile = new File(zipDir, secondaryNumber + Constants.ODEX_SUFFIX); 173 | dexHolderList.add(DexHolder.obtainValidZipDex(mPreferences, secondaryNumber, zipFile, zipOptFile, apkZipFile, dexEntry)); 174 | } 175 | } 176 | secondaryNumber++; 177 | } 178 | apkZipFile.close(); 179 | 180 | edit.putInt(keyApkDexNum, secondaryNumber - 1); 181 | edit.putLong(keyApkTime, archiveTimeStamp); 182 | edit.putLong(keyApkCrc, archiveCheckSum); 183 | edit.commit(); 184 | } else { 185 | // ensure valid dex cache 186 | int totalDexNum = mPreferences.getInt(keyApkDexNum, 0); 187 | for (int secondaryNumber = 2; secondaryNumber <= totalDexNum; secondaryNumber++) { 188 | dexHolderList.add(obtainDexHolder(secondaryNumber, 189 | apkFile, dexDir, odexDir, zipDir)); 190 | } 191 | } 192 | 193 | return dexHolderList; 194 | } 195 | 196 | private DexHolder obtainDexHolder(int secondaryNumber, File apkFile, File dexDir, File odexDir, File zipDir) 197 | throws IOException { 198 | int type = mPreferences.getInt(Constants.KEY_DEX_OBJ_TYPE + secondaryNumber, Constants.LOAD_TYPE_INVALID); 199 | if (type == Constants.LOAD_TYPE_INVALID) { 200 | if (BoostNative.isSupportFastLoad()) { 201 | type = Utility.isBetterUseApkBuf() 202 | ? Constants.LOAD_TYPE_APK_BUF 203 | : Constants.LOAD_TYPE_DEX_BUF; 204 | } else { 205 | type = Constants.LOAD_TYPE_ZIP_OPT; 206 | } 207 | } 208 | 209 | if (type == Constants.LOAD_TYPE_ZIP_OPT) { 210 | File zipFile = new File(zipDir, secondaryNumber + Constants.ZIP_SUFFIX); 211 | File zipOptFile = new File(zipDir, secondaryNumber + Constants.ODEX_SUFFIX); 212 | 213 | if (isZipFileValid(zipFile, secondaryNumber)) { 214 | return new DexHolder.ZipOpt(secondaryNumber, zipFile, zipOptFile); 215 | } else { 216 | ZipFile apkZipFile = new ZipFile(apkFile); 217 | ZipEntry dexFileEntry = apkZipFile.getEntry(Constants.DEX_PREFIX + secondaryNumber + Constants.DEX_SUFFIX); 218 | DexHolder.ZipOpt zipOpt = DexHolder.obtainValidZipDex(mPreferences, secondaryNumber, zipFile, zipOptFile, apkZipFile, dexFileEntry); 219 | apkZipFile.close(); 220 | return zipOpt; 221 | } 222 | } 223 | 224 | File dexFile = new File(dexDir, secondaryNumber + Constants.DEX_SUFFIX); 225 | File optDexFile = new File(odexDir, secondaryNumber + Constants.ODEX_SUFFIX); 226 | 227 | if (type == Constants.LOAD_TYPE_DEX_OPT) { 228 | File validDexFile = getValidDexFile(dexFile, secondaryNumber); 229 | if (validDexFile != null) { 230 | File validOptDexFile = getValidOptDexFile(optDexFile, secondaryNumber); 231 | if (validOptDexFile != null) { 232 | return new DexHolder.DexOpt(secondaryNumber, validDexFile, validOptDexFile, false); 233 | } else { 234 | if (BoostNative.isSupportFastLoad()) { 235 | type = Constants.LOAD_TYPE_DEX_BUF; 236 | } else { 237 | return new DexHolder.DexOpt(secondaryNumber, validDexFile, optDexFile, true); 238 | } 239 | } 240 | } else { 241 | if (BoostNative.isSupportFastLoad()) { 242 | type = Constants.LOAD_TYPE_APK_BUF; 243 | } else { 244 | ZipFile apkZipFile = new ZipFile(apkFile); 245 | ZipEntry dexFileEntry = apkZipFile.getEntry(Constants.DEX_PREFIX + secondaryNumber + Constants.DEX_SUFFIX); 246 | return DexHolder.obtainValidForceDexOpt(mPreferences, secondaryNumber, dexFile, optDexFile, apkZipFile, dexFileEntry); 247 | } 248 | } 249 | } 250 | 251 | if (type == Constants.LOAD_TYPE_DEX_BUF) { 252 | File validDexFile = getValidDexFile(dexFile, secondaryNumber); 253 | if (BoostNative.isSupportFastLoad()) { 254 | if (validDexFile != null) { 255 | return new DexHolder.DexBuffer(secondaryNumber, validDexFile, optDexFile); 256 | } else { 257 | type = Constants.LOAD_TYPE_APK_BUF; 258 | } 259 | } else { 260 | if (validDexFile != null) { 261 | return new DexHolder.DexOpt(secondaryNumber, validDexFile, optDexFile, true); 262 | } else { 263 | ZipFile apkZipFile = new ZipFile(apkFile); 264 | ZipEntry dexFileEntry = apkZipFile.getEntry(Constants.DEX_PREFIX + secondaryNumber + Constants.DEX_SUFFIX); 265 | return DexHolder.obtainValidForceDexOpt(mPreferences, secondaryNumber, dexFile, optDexFile, apkZipFile, dexFileEntry); 266 | } 267 | } 268 | } 269 | 270 | if (type == Constants.LOAD_TYPE_APK_BUF) { 271 | if (!BoostNative.isSupportFastLoad()) { 272 | Monitor.get().logError("Do not support apk buf!"); 273 | } 274 | ZipFile apkZipFile = new ZipFile(apkFile); 275 | ZipEntry dexFileEntry = apkZipFile.getEntry(Constants.DEX_PREFIX + secondaryNumber + Constants.DEX_SUFFIX); 276 | byte[] bytes = obtainEntryBytesInApk(apkZipFile, dexFileEntry); 277 | DexHolder dexHolder = new DexHolder.ApkBuffer(secondaryNumber, bytes, dexFile, optDexFile); 278 | apkZipFile.close(); 279 | return dexHolder; 280 | } 281 | 282 | return null; 283 | } 284 | 285 | private byte[] obtainEntryBytesInApk(ZipFile apkZipFile, ZipEntry dexFileEntry) throws IOException { 286 | return Utility.obtainEntryBytesInZip(apkZipFile, dexFileEntry); 287 | } 288 | 289 | private File obtainEntryFileInApk(ZipFile apkZipFile, ZipEntry dexFileEntry, File target) throws IOException { 290 | return Utility.obtainEntryFileInZip(apkZipFile, dexFileEntry, target); 291 | } 292 | 293 | 294 | private File getValidDexFile(File file, int secondaryNumber) throws IOException { 295 | if (!checkFileValid(secondaryNumber, Constants.KEY_DEX_CHECKSUM, Constants.KEY_DEX_TIME, 296 | file, false)) { 297 | return null; 298 | } 299 | 300 | return file; 301 | } 302 | 303 | private File getValidOptDexFile(File file, int secondaryNumber) throws IOException { 304 | if (!file.exists()) { 305 | Monitor.get().logInfo("opt file does not exist: " + file.getPath()); 306 | return null; 307 | } 308 | 309 | if (!checkFileValid(secondaryNumber, Constants.KEY_ODEX_CHECKSUM, Constants.KEY_ODEX_TIME, 310 | file, false)) { 311 | return null; 312 | } 313 | 314 | return file; 315 | } 316 | 317 | private boolean checkFileValid(int secondaryNumber, 318 | String keyCheckSum, String keyTime, 319 | File file, boolean isZip) { 320 | if (!file.exists()) { 321 | Monitor.get().logWarning("File does not exist! " + file.getPath()); 322 | return false; 323 | } 324 | 325 | long expectedModTime = mPreferences.getLong(keyTime + secondaryNumber, Constants.NO_VALUE); 326 | long lastModified = file.lastModified(); 327 | if (expectedModTime != lastModified) { 328 | Monitor.get().logWarning("Invalid file: " 329 | + " (key \"" + (keyCheckSum + keyTime + secondaryNumber) + "\"), expected modification time: " 330 | + expectedModTime + ", modification time: " + lastModified); 331 | return false; 332 | } 333 | 334 | long checkSum = 0; 335 | boolean doCheckSum = true; 336 | if (Constants.KEY_DEX_CHECKSUM.equals(keyCheckSum)) { 337 | try { 338 | if (isZip) { 339 | checkSum = Utility.doZipCheckSum(file); 340 | } else if (mDoCheckSum) { 341 | checkSum = Utility.doFileCheckSum(file); 342 | } else { 343 | doCheckSum = false; 344 | } 345 | } catch (IOException e) { 346 | return false; 347 | } 348 | } else if (Constants.KEY_ODEX_CHECKSUM.equals(keyCheckSum)) { 349 | checkSum = file.length(); 350 | } else { 351 | Monitor.get().logWarning("unsupported checksum key: " + keyCheckSum); 352 | return false; 353 | } 354 | 355 | if (doCheckSum) { 356 | long expectedCheckSum = mPreferences.getLong(keyCheckSum + secondaryNumber, Constants.NO_VALUE); 357 | if (expectedCheckSum != checkSum) { 358 | Monitor.get().logWarning("Invalid file: " 359 | + " (key \"" + (keyCheckSum + keyTime + secondaryNumber) + "\"), expected checksum: " 360 | + expectedCheckSum + ", file checksum: " + checkSum); 361 | return false; 362 | } 363 | } 364 | 365 | return true; 366 | } 367 | 368 | private boolean isZipFileValid(File zipFile, int secondaryNumber) { 369 | return checkFileValid(secondaryNumber, Constants.KEY_DEX_CHECKSUM, 370 | Constants.KEY_DEX_TIME, zipFile, true); 371 | } 372 | 373 | } 374 | -------------------------------------------------------------------------------- /boost_multidex/src/main/java/com/bytedance/boost_multidex/DexLoader.java: -------------------------------------------------------------------------------- 1 | package com.bytedance.boost_multidex; 2 | 3 | import android.content.SharedPreferences; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.lang.reflect.Constructor; 8 | import java.lang.reflect.Field; 9 | import java.lang.reflect.InvocationTargetException; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.zip.ZipFile; 13 | 14 | import dalvik.system.DexFile; 15 | 16 | /** 17 | * Created by Xiaolin(xiaolin.gan@bytedance.com) on 2019/3/28. 18 | */ 19 | abstract class DexLoader { 20 | ElementConstructor mElementConstructor; 21 | 22 | static DexLoader create(int sdkInt) { 23 | if (sdkInt >= 19) { 24 | return new V19(); 25 | } else if (sdkInt >= 14) { 26 | return new V14(); 27 | } else { 28 | throw new UnsupportedOperationException( 29 | "only support SDK_INT >= 14, give up when < 14"); 30 | } 31 | } 32 | 33 | void install(ClassLoader loader, List dexHolderList, SharedPreferences preferences) throws Exception { 34 | Field pathListField = Utility.findFieldRecursively(loader.getClass(), "pathList"); 35 | Object dexPathList = pathListField.get(loader); 36 | 37 | Object[] elements = makeDexElements(dexHolderList, preferences); 38 | Utility.expandFieldArray(dexPathList, "dexElements", elements); 39 | } 40 | 41 | void install(ClassLoader loader, List dexHolderList) throws Exception { 42 | Field pathListField = Utility.findFieldRecursively(loader.getClass(), "pathList"); 43 | Object dexPathList = pathListField.get(loader); 44 | 45 | ArrayList elements = new ArrayList<>(); 46 | for (int i = 0; i < dexHolderList.size(); ++i) { 47 | DexHolder dexHolder = dexHolderList.get(i); 48 | elements.add(dexHolder.toDexListElement(mElementConstructor)); 49 | Monitor.get().logInfo("Install holder: " + dexHolder.getClass().getName() + ", index " + i); 50 | } 51 | 52 | Utility.expandFieldArray(dexPathList, "dexElements", elements.toArray()); 53 | } 54 | 55 | /** 56 | * An emulation of {@code private static final dalvik.system.DexPathList#makeDexElements} 57 | * accepting only extracted secondary dex files. 58 | * OS version is catching IOException and just logging some of them, this version is letting 59 | * them through. 60 | */ 61 | private Object[] makeDexElements(List dexHolderList, SharedPreferences preferences) throws Exception { 62 | ArrayList elements = new ArrayList<>(); 63 | 64 | for (int i = 0; i < dexHolderList.size(); ++i) { 65 | DexHolder dexHolder = dexHolderList.get(i); 66 | 67 | Object element = dexHolder.toDexListElement(mElementConstructor); 68 | while (element == null && dexHolder != null) { 69 | Monitor.get().logWarning("Load faster dex in holder " + dexHolder.toString()); 70 | dexHolder = dexHolder.toFasterHolder(preferences); 71 | if (dexHolder != null) { 72 | element = dexHolder.toDexListElement(mElementConstructor); 73 | } 74 | } 75 | 76 | if (element != null) { 77 | Monitor.get().logInfo("Load dex in holder " + dexHolder.toString()); 78 | dexHolderList.set(i, dexHolder); 79 | elements.add(element); 80 | } else { 81 | throw new RuntimeException("Fail to load dex, index is " + i); 82 | } 83 | 84 | String dexInfo = dexHolder.toString(); 85 | Result.get().addDexInfo(dexInfo); 86 | Monitor.get().logInfo("Add info: " + dexInfo); 87 | } 88 | 89 | return elements.toArray(); 90 | } 91 | 92 | /** 93 | * A wrapper around 94 | * {@code private static final dalvik.system.DexPathList#makeDexElements}. 95 | */ 96 | private static class V19 extends DexLoader { 97 | private V19() { 98 | try { 99 | Class elementClass = Class.forName("dalvik.system.DexPathList$Element"); 100 | mElementConstructor = new KKElementConstructor(elementClass); 101 | } catch (Throwable tr) { 102 | Monitor.get().logError("fail to get Element constructor", tr); 103 | } 104 | } 105 | } 106 | 107 | /** 108 | * Installer for platform versions 14, 15, 16, 17 and 18. 109 | */ 110 | private static class V14 extends DexLoader { 111 | private V14() { 112 | ElementConstructor constructor = null; 113 | Class elementClass; 114 | try { 115 | elementClass = Class.forName("dalvik.system.DexPathList$Element"); 116 | } catch (Exception e) { 117 | Monitor.get().logError("can not find DexPathList$Element", e); 118 | return; 119 | } 120 | 121 | try { 122 | constructor = new ICSElementConstructor(elementClass); 123 | } catch (Exception ignored) { 124 | } 125 | 126 | if (constructor == null) { 127 | try { 128 | constructor = new JBMR11ElementConstructor(elementClass); 129 | } catch (Exception ignored) { 130 | } 131 | } 132 | 133 | if (constructor == null) { 134 | try { 135 | constructor = new JBMR2ElementConstructor(elementClass); 136 | } catch (Exception ignored) { 137 | } 138 | } 139 | 140 | mElementConstructor = constructor; 141 | } 142 | } 143 | 144 | interface ElementConstructor { 145 | Object newInstance(File file, Object dex) 146 | throws IllegalArgumentException, InstantiationException, 147 | IllegalAccessException, InvocationTargetException, IOException; 148 | } 149 | 150 | /** 151 | * Applies for ICS and early JB (initial release and MR1). 152 | */ 153 | private static class ICSElementConstructor implements ElementConstructor { 154 | private final Constructor mConstructor; 155 | 156 | ICSElementConstructor(Class elementClass) 157 | throws SecurityException, NoSuchMethodException { 158 | mConstructor = 159 | elementClass.getConstructor(File.class, ZipFile.class, DexFile.class); 160 | mConstructor.setAccessible(true); 161 | } 162 | 163 | @Override 164 | public Object newInstance(File file, Object dex) 165 | throws IllegalArgumentException, InstantiationException, 166 | IllegalAccessException, InvocationTargetException, IOException { 167 | // it is no use to set zip of dex here, because apk has contained resources. 168 | return mConstructor.newInstance(file, null, dex); 169 | } 170 | } 171 | 172 | /** 173 | * Applies for some intermediate JB (MR1.1). 174 | *

175 | * See Change-Id: I1a5b5d03572601707e1fb1fd4424c1ae2fd2217d 176 | */ 177 | private static class JBMR11ElementConstructor implements ElementConstructor { 178 | private final Constructor mConstructor; 179 | 180 | JBMR11ElementConstructor(Class elementClass) 181 | throws SecurityException, NoSuchMethodException { 182 | mConstructor = elementClass 183 | .getConstructor(File.class, File.class, DexFile.class); 184 | mConstructor.setAccessible(true); 185 | } 186 | 187 | @Override 188 | public Object newInstance(File file, Object dex) 189 | throws IllegalArgumentException, InstantiationException, 190 | IllegalAccessException, InvocationTargetException { 191 | return mConstructor.newInstance(file, null, dex); 192 | } 193 | } 194 | 195 | /** 196 | * Applies for latest JB (MR2). 197 | *

198 | * See Change-Id: Iec4dca2244db9c9c793ac157e258fd61557a7a5d 199 | */ 200 | private static class JBMR2ElementConstructor implements ElementConstructor { 201 | private final Constructor mConstructor; 202 | 203 | JBMR2ElementConstructor(Class elementClass) 204 | throws SecurityException, NoSuchMethodException { 205 | mConstructor = elementClass 206 | .getConstructor(File.class, boolean.class, File.class, DexFile.class); 207 | mConstructor.setAccessible(true); 208 | } 209 | 210 | @Override 211 | public Object newInstance(File file, Object dex) 212 | throws IllegalArgumentException, InstantiationException, 213 | IllegalAccessException, InvocationTargetException { 214 | return mConstructor.newInstance(file, false, null, dex); 215 | } 216 | } 217 | 218 | private static class KKElementConstructor implements ElementConstructor { 219 | private final Constructor mConstructor; 220 | 221 | KKElementConstructor(Class elementClass) 222 | throws SecurityException, NoSuchMethodException { 223 | mConstructor = Utility.findConstructor(elementClass, File.class, boolean.class, File.class, DexFile.class); 224 | mConstructor.setAccessible(true); 225 | } 226 | 227 | @Override 228 | public Object newInstance(File file, Object dex) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, IOException { 229 | return mConstructor.newInstance(file, false, null, dex); 230 | } 231 | } 232 | 233 | } 234 | -------------------------------------------------------------------------------- /boost_multidex/src/main/java/com/bytedance/boost_multidex/Locker.java: -------------------------------------------------------------------------------- 1 | package com.bytedance.boost_multidex; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.io.RandomAccessFile; 6 | import java.nio.channels.FileChannel; 7 | import java.nio.channels.FileLock; 8 | 9 | /** 10 | * Created by Xiaolin(xiaolin.gan@bytedance.com) on 2019/3/31. 11 | */ 12 | class Locker { 13 | private RandomAccessFile lockRaf; 14 | private FileLock cacheLock; 15 | private FileChannel lockChannel; 16 | 17 | private File lockFile; 18 | 19 | Locker(File lockFile) { 20 | this.lockFile = lockFile; 21 | } 22 | 23 | void lock() throws IOException { 24 | lockRaf = new RandomAccessFile(lockFile, "rw"); 25 | try { 26 | lockChannel = lockRaf.getChannel(); 27 | try { 28 | Monitor.get().logInfo("Blocking on lock " + lockFile.getPath()); 29 | cacheLock = lockChannel.lock(); 30 | } catch (IOException e) { 31 | Utility.closeQuietly(lockChannel); 32 | throw e; 33 | } 34 | Monitor.get().logInfo("Acquired on lock " + lockFile.getPath()); 35 | } catch (IOException e) { 36 | Utility.closeQuietly(lockRaf); 37 | throw e; 38 | } 39 | } 40 | 41 | boolean test() throws IOException { 42 | lockRaf = new RandomAccessFile(lockFile, "rw"); 43 | lockChannel = lockRaf.getChannel(); 44 | try { 45 | Monitor.get().logInfo("Blocking on lock " + lockFile.getPath()); 46 | cacheLock = lockChannel.tryLock(); 47 | return cacheLock != null; 48 | } catch (IOException e) { 49 | Monitor.get().logInfo("Aborting on lock " + lockFile.getPath()); 50 | return false; 51 | } finally { 52 | Monitor.get().logInfo("Acquired on lock " + lockFile.getPath()); 53 | } 54 | } 55 | 56 | void close() { 57 | if (cacheLock != null) { 58 | try { 59 | cacheLock.release(); 60 | } catch (IOException ignored) { 61 | } 62 | } 63 | Monitor.get().logInfo("Released lock " + lockFile.getPath()); 64 | Utility.closeQuietly(lockChannel); 65 | Utility.closeQuietly(lockRaf); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /boost_multidex/src/main/java/com/bytedance/boost_multidex/Monitor.java: -------------------------------------------------------------------------------- 1 | package com.bytedance.boost_multidex; 2 | 3 | import android.os.Looper; 4 | import android.os.MessageQueue; 5 | import android.util.Log; 6 | 7 | import java.util.concurrent.Executor; 8 | import java.util.concurrent.Executors; 9 | import java.util.concurrent.ScheduledExecutorService; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | /** 13 | * Created by Xiaolin(xiaolin.gan@bytedance.com) on 2019/3/3. 14 | */ 15 | public class Monitor { 16 | private static final boolean enableLog = true; 17 | 18 | private static Monitor sMonitor; 19 | 20 | private ScheduledExecutorService mExecutor = Executors.newScheduledThreadPool(1); 21 | 22 | private String mProcessName; 23 | 24 | static void init(Monitor monitor) { 25 | sMonitor = monitor != null ? monitor : new Monitor(); 26 | } 27 | 28 | static Monitor get() { 29 | return sMonitor; 30 | } 31 | 32 | private ScheduledExecutorService getExecutor() { 33 | return mExecutor; 34 | } 35 | 36 | String getProcessName() { 37 | return mProcessName; 38 | } 39 | 40 | public Monitor setExecutor(ScheduledExecutorService executor) { 41 | mExecutor = executor; 42 | return this; 43 | } 44 | 45 | public Monitor setProcessName(String processName) { 46 | mProcessName = processName; 47 | return this; 48 | } 49 | 50 | protected void loadLibrary(String libName) { 51 | System.loadLibrary(libName); 52 | } 53 | 54 | protected void logError(String msg) { 55 | if (!enableLog) { 56 | return; 57 | } 58 | 59 | Log.println(Log.ERROR, Constants.TAG, msg); 60 | } 61 | 62 | protected void logWarning(String msg) { 63 | if (!enableLog) { 64 | return; 65 | } 66 | 67 | Log.w(Constants.TAG, msg); 68 | } 69 | 70 | protected void logInfo(String msg) { 71 | if (!enableLog) { 72 | return; 73 | } 74 | 75 | // Log.println(Log.INFO, Constants.TAG, msg); 76 | Log.i(Constants.TAG, msg); 77 | } 78 | 79 | protected void logDebug(String msg) { 80 | if (!enableLog) { 81 | return; 82 | } 83 | 84 | // Log.println(Log.DEBUG, Constants.TAG, msg); 85 | Log.d(Constants.TAG, msg); 86 | } 87 | 88 | protected void logError(String msg, Throwable tr) { 89 | if (!enableLog) { 90 | return; 91 | } 92 | 93 | Log.e(Constants.TAG, msg, tr); 94 | } 95 | 96 | protected void logWarning(String msg, Throwable tr) { 97 | if (!enableLog) { 98 | return; 99 | } 100 | 101 | Log.w(Constants.TAG, msg, tr); 102 | } 103 | 104 | protected boolean isEnableNativeCheckSum() { 105 | return true; 106 | } 107 | 108 | protected void logErrorAfterInstall(String msg, Throwable tr) { 109 | Log.e(Constants.TAG, msg, tr); 110 | } 111 | 112 | protected void reportAfterInstall(long cost, long freeSpace, long reducedSpace, String dexHolderInfo) { 113 | Log.println(Log.INFO, Constants.TAG, "Cost time: " + cost + ", free space: " + freeSpace 114 | + ", reduced space: " + reducedSpace + ", holder: " + dexHolderInfo); 115 | } 116 | 117 | protected void doBeforeHandleOpt() { 118 | try { 119 | Thread.sleep(10_000); 120 | } catch (InterruptedException e) { 121 | e.printStackTrace(); 122 | } 123 | } 124 | 125 | protected void doAfterInstall(final Runnable optRunnable) { 126 | Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { 127 | @Override 128 | public boolean queueIdle() { 129 | getExecutor().schedule(optRunnable, 5_000, TimeUnit.MILLISECONDS); 130 | return false; 131 | } 132 | }); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /boost_multidex/src/main/java/com/bytedance/boost_multidex/OptimizeService.java: -------------------------------------------------------------------------------- 1 | package com.bytedance.boost_multidex; 2 | 3 | import android.app.IntentService; 4 | import android.content.Intent; 5 | import android.content.Context; 6 | import android.content.SharedPreferences; 7 | import android.content.pm.ApplicationInfo; 8 | import android.os.Environment; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.util.zip.ZipEntry; 13 | import java.util.zip.ZipFile; 14 | 15 | public class OptimizeService extends IntentService { 16 | static volatile boolean sAlreadyOpt; 17 | 18 | File mRootDir; 19 | File mDexDir; 20 | File mOptDexDir; 21 | File mZipDir; 22 | 23 | public OptimizeService() { 24 | super("OptimizeService"); 25 | Monitor monitor = Monitor.get(); 26 | if (monitor == null) { 27 | Monitor.init(null); 28 | } 29 | Monitor.get().logDebug("Starting OptimizeService"); 30 | } 31 | 32 | @Override 33 | public void onCreate() { 34 | super.onCreate(); 35 | 36 | try { 37 | File filesDir = this.getFilesDir(); 38 | if (!filesDir.exists()) { 39 | Utility.mkdirChecked(filesDir); 40 | } 41 | 42 | mRootDir = Utility.ensureDirCreated(filesDir, Constants.BOOST_MULTIDEX_DIR_NAME); 43 | mDexDir = Utility.ensureDirCreated(mRootDir, Constants.DEX_DIR_NAME); 44 | mOptDexDir = Utility.ensureDirCreated(mRootDir, Constants.ODEX_DIR_NAME); 45 | mZipDir = Utility.ensureDirCreated(mRootDir, Constants.ZIP_DIR_NAME); 46 | } catch (IOException e) { 47 | Monitor.get().logError("fail to create files", e); 48 | sAlreadyOpt = true; 49 | } 50 | } 51 | 52 | public static void startOptimizeService(Context context) { 53 | Intent intent = new Intent(context, OptimizeService.class); 54 | context.startService(intent); 55 | } 56 | 57 | @Override 58 | protected void onHandleIntent(Intent intent) { 59 | if (intent != null) { 60 | try { 61 | handleOptimize(); 62 | } catch (IOException e) { 63 | Monitor.get().logError("fail to handle opt", e); 64 | } 65 | } 66 | } 67 | 68 | /** 69 | * Handle action Foo in the provided background thread with the provided 70 | * parameters. 71 | */ 72 | private void handleOptimize() throws IOException { 73 | if (sAlreadyOpt) { 74 | Monitor.get().logInfo("opt had already done, skip"); 75 | return; 76 | } 77 | 78 | sAlreadyOpt = true; 79 | 80 | Monitor.get().doBeforeHandleOpt(); 81 | 82 | String keyApkDexNum = Constants.KEY_DEX_NUMBER; 83 | 84 | Locker locker = new Locker(new File(mRootDir, Constants.LOCK_INSTALL_FILENAME)); 85 | 86 | locker.lock(); 87 | 88 | try { 89 | ApplicationInfo applicationInfo = this.getApplicationInfo(); 90 | if (applicationInfo == null) { 91 | throw new RuntimeException("No ApplicationInfo available, i.e. running on a test Context:" 92 | + " BoostMultiDex support library is disabled."); 93 | } 94 | 95 | File apkFile = new File(applicationInfo.sourceDir); 96 | 97 | SharedPreferences preferences = this.getSharedPreferences(Constants.PREFS_FILE, Context.MODE_PRIVATE); 98 | int totalDexNum = preferences.getInt(keyApkDexNum, 0); 99 | for (int secondaryNumber = 2; secondaryNumber <= totalDexNum; secondaryNumber++) { 100 | int type = preferences.getInt(Constants.KEY_DEX_OBJ_TYPE + secondaryNumber, Constants.LOAD_TYPE_APK_BUF); 101 | 102 | File dexFile = new File(mDexDir, secondaryNumber + Constants.DEX_SUFFIX); 103 | File optDexFile = new File(mOptDexDir, secondaryNumber + Constants.ODEX_SUFFIX); 104 | 105 | DexHolder dexHolder; 106 | if (type == Constants.LOAD_TYPE_APK_BUF) { 107 | ZipFile apkZipFile = new ZipFile(apkFile); 108 | ZipEntry dexFileEntry = apkZipFile.getEntry(Constants.DEX_PREFIX + secondaryNumber + Constants.DEX_SUFFIX); 109 | byte[] bytes = Utility.obtainEntryBytesInZip(apkZipFile, dexFileEntry); 110 | dexHolder = new DexHolder.ApkBuffer(secondaryNumber, bytes, dexFile, optDexFile); 111 | } else if (type == Constants.LOAD_TYPE_DEX_BUF) { 112 | dexHolder = new DexHolder.DexBuffer(secondaryNumber, dexFile, optDexFile); 113 | } else if (type == Constants.LOAD_TYPE_DEX_OPT) { 114 | dexHolder = new DexHolder.DexOpt(secondaryNumber, dexFile, optDexFile, false); 115 | } else if (type == Constants.LOAD_TYPE_ZIP_OPT) { 116 | File zipFile = new File(mZipDir, secondaryNumber + Constants.ZIP_SUFFIX); 117 | File zipOptFile = new File(mZipDir, secondaryNumber + Constants.ODEX_SUFFIX); 118 | dexHolder = new DexHolder.ZipOpt(secondaryNumber, zipFile, zipOptFile); 119 | } else { 120 | dexHolder = null; 121 | } 122 | 123 | Monitor.get().logInfo("Process beginning holder " + dexHolder.toString() + ", type: " + type); 124 | 125 | DexHolder fasterHolder = dexHolder; 126 | 127 | while (fasterHolder != null) { 128 | long freeSpace = Environment.getDataDirectory().getFreeSpace(); 129 | if (freeSpace < Constants.SPACE_MIN_THRESHOLD) { 130 | Monitor.get().logWarning("Free space is too small: " + freeSpace 131 | + ", compare to " + Constants.SPACE_THRESHOLD); 132 | return; 133 | } else { 134 | Monitor.get().logInfo("Free space is enough: " + freeSpace + ", continue..."); 135 | } 136 | 137 | Monitor.get().logDebug("Process holder, " + fasterHolder); 138 | 139 | try { 140 | long start = System.nanoTime(); 141 | 142 | fasterHolder = fasterHolder.toFasterHolder(preferences); 143 | 144 | if (fasterHolder != null) { 145 | long cost = System.nanoTime() - start; 146 | 147 | DexHolder.StoreInfo info = fasterHolder.getInfo(); 148 | 149 | Monitor.get().logDebug("Put info, " + info.index + " file is " + info.file.getPath()); 150 | 151 | long reducedSpace = Environment.getDataDirectory().getFreeSpace() - freeSpace; 152 | 153 | Monitor.get().reportAfterInstall(cost, freeSpace, reducedSpace, fasterHolder.toString()); 154 | } 155 | } catch (Throwable tr) { 156 | Monitor.get().logErrorAfterInstall("Fail to be faster", tr); 157 | Result.get().unFatalThrowable.add(tr); 158 | } 159 | 160 | Locker prepareLocker = new Locker(new File(mRootDir, Constants.LOCK_PREPARE_FILENAME)); 161 | if (prepareLocker.test()) { 162 | prepareLocker.close(); 163 | } else { 164 | Monitor.get().logInfo("Other process is waiting for installing"); 165 | return; 166 | } 167 | } 168 | } 169 | } catch (Throwable e) { 170 | Monitor.get().logWarning("Failed to install extracted secondary dex files", e); 171 | } finally { 172 | locker.close(); 173 | Monitor.get().logInfo("Exit quietly"); 174 | stopSelf(); 175 | System.exit(0); 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /boost_multidex/src/main/java/com/bytedance/boost_multidex/Result.java: -------------------------------------------------------------------------------- 1 | package com.bytedance.boost_multidex; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.File; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * Created by Xiaolin(xiaolin.gan@bytedance.com) on 2019/4/8. 11 | */ 12 | public class Result { 13 | private static Result result = new Result(); 14 | 15 | public boolean modified; 16 | 17 | public long freeSpaceBefore; 18 | 19 | public long freeSpaceAfter; 20 | 21 | public String vmLibName; 22 | 23 | public boolean isYunOS; 24 | 25 | public File dataDir; 26 | 27 | public File rootDir; 28 | 29 | public File dexDir; 30 | 31 | public File optDexDir; 32 | 33 | public File zipDir; 34 | 35 | public Throwable fatalThrowable; 36 | 37 | public List unFatalThrowable = new ArrayList<>(); 38 | 39 | public List dexInfoList = new ArrayList<>(); 40 | 41 | public boolean supportFastLoadDex; 42 | 43 | public static Result get() { 44 | if (result != null) { 45 | return result; 46 | } else { 47 | Log.w(Constants.TAG, "Avoid npe, but return a invalid tmp result"); 48 | return new Result(); 49 | } 50 | } 51 | 52 | private Result() { 53 | } 54 | 55 | public void setDirs(File dataDir, File rootDir, File dexDir, File optDexDir, File zipDir) { 56 | this.dataDir = dataDir; 57 | this.rootDir = rootDir; 58 | this.dexDir = dexDir; 59 | this.optDexDir = optDexDir; 60 | this.zipDir = zipDir; 61 | } 62 | 63 | public void setFatalThrowable(Throwable tr) { 64 | fatalThrowable = tr; 65 | } 66 | 67 | public void addUnFatalThrowable(Throwable tr) { 68 | unFatalThrowable.add(tr); 69 | } 70 | 71 | public void addDexInfo(String dexInfo) { 72 | dexInfoList.add(dexInfo); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /boost_multidex/src/main/java/com/bytedance/boost_multidex/Utility.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | /* Apache Harmony HEADER because the code in this class comes mostly from ZipFile, ZipEntry and 18 | * ZipConstants from android libcore. 19 | */ 20 | 21 | package com.bytedance.boost_multidex; 22 | 23 | import android.app.ActivityManager; 24 | import android.content.Context; 25 | import android.os.Environment; 26 | 27 | import java.io.BufferedOutputStream; 28 | import java.io.ByteArrayOutputStream; 29 | import java.io.Closeable; 30 | import java.io.File; 31 | import java.io.FileInputStream; 32 | import java.io.FileOutputStream; 33 | import java.io.IOException; 34 | import java.io.InputStream; 35 | import java.io.RandomAccessFile; 36 | import java.lang.reflect.Array; 37 | import java.lang.reflect.Constructor; 38 | import java.lang.reflect.Field; 39 | import java.lang.reflect.Method; 40 | import java.util.Arrays; 41 | import java.util.zip.Adler32; 42 | import java.util.zip.CRC32; 43 | import java.util.zip.CheckedInputStream; 44 | import java.util.zip.ZipEntry; 45 | import java.util.zip.ZipException; 46 | import java.util.zip.ZipFile; 47 | import java.util.zip.ZipOutputStream; 48 | 49 | /** 50 | * Created by Xiaolin(xiaolin.gan@bytedance.com) on 2019/3/28. 51 | */ 52 | public class Utility { 53 | /** 54 | * Replace the value of a field containing a non null array, by a new array containing the 55 | * elements of the original array plus the elements of extraElements. 56 | * @param instance the instance whose field is to be modified. 57 | * @param fieldName the field to modify. 58 | * @param extraElements elements to append at the end of the array. 59 | */ 60 | static void expandFieldArray(Object instance, String fieldName, 61 | Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException, 62 | IllegalAccessException { 63 | Field field = findField(instance.getClass(), fieldName); 64 | Object[] original = (Object[]) field.get(instance); 65 | Object[] combined = (Object[]) Array.newInstance( 66 | original.getClass().getComponentType(), original.length + extraElements.length); 67 | System.arraycopy(original, 0, combined, 0, original.length); 68 | System.arraycopy(extraElements, 0, combined, original.length, extraElements.length); 69 | field.set(instance, combined); 70 | } 71 | 72 | static boolean isBetterUseApkBuf() { 73 | Runtime runtime = Runtime.getRuntime(); 74 | long freeSpaceAfter = Environment.getDataDirectory().getFreeSpace(); 75 | long remainedMem = runtime.maxMemory() - runtime.totalMemory(); 76 | Monitor.get().logInfo("Memory remains " + remainedMem 77 | + ", free space " + freeSpaceAfter); 78 | return remainedMem > Constants.MEM_THRESHOLD 79 | || freeSpaceAfter < Constants.SPACE_MIN_THRESHOLD; 80 | } 81 | 82 | static void clearDirFiles(File dir) { 83 | if (!dir.exists()) { 84 | return; 85 | } 86 | 87 | File[] files = dir.listFiles(); 88 | if (files == null) { 89 | Monitor.get().logWarning("Failed to list secondary dex dir content (" + dir.getPath() + ")."); 90 | return; 91 | } 92 | for (File oldFile : files) { 93 | Monitor.get().logInfo("Trying to delete old file " + oldFile.getPath() + " of size " + 94 | oldFile.length()); 95 | if (!oldFile.delete()) { 96 | Monitor.get().logWarning("Failed to delete old file " + oldFile.getPath()); 97 | } else { 98 | Monitor.get().logInfo("Deleted old file " + oldFile.getPath()); 99 | } 100 | } 101 | } 102 | 103 | static long doFileCheckSum(File file) throws IOException { 104 | long result = 0; 105 | 106 | if (!file.exists()) { 107 | Monitor.get().logInfo("File is not exist: " + file.getPath()); 108 | return result; 109 | } 110 | 111 | if (Monitor.get().isEnableNativeCheckSum()) { 112 | try { 113 | result = BoostNative.obtainCheckSum(file.getPath()); 114 | } catch(Throwable tr) { 115 | Monitor.get().logWarning("Failed to native obtainCheckSum in " + file.getPath(), tr); 116 | } 117 | } 118 | 119 | if (result == 0) { 120 | Monitor.get().logWarning("Fall back to java impl"); 121 | CheckedInputStream checkedInputStream = null; 122 | byte[] buf = new byte[Constants.BUFFER_SIZE]; 123 | try { 124 | checkedInputStream = new CheckedInputStream( 125 | new FileInputStream(file), new Adler32()); 126 | 127 | for (;;) { 128 | if (checkedInputStream.read(buf) < 0) { 129 | break; 130 | } 131 | } 132 | 133 | result = checkedInputStream.getChecksum().getValue(); 134 | } finally { 135 | Utility.closeQuietly(checkedInputStream); 136 | } 137 | } 138 | 139 | return result; 140 | } 141 | 142 | /** 143 | * Compute crc32 of the central directory of an apk. The central directory contains 144 | * the crc32 of each entries in the zip so the computed result is considered valid for the whole 145 | * zip file. Does not support zip64 nor multidisk but it should be OK for now since ZipFile does 146 | * not either. 147 | */ 148 | static long doZipCheckSum(File apk) throws IOException { 149 | RandomAccessFile raf = null; 150 | 151 | try { 152 | raf = new RandomAccessFile(apk, "r"); 153 | long scanOffset = raf.length() - Constants.ENDHDR; 154 | if (scanOffset < 0) { 155 | throw new ZipException("File too short to be a zip file: " + raf.length()); 156 | } 157 | 158 | long stopOffset = scanOffset - 0x10000 /* ".ZIP file comment"'s max length */; 159 | if (stopOffset < 0) { 160 | stopOffset = 0; 161 | } 162 | 163 | int endSig = Integer.reverseBytes(Constants.ENDSIG); 164 | while (true) { 165 | raf.seek(scanOffset); 166 | if (raf.readInt() == endSig) { 167 | break; 168 | } 169 | 170 | scanOffset--; 171 | if (scanOffset < stopOffset) { 172 | throw new ZipException("End Of Central Directory signature not found"); 173 | } 174 | } 175 | // Read the End Of Central Directory. ENDHDR includes the signature 176 | // bytes, 177 | // which we've already read. 178 | 179 | // Pull out the information we need. 180 | raf.skipBytes(2); // diskNumber 181 | raf.skipBytes(2); // diskWithCentralDir 182 | raf.skipBytes(2); // numEntries 183 | raf.skipBytes(2); // totalNumEntries 184 | long size = Integer.reverseBytes(raf.readInt()) & 0xFFFFFFFFL; 185 | long offset = Integer.reverseBytes(raf.readInt()) & 0xFFFFFFFFL; 186 | 187 | // computeCrcOfCentralDir 188 | CRC32 crc = new CRC32(); 189 | long stillToRead = size; 190 | raf.seek(offset); 191 | int length = (int) Math.min(Constants.BUFFER_SIZE, stillToRead); 192 | byte[] buffer = new byte[Constants.BUFFER_SIZE]; 193 | length = raf.read(buffer, 0, length); 194 | while (length != -1) { 195 | crc.update(buffer, 0, length); 196 | stillToRead -= length; 197 | if (stillToRead == 0) { 198 | break; 199 | } 200 | length = (int) Math.min(Constants.BUFFER_SIZE, stillToRead); 201 | length = raf.read(buffer, 0, length); 202 | } 203 | return crc.getValue(); 204 | } finally { 205 | closeQuietly(raf); 206 | } 207 | } 208 | 209 | static void closeQuietly(Closeable closeable) { 210 | if (closeable == null) { 211 | return; 212 | } 213 | 214 | try { 215 | closeable.close(); 216 | } catch (IOException e) { 217 | Monitor.get().logWarning("Failed to close resource", e); 218 | } 219 | } 220 | 221 | static void mkdirChecked(File dir) throws IOException { 222 | if (!dir.exists()) { 223 | dir.mkdirs(); 224 | } 225 | 226 | if (!dir.isDirectory()) { 227 | File parent = dir.getParentFile(); 228 | if (parent == null) { 229 | Monitor.get().logError("Failed to create dir " + dir.getPath() + ". Parent file is null."); 230 | } else { 231 | Monitor.get().logError("Failed to create dir " + dir.getPath() + 232 | ". parent file is a dir " + parent.isDirectory() + 233 | ", a file " + parent.isFile() + 234 | ", exists " + parent.exists() + 235 | ", readable " + parent.canRead() + 236 | ", writable " + parent.canWrite()); 237 | } 238 | throw new IOException("Failed to create directory " + dir.getPath()); 239 | } 240 | } 241 | 242 | 243 | static Field findFieldRecursively(Class targetClazz, String name) throws NoSuchFieldException { 244 | for (Class clazz = targetClazz; clazz != null; clazz = clazz.getSuperclass()) { 245 | try { 246 | return findField(clazz, name); 247 | } catch (NoSuchFieldException e) { 248 | // ignore and search next 249 | } 250 | } 251 | 252 | throw new NoSuchFieldException("Field " + name + " not found in " + targetClazz); 253 | } 254 | 255 | static Field findField(Class targetClazz, String name) throws NoSuchFieldException { 256 | Field field = targetClazz.getDeclaredField(name); 257 | if (!field.isAccessible()) { 258 | field.setAccessible(true); 259 | } 260 | 261 | return field; 262 | } 263 | 264 | static Method findMethodRecursively(Class targetClazz, String name, Class... parameterTypes) 265 | throws NoSuchMethodException { 266 | for (Class clazz = targetClazz; clazz != null; clazz = clazz.getSuperclass()) { 267 | try { 268 | return findMethod(clazz, name, parameterTypes); 269 | } catch (NoSuchMethodException e) { 270 | // ignore and search next 271 | } 272 | } 273 | 274 | throw new NoSuchMethodException("Method " + name + " with parameters " + 275 | Arrays.asList(parameterTypes) + " not found in " + targetClazz); 276 | } 277 | 278 | static Method findMethod(Class targetClazz, String name, Class... parameterTypes) 279 | throws NoSuchMethodException { 280 | Method method = targetClazz.getDeclaredMethod(name, parameterTypes); 281 | if (!method.isAccessible()) { 282 | method.setAccessible(true); 283 | } 284 | 285 | return method; 286 | } 287 | 288 | static Constructor findConstructor(Class targetClazz, Class... parameterTypes) 289 | throws NoSuchMethodException { 290 | Constructor constructor = targetClazz.getDeclaredConstructor(parameterTypes); 291 | if (!constructor.isAccessible()) { 292 | constructor.setAccessible(true); 293 | } 294 | 295 | return constructor; 296 | } 297 | 298 | static File ensureDirCreated(File parentDir, String dirName) throws IOException { 299 | File resultDir = new File(parentDir, dirName); 300 | mkdirChecked(resultDir); 301 | return resultDir; 302 | } 303 | 304 | static File obtainEntryFileInZip(ZipFile apkZipFile, ZipEntry fileEntry, File target) throws IOException { 305 | IOException suppressedException = null; 306 | 307 | int retriedCount = Constants.MAX_EXTRACT_ATTEMPTS; 308 | while (retriedCount > 0) { 309 | InputStream in = apkZipFile.getInputStream(fileEntry); 310 | try { 311 | return obtainEntryFileFromInputStream(in, target); 312 | } catch (IOException e) { 313 | suppressedException = e; 314 | } finally { 315 | closeQuietly(in); 316 | } 317 | 318 | retriedCount--; 319 | } 320 | 321 | throw suppressedException; 322 | } 323 | 324 | static File obtainEntryFileFromInputStream(InputStream in, File target) throws IOException { 325 | // Temp files must not start with extractedFilePrefix to get cleaned up in prepareDexDir() 326 | File tmp = File.createTempFile("tmp-", target.getName(), 327 | target.getParentFile()); 328 | Monitor.get().logInfo("Extracting " + tmp.getPath()); 329 | FileOutputStream out = new FileOutputStream(tmp); 330 | try { 331 | byte[] buffer = new byte[Constants.BUFFER_SIZE]; 332 | int length = in.read(buffer); 333 | while (length != -1) { 334 | out.write(buffer, 0, length); 335 | length = in.read(buffer); 336 | } 337 | if (!tmp.setReadOnly()) { 338 | throw new IOException("Failed to mark readonly \"" + tmp.getAbsolutePath() + 339 | "\" (tmp of \"" + target.getAbsolutePath() + "\")"); 340 | } 341 | Monitor.get().logInfo("Renaming to " + target.getPath()); 342 | if (!tmp.renameTo(target)) { 343 | throw new IOException("Failed to rename \"" + tmp.getAbsolutePath() + 344 | "\" to \"" + target.getAbsolutePath() + "\""); 345 | } 346 | return target; 347 | } finally { 348 | closeQuietly(out); 349 | tmp.delete(); // return status ignored 350 | } 351 | } 352 | 353 | static byte[] obtainEntryBytesInZip(ZipFile apkZipFile, ZipEntry dexFileEntry) throws IOException { 354 | IOException suppressedException = null; 355 | 356 | int retriedCount = Constants.MAX_EXTRACT_ATTEMPTS; 357 | while (retriedCount > 0) { 358 | InputStream in = null; 359 | try { 360 | in = apkZipFile.getInputStream(dexFileEntry); 361 | return obtainBytesFromInputStream(in); 362 | } catch (IOException e) { 363 | suppressedException = e; 364 | } finally { 365 | closeQuietly(in); 366 | } 367 | retriedCount--; 368 | } 369 | 370 | throw suppressedException; 371 | } 372 | 373 | static byte[] obtainBytesFromInputStream(InputStream in) throws IOException { 374 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 375 | try { 376 | byte[] buffer = new byte[Constants.BUFFER_SIZE]; 377 | int length; 378 | while ((length = in.read(buffer)) != -1) { 379 | byteArrayOutputStream.write(buffer, 0, length); 380 | } 381 | return byteArrayOutputStream.toByteArray(); 382 | } finally { 383 | closeQuietly(byteArrayOutputStream); 384 | } 385 | } 386 | 387 | static void obtainZipForEntryFileInZip(ZipFile apkZipFile, ZipEntry dexFileEntry, File validZipFile) throws IOException { 388 | IOException suppressedException = null; 389 | 390 | int retriedCount = Constants.MAX_EXTRACT_ATTEMPTS; 391 | while (retriedCount > 0) { 392 | InputStream in = apkZipFile.getInputStream(dexFileEntry); 393 | 394 | File tmp = File.createTempFile("tmp-", Constants.ZIP_SUFFIX, 395 | validZipFile.getParentFile()); 396 | 397 | try { 398 | ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tmp))); 399 | try { 400 | ZipEntry classesDex = new ZipEntry("classes.dex"); 401 | // keep zip entry time since it is the criteria used by Dalvik 402 | classesDex.setTime(dexFileEntry.getTime()); 403 | out.putNextEntry(classesDex); 404 | 405 | byte[] buffer = new byte[Constants.BUFFER_SIZE]; 406 | int length = in.read(buffer); 407 | while (length != -1) { 408 | out.write(buffer, 0, length); 409 | length = in.read(buffer); 410 | } 411 | out.closeEntry(); 412 | } finally { 413 | out.close(); 414 | } 415 | if (!tmp.setReadOnly()) { 416 | throw new IOException("Failed to mark readonly \"" + tmp.getAbsolutePath() + 417 | "\" (tmp of \"" + validZipFile.getAbsolutePath() + "\")"); 418 | } 419 | Monitor.get().logInfo("Renaming to " + validZipFile.getPath()); 420 | if (!tmp.renameTo(validZipFile)) { 421 | throw new IOException("Failed to rename \"" + tmp.getAbsolutePath() + 422 | "\" to \"" + validZipFile.getAbsolutePath() + "\""); 423 | } 424 | return; 425 | } catch (IOException e) { 426 | suppressedException = e; 427 | }finally { 428 | Utility.closeQuietly(in); 429 | tmp.delete(); // return status ignored 430 | } 431 | 432 | retriedCount--; 433 | } 434 | 435 | if (suppressedException != null) { 436 | throw suppressedException; 437 | } 438 | } 439 | 440 | static byte[] obtainBytesInFile(File file) { 441 | RandomAccessFile randomAccessFile = null; 442 | try { 443 | randomAccessFile = new RandomAccessFile(file.getPath(), "r"); 444 | byte[] bytes = new byte[(int)randomAccessFile.length()]; 445 | randomAccessFile.readFully(bytes); 446 | return bytes; 447 | } catch (IOException e) { 448 | Monitor.get().logWarning("Fail to get bytes of file", e); 449 | return null; 450 | } finally { 451 | closeQuietly(randomAccessFile); 452 | } 453 | } 454 | 455 | static boolean storeBytesToFile(byte[] bytes, File file) { 456 | FileOutputStream fileOutputStream = null; 457 | try { 458 | fileOutputStream = new FileOutputStream(file); 459 | fileOutputStream.write(bytes); 460 | return true; 461 | } catch (IOException e) { 462 | Monitor.get().logError("fail to store bytes to file", e); 463 | return false; 464 | } finally { 465 | closeQuietly(fileOutputStream); 466 | } 467 | } 468 | 469 | static boolean isOptimizeProcess(String processName) { 470 | return processName != null && processName.endsWith(":boost_multidex"); 471 | } 472 | 473 | // static boolean isMainProcess(Context context) { 474 | // String processName = getCurProcessName(context); 475 | // if (processName != null && processName.contains(":")) { 476 | // return false; 477 | // } 478 | // return (processName != null && processName.equals(context.getPackageName())); 479 | // } 480 | 481 | static String getCurProcessName(Context context) { 482 | try{ 483 | int pid = android.os.Process.myPid(); 484 | ActivityManager mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 485 | for (ActivityManager.RunningAppProcessInfo appProcess : mActivityManager.getRunningAppProcesses()) { 486 | if (appProcess.pid == pid) { 487 | return appProcess.processName; 488 | } 489 | } 490 | } catch(Exception e) { 491 | e.printStackTrace(); 492 | } 493 | 494 | return null; 495 | } 496 | } 497 | -------------------------------------------------------------------------------- /boost_multidex/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | boost_multidex 3 | 4 | -------------------------------------------------------------------------------- /boost_multidex/src/test/java/com/bytedance/boost_multidex/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.bytedance.boost_multidex; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | mavenCentral() 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.5.0' 12 | //classpath 'com.android.tools.build:gradle:3.2.1' 13 | classpath 'com.novoda:bintray-release:0.9.2' 14 | // NOTE: Do not place your application dependencies here; they belong 15 | // in the individual module build.gradle files 16 | } 17 | } 18 | 19 | allprojects { 20 | repositories { 21 | google() 22 | jcenter() 23 | mavenCentral() 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /docs/bmd-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedance/BoostMultiDex/29d4110f28a4bc3571ad336fa2c27c941d185143/docs/bmd-logo.png -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | 15 | 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytedance/BoostMultiDex/29d4110f28a4bc3571ad336fa2c27c941d185143/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Jun 01 11:05:38 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':boost_multidex' 2 | --------------------------------------------------------------------------------