├── .gitignore
├── .idea
├── Zucker.iml
├── encodings.xml
├── misc.xml
├── modules.xml
└── vcs.xml
├── CONTRIBUTING.rst
├── ChangeLog.txt
├── LICENSE
├── README.md
├── README_EN.md
├── Sample
├── .gitignore
├── .idea
│ ├── codeStyles
│ │ └── Project.xml
│ ├── gradle.xml
│ ├── inspectionProfiles
│ │ └── Project_Default.xml
│ └── runConfigurations.xml
├── README.md
├── app
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── wuba
│ │ │ └── zucker
│ │ │ └── sample
│ │ │ └── ExampleInstrumentedTest.java
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── wuba
│ │ │ │ └── zucker
│ │ │ │ └── sample
│ │ │ │ └── MainActivity.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
│ │ └── wuba
│ │ └── zucker
│ │ └── sample
│ │ └── ExampleUnitTest.java
├── app2
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── wuba
│ │ │ └── zucker
│ │ │ └── app2
│ │ │ └── ExampleInstrumentedTest.java
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── wuba
│ │ │ │ └── zucker
│ │ │ │ └── app2
│ │ │ │ └── MainActivity.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
│ │ └── wuba
│ │ └── zucker
│ │ └── app2
│ │ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── imgs
│ ├── s1.png
│ ├── s2.png
│ ├── s3.png
│ └── s4.png
├── module1
│ ├── .gitignore
│ ├── build.gradle
│ ├── consumer-rules.pro
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── wuba
│ │ │ └── zucker
│ │ │ └── module1
│ │ │ └── ExampleInstrumentedTest.java
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ └── res
│ │ │ └── values
│ │ │ └── strings.xml
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── wuba
│ │ └── zucker
│ │ └── module1
│ │ └── ExampleUnitTest.java
├── module2
│ ├── .gitignore
│ ├── build.gradle
│ ├── consumer-rules.pro
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── wuba
│ │ │ └── zucker
│ │ │ └── module2
│ │ │ └── ExampleInstrumentedTest.java
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ └── res
│ │ │ └── values
│ │ │ └── strings.xml
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── wuba
│ │ └── zucker
│ │ └── module2
│ │ └── ExampleUnitTest.java
└── settings.gradle
├── docs
├── Python使用规范.md
├── imgs
│ ├── analysis.png
│ ├── app-size.png
│ ├── clone.png
│ ├── node-flow.png
│ ├── node-tree.png
│ ├── node.png
│ ├── reflect.png
│ ├── zucker-arc.png
│ └── zucker_contact.png
└── 介绍文档.md
├── imgs
├── sample_aar.png
├── sample_aar_size.png
└── sample_clone.png
└── zucker.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | encodings.txt
3 | misc.xml
4 | modules.xml
5 | vcs.xml
6 | Zucker.iml
7 | /.idea/
8 |
--------------------------------------------------------------------------------
/.idea/Zucker.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/CONTRIBUTING.rst:
--------------------------------------------------------------------------------
1 |
2 | 【Zucker】
3 | ===========================
4 |
5 |
6 | How to contribute to Zucker
7 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
8 |
9 | Thank you for considering contributing to Zucker !
10 |
11 |
12 | Start coding
13 | ^^^^^^^^^^^^^
14 |
15 | * Create a branch to identify the issue you would like to work on. If you're submitting a bug or documentation fix, branch off of the latest ".x" branch
16 |
17 | ::
18 |
19 | $ git checkout -b your-branch-name origin/remote-branch-name
20 |
21 | * Add your code and push it to the remote branch **'origin/zucker-dev'**
22 |
23 | ::
24 |
25 | $ git push origin zucker-dev
26 |
27 | * Comment your change and issue
28 |
29 | A Simple Example
30 | ^^^^^^^^^^^^^^^^
31 | The python script usage as follows.
32 | ::
33 |
34 | $ python3 xxx/xxx/zucker.py xxx/xxx/targetProject
35 |
36 |
--------------------------------------------------------------------------------
/ChangeLog.txt:
--------------------------------------------------------------------------------
1 | # zucker.py changelog
2 |
3 | ## 2019-12-31 0.1
4 |
5 | * First public version.
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Copyright (C) 2005-present, 58.com. All rights reserved.
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License").
5 | You may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | License for the specific language governing permissions and limitations
14 | under the License.
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Android APP模块化大小自动分析统计工具-Zucker
2 |
3 | 基于APP模块的,一个简单无侵害计算AAR独有大小的工具(An easier way to automatically calculate the size of AAR in apk based on APP module)
4 |
5 | [介绍文档](/docs/介绍文档.md)
6 |
7 | [英文文档](README_EN.md)
8 |
9 | > AAR独立大小和被引入到工程后打包后占用的大小是不一样的,这个有经验的开发者都应该了解。AAR独立大小一目了然,但是怎么计算AAR在APK中的独立占有大小(独有大小)呢?Zucker就此开源给出了一份答案。
10 |
11 | ## 依赖环境
12 | - Python 3.0+
13 | - Android编译环境
14 |
15 | ## 开始使用
16 | ### Demo工程测试
17 | 1. 克隆本工程
18 | 2. 终端cd到本工程下的src目录
19 | 3. 执行python脚本:python3 zucker.py Sample
20 | 4. 根据终端列出的AAR列表,选择一个目标AAR输入得到结果
21 |
22 | ### 项目工程测试
23 | 1. 将zucker.py脚本放置在需要测试工程的同级目录
24 | 2. 同[Demo工程测试]步骤2
25 | 3. 同[Demo工程测试]步骤3:python3 zucker.py [targetProjectName](Android工程名)
26 | 4. 同[Demo工程测试]步骤4
27 |
28 | ### 注意事项
29 | 1. 确保目标工程在不依赖Zucker脚本的前提下可以正常编译
30 | 2. 编译时使用`gradlew`命令,以保证采用了项目的`gradle`配置
31 | 3. 首次运行时间较长,请耐心等待...
32 |
33 | ```
34 | ./gradlew build
35 |
36 | ```
37 | ```
38 | -> 〜python3 /Users/iann/EnterpriseProject/Zucker/src/zucker.py /Users/iann/EnterpriseProject/Zucker/Sample
39 | cloneBaseProject DONE
40 | findBaseAppDirs DONE
41 | clearBaseFlavors DONE
42 | insertBaseScript DONE
43 |
44 | > Configure project :app
45 | WARNING: API 'variantOutput.getPackageApplication()' is obsolete and has been re placed with 'variant.getPackageApplicationProvider()'.
46 | It will be removed at the end of 2019.
47 | For more information, see https://d.android.com/r/tools/task-configuration-avoid ance.
48 | To determine what is calling variantOutput.getPackageApplication(), use -Pandroi d.debug.obsoleteApi=true on the command line to display more information.
49 |
50 | BUILD SUCCESSFUL in 2s
51 | 5 actionable tasks: 4 executed, 1 up-to-date
52 | ```
53 |
54 | 脚本会自动执行,获取项目中的依赖关系并输出一级节点,可以选择目标节点进行AAR大小计算。
55 |
56 | ```
57 | ['app', 'app2']
58 | com.github.moduth:blockcanary-android:1.2.1
59 | com.squareup.okhttp3:okhttp:4.2.1
60 | com.airbnb.android:lottie:2.5.6
61 | 输入AAR名称及版本,格式xxx.xxx:xxx:xxx:com.github.moduth:blockcanary-android:1.2.1
62 | 输出AAR------
63 | ['com.github.moduth:blockcanary-android:1.2.1', 'com.github.moduth:blockcanary-core:1.2.1']
64 | /Users/iann/.gradle/caches/modules-2/files-2.1/com.github.moduth/blockcanary-android/1.2.l/78f65b7622338d512e79a26fe76e7bb9f7614190/blockcanary-android-l.2.1.aar
65 | /Users/iann/.gradle/caches/modules-2/files-2.1/com.github.moduth/blockcanary-android/1.2.l/78f65b7622338d512e79a26fe76e7bb9f7614190/blockcanary-android-l.2.1.aar
66 | 替换 build.gradle
67 | ```
68 |
69 | 最后经过打包后,AAR大小就会显示在终端上。
70 |
71 | ```
72 | Deprecated Gradle features were used in this build, making it incompatible with Gradle 6.0.
73 | Use f--warning-mode all1 to show the individual deprecation warnings.
74 | See https://docs.gradle.org/5.4.l/userguide/command_line_interface.html#sec:comm ancLline一warnings
75 |
76 | BUILD SUCCESSFUL in 9s
77 | 137 actionable tasks: 133 executed, 4 up-to-date
78 | compile DONE
79 | basePackSize: 2908419
80 | aarPackSize: 2873610
81 | aarSize: 34809
82 | ```
83 |
84 | > 建议先在本项目的`sample工程`进行测试,具体流程见工程[README](Sample/README.md)
85 |
86 | ## 联系我们
87 |
88 | - 想了解更多开源项目信息?
89 | - 与项目成员零距离交流?
90 | - 扫码加小秘书微信
91 | - 一切应有尽有
92 | 
93 | - 微信号 : jishu-58
94 | - 添加小秘书微信后由小秘书拉您进项目交流群
95 |
96 | ## 常见问题处理
97 | - 暂不支持工程依赖类型的测量 `implementation project(':xxx')`
98 | - 在build过程中发生报错:Could not get resource 'https://jcenter.bintray.com/com/google/guava/guava/27.0.1-jre/guava-27.0.1-jre.jar'
99 |
100 | 解决办法:使用阿里云镜像,重新进行下载;修改build.gradle中的buildscript和allprojects的jcenter,添加url 'https://maven.aliyun.com/repository/jcenter'即可。
101 |
102 | ## 贡献代码
103 | 详见 [CONTRIBUTING](CONTRIBUTING.rst)
104 |
--------------------------------------------------------------------------------
/README_EN.md:
--------------------------------------------------------------------------------
1 | # Zucker: An easier way to automatically calculate the size of AAR in apk.
2 |
3 |
4 | [中文文档](README_CN.md)
5 | [Python使用规范](./docs/Python使用规范.md)
6 |
7 | > As we all know,that when the project was introduced with AAR, it was not accurate to calculate only the aAR file size. Because aar is unzipped during packaging, the resource files are merged and then compressed, the actual apk footprint may be smaller than the current aar file size. Zucker was born to calculate the size of the target AAR at apk.
8 |
9 |
10 | ## Requirements
11 |
12 | - Python 3.7+
13 | - Android Dev
14 | - Gradle 2.0+
15 | - *unx/Windows
16 |
17 | ## Get Start
18 |
19 | The first time you use Zucker for AAR size calculations, it is recommended to compile once on the command line.
20 |
21 | Compile-time recommended `gradlew` command to ensure the use of the project's 'gradle' configuration
22 | Perform the following command:
23 | ```
24 | ./gradlew build
25 | ```
26 | Execute the following command in the terminal:
27 | ```
28 | python3 xxx/zucker.py xxx/targetProjectName(Android project)
29 | ```
30 | 
31 |
32 | The script is executed automatically, obtaining the dependencies in the project and outputing a level-level node, which can select the target node for AAR size calculation.
33 |
34 | 
35 |
36 | Finally, after packaging, the AAR size is displayed on the terminal.
37 |
38 | 
39 |
40 | > It is recommended to test the project's 'sample project' first, as detailed in the project [README](Sample/README.md)
41 |
42 | ## Q&A
43 | - a dependency on local library module (implementation project(':xxx')) is not support.
44 |
45 | ## Contribute
46 |
47 | See [CONTRIBUTING](CONTRIBUTING.rst)
48 |
49 |
50 | ## Licence
51 |
52 |
53 | Copyright (C) 2005-present, 58.com. All rights reserved.
54 |
55 | Licensed under the Apache License, Version 2.0 (the "License"); you may
56 | not use this file except in compliance with the License. You may obtain
57 | a copy of the License at
58 | http://www.apache.org/licenses/LICENSE-2.0
59 |
60 | Unless required by applicable law or agreed to in writing, software
61 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
62 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
63 | License for the specific language governing permissions and limitations
64 | under the License.
--------------------------------------------------------------------------------
/Sample/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 |
--------------------------------------------------------------------------------
/Sample/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | xmlns:android
14 |
15 | ^$
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | xmlns:.*
25 |
26 | ^$
27 |
28 |
29 | BY_NAME
30 |
31 |
32 |
33 |
34 |
35 |
36 | .*:id
37 |
38 | http://schemas.android.com/apk/res/android
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | .*:name
48 |
49 | http://schemas.android.com/apk/res/android
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | name
59 |
60 | ^$
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | style
70 |
71 | ^$
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | .*
81 |
82 | ^$
83 |
84 |
85 | BY_NAME
86 |
87 |
88 |
89 |
90 |
91 |
92 | .*
93 |
94 | http://schemas.android.com/apk/res/android
95 |
96 |
97 | ANDROID_ATTRIBUTE_ORDER
98 |
99 |
100 |
101 |
102 |
103 |
104 | .*
105 |
106 | .*
107 |
108 |
109 | BY_NAME
110 |
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/Sample/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
15 |
16 |
--------------------------------------------------------------------------------
/Sample/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/Sample/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Sample/README.md:
--------------------------------------------------------------------------------
1 | # sample
2 |
3 | ## 步骤
4 | 1. 将sample工程和`zucker.py`脚本放置到空文件夹中
5 | 2. 终端cd到该目录下执行`python zucker.py Sample`
6 | 3. 看到输出一级节点,选择一个非support包和jetbean相关的AAR名称。输入规则:`xxx.xxx:xxx:xxx`
7 | 4. 等待计算完成即可
8 |
9 |
10 | ### 执行脚本
11 | 
12 |
13 | ### 选择要计算的AAR
14 | 
15 |
16 | ### 计算结果
17 | 
--------------------------------------------------------------------------------
/Sample/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/Sample/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 29
5 | buildToolsVersion "29.0.2"
6 | defaultConfig {
7 | applicationId "com.wuba.zucker.sample"
8 | minSdkVersion 21
9 | targetSdkVersion 29
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
13 | }
14 | compileOptions {
15 | sourceCompatibility JavaVersion.VERSION_1_8
16 | targetCompatibility JavaVersion.VERSION_1_8
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | }
26 |
27 | dependencies {
28 | implementation fileTree(dir: 'libs', include: ['*.jar'])
29 | implementation 'androidx.appcompat:appcompat:1.0.2'
30 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
31 | testImplementation 'junit:junit:4.12'
32 | androidTestImplementation 'androidx.test:runner:1.1.1'
33 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
34 | implementation 'com.github.moduth:blockcanary-android:1.2.1'
35 | implementation("com.squareup.okhttp3:okhttp:4.2.1")
36 | implementation 'com.airbnb.android:lottie:2.5.6'
37 | implementation project(path: ':module1')
38 | implementation project(path: ':module2')
39 | }
40 |
--------------------------------------------------------------------------------
/Sample/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 |
--------------------------------------------------------------------------------
/Sample/app/src/androidTest/java/com/wuba/zucker/sample/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.wuba.zucker.sample;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 |
25 | assertEquals("com.wuba.zucker.sample", appContext.getPackageName());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sample/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Sample/app/src/main/java/com/wuba/zucker/sample/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.wuba.zucker.sample;
2 |
3 | import androidx.appcompat.app.AppCompatActivity;
4 |
5 | import android.os.Bundle;
6 |
7 | public class MainActivity extends AppCompatActivity {
8 |
9 | @Override
10 | protected void onCreate(Bundle savedInstanceState) {
11 | super.onCreate(savedInstanceState);
12 | setContentView(R.layout.activity_main);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Sample/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/Sample/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 |
--------------------------------------------------------------------------------
/Sample/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
--------------------------------------------------------------------------------
/Sample/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Sample/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Sample/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Sample/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Sample/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Sample/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Sample/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Sample/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Sample/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Sample/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Sample/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Sample/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Sample/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/Sample/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Sample
3 |
4 |
--------------------------------------------------------------------------------
/Sample/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Sample/app/src/test/java/com/wuba/zucker/sample/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.wuba.zucker.sample;
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 | }
--------------------------------------------------------------------------------
/Sample/app2/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/Sample/app2/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 29
5 | buildToolsVersion "29.0.2"
6 |
7 |
8 | defaultConfig {
9 | applicationId "com.wuba.zucker.app2"
10 | minSdkVersion 21
11 | targetSdkVersion 29
12 | versionCode 1
13 | versionName "1.0"
14 |
15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 |
25 | }
26 |
27 | dependencies {
28 | implementation fileTree(dir: 'libs', include: ['*.jar'])
29 |
30 | implementation 'androidx.appcompat:appcompat:1.0.2'
31 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
32 | testImplementation 'junit:junit:4.12'
33 | androidTestImplementation 'androidx.test:runner:1.1.1'
34 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
35 | implementation project(path: ':module2')
36 | implementation project(path: ':module1')
37 | }
38 |
--------------------------------------------------------------------------------
/Sample/app2/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 |
--------------------------------------------------------------------------------
/Sample/app2/src/androidTest/java/com/wuba/zucker/app2/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.wuba.zucker.app2;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 |
25 | assertEquals("com.wuba.zucker.app2", appContext.getPackageName());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sample/app2/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Sample/app2/src/main/java/com/wuba/zucker/app2/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.wuba.zucker.app2;
2 |
3 | import androidx.appcompat.app.AppCompatActivity;
4 |
5 | import android.os.Bundle;
6 |
7 | public class MainActivity extends AppCompatActivity {
8 |
9 | @Override
10 | protected void onCreate(Bundle savedInstanceState) {
11 | super.onCreate(savedInstanceState);
12 | setContentView(R.layout.activity_main);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Sample/app2/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/Sample/app2/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 |
--------------------------------------------------------------------------------
/Sample/app2/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
--------------------------------------------------------------------------------
/Sample/app2/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Sample/app2/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Sample/app2/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/app2/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Sample/app2/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/app2/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Sample/app2/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/app2/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Sample/app2/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/app2/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Sample/app2/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/app2/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Sample/app2/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/app2/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Sample/app2/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/app2/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Sample/app2/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/app2/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Sample/app2/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/app2/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Sample/app2/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/app2/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Sample/app2/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/Sample/app2/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | app2
3 |
4 |
--------------------------------------------------------------------------------
/Sample/app2/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Sample/app2/src/test/java/com/wuba/zucker/app2/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.wuba.zucker.app2;
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 | }
--------------------------------------------------------------------------------
/Sample/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | jcenter(){
7 | url 'https://maven.aliyun.com/repository/jcenter'
8 | }
9 |
10 | }
11 | dependencies {
12 | classpath 'com.android.tools.build:gradle:3.5.0'
13 |
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 | url 'https://maven.aliyun.com/repository/jcenter'
24 | }
25 | }
26 | }
27 |
28 | task clean(type: Delete) {
29 | delete rootProject.buildDir
30 | }
31 |
--------------------------------------------------------------------------------
/Sample/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 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 |
21 |
--------------------------------------------------------------------------------
/Sample/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/Sample/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Jan 02 11:32:46 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 |
--------------------------------------------------------------------------------
/Sample/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 |
--------------------------------------------------------------------------------
/Sample/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 |
--------------------------------------------------------------------------------
/Sample/imgs/s1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/imgs/s1.png
--------------------------------------------------------------------------------
/Sample/imgs/s2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/imgs/s2.png
--------------------------------------------------------------------------------
/Sample/imgs/s3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/imgs/s3.png
--------------------------------------------------------------------------------
/Sample/imgs/s4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/imgs/s4.png
--------------------------------------------------------------------------------
/Sample/module1/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/Sample/module1/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 29
5 | buildToolsVersion "29.0.2"
6 |
7 |
8 | defaultConfig {
9 | minSdkVersion 21
10 | targetSdkVersion 29
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15 | consumerProguardFiles 'consumer-rules.pro'
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 |
25 | }
26 |
27 | dependencies {
28 | implementation fileTree(dir: 'libs', include: ['*.jar'])
29 |
30 | implementation 'androidx.appcompat:appcompat:1.0.2'
31 | testImplementation 'junit:junit:4.12'
32 | androidTestImplementation 'androidx.test:runner:1.1.1'
33 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
34 | implementation("com.squareup.okhttp3:okhttp:4.2.1")
35 | }
36 |
--------------------------------------------------------------------------------
/Sample/module1/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/module1/consumer-rules.pro
--------------------------------------------------------------------------------
/Sample/module1/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 |
--------------------------------------------------------------------------------
/Sample/module1/src/androidTest/java/com/wuba/zucker/module1/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.wuba.zucker.module1;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 |
25 | assertEquals("com.wuba.zucker.module1.test", appContext.getPackageName());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sample/module1/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Sample/module1/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | module1
3 |
4 |
--------------------------------------------------------------------------------
/Sample/module1/src/test/java/com/wuba/zucker/module1/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.wuba.zucker.module1;
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 | }
--------------------------------------------------------------------------------
/Sample/module2/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/Sample/module2/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 29
5 | buildToolsVersion "29.0.2"
6 |
7 |
8 | defaultConfig {
9 | minSdkVersion 21
10 | targetSdkVersion 29
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15 | consumerProguardFiles 'consumer-rules.pro'
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 |
25 | }
26 |
27 | dependencies {
28 | implementation fileTree(dir: 'libs', include: ['*.jar'])
29 |
30 | implementation 'androidx.appcompat:appcompat:1.0.2'
31 | testImplementation 'junit:junit:4.12'
32 | androidTestImplementation 'androidx.test:runner:1.1.1'
33 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
34 | }
35 |
--------------------------------------------------------------------------------
/Sample/module2/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/Sample/module2/consumer-rules.pro
--------------------------------------------------------------------------------
/Sample/module2/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 |
--------------------------------------------------------------------------------
/Sample/module2/src/androidTest/java/com/wuba/zucker/module2/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.wuba.zucker.module2;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 |
25 | assertEquals("com.wuba.zucker.module2.test", appContext.getPackageName());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sample/module2/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/Sample/module2/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | module2
3 |
4 |
--------------------------------------------------------------------------------
/Sample/module2/src/test/java/com/wuba/zucker/module2/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.wuba.zucker.module2;
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 | }
--------------------------------------------------------------------------------
/Sample/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':app2', ':module1', ':module2'
2 | rootProject.name='Sample'
3 |
--------------------------------------------------------------------------------
/docs/Python使用规范.md:
--------------------------------------------------------------------------------
1 | #Python使用规范
2 |
3 | 为了更好地地迭代扎克,在合并代码前,应该进行提交代码的检查校验,其规则如下:
4 |
5 | ##编码
6 | - 统一使用 UTF-8 编码;
7 | - 导入的包在代码的最前面;
8 | - 每行代码不宜过长,不要超过120字符;
9 | - 使用面向对象思想编程,按照功能划分类,按照职责划分方法;
10 |
11 | ##关于注释
12 | - 单行文档注释可以使用#标注;
13 | - 多行可以使用三个双引号"""...""";
14 | - 关键方法要使用注释,方便维护;
15 |
16 | ##类和方法
17 | - 对于一个类,里面有多个方法可以使用_init_()方法来初始化变量,以便该类中各个方法使用;假如接入该类只有一个方法,那么可以忽略_init()方法,直接自定义方法;
18 | - 方法之间可以不空行,类与类之间要空行;
19 | - 类的名字使用驼峰命名法,首字母大写;
20 | - 所有方法,全部使用小写;
21 | - 私有方法,使用_下划线开头;
22 | - 常量使用大写;
23 | - 每个语句执行完可以不加";",但是要注意使用换行
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/docs/imgs/analysis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/docs/imgs/analysis.png
--------------------------------------------------------------------------------
/docs/imgs/app-size.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/docs/imgs/app-size.png
--------------------------------------------------------------------------------
/docs/imgs/clone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/docs/imgs/clone.png
--------------------------------------------------------------------------------
/docs/imgs/node-flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/docs/imgs/node-flow.png
--------------------------------------------------------------------------------
/docs/imgs/node-tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/docs/imgs/node-tree.png
--------------------------------------------------------------------------------
/docs/imgs/node.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/docs/imgs/node.png
--------------------------------------------------------------------------------
/docs/imgs/reflect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/docs/imgs/reflect.png
--------------------------------------------------------------------------------
/docs/imgs/zucker-arc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/docs/imgs/zucker-arc.png
--------------------------------------------------------------------------------
/docs/imgs/zucker_contact.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/docs/imgs/zucker_contact.png
--------------------------------------------------------------------------------
/docs/介绍文档.md:
--------------------------------------------------------------------------------
1 | ## Zucker介绍
2 |
3 | ### 导语
4 | 本文介绍了在实际场景中,随着App模块功能增多包体积不断增大,如何通过脚本自动化分析模块体积大小,无侵入且快速差值计算,具有一定实践意义,希望本文的介绍对大家有所启发。
5 |
6 | ### 背景
7 | 随着业务功能迭代、包体积大小呈曲线快速上升,如何做到业务功能增加映射为包体积大小增加。在手动统计将近20个版本(如下图一:各版本大小比对),从统计各版本APK包体积大小到统计当前版本APK中某需求增加多大体积,再到自动化统计APK中各模块所占大小,经历了从手动到自动化、脚本化的完整过程。
8 |
9 | 
10 | 图一:各版本大小比对
11 |
12 | ### Zucker
13 | 正是基于这样的背景,我们隆重推出了开源工程Zucker,一个自动化无侵入计算模块化大小的工具,帮助我们深入了解每个模块在工程打包生成安装包APK中的体积大小占比,这样就可对其进行迭代监控及优化。例如,App包大小不可能是完全的增量,从功能层面监控其大小方便日后对其功能进行删减;此外,一个AAR被引入后导致APK体积增大多少,针对增大的数量级别我们可以选择是否接入或者考虑其他的实现方式。
14 |
15 | #### 核心功能
16 | Zucker模块化统计实现是通过Python脚本利用Gradle缓存将目标AAR替换为模拟AAR,结合Gradle打包命令,分别打出基础包和模拟AAR后的安装包,最后通过差值计算两个APK体积大小得出AAR大小。如下图二:Zucker统计流程:
17 |
18 | 
19 |
20 | 图二 - Zucker统计流程
21 |
22 | Zucker特点:
23 | - 无侵入:无需添加或修改项目代码,只需将Zucker放置于待统计工程的目录下,运行Zucker脚本。
24 | - 自动化:自动化寻路App入口、自动化分析依赖关系以及完成打包流程、自动化输出结果。
25 | - 操作简单:脚本运行后输入统计列表中目标AAR名称,待打包完成即可得出结果。
26 |
27 | ### 具体实现
28 | #### 自动化打包统计
29 |
30 | 对于一个给定的目标工程首先避免产生侵害和改动,需要对源码工程做拷贝处理。则第一步把源码工程克隆到一个输出目录里。然后编译克隆后的工程,产出一个基础的APK并计算基础包大小。接着,脚本会根据用户指定要统计的AAR名称及版本信息,分析工程中AAR的依赖关系,自动产生一个空AAR来模拟目标AAR,并保持原工程中依赖关系。此时对模拟并替换了目标AAR后的克隆工程进行打包,计算出模拟AAR的APK大小。最后,通过打出的两个包的大小差值,可以得到目标AAR的大小。
31 |
32 | #### 克隆工程
33 |
34 | 为了实现无侵害计算,需要修改工程的gradle脚本来实现自动模拟AAR以及计算包大小,将原有工程进行克隆再分析模拟AAR及打包统计。
35 | Zucker的Python脚本文件需要放置目标工程的同级目录,运行脚本在同级目录产生一个output目录,统一存放拷贝的工程文件。
36 |
37 | 
38 |
39 | 图三 - 工程及AAR克隆
40 |
41 | #### 编译过程
42 |
43 | 整个编译过程包括以下步骤:初始化工程→自动寻路查找工程入口→清除flavors→插入计算脚本→执行打包命令。通过此流程,可完整实现自动化寻路APP入口并修改配置来实现基础包打包和计算包体积大小任务。
44 |
45 | 
46 |
47 | 图四 - 编译打包流程
48 |
49 | #### 依赖分析
50 |
51 | 在上文中提到获取用户输入的AAR,分析工程中各模块依赖关系。获取AAR的相互依赖关系,对于计算AAR大小起到关键作用。首先明确两个依赖关系概念。
52 | • 独有依赖:对于某一AAR,内部引用库仅被当前AAR所依赖,再无其他依赖关系,则这个库被称为AAR的独有依赖。
53 | • 公有依赖库:不同于独有依赖,一个库可能被多个AAR所引用,则这个库被称为公有依赖。
54 | 回到上面说的,若要统计AAR在App包大小中的占比,除了它自己还不够,还要分析该AAR的独有依赖才能正确计算引入该AAR后的大小。
55 |
56 | #### 依赖树转化
57 |
58 | 在项目的Gradle文件中可以找到该项目的依赖引用,项目执行`./gradlew dependencies`命令后会获取当前项目的依赖树结构,查看gradle命令输出的依赖树可知,依赖树有且比较严格的格式标准,特殊字符+,\,|表示依赖关系起始,空格缩进表示上下级引用关系。部分依赖关系如下:
59 |
60 | ``` xml
61 | +--- com.android.support:design:28.0.0
62 | | +--- com.android.support:support-annotations:28.0.0
63 | | +--- com.android.support:support-compat:28.0.0 (*)
64 | | +--- com.android.support:support-core-ui:28.0.0 (*)
65 | | +--- com.android.support:support-core-utils:28.0.0 (*)
66 | | +--- com.android.support:support-fragment:28.0.0 (*)
67 | | +--- com.android.support:transition:28.0.0
68 | | | +--- com.android.support:support-annotations:28.0.0
69 | | | \--- com.android.support:support-compat:28.0.0 (*)
70 | | +--- com.android.support:appcompat-v7:28.0.0 (*)
71 | | +--- com.android.support:cardview-v7:28.0.0
72 | | | \--- com.android.support:support-annotations:28.0.0
73 | | \--- com.android.support:recyclerview-v7:28.0.0
74 | | +--- com.android.support:support-annotations:28.0.0
75 | | +--- com.android.support:support-compat:28.0.0 (*)
76 | | \--- com.android.support:support-core-ui:28.0.0 (*)
77 | ```
78 |
79 | 我们可以将项目中的依赖库简化成A,B,C来表示,如图所示,A,A1,A2表示项目直接引用的AAR依赖。B,C是A的子依赖,同时C,D是A1的子依赖。根据上文所述,A的内部依赖B没有其他的被依赖关系,因此称B为A的独有依赖。另外的由于C同时被A和A1依赖,因此称C为公有依赖。
80 |
81 | 
82 |
83 | 图五 - 节点依赖关系图
84 |
85 | 理解依赖树结构特点,可将依赖树形结构转化成列表来显示。由上述分析可知一个依赖库可能被多个库依赖,产生依赖关系。同时依赖关系是单向有序的,箭头指向表示A依赖B:A是B的父依赖,B是A的子依赖。现挑出A,A1,C作为例子分析,将A,A1作为一个父节点,节点内部同时维护两个列表,父节点列表(parents)和子节点列表(children)。依次就可以将依赖树中所有的依赖关系放置在节点类型的数据结构中。另外root节点是工程总节点,它是A,A1和A2的父节点。通过此步骤,得到依赖列表,记录工程中所有依赖节点:
86 |
87 | 
88 |
89 | 图六 - 节点依赖列表
90 |
91 | #### 独有依赖分析
92 |
93 | 统计目标AAR大小时,不仅要统计目标AAR还要包括它的独有依赖。通过上文步骤获取到了项目所有AAR列表,在输入目标依赖名称后,我们在列表中遍历找到该目标,检查其子依赖然后获取最后的独有依赖,检查流程如下:
94 | 1. 罗列目标依赖下的所有子依赖,并且遍历子依赖的子依赖,将它们记录到新列表中;
95 | 2. 采用深度便算法来逐一去判断新列表中的依赖,判断其父依赖是否仅在该列表中,如果父依赖全部都在该列表中则保留,否则从列表中可删除该依赖;
96 | 3. 经过步骤2后的筛选,剩下的节点则为目标依赖的独有依赖。
97 |
98 | 下面我们以上文图中的节点A1为例,将A1节点所有的子节点记录到列表。首先独有依赖包括它自身,故A1保留。C节点的父父节点有A和A1,A不在当前列表中,因此C不是独有依赖,将C移除。D的父节点仅有A1且在列表中,因此D是独有依赖。以此类推,在判断I节点时,由于C节点已被移除不在列表中,因此I也不是A1的独有依赖。如下图九:节点独有依赖寻找流程:
99 |
100 | 
101 |
102 | 图七 - 节点独有依赖寻找流程
103 |
104 | #### 模拟目标AAR
105 |
106 | 在项目打包生成Apk过程中,会利用Gradle缓存特性,工程编译前,获取用户输入的目标AAR,脚本通过目标AAR名称在缓存目录下自动寻找。然后将本地的目标AAR文件进行模拟处理,打包时将该模拟后的AAR打入APK中。如下图八 :模拟替换目标AAR流程:
107 |
108 | 
109 |
110 | 图八 - 模拟替换目标AAR
111 |
112 | 我们知道AAR是二进制归档文件,也是压缩文件,只不过它是AAPT打包命令中的一个结果,通常会压缩:资源文件、类文件、系统文件等。所以找到该AAR后,我们进行“解剖”,步骤如下:
113 | 1. 将目标AAR在当前目录下备份一份;
114 | 2. 将AAR文件重命名变成.zip文件并进行解压缩;
115 | 3. 遍历解压缩文件目录,当目标是文件时,判断其文件类型是否为.xml或.9.png,是则跳过;否则,将其文件大小置为0KB;
116 | 4. 解压缩文件完全按照3步骤处理完成后,将其重新压缩为一个模拟的AAR文件,参与打包计算;
117 | 5. 打包完成后,为了不影响后续打包任务,删除模拟的AAR将备份的AAR文件恢复;
118 | 6. 修改build.gradle文件,使用Gradle的打包特性配置所有all*.exclude移除对应的group和module;
119 |
--------------------------------------------------------------------------------
/imgs/sample_aar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/imgs/sample_aar.png
--------------------------------------------------------------------------------
/imgs/sample_aar_size.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/imgs/sample_aar_size.png
--------------------------------------------------------------------------------
/imgs/sample_clone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuba/Zucker/35be5818c3c6461c3fdaf317472cb8393d1d1c8b/imgs/sample_clone.png
--------------------------------------------------------------------------------
/zucker.py:
--------------------------------------------------------------------------------
1 | # coding=utf-8
2 |
3 | #
4 | # Copyright Zucker
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
7 | # not use this file except in compliance with the License. You may obtain
8 | # a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15 | # License for the specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import os
19 | import sys
20 | import subprocess
21 | from shutil import copyfile
22 | import shutil
23 | import re
24 | import zipfile
25 | import copy
26 | from pathlib import Path
27 |
28 |
29 | class CloneProject:
30 | target_project_name = ""
31 |
32 | def clone(self, current_path, output_path, target_project_name, output_project_name):
33 | self.target_project_name = target_project_name
34 | # 如果output目录不存在,则创建文件夹
35 | if not os.path.exists(output_path):
36 | os.mkdir(output_path)
37 | self.__copy_dir(current_path, output_path, output_project_name, 0)
38 |
39 | # 拷贝文件夹
40 | def __copy_dir(self, source_dir, target_dir, target, index):
41 | # 源文件夹
42 | dir = os.path.join(source_dir, target)
43 | if index == 0:
44 | dir = os.path.join(source_dir, self.target_project_name)
45 | else:
46 | dir = os.path.join(source_dir, target)
47 | # 源文件夹下的文件列表
48 | files = os.listdir(dir)
49 | # 目标文件夹
50 | target_dir = os.path.join(target_dir, target)
51 | if not os.path.exists(target_dir):
52 | os.mkdir(target_dir)
53 | if index == 0:
54 | self.fileSize = 0
55 | for dirpath, dirnames, filenames in os.walk(dir):
56 | for file in filenames:
57 | self.fileSize = self.fileSize + 1
58 | self.count = 0
59 | for f in files:
60 | sourcefile = os.path.join(dir, f)
61 | targetfile = os.path.join(target_dir, f)
62 | if os.path.isdir(sourcefile):
63 | index += 1
64 | self.__copy_dir(dir, target_dir, f, index)
65 | if os.path.isfile(sourcefile) and not os.path.exists(targetfile):
66 | copyfile(sourcefile, targetfile)
67 | self.count += 1
68 | print("Cloning: {0}%".format(round((self.count + 1) * 100 / self.fileSize)), end="\r")
69 |
70 |
71 | class TreeNode:
72 | children = set()
73 | parents = set()
74 | parent = None
75 | value = ""
76 |
77 | def __init__(self, value):
78 | self.value = value
79 | self.children = set()
80 | self.parents = set()
81 | self.parent = None
82 |
83 | def add_child(self, node):
84 | self.children.add(node)
85 |
86 | def add_parent(self, node):
87 | self.parent = node
88 | self.parents.add(node)
89 |
90 | def is_root(self):
91 | return len(self.parents) == 0 or self.parent is None
92 |
93 | def get_level(self):
94 | if self.is_root():
95 | return 0
96 | return self.parent.get_level() + 1
97 |
98 |
99 | class Dependency:
100 | # gradle命令,获取release下依赖树,写入文件
101 | commend = "./gradlew -q dependencies :%s:dependencies --configuration releaseRuntimeClasspath>"
102 | # 去重set
103 | __node_set = set()
104 | # root节点
105 | rootNode = None
106 | # 记录当前的节点
107 | stack = []
108 | # 所有依赖节点
109 | allNode = []
110 | # 移除support包和Android原生依赖包
111 | exportArr = ["com.android.support", "android.arch.lifecycle", "com.google.android",
112 | "com.squareup.leakcanary:leakcanary-android", "android.arch.core",
113 | "org.jetbrains.kotlin:kotlin-stdlib-common", "org.jetbrains:annotations",
114 | "androidx.", "project :"]
115 |
116 | def __init__(self, output_project_path, app_dir):
117 | self.file_name = "dependency_" + app_dir + ".txt"
118 | self.projectPath = output_project_path
119 | self.appDir = app_dir
120 |
121 | def get_top_level_aars(self):
122 | self.rootNode = TreeNode(self.appDir)
123 | self.stack.append(self.rootNode)
124 | self.allNode.append(self.rootNode)
125 | # 执行gradle命令
126 | self.commend = (self.commend % self.appDir) + os.path.join(self.projectPath, self.file_name)
127 | # cd到工程目录下,才能正常的读取gradle命令
128 | self.commend = ("cd %s\nchmod +x gradlew\n" % self.projectPath) + self.commend
129 | subprocess.check_call(self.commend, shell=True)
130 | self.__check_dependency_file()
131 |
132 | nodes = []
133 | for n in self.allNode:
134 | nodeName = n.value
135 | if len(n.parents) >= 1 and not self.__check_aar_in_export(nodeName):
136 | for p in n.parents:
137 | if p == self.rootNode:
138 | nodes.append(nodeName)
139 | continue
140 | return nodes
141 |
142 | def __check_dependency_file(self):
143 | dep_file = os.path.join(self.projectPath, self.file_name)
144 | # 逐行读取文件
145 | with open(dep_file) as f:
146 | line = f.readline()
147 | while line:
148 | line = line.rstrip("\n")
149 | if len(line) == 0 or (
150 | not line.startswith("+") and (not line.startswith("|")) and (not line.startswith("\\"))):
151 | line = f.readline()
152 | continue
153 | line = line.replace("\\", "+").replace("+---", " ").replace("|", " ").replace(" ", "!")
154 | current_level = line.count("!")
155 | if current_level == 0:
156 | line = f.readline()
157 | continue
158 | last_parent = self.stack.pop()
159 | parent_level = last_parent.get_level()
160 | while not (current_level > parent_level):
161 | last_parent = self.stack.pop()
162 | parent_level = last_parent.get_level()
163 | line = line.replace("!", "").replace(" -> ", ":").replace(" (*)", "")
164 | buffer = line.split(":")
165 | tmp_length = len(buffer)
166 | if tmp_length > 2:
167 | line = "%s:%s:%s" % (buffer[0], buffer[1], buffer[-1])
168 | if line in self.__node_set:
169 | for node in self.allNode:
170 | if node.value == line:
171 | self.__update_node(node, last_parent)
172 | break
173 | else:
174 | node = TreeNode(line)
175 | self.__update_node(node, last_parent)
176 | self.__node_set.add(node.value)
177 | self.allNode.append(node)
178 |
179 | node.add_parent(last_parent)
180 | last_parent.add_child(node)
181 | self.stack.append(last_parent)
182 | self.stack.append(node)
183 | line = f.readline()
184 |
185 | # 更新依赖关系
186 | @staticmethod
187 | def __update_node(node, parent):
188 | node.add_parent(parent)
189 | parent.add_child(node)
190 |
191 | """
192 | # 获取输入的多个aar依赖:该方法是要统计系列依赖比如fresco,animated-gif,webpsupport,animated-webp
193 | 方法步骤:
194 | 遍历输入的节点,将该节点的依赖树记录到result set中
195 | copy一份des set
196 | 遍历该result set,检查每个节点的父节点是否仅在这个des set中
197 | 在-->保留该节点,否则将该节点不是独有依赖,从des set中移除
198 | 经过一次遍历后,该des set即为结果set
199 | """
200 |
201 | def __get_array_node(self, array):
202 | input_set = set()
203 | result = set()
204 | # 遍历输入的aar列表
205 | for s in array:
206 | n = self.__find_node_by_name(s)
207 | if n is None:
208 | print("暂无该依赖节点%s" % s)
209 | continue
210 | input_set.add(n)
211 | result.add(n)
212 | self.__add_children_node(n, result)
213 | # copy依赖的节点,作为结果set
214 | des = result.copy()
215 | for rNode in result:
216 | if rNode in input_set or 1 == len(rNode.parents):
217 | continue
218 | # 移除support库
219 | for p in rNode.parents:
220 | if p not in des:
221 | des.remove(rNode)
222 | break
223 | return des
224 |
225 | def get_input_aar(self, target_aar):
226 | if self.__check_aar_in_export(target_aar):
227 | print("不支持【%s】该类型的库统计!" % target_aar)
228 | return []
229 | array = [target_aar.lstrip(" ").rstrip(" ")]
230 | aars = self.__get_array_node(array)
231 | result = []
232 | for aar in aars:
233 | if self.__check_aar_in_export(aar.value):
234 | continue
235 | result.append(aar.value)
236 | return result
237 |
238 | # 校验传入的aar是否在去除列表中
239 | def __check_aar_in_export(self, aar_name):
240 | result = False
241 | for s in self.exportArr:
242 | if aar_name.startswith(s):
243 | result = True
244 | break
245 | return result
246 |
247 | # 遍历所有节点,记录在set中
248 | def __add_children_node(self, node, node_set):
249 | if len(node.children) == 0:
250 | node_set.add(node)
251 | return
252 | for child in node.children:
253 | node_set.add(child)
254 | self.__add_children_node(child, node_set)
255 |
256 | # 根据名称获取依赖节点
257 | def __find_node_by_name(self, node_name):
258 | node = None
259 | for n in self.allNode:
260 | if n.value.find(node_name) != -1:
261 | node = n
262 | break
263 | return node
264 |
265 |
266 | class AarCache:
267 | # 记录传入的aar本地缓存路径
268 | targetAarPath = ""
269 | # gradle本地目录
270 | gradleUserHome = ""
271 | __envMap = os.environ
272 |
273 | def __init__(self):
274 | # 先检查是否将路径写在环境变量中
275 | gradle_home = self.__envMap.get("GRADLE_USER_HOME")
276 | if gradle_home is None or not os.path.exists(gradle_home):
277 | # 查看默认路径 ~/user/.gradle
278 | gradle_home = os.path.join(self.__envMap.get('HOME'), ".gradle")
279 | self.gradleUserHome = os.path.join(gradle_home, "caches", "modules-2", "files-2.1")
280 |
281 | # 获取率AAR
282 | def get_aar_file(self, aar_name):
283 | aar_info = aar_name.split(":")
284 | aar_path = os.path.join(self.gradleUserHome, aar_info[0], aar_info[1], aar_info[2])
285 | # print(aarPath)
286 | if not os.path.exists(aar_path):
287 | print("aar 本地缓存不存在")
288 | return False
289 | aar_file = self.__get_aar_file_(aar_path)
290 | self.targetAarPath = aar_file
291 | return aar_file and os.path.exists(aar_file)
292 |
293 | @staticmethod
294 | def __get_aar_file_(file):
295 | for root, dirs, files in os.walk(file, topdown=False):
296 | for name in files:
297 | if name.endswith(".aar"):
298 | return os.path.join(root, name)
299 |
300 |
301 | class Compile:
302 | def __init__(self, output_project_path):
303 | self.outputProjectPath = output_project_path
304 |
305 | def new_module(self, app_dirs):
306 | compile_sdk_version = ""
307 | build_tools_version = ""
308 | # settings.gradle
309 | settings = os.path.join(self.outputProjectPath, "settings.gradle")
310 | with open(settings, 'a+') as f:
311 | f.write("\ninclude ':zucker'")
312 | # app build.gradle
313 | regex = re.compile(r'dependencies([\s]*){')
314 | STATEMENT = " implementation project(':zucker')\n"
315 | for dir in app_dirs:
316 | lines = []
317 | gradle_file = os.path.join(self.outputProjectPath, dir, "build.gradle")
318 | with open(gradle_file, 'r') as f:
319 | has_found_dependencies = False
320 | bracket_count = 0
321 | for line in f.readlines():
322 | lines.append(line)
323 | if line.lstrip().startswith("//"):
324 | pass
325 | if has_found_dependencies:
326 | for index, c in enumerate(line):
327 | if c == '{':
328 | bracket_count += 1
329 | elif c == '}':
330 | bracket_count -= 1
331 | if bracket_count < 0:
332 | lines.remove(STATEMENT)
333 | has_found_dependencies = False
334 | bracket_count = 0
335 | if "compileSdkVersion" in line:
336 | compile_sdk_version = line
337 | elif "buildToolsVersion" in line:
338 | build_tools_version = line
339 | elif regex.search(line):
340 | has_found_dependencies = True
341 | bracket_count += 1
342 | lines.append(STATEMENT)
343 | with open(gradle_file, "w") as f:
344 | f.writelines(lines)
345 | f.close()
346 | # zucker dir
347 | zucker = os.path.join(self.outputProjectPath, "zucker")
348 | if not Path(zucker).exists():
349 | os.mkdir(zucker)
350 | # src dir
351 | src = os.path.join(zucker, "src")
352 | if not Path(src).exists():
353 | os.mkdir(src)
354 | # zucker main
355 | main = os.path.join(src, "main")
356 | if not Path(main).exists():
357 | os.mkdir(main)
358 | # AndroidManifest
359 | manifest = os.path.join(main, "AndroidManifest.xml")
360 | with open(manifest, 'w') as f:
361 | f.write("")
362 | # project build.gradle
363 | build = os.path.join(zucker, "build.gradle")
364 | with open(build, 'w') as f:
365 | f.write("apply plugin: 'com.android.library'\n\n")
366 | f.write("android {\n")
367 | f.write(compile_sdk_version + "\n")
368 | f.write(build_tools_version + "\n")
369 | f.write("}\n\n")
370 | f.write("dependencies {\n\n}")
371 | return main
372 |
373 | def clear_flavors(self, app_dirs):
374 | self.__clear_bucket_content('productFlavors', app_dirs)
375 |
376 | def insert_script(self, app_dirs):
377 | PATH = self.outputProjectPath
378 | for targetFile in app_dirs:
379 | package_size_path = os.path.join(PATH, targetFile, "zucker.txt")
380 | open(package_size_path, 'w')
381 | gradle_file = os.path.join(PATH, targetFile, 'build.gradle')
382 | with open(gradle_file, 'a+') as f:
383 | f.write("\n")
384 | f.write("android.applicationVariants.all { variant ->\n")
385 | f.write(" variant.outputs.all { output ->\n")
386 | f.write(" if (output.outputFileName.contains('debug.apk')) {\n")
387 | f.write(" Task assembleDebug = tasks.getByName('assembleDebug')\n")
388 | f.write(" File file = output.outputFile\n")
389 | f.write(" assembleDebug.doLast {\n")
390 | f.write(" def apkSize = file.length().toString()\n")
391 | f.write(" print('ApkSize: '+apkSize)\n")
392 | f.write(" def packageSizeFile = new File(\"" + package_size_path + "\")\n")
393 | f.write(" packageSizeFile.withWriter { writer ->\n")
394 | f.write(" writer.write(apkSize)\n")
395 | f.write(" }\n")
396 | f.write(" }\n")
397 | f.write(" }\n")
398 | f.write(" }\n")
399 | f.write("}\n\n")
400 |
401 | def find_app_dirs(self):
402 | app_dirs = []
403 | PATH = self.outputProjectPath
404 | dir_list = [x for x in os.listdir(PATH) if
405 | os.path.isdir(os.path.join(PATH, x)) and not x.startswith('.') and not x == 'gradle']
406 | for targetFile in dir_list:
407 | gradle_file = os.path.join(PATH, targetFile, 'build.gradle')
408 | if os.path.isfile(gradle_file):
409 | with open(gradle_file) as f:
410 | for index, line in enumerate(f.readlines()):
411 | if "apply plugin: 'com.android.application'" in line:
412 | app_dirs.append(targetFile)
413 | break
414 | return app_dirs
415 |
416 | def __clear_bucket_content(self, TAG, appDirs):
417 | PATH = self.outputProjectPath
418 | for targetFile in appDirs:
419 | gradle_file = os.path.join(PATH, targetFile, 'build.gradle')
420 | with open(gradle_file, 'r') as f:
421 | tag_lines = []
422 | has_find_tag = False
423 | has_find_start_tag = False
424 | has_find_end_tag = False
425 | bracket_count = 0
426 | for line in f.readlines():
427 | if line.lstrip().startswith("//"):
428 | tag_lines.append(line)
429 | continue
430 | if not has_find_tag:
431 | index = line.find(TAG)
432 | if index >= 0:
433 | has_find_tag = True
434 | start_index = 0
435 | end_index = len(line)
436 | for index, c in enumerate(line):
437 | if c == '{':
438 | if not has_find_start_tag:
439 | has_find_start_tag = True
440 | start_index = index + 1
441 | bracket_count += 1
442 | elif c == '}':
443 | bracket_count -= 1
444 | if has_find_start_tag and bracket_count == 0:
445 | has_find_end_tag = True
446 | end_index = index
447 | break
448 | if has_find_end_tag:
449 | tag_lines.append(line[0:start_index] + line[end_index:len(line)])
450 | else:
451 | if has_find_start_tag:
452 | tag_lines.append(line[0:start_index] + "\n")
453 | else:
454 | tag_lines.append(line)
455 | if has_find_tag and not has_find_end_tag:
456 | start_index = -1
457 | end_index = len(line)
458 | for index, c in enumerate(line):
459 | if c == '{':
460 | if not has_find_start_tag:
461 | has_find_start_tag = True
462 | start_index = index + 1
463 | bracket_count += 1
464 | elif c == '}':
465 | bracket_count -= 1
466 | if has_find_start_tag and bracket_count == 0:
467 | has_find_end_tag = True
468 | end_index = index
469 | break
470 | if has_find_start_tag:
471 | linebreak = ""
472 | if start_index >= 0:
473 | linebreak = "\n"
474 | else:
475 | start_index = 0
476 | if has_find_end_tag:
477 | tag_lines.append(line[0:start_index] + linebreak + " " + line[end_index:len(line)])
478 | else:
479 | tag_lines.append(line[0:start_index] + linebreak)
480 | else:
481 | tag_lines.append(line)
482 | if has_find_tag and has_find_end_tag:
483 | tag_lines.append(line)
484 | if not has_find_tag and not has_find_end_tag:
485 | tag_lines.append(line)
486 | if has_find_tag:
487 | fd = open(gradle_file, "w")
488 | fd.writelines(tag_lines)
489 | fd.close()
490 |
491 | def compile(self):
492 | command = "cd " + self.outputProjectPath + "\n"
493 | command += "chmod +x gradlew" + "\n"
494 | command += "./gradlew clean" + "\n"
495 | command += "./gradlew assembleDebug"
496 | subprocess.call(command, shell=True)
497 |
498 |
499 | class MockCache:
500 | zucker_res_size = 0
501 |
502 | def __init__(self, origin_aar_cache_path, target_main_src_path):
503 | # 复制文件
504 | self.originAarCachePath = origin_aar_cache_path
505 | self.targetMainSrcPath = target_main_src_path
506 | self.mockAarCachePath = origin_aar_cache_path.replace(".aar", "-origin.zip")
507 | self.mockAarOriginPath = origin_aar_cache_path.replace(".aar", "-origin.aar")
508 |
509 | def mock_cache(self):
510 | copyfile(self.originAarCachePath, self.mockAarCachePath)
511 | copyfile(self.originAarCachePath, self.mockAarOriginPath)
512 |
513 | # 解压
514 | unzip_file = os.path.dirname(self.originAarCachePath) + "/" + (
515 | os.path.basename(self.originAarCachePath)).replace(".aar", "")
516 | file_zip = zipfile.ZipFile(self.mockAarCachePath, 'r')
517 | for file in file_zip.namelist():
518 | file_zip.extract(file, unzip_file)
519 | file_zip.close()
520 |
521 | self._copy_mock_file(unzip_file, self.targetMainSrcPath)
522 | # 基础Mock
523 | for root, dirs, files in os.walk(os.path.dirname(self.targetMainSrcPath + "/res"), topdown=False):
524 | for name in files:
525 | if name.startswith('values') and name.endswith('.xml'):
526 | pass
527 | elif name in ('AndroidManifest.xml'):
528 | pass
529 | elif ('layout' in root) and name.endswith('.xml'):
530 | mypath = os.path.join(root, name)
531 | fd = open(mypath, "w")
532 | fd.write("")
533 | fd.write("")
534 | fd.close()
535 | elif ('drawable' in root) and name.endswith('.xml'):
536 | mypath = os.path.join(root, name)
537 | fd = open(mypath, "w")
538 | fd.write("")
539 | fd.write("")
540 | fd.close()
541 | elif ('drawable' in root) and name.endswith('.9.png'):
542 | pass
543 | elif ('mipmap' in root) and name.endswith('.9.png'):
544 | pass
545 | elif ('anim' in root) and name.endswith('.xml'):
546 | mypath = os.path.join(root, name)
547 | fd = open(mypath, "w")
548 | fd.write("")
549 | fd.write("")
550 | fd.close()
551 | elif ('color' in root) and name.endswith('.xml'):
552 | mypath = os.path.join(root, name)
553 | fd = open(mypath, "w")
554 | fd.write("")
555 | fd.write("")
556 | fd.close()
557 | elif ('xml' in root) and name.endswith('.xml'):
558 | mypath = os.path.join(root, name)
559 | fd = open(mypath, "w")
560 | fd.write("")
561 | fd.write("")
562 | fd.close()
563 | else:
564 | mypath = os.path.join(root, name)
565 | fd = open(mypath, "w")
566 | fd.writelines([])
567 | fd.close()
568 |
569 | # 遍历文件,并删除 os.path.join(path, file)
570 | white_list = ["classes.jar"]
571 | dirs = os.listdir(unzip_file)
572 | for root, dirs, files in os.walk(unzip_file, topdown=False):
573 | for name in files:
574 | if name in white_list:
575 | pass
576 | else:
577 | os.remove(os.path.join(root, name))
578 | # 删除原有AAR
579 | for root, dirs, files in os.walk(os.path.dirname(self.originAarCachePath), topdown=False):
580 | for name in files:
581 | if name in os.path.basename(self.originAarCachePath):
582 | os.remove(os.path.join(root, name))
583 |
584 | # 计算zucker库里面res的文件大小
585 | self.zucker_res_size = self._get_dir_size(os.path.dirname(self.targetMainSrcPath + "/res"))
586 | # 压缩Mock的File
587 | self._zip_mock_file(unzip_file)
588 |
589 | @staticmethod
590 | def _get_dir_size(path):
591 | # 计算指定的路径下的所有文件的大小
592 | if os.path.isdir(path):
593 | file_size, dir_list = 0, [path]
594 | while dir_list:
595 | path = dir_list.pop()
596 | dirs = os.listdir(path)
597 | for name in dirs:
598 | file_path = os.path.join(path, name)
599 | if os.path.isfile(file_path):
600 | file_size += os.path.getsize(file_path)
601 | else:
602 | dir_list.append(file_path)
603 | return file_size
604 | elif os.path.isfile(path):
605 | return os.path.getsize(path)
606 | else:
607 | print('找不到%s文件' % path)
608 |
609 | def _copytree(self, src, dst, symlinks=False, ignore=None, copy_function=shutil.copy2):
610 | names = os.listdir(src)
611 | if ignore is not None:
612 | ignored_names = ignore(src, names)
613 | else:
614 | ignored_names = set()
615 | if not os.path.exists(dst):
616 | os.makedirs(dst)
617 | errors = []
618 | for name in names:
619 | if name in ignored_names:
620 | continue
621 | srcname = os.path.join(src, name)
622 | dstname = os.path.join(dst, name)
623 | try:
624 | if os.path.islink(srcname):
625 | linkto = os.readlink(srcname)
626 | if symlinks:
627 | # We can't just leave it to `copy_function` because legacy
628 | # code with a custom `copy_function` may rely on copytree
629 | # doing the right thing.
630 | os.symlink(linkto, dstname)
631 | shutil.copystat(srcname, dstname, follow_symlinks=not symlinks)
632 | else:
633 | # ignore dangling symlink if the flag is on
634 | if not os.path.exists(linkto) and False:
635 | continue
636 | # otherwise let the copy occurs. copy2 will raise an error
637 | if os.path.isdir(srcname):
638 | self._copytree(srcname, dstname, symlinks, ignore,
639 | copy_function)
640 | else:
641 | copy_function(srcname, dstname)
642 | elif os.path.isdir(srcname):
643 | self._copytree(srcname, dstname, symlinks, ignore, copy_function)
644 | else:
645 | # Will raise a SpecialFileError for unsupported file types
646 | copy_function(srcname, dstname)
647 | # catch the Error from the recursive copytree so that we can
648 | # continue with other files
649 | except shutil.Error as err:
650 | errors.extend(err.args[0])
651 | except OSError as why:
652 | errors.append((srcname, dstname, str(why)))
653 | try:
654 | shutil.copystat(src, dst)
655 | except OSError as why:
656 | # Copying file access times may fail on Windows
657 | if getattr(why, 'winerror', None) is None:
658 | errors.append((src, dst, str(why)))
659 | if errors:
660 | raise shutil.Error(errors)
661 | return dst
662 |
663 | def _copy_mock_file(self, origin_path, target_main_src_path):
664 | origin_path = origin_path + "/res"
665 | if os.path.exists(origin_path):
666 | self._copytree(origin_path, target_main_src_path + "/res")
667 | elif not os.path.exists(target_main_src_path + "/res"):
668 | os.makedirs(target_main_src_path + "/res")
669 |
670 | @staticmethod
671 | def _zip_mock_file(start_dir):
672 | start_dir = start_dir
673 | file_news = start_dir + '.aar'
674 |
675 | z = zipfile.ZipFile(file_news, 'w', zipfile.ZIP_DEFLATED)
676 | for dir_path, dir_names, file_names in os.walk(start_dir):
677 | f_path = dir_path.replace(start_dir, '')
678 | f_path = f_path and f_path + os.sep or ''
679 | for filename in file_names:
680 | z.write(os.path.join(dir_path, filename), f_path + filename)
681 | z.close()
682 | return file_news
683 |
684 | @staticmethod
685 | def add_configurations(aar, output_project_path, app_dir):
686 | aars = aar.split(":")
687 | name = aars[1] + "-" + aars[2]
688 | sub_path = os.path.join(output_project_path, app_dir)
689 |
690 | build_gradle = sub_path + "/build.gradle"
691 | if os.path.exists(build_gradle):
692 | # 先读文件
693 | configurations = open(build_gradle, 'r')
694 | content = configurations.read()
695 | post = content.find("configurations {")
696 | if post != -1:
697 | configurations = open(build_gradle, 'w')
698 | content = content[:post + len("configurations {")] + "\n" + "all*.exclude group: \'" + aars[
699 | 0] + "\'" + " ,module: " + "\'" + aars[1] + "\'\n" + content[post + len("configurations {"):]
700 | configurations.write(content)
701 | else:
702 | configurations = open(build_gradle, 'a+')
703 | configurations.write("configurations {")
704 | configurations.write("\n")
705 | configurations.write("all*.exclude group: \'" + aars[0] + "\'" + " ,module: " + "\'" + aars[1] + "\'")
706 | configurations.write("\n")
707 | configurations.write("}")
708 | configurations.close()
709 |
710 |
711 | class RevertCache:
712 | # 回滚修改的Cache目标AAR
713 | def __init__(self, origin_aar_cache_path):
714 | self.originAarCachePath = origin_aar_cache_path
715 |
716 | def revert(self):
717 | file_name = os.path.dirname(self.originAarCachePath)
718 | dirs = os.listdir(file_name)
719 | for root, dirs, files in os.walk(file_name, topdown=False):
720 | for name in files:
721 | if name.endswith("-origin.aar"):
722 | pass
723 | else:
724 | os.remove(os.path.join(root, name))
725 | for name in dirs:
726 | os.rmdir(os.path.join(root, name))
727 |
728 | for root, dirs, files in os.walk(file_name, topdown=False):
729 | for name in files:
730 | if name.endswith("-origin.aar"):
731 | new_name = copy.deepcopy(name)
732 | new_name = new_name.replace("-origin.aar", ".aar")
733 | os.rename(root + "/" + name, root + "/" + new_name)
734 |
735 |
736 | class PackageSize:
737 | # 统计大小,并输出最终结果
738 | @staticmethod
739 | def get_result(base_output_project_path, aar_output_project_path, app_dir, zucker_res_size):
740 | base_pack_size = 0
741 | aar_pack_size = 0
742 | base_pack_size_path = os.path.join(base_output_project_path, app_dir, "zucker.txt")
743 | aar_pack_size_path = os.path.join(aar_output_project_path, app_dir, "zucker.txt")
744 | with open(base_pack_size_path) as f:
745 | for line in f.readlines():
746 | base_pack_size = line
747 | print("基础包大小(basePackSize,单位Byte): " + line)
748 | break
749 | with open(aar_pack_size_path) as f:
750 | for line in f.readlines():
751 | aar_pack_size = line
752 | aar_pack_size = int(aar_pack_size) - (int(zucker_res_size) * 2)
753 | print("替换后的APK大小(aarPackSize,单位Byte): " + str(aar_pack_size))
754 | break
755 | aar_size = int(base_pack_size) - int(aar_pack_size)
756 | print("AAR大小(aarSize,单位Byte): " + str(aar_size))
757 |
758 |
759 | if __name__ == '__main__':
760 | sys.argv.append("ZuckerDemo")
761 | # 工程文件夹名称
762 | projectName = sys.argv[1]
763 | # 当前目录
764 | currentPath = os.getcwd()
765 | # 输出目录
766 | outputPath = os.path.join(currentPath, "output")
767 | # 资源大小
768 | zuckerResSize = ""
769 | # 是否找到缓存
770 | isCacheExist = False
771 |
772 | # 基础包:克隆、打包流程======================================
773 | # 基础包AAR工程目录
774 | baseOutputProjectPath = os.path.join(outputPath, projectName + "_BASE")
775 | if not os.path.exists(baseOutputProjectPath):
776 | # 克隆工程
777 | cloneBaseProject = CloneProject()
778 | cloneBaseProject.clone(currentPath, outputPath, projectName, projectName + "_BASE")
779 | print("cloneBaseProject DONE")
780 | # 编译工程
781 | baseCompile = Compile(baseOutputProjectPath)
782 | baseAppDirs = baseCompile.find_app_dirs()
783 | print("findBaseAppDirs DONE")
784 | baseCompile.clear_flavors(baseAppDirs)
785 | print("clearBaseFlavors DONE")
786 | baseCompile.insert_script(baseAppDirs)
787 | print("insertBaseScript DONE")
788 | baseCompile.compile()
789 |
790 | # AAR:克隆、依赖、mock、打包流程======================================
791 | # AAR工程目录
792 | outputProjectPath = os.path.join(outputPath, projectName + "_AAR")
793 | # 如果已经存在,则删除
794 | if os.path.exists(outputProjectPath):
795 | shutil.rmtree(outputProjectPath, True)
796 | # 克隆工程
797 | cloneProject = CloneProject()
798 | cloneProject.clone(currentPath, outputPath, projectName, projectName + "_AAR")
799 | print("cloneAARProject DONE")
800 | # 编译工程
801 | compile = Compile(outputProjectPath)
802 | appDirs = compile.find_app_dirs()
803 | print("findAARAppDirs DONE")
804 | zuckerModuleMainDir = compile.new_module(appDirs)
805 | print("newModule DONE: " + zuckerModuleMainDir)
806 | compile.clear_flavors(appDirs)
807 | print("clearAARFlavors DONE")
808 | compile.insert_script(appDirs)
809 | print("insertAARScript DONE")
810 |
811 | # 遍历工程下依赖的所有AAR
812 | for appdir in appDirs:
813 | dependency = Dependency(outputProjectPath, appdir)
814 | aars = dependency.get_top_level_aars()
815 | for aar in aars:
816 | print(aar)
817 | target_aar = input("输入AAR名称及版本,格式xxx.xxx:xxx:xxx:")
818 | result_aars = dependency.get_input_aar(target_aar)
819 | print("输出AAR:")
820 | print(result_aars)
821 |
822 | targetAarArray = []
823 | for aar in result_aars:
824 | aar_cache = AarCache()
825 | if aar_cache.get_aar_file(aar):
826 | print(aar_cache.targetAarPath)
827 | mockCache = MockCache(aar_cache.targetAarPath, zuckerModuleMainDir)
828 | mockCache.mock_cache()
829 | mockCache.add_configurations(aar, outputProjectPath, appdir)
830 | zuckerResSize = mockCache.zucker_res_size
831 | targetAarArray.append(aar_cache.targetAarPath)
832 | isCacheExist = True
833 | else:
834 | isCacheExist = False
835 |
836 | if isCacheExist:
837 | compile.compile()
838 | print("compile DONE")
839 |
840 | # 将修改的AAR进行回滚
841 | for path in targetAarArray:
842 | revertCache = RevertCache(path)
843 | revertCache.revert()
844 |
845 | # 统计大小并输出
846 | packSize = PackageSize()
847 | packSize.get_result(baseOutputProjectPath, compile.outputProjectPath, appdir, zuckerResSize)
848 | else:
849 | print("缓存aar未找到,请重新尝试")
850 | # break
851 |
--------------------------------------------------------------------------------