├── .gitignore
├── .idea
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── gradle.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── UNMAINTAINED.md
├── app
├── .gitignore
├── build.gradle
├── maindexlist.txt
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── dim
│ │ └── tinkerimitator
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── dim
│ │ │ └── tinkerimitator
│ │ │ ├── App.java
│ │ │ ├── InstallDexActivity.java
│ │ │ └── MainActivity.java
│ └── res
│ │ ├── layout
│ │ ├── activity_install_dex.xml
│ │ ├── activity_main.xml
│ │ └── content_main.xml
│ │ ├── menu
│ │ └── menu_main.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-v21
│ │ └── styles.xml
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── dim
│ └── tinkerimitator
│ └── ExampleUnitTest.java
├── build.gradle
├── buildSrc
├── .gitignore
├── build.gradle
├── libs
│ └── dx.jar
└── src
│ └── main
│ ├── groovy
│ └── com
│ │ └── dim
│ │ ├── HockDexProcessBuilder.java
│ │ ├── HookProcessOutputHandler.groovy
│ │ ├── MultiDexAndroidBuilder.groovy
│ │ ├── TinkerPlugin.groovy
│ │ ├── bean
│ │ ├── Config.groovy
│ │ ├── Dex.groovy
│ │ ├── DexHolder.groovy
│ │ ├── HashRecord.groovy
│ │ └── Patch.groovy
│ │ └── common
│ │ ├── IoUtils.groovy
│ │ └── Logger.groovy
│ └── resources
│ └── META-INF
│ └── gradle-plugins
│ └── tinker-imitator.properties
├── demo
├── app-debug.apk
├── log_-20160718-0258.txt
└── patch-20160718-0258
│ ├── patchclasses.dex
│ └── patchclasses2.dex
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── library
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── dim
│ │ └── library
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── dim
│ │ │ ├── common
│ │ │ ├── Bsdiff.java
│ │ │ └── Logger.java
│ │ │ └── library
│ │ │ ├── DexUtils.java
│ │ │ ├── NoneService.java
│ │ │ ├── ReflectionUtils.java
│ │ │ └── Tinker.java
│ ├── jniLibs
│ │ ├── arm64-v8a
│ │ │ └── libbsdiff.so
│ │ ├── armeabi-v7a
│ │ │ └── libbsdiff.so
│ │ ├── armeabi
│ │ │ └── libbsdiff.so
│ │ ├── mips
│ │ │ └── libbsdiff.so
│ │ ├── mips64
│ │ │ └── libbsdiff.so
│ │ ├── x86
│ │ │ └── libbsdiff.so
│ │ └── x86_64
│ │ │ └── libbsdiff.so
│ └── res
│ │ └── values
│ │ └── strings.xml
│ └── test
│ └── java
│ └── com
│ └── dim
│ └── library
│ └── ExampleUnitTest.java
├── plugin
└── Tinker-Plugin.zip
├── screenshot
└── img.png
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
27 |
28 |
--------------------------------------------------------------------------------
/.idea/misc.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 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/UNMAINTAINED.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | 
4 |
5 | ##[原理: 微信热更新方案](http://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=2649286306&idx=1&sn=d6b2865e033a99de60b2d4314c6e0a25&scene=0#wechat_redirect)
6 | 简单的讲: 增量更新
7 | [Tinker_imitator地址](https://github.com/zzz40500/Tinker_imitator)
8 |
9 |
10 | 电脑:mac
11 | 编译工具:as & intellj
12 | gradle版本 com.android.tools.build:gradle:2.1.2
13 | android版本:6.0
14 | ##准备动作:
15 | ###1. 安装bsdiff:
16 | mac 端命令:
17 | ```
18 | brew install bsdiff
19 | ```
20 | linux端命令:
21 | ```
22 | brew install bsdiff
23 | ```
24 | Windows:
25 | 使用cygwin安装
26 | 然后将bsdiff 安装的位置写入local.properties
27 | 
28 | mac 端不写.默认为/usr/local/bin/bsdiff
29 | linux 和Windows要写.
30 | >注意 我只测试了mac 的使用.
31 |
32 | ### 2. 安装ide插件.
33 | [Tinker-Plugin地址](https://github.com/zzz40500/Tinker_imitator/blob/master/plugin/Tinker-Plugin.zip)
34 | 安装方式:[这篇文章](https://github.com/zzz40500/GsonFormat)第2种方式.
35 |
36 | ##3. 编译运行.
37 | 这里暂时不支持使用instant run 的情况. 所以你要关闭instant run
38 | 关闭方式:自行google|bing
39 | 第一次编译:
40 | 
41 | 编译完成会产生几个文件:
42 |
43 | 
44 | 然后修改代码:
45 | 打补丁包:
46 |
47 | 
48 | 会有下列产物:
49 |
50 | 
51 | patchclasses.dex 是生成的patch dex. 如果你连接手机的话,ide插件会帮你push 到手机的/sdcard/hot/中
52 | classes和class2 分别对应apk 中的classes.dex和classes2.dex.
53 | log 是运行日志. 你可以直接使用日志中的命令执行,而不使用我提供的插件
54 |
55 | ##查看效果:
56 | 方式一: app 重启
57 | 方式二: 点击app 的内部的热修复按钮.
58 |
59 | ##4. 不足:
60 | 1. 热修复. 需要重启
61 | * 只是代码级别的热修复. 不支持资源的替换.修改代码的时候不能新增资源id.
62 | * 如果改变了两个dex里面的东西的话,那么占得内存就有点大了
63 |
64 |
65 | ##5. todo:
66 | 1. 签名验证;
67 | * gradle配置热修复
68 | * 支持instant run
69 | * 包裹dex.而不是直接传递dex;
70 | * patch版本控制;
71 | * 部分情况下不用重启app就能生效;
72 | * 更智能的dex管理;
73 | * 安全模式.防止因为错误的patch导致的app启动不起来;
74 | * 更好的差分算法;
75 | * 资源更新;
76 |
77 | ##6. 尾巴
78 | 最近[阿宅](https://github.com/markzhai)开了个QQ实践群(568863373),欢迎大家进来玩耍,也可以关注我们的公众号:**魔都三帅**
79 |
80 | 
81 |
82 | 特别感谢:
83 | https://github.com/jasonross/Nuwa
84 | https://github.com/ceabie/DexKnifePlugin
85 | https://github.com/brok1n/androidBsdiffUpdate
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.3"
6 |
7 | defaultConfig {
8 | applicationId "com.dim.tinkerimitator"
9 | minSdkVersion 15
10 | targetSdkVersion 23
11 | versionCode 1
12 | versionName "1.0"
13 | multiDexEnabled true
14 | }
15 | buildTypes {
16 |
17 | debug {
18 | minifyEnabled true
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | release {
22 | minifyEnabled true
23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 | }
27 |
28 | dependencies {
29 | compile fileTree(include: ['*.jar'], dir: 'libs')
30 | testCompile 'junit:junit:4.12'
31 | compile 'com.android.support:appcompat-v7:23.4.0'
32 | compile 'com.android.support:design:23.4.0'
33 | compile project(':library')
34 | }
35 |
36 | apply plugin: com.dim.TinkerPlugin
37 |
--------------------------------------------------------------------------------
/app/maindexlist.txt:
--------------------------------------------------------------------------------
1 |
2 | android/support/v4/app/TaskStackBuilder$SupportParentable.class
3 | android/support/annotation/DrawableRes.class
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Applications/Android Studio.app/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class
15 |
16 |
17 | -dontwarn **
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/dim/tinkerimitator/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.dim.tinkerimitator;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
14 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dim/tinkerimitator/App.java:
--------------------------------------------------------------------------------
1 | package com.dim.tinkerimitator;
2 |
3 | import android.support.multidex.MultiDexApplication;
4 |
5 | import com.dim.library.Tinker;
6 |
7 | /**
8 | * App
9 | * Created by dim on 2016-07-09.
10 | */
11 | public class App extends MultiDexApplication {
12 |
13 | private static final String TAG = "App";
14 |
15 | @Override
16 | public void onCreate() {
17 | super.onCreate();
18 | Tinker.init(this);
19 | Tinker.setBackgroundPolicy(new Tinker.BackgroundPolicy() {
20 | @Override
21 | public boolean isReadyForFix() {
22 | return true;
23 | }
24 | });
25 | }
26 |
27 | @Override
28 | public void onTrimMemory(int level) {
29 | super.onTrimMemory(level);
30 | Tinker.onTrimMemory(level);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dim/tinkerimitator/InstallDexActivity.java:
--------------------------------------------------------------------------------
1 | package com.dim.tinkerimitator;
2 |
3 | import android.Manifest;
4 | import android.content.Intent;
5 | import android.content.pm.PackageManager;
6 | import android.os.AsyncTask;
7 | import android.os.Bundle;
8 | import android.support.annotation.NonNull;
9 | import android.support.v4.app.ActivityCompat;
10 | import android.support.v4.content.ContextCompat;
11 | import android.support.v7.app.AppCompatActivity;
12 | import android.widget.TextView;
13 |
14 | import com.dim.library.Tinker;
15 |
16 | import java.util.List;
17 |
18 |
19 | public class InstallDexActivity extends AppCompatActivity {
20 |
21 |
22 | private final int WRITE_EXTERNAL_STORAGE_CODE = 22;
23 |
24 | private TextView mTv;
25 |
26 | @Override
27 | protected void onCreate(Bundle savedInstanceState) {
28 | super.onCreate(savedInstanceState);
29 | setContentView(R.layout.activity_install_dex);
30 | mTv = (TextView) findViewById(R.id.resultTv);
31 | if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
32 | != PackageManager.PERMISSION_GRANTED) {
33 | ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
34 | WRITE_EXTERNAL_STORAGE_CODE);
35 | }else{
36 | installDex();
37 | }
38 | }
39 |
40 | @Override
41 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
42 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
43 | if (requestCode == WRITE_EXTERNAL_STORAGE_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
44 | installDex();
45 | }
46 | }
47 | private void installDex() {
48 | new InstallDexTask().execute();
49 | }
50 |
51 | /**
52 | * 安装热更新的dex
53 | */
54 | private class InstallDexTask extends AsyncTask> {
55 |
56 | @Override
57 | protected List doInBackground(Void... params) {
58 |
59 | return Tinker.install();
60 | }
61 |
62 |
63 | @Override
64 | protected void onPostExecute(List installList) {
65 | super.onPostExecute(installList);
66 | if (installList.size() > 0) {
67 | StringBuffer sb = new StringBuffer();
68 | for (String string : installList) {
69 | sb.append("成功安装: " + string + "\n");
70 | }
71 | sb.append("3秒后进入后台");
72 | mTv.setText(sb);
73 | mTv.postDelayed(new Runnable() {
74 | @Override
75 | public void run() {
76 | Intent intent = new Intent(Intent.ACTION_MAIN);
77 | intent.addCategory(Intent.CATEGORY_HOME);
78 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
79 | startActivity(intent);
80 | }
81 | }, 3000);
82 | } else {
83 | mTv.setText("没有新的patch安装");
84 | }
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dim/tinkerimitator/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.dim.tinkerimitator;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.design.widget.FloatingActionButton;
6 | import android.support.design.widget.Snackbar;
7 | import android.support.v7.app.AppCompatActivity;
8 | import android.support.v7.widget.Toolbar;
9 | import android.view.Menu;
10 | import android.view.MenuItem;
11 | import android.view.View;
12 | import android.widget.Button;
13 | import android.widget.TextView;
14 |
15 |
16 | public class MainActivity extends AppCompatActivity implements View.OnClickListener {
17 |
18 | private static final String TAG = "MainActivity";
19 | @Override
20 | protected void onCreate(Bundle savedInstanceState) {
21 | super.onCreate(savedInstanceState);
22 | setContentView(R.layout.activity_main);
23 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
24 | setSupportActionBar(toolbar);
25 | TextView tv= (TextView) findViewById(R.id.tv);
26 | tv.setText("错误的显示");
27 | // tv.setText("如果你看到这个,说明已经热修复成功了");
28 | // tv.setTextSize(22);
29 | Button button = (Button) findViewById(R.id.install);
30 | button.setOnClickListener(this);
31 | // FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
32 | // fab.setOnClickListener(new View.OnClickListener() {
33 | // @Override
34 | // public void onClick(View view) {
35 | // Snackbar.make(view, "新加的 button 点击", Snackbar.LENGTH_LONG)
36 | // .setAction("Action", null).show();
37 | // }
38 | // });
39 | }
40 |
41 | @Override
42 | public boolean onCreateOptionsMenu(Menu menu) {
43 | // Inflate the menu; this adds items to the action bar if it is present.
44 | getMenuInflater().inflate(R.menu.menu_main, menu);
45 | return true;
46 | }
47 |
48 | @Override
49 | public boolean onOptionsItemSelected(MenuItem item) {
50 | // Handle action bar item clicks here. The action bar will
51 | // automatically handle clicks on the Home/Up button, so long
52 | // as you specify a parent activity in AndroidManifest.xml.
53 | int id = item.getItemId();
54 |
55 | //noinspection SimplifiableIfStatement
56 | if (id == R.id.action_settings) {
57 | return true;
58 | }
59 | return super.onOptionsItemSelected(item);
60 | }
61 |
62 | @Override
63 | public void onClick(View v) {
64 | startActivity(new Intent(this, InstallDexActivity.class));
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_install_dex.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
15 |
16 |
22 |
23 |
24 |
25 |
26 |
27 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
21 |
22 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzz40500/Tinker_imitator/167022c62f486a3b3480c073bf2977f64aabcbd0/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzz40500/Tinker_imitator/167022c62f486a3b3480c073bf2977f64aabcbd0/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzz40500/Tinker_imitator/167022c62f486a3b3480c073bf2977f64aabcbd0/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzz40500/Tinker_imitator/167022c62f486a3b3480c073bf2977f64aabcbd0/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzz40500/Tinker_imitator/167022c62f486a3b3480c073bf2977f64aabcbd0/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 16dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Tinker imitator
3 | Settings
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/test/java/com/dim/tinkerimitator/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.dim.tinkerimitator;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | jcenter()
4 | }
5 | dependencies {
6 | classpath 'com.android.tools.build:gradle:2.1.2'
7 | }
8 | }
9 |
10 | allprojects {
11 | repositories {
12 | jcenter()
13 | }
14 | }
15 |
16 | task clean(type: Delete) {
17 | delete rootProject.buildDir
18 | }
19 |
--------------------------------------------------------------------------------
/buildSrc/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/buildSrc/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'groovy'
2 | apply plugin: 'maven'
3 |
4 | repositories {
5 | jcenter()
6 | }
7 | configurations {
8 | provided
9 | }
10 |
11 | sourceSets {
12 | main {
13 | compileClasspath += configurations.provided
14 | }
15 | }
16 | dependencies {
17 | compile 'commons-io:commons-io:1.4'
18 | compile 'commons-codec:commons-codec:1.6'
19 | compile 'com.android.tools.build:builder:2.1.2'
20 | compile 'com.android.tools.build:gradle-core:2.1.2'
21 | compile 'com.google.code.gson:gson:2.7'
22 |
23 |
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/buildSrc/libs/dx.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzz40500/Tinker_imitator/167022c62f486a3b3480c073bf2977f64aabcbd0/buildSrc/libs/dx.jar
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/com/dim/HockDexProcessBuilder.java:
--------------------------------------------------------------------------------
1 | package com.dim;
2 |
3 | import com.android.annotations.NonNull;
4 | import com.android.annotations.Nullable;
5 | import com.android.builder.core.DexOptions;
6 | import com.android.builder.core.DexProcessBuilder;
7 | import com.android.ide.common.process.JavaProcessInfo;
8 | import com.android.ide.common.process.ProcessException;
9 | import com.android.ide.common.process.ProcessInfoBuilder;
10 | import com.android.repository.Revision;
11 | import com.android.sdklib.BuildToolInfo;
12 | import com.dim.common.Logger;
13 | import com.google.common.base.Predicate;
14 | import com.google.common.collect.Lists;
15 | import com.google.common.collect.Sets;
16 |
17 | import java.io.File;
18 | import java.util.Collection;
19 | import java.util.Collections;
20 | import java.util.Comparator;
21 | import java.util.List;
22 | import java.util.Set;
23 |
24 | import static com.google.common.base.Preconditions.checkState;
25 |
26 | /**
27 | * Created by dim on 16/7/18.
28 | */
29 | public class HockDexProcessBuilder extends DexProcessBuilder {
30 | public static final Revision MIN_MULTIDEX_BUILD_TOOLS_REV = new Revision(21, 0, 0);
31 | public static final Revision MIN_MULTI_THREADED_DEX_BUILD_TOOLS_REV = new Revision(22, 0, 2);
32 | public static final Revision FIXED_DX_MERGER = new Revision(23, 0, 2);
33 |
34 | @NonNull
35 | private final File mOutputFile;
36 | private String dxPath;
37 | private boolean mVerbose = false;
38 | private boolean mIncremental = false;
39 | private boolean mNoOptimize = false;
40 | private boolean mMultiDex = false;
41 | private File mMainDexList = null;
42 | private Set mInputs = Sets.newHashSet();
43 | private List mAdditionalParams = null;
44 |
45 | public HockDexProcessBuilder(@NonNull File outputFile, String dxPath) {
46 | super(outputFile);
47 | mOutputFile = outputFile;
48 | this.dxPath = dxPath;
49 | }
50 |
51 | @NonNull
52 | public DexProcessBuilder setVerbose(boolean verbose) {
53 | mVerbose = verbose;
54 | return this;
55 | }
56 |
57 | @NonNull
58 | public DexProcessBuilder setIncremental(boolean incremental) {
59 | mIncremental = incremental;
60 | return this;
61 | }
62 |
63 | @NonNull
64 | public DexProcessBuilder setNoOptimize(boolean noOptimize) {
65 | mNoOptimize = noOptimize;
66 | return this;
67 | }
68 |
69 | @NonNull
70 | public DexProcessBuilder setMultiDex(boolean multiDex) {
71 | mMultiDex = multiDex;
72 | return this;
73 | }
74 |
75 | @NonNull
76 | public DexProcessBuilder setMainDexList(File mainDexList) {
77 | mMainDexList = mainDexList;
78 | return this;
79 | }
80 |
81 | @NonNull
82 | public DexProcessBuilder addInput(File input) {
83 | mInputs.add(input);
84 | return this;
85 | }
86 |
87 | @NonNull
88 | public DexProcessBuilder addInputs(@NonNull Collection inputs) {
89 | mInputs.addAll(inputs);
90 | return this;
91 | }
92 |
93 | @NonNull
94 | public DexProcessBuilder additionalParameters(@NonNull List params) {
95 | if (mAdditionalParams == null) {
96 | mAdditionalParams = Lists.newArrayListWithExpectedSize(params.size());
97 | }
98 |
99 | mAdditionalParams.addAll(params);
100 |
101 | return this;
102 | }
103 |
104 | @NonNull
105 | public File getOutputFile() {
106 | return mOutputFile;
107 | }
108 |
109 | public boolean isVerbose() {
110 | return mVerbose;
111 | }
112 |
113 | public boolean isIncremental() {
114 | return mIncremental;
115 | }
116 |
117 | public boolean isNoOptimize() {
118 | return mNoOptimize;
119 | }
120 |
121 | public boolean isMultiDex() {
122 | return mMultiDex;
123 | }
124 |
125 | public File getMainDexList() {
126 | return mMainDexList;
127 | }
128 |
129 | public Set getInputs() {
130 | return mInputs;
131 | }
132 |
133 | @NonNull
134 | public JavaProcessInfo build(
135 | @NonNull BuildToolInfo buildToolInfo,
136 | @NonNull DexOptions dexOptions) throws ProcessException {
137 |
138 | Revision buildToolsRevision = buildToolInfo.getRevision();
139 | checkState(
140 | !mMultiDex
141 | || buildToolsRevision.compareTo(MIN_MULTIDEX_BUILD_TOOLS_REV) >= 0,
142 | "Multi dex requires Build Tools " +
143 | MIN_MULTIDEX_BUILD_TOOLS_REV.toString() +
144 | " / Current: " +
145 | buildToolsRevision.toShortString());
146 |
147 |
148 | ProcessInfoBuilder builder = new ProcessInfoBuilder();
149 | builder.addEnvironments(mEnvironment);
150 |
151 |
152 | String dx = dxPath;
153 | if (dx == null || !new File(dx).isFile()) {
154 | dx = buildToolInfo.getPath(BuildToolInfo.PathId.DX_JAR);
155 | }
156 | if (dx == null || !new File(dx).isFile()) {
157 | throw new IllegalStateException("dx.jar is missing");
158 | }
159 |
160 | builder.setClasspath(dx);
161 | builder.setMain("com.android.dx.command.Main");
162 |
163 | if (dexOptions.getJavaMaxHeapSize() != null) {
164 | builder.addJvmArg("-Xmx" + dexOptions.getJavaMaxHeapSize());
165 | } else {
166 | builder.addJvmArg("-Xmx1024M");
167 | }
168 |
169 | builder.addArgs("--dex");
170 |
171 | if (mVerbose) {
172 | builder.addArgs("--verbose");
173 | }
174 |
175 | if (dexOptions.getJumboMode()) {
176 | builder.addArgs("--force-jumbo");
177 | }
178 |
179 | if (mIncremental) {
180 | builder.addArgs("--incremental", "--no-strict");
181 | }
182 |
183 | if (mNoOptimize) {
184 | builder.addArgs("--no-optimize");
185 | }
186 |
187 | // only change thread count is build tools is 22.0.2+
188 | if (buildToolsRevision.compareTo(MIN_MULTI_THREADED_DEX_BUILD_TOOLS_REV) >= 0) {
189 | Integer threadCount = dexOptions.getThreadCount();
190 | if (threadCount == null) {
191 | builder.addArgs("--num-threads=4");
192 | } else {
193 | builder.addArgs("--num-threads=" + threadCount);
194 | }
195 | }
196 |
197 | if (mMultiDex) {
198 | builder.addArgs("--multi-dex");
199 |
200 | if (mMainDexList != null) {
201 | builder.addArgs("--main-dex-list", mMainDexList.getAbsolutePath());
202 | }
203 | }
204 |
205 | if (mAdditionalParams != null) {
206 | for (String arg : mAdditionalParams) {
207 | builder.addArgs(arg);
208 | }
209 | }
210 |
211 |
212 | builder.addArgs("--output", mOutputFile.getAbsolutePath());
213 |
214 | // input
215 | builder.addArgs(getFilesToAdd(buildToolsRevision));
216 |
217 | return builder.createJavaProcess();
218 | }
219 |
220 | @NonNull
221 | public List getFilesToAdd(@Nullable Revision buildToolsRevision)
222 | throws ProcessException {
223 | // remove non-existing files.
224 | Set existingFiles = Sets.filter(mInputs, new Predicate() {
225 | @Override
226 | public boolean apply(@Nullable File input) {
227 | return input != null && input.exists();
228 | }
229 | });
230 |
231 | if (existingFiles.isEmpty()) {
232 | throw new ProcessException("No files to pass to dex.");
233 | }
234 |
235 | Collection files = existingFiles;
236 |
237 | // sort the inputs
238 | if (buildToolsRevision != null && buildToolsRevision.compareTo(FIXED_DX_MERGER) < 0) {
239 | List sortedList = Lists.newArrayList(existingFiles);
240 | Collections.sort(sortedList, new Comparator() {
241 | @Override
242 | public int compare(File file, File file2) {
243 | boolean file2IsDir = file2.isDirectory();
244 | if (file.isDirectory()) {
245 | return file2IsDir ? 0 : -1;
246 | } else if (file2IsDir) {
247 | return 1;
248 | }
249 |
250 | long diff = file.length() - file2.length();
251 | return diff > 0 ? 1 : (diff < 0 ? -1 : 0);
252 | }
253 | });
254 |
255 | files = sortedList;
256 | }
257 |
258 | // convert to String-based paths.
259 | List filePathList = Lists.newArrayListWithCapacity(files.size());
260 | for (File f : files) {
261 | filePathList.add(f.getAbsolutePath());
262 | }
263 |
264 | return filePathList;
265 | }
266 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/com/dim/HookProcessOutputHandler.groovy:
--------------------------------------------------------------------------------
1 | package com.dim;
2 |
3 | import com.android.ide.common.blame.ParsingProcessOutputHandler;
4 | import com.android.ide.common.process.BaseProcessOutputHandler;
5 | import com.android.ide.common.process.ProcessException;
6 | import com.android.ide.common.process.ProcessOutput
7 | import com.dim.bean.Patch
8 | import com.dim.common.Logger
9 | import org.apache.commons.io.FileUtils;
10 |
11 | import java.io.*;
12 |
13 | /**
14 | * HookProcessOutputHandler
15 | * Created by dim on 2016-07-10.
16 | */
17 | public class HookProcessOutputHandler extends BaseProcessOutputHandler {
18 |
19 | private ParsingProcessOutputHandler mOutputHandler;
20 | private Patch patch;
21 | private File output;
22 |
23 | public HookProcessOutputHandler(ParsingProcessOutputHandler outputHandler, Patch patch, File output) {
24 | mOutputHandler = outputHandler;
25 | this.patch = patch;
26 | this.output = output;
27 | }
28 |
29 | @Override
30 | public void handleOutput(ProcessOutput processOutput) throws ProcessException {
31 | mOutputHandler.handleOutput(processOutput);
32 | String stdout = processOutput.getStandardOutputAsString();
33 | if (!stdout.isEmpty()) {
34 | String info = "";
35 | String[] split = stdout.split("\n");
36 | int classIndex = 1;
37 | for (String item : split) {
38 | if (item.startsWith("create dex...")) {
39 | if (classIndex == 1) {
40 | info += "classes.dex\n";
41 | } else {
42 | info += "classes" + classIndex + ".dex\n";
43 | }
44 | classIndex++;
45 | } else {
46 |
47 | if (!item.startsWith("processing archive")) {
48 | info += item.substring(11, item.length() - 3) + "\n";
49 | }
50 | }
51 | }
52 |
53 | try {
54 | FileUtils.writeStringToFile(patch.getDexInfoFile(), info);
55 | } catch (IOException e) {
56 | e.printStackTrace();
57 | }
58 |
59 | try {
60 | FileUtils.copyDirectory(output, patch.getClassFile());
61 | } catch (IOException e) {
62 | e.printStackTrace();
63 | }
64 | }
65 | String stderr = processOutput.getErrorOutputAsString();
66 | if (!stderr.isEmpty()) {
67 | Logger.dim("dex 错误 " + stderr);
68 | }
69 |
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/com/dim/MultiDexAndroidBuilder.groovy:
--------------------------------------------------------------------------------
1 | package com.dim
2 |
3 | import com.android.annotations.NonNull
4 | import com.android.build.gradle.internal.transforms.DexTransform
5 | import com.android.builder.core.AndroidBuilder
6 | import com.android.builder.core.DexOptions
7 | import com.android.builder.core.DexProcessBuilder
8 | import com.android.builder.core.ErrorReporter
9 | import com.android.ide.common.blame.ParsingProcessOutputHandler
10 | import com.android.ide.common.process.JavaProcessExecutor
11 | import com.android.ide.common.process.ProcessException
12 | import com.android.ide.common.process.ProcessExecutor
13 | import com.android.ide.common.process.ProcessOutputHandler
14 | import com.android.utils.ILogger
15 | import com.dim.bean.Patch
16 | import com.dim.common.Logger
17 | import com.google.common.collect.ImmutableList
18 |
19 | import java.lang.reflect.Field
20 | import java.lang.reflect.Method
21 | import java.util.zip.ZipEntry
22 | import java.util.zip.ZipFile
23 |
24 | import static com.android.SdkConstants.DOT_CLASS
25 | import static com.android.SdkConstants.DOT_DEX
26 |
27 | /**
28 | * proxy the androidBuilder that plugin 1.5.0 to add '--minimal-main-dex' options.
29 | *
30 | * @author ceabie
31 | */
32 | public class MultiDexAndroidBuilder extends AndroidBuilder {
33 |
34 | Set mAddParams;
35 | Patch patch
36 | String dxPath;
37 |
38 | public MultiDexAndroidBuilder(String projectId, String createdBy, ProcessExecutor processExecutor, JavaProcessExecutor javaProcessExecutor, ErrorReporter errorReporter, ILogger logger, boolean verboseExec, Set addParams, Patch patch, String dxPath) {
39 | super(projectId, createdBy, processExecutor, javaProcessExecutor, errorReporter, logger, verboseExec)
40 | this.mAddParams = addParams;
41 | this.patch = patch;
42 | this.dxPath = dxPath
43 | }
44 |
45 | @Override
46 | public void convertByteCode(Collection inputs,
47 | File outDexFolder,
48 | boolean multidex,
49 | File mainDexList,
50 | DexOptions dexOptions,
51 | List additionalParameters,
52 | boolean incremental,
53 | boolean optimize,
54 | ProcessOutputHandler processOutputHandler)
55 |
56 | throws IOException, InterruptedException, ProcessException {
57 | if (mAddParams != null) {
58 | if (additionalParameters == null) {
59 | additionalParameters = []
60 | }
61 | mAddParams.each {
62 | additionalParameters += it //'--minimal-main-dex'
63 | }
64 | }
65 | if (patch == null) {
66 | super.convertByteCode(inputs, outDexFolder, multidex, mainDexList, dexOptions,
67 | additionalParameters, incremental, optimize, processOutputHandler);
68 | } else {
69 | HookProcessOutputHandler hookProcessOutputHandler = new HookProcessOutputHandler(processOutputHandler as ParsingProcessOutputHandler, patch, outDexFolder);
70 |
71 | try {
72 |
73 | ImmutableList.Builder verifiedInputs = ImmutableList.builder();
74 | for (File input : inputs) {
75 | if (checkLibraryClassesJar(input)) {
76 | verifiedInputs.add(input);
77 | }
78 | }
79 |
80 | DexProcessBuilder builder = new HockDexProcessBuilder(outDexFolder,dxPath);
81 |
82 | builder.setVerbose(true)
83 | .setIncremental(incremental)
84 | .setNoOptimize(!optimize)
85 | .setMultiDex(multidex)
86 | .setMainDexList(mainDexList)
87 | .addInputs(verifiedInputs.build());
88 |
89 | if (additionalParameters != null) {
90 | builder.additionalParameters(additionalParameters);
91 | }
92 | def method = AndroidBuilder.class.getDeclaredMethod("runDexer", DexProcessBuilder.class, DexOptions.class, ProcessOutputHandler.class);
93 | method.setAccessible(true);
94 | method.invoke(this, builder, dexOptions, hookProcessOutputHandler)
95 |
96 | } catch (Exception e) {
97 | e.printStackTrace();
98 | super.convertByteCode(inputs, outDexFolder, multidex, mainDexList, dexOptions,
99 | additionalParameters, incremental, optimize, hookProcessOutputHandler);
100 | }
101 |
102 |
103 | }
104 | }
105 | /**
106 | * Returns true if the library (jar or folder) contains class files, false otherwise.
107 | */
108 | private static boolean checkLibraryClassesJar(@NonNull File input) throws IOException {
109 |
110 | if (!input.exists()) {
111 | return false;
112 | }
113 |
114 | if (input.isDirectory()) {
115 | return checkFolder(input);
116 | }
117 |
118 | ZipFile zipFile = new ZipFile(input);
119 | try {
120 | Enumeration extends ZipEntry> entries = zipFile.entries();
121 | while (entries.hasMoreElements()) {
122 | String name = entries.nextElement().getName();
123 | if (name.endsWith(DOT_CLASS) || name.endsWith(DOT_DEX)) {
124 | return true;
125 | }
126 | }
127 | return false;
128 | } finally {
129 | zipFile.close();
130 | }
131 | }
132 | /**
133 | * Returns true if this folder or one of its subfolder contains a class file, false otherwise.
134 | */
135 | private static boolean checkFolder(@NonNull File folder) {
136 | File[] subFolders = folder.listFiles();
137 | if (subFolders != null) {
138 | for (File childFolder : subFolders) {
139 | if (childFolder.isFile()) {
140 | String name = childFolder.getName();
141 | if (name.endsWith(DOT_CLASS) || name.endsWith(DOT_DEX)) {
142 | return true;
143 | }
144 | }
145 | if (childFolder.isDirectory()) {
146 | // if childFolder returns false, continue search otherwise return success.
147 | if (checkFolder(childFolder)) {
148 | return true;
149 | }
150 | }
151 | }
152 | }
153 | return false;
154 | }
155 |
156 |
157 | public
158 | static void proxyAndroidBuilder(DexTransform transform, Collection addParams, Patch patch1,String dxPath) {
159 | if (addParams != null && addParams.size() > 0) {
160 | def get = accessibleField(DexTransform.class, "androidBuilder").get(transform);
161 | if (!get.getClass().getSimpleName().equals("MultiDexAndroidBuilder")) {
162 | def builder = getProxyAndroidBuilder(get, addParams, patch1,dxPath);
163 | accessibleField(DexTransform.class, "androidBuilder")
164 | .set(transform, builder)
165 |
166 | }
167 | }
168 | }
169 |
170 | private static AndroidBuilder getProxyAndroidBuilder(AndroidBuilder orgAndroidBuilder,
171 | Collection addParams, Patch patch,String dxPath) {
172 | MultiDexAndroidBuilder myAndroidBuilder = new MultiDexAndroidBuilder(
173 | orgAndroidBuilder.mProjectId,
174 | orgAndroidBuilder.mCreatedBy,
175 | orgAndroidBuilder.getProcessExecutor(),
176 | orgAndroidBuilder.mJavaProcessExecutor,
177 | orgAndroidBuilder.getErrorReporter(),
178 | orgAndroidBuilder.getLogger(),
179 | orgAndroidBuilder.mVerboseExec, addParams, patch,dxPath)
180 |
181 | myAndroidBuilder.setTargetInfo(
182 | orgAndroidBuilder.getSdkInfo(),
183 | orgAndroidBuilder.getTargetInfo(),
184 | orgAndroidBuilder.mLibraryRequests)
185 |
186 | myAndroidBuilder;
187 |
188 | }
189 |
190 | private static Field accessibleField(Class cls, String field) {
191 | Field f = cls.getDeclaredField(field)
192 | f.setAccessible(true)
193 | return f;
194 | }
195 |
196 | }
197 |
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/com/dim/TinkerPlugin.groovy:
--------------------------------------------------------------------------------
1 | package com.dim
2 |
3 | import com.android.build.api.transform.Format
4 | import com.android.build.api.transform.Transform
5 | import com.android.build.gradle.internal.transforms.DexTransform
6 | import com.dim.bean.Config
7 | import com.dim.bean.Dex
8 | import com.dim.bean.DexHolder
9 | import com.dim.bean.HashRecord
10 | import com.dim.bean.Patch
11 | import com.dim.common.IoUtils
12 | import com.dim.common.Logger
13 | import org.apache.commons.codec.digest.DigestUtils
14 | import org.apache.commons.io.FileUtils
15 | import org.apache.commons.io.IOUtils
16 | import org.apache.tools.ant.taskdefs.condition.Os
17 | import org.gradle.api.InvalidUserDataException
18 | import org.gradle.api.Plugin
19 | import org.gradle.api.Project
20 | import org.gradle.api.Task
21 | import org.gradle.api.artifacts.Dependency
22 |
23 | import java.util.jar.JarEntry
24 | import java.util.jar.JarFile
25 |
26 | /**
27 | * Created by dim on 16/7/9.
28 | */
29 | class TinkerPlugin implements Plugin {
30 |
31 | private static final String BASIS_DIR = "basis"
32 | private static final String PATCH_SERIAL_NUMBER = "serial_number"
33 | File basisDir
34 | Project project;
35 | Map allProjectMap = new HashMap<>();
36 |
37 | public static String getProperty(Project project, String property) {
38 | if (project.hasProperty(property)) {
39 | return project.getProperties()[property];
40 | }
41 | return null;
42 | }
43 |
44 | def getAndroid() {
45 | return project.android
46 | }
47 |
48 | @Override
49 | void apply(Project project) {
50 | this.project = project;
51 | project.afterEvaluate {
52 | project.android.applicationVariants.each { variant ->
53 |
54 | String basisPath = getProperty(project, BASIS_DIR);
55 | if (basisPath != null) {
56 | basisDir = new File(basisPath);
57 | }
58 | //todo versionCode 暂时从defaultConfig 中获取
59 | Patch patch = new Patch(project.projectDir.getAbsolutePath(), project.android.defaultConfig.versionCode + "", variant.name, (basisDir != null && basisDir.exists()));
60 | patch.setPatchSerialNumber(getProperty(project, PATCH_SERIAL_NUMBER))
61 | Config config = new Config(android.buildToolsVersion, android.compileSdkVersion, android.sdkDirectory.absolutePath, variant.flavorName, variant.buildType.name,);
62 |
63 | boolean minifyEnabled = variant.buildType.minifyEnabled
64 | if (minifyEnabled) {
65 | String transformClassesAndResourcesWithProguardForName = "transformClassesAndResourcesWithProguardFor${variant.name.capitalize()}";
66 | def transformClassesAndResourcesWithProguardForTask = project.tasks.findByName(transformClassesAndResourcesWithProguardForName);
67 | if (transformClassesAndResourcesWithProguardForTask) {
68 | Transform transform = transformClassesAndResourcesWithProguardForTask.transform
69 | def outputProvider = transformClassesAndResourcesWithProguardForTask.outputStream.asOutput()
70 | String mergedJar = outputProvider.getContentLocation("main",
71 | transform.getOutputTypes(),
72 | transform.getScopes(), Format.JAR)
73 | patch.setCombinedJar(mergedJar);
74 | } else {
75 | Logger.dim("task ${transformClassesAndResourcesWithProguardForName} not found ")
76 | return;
77 | }
78 | } else {
79 | def transformClassesWithJarMergingFor = project.tasks["transformClassesWithJarMergingFor${variant.name.capitalize()}"];
80 | if (transformClassesWithJarMergingFor) {
81 | Transform transform = transformClassesWithJarMergingFor.transform
82 | def outputProvider = transformClassesWithJarMergingFor.outputStream.asOutput()
83 | String mergedJar = outputProvider.getContentLocation("combined",
84 | transform.getOutputTypes(),
85 | transform.getScopes(), Format.JAR)
86 | patch.setCombinedJar(mergedJar);
87 | } else {
88 | Logger.dim("task transformClassesWithJarMergingFor${variant.name.capitalize()} not found ")
89 | return;
90 | }
91 | }
92 | findResPath(project, config, variant)
93 | config.save(patch.getPluginConfigFile())
94 | if (basisDir != null && minifyEnabled) {
95 | //适应mapping
96 | String buildTypeName = variant.buildType.name;
97 | def buildType = android.buildTypes.properties["asMap"][buildTypeName];
98 | List proguardFiles = buildType.proguardFiles;
99 | if (proguardFiles.size() > 0) {
100 |
101 | if (proguardFiles.size() > 1) {
102 | proguardFiles.set(1, new File(basisDir.absolutePath + "/" + Patch.BASIS_FILE_NAME + "/" + "proguard-mapping.pro"));
103 | } else {
104 | proguardFiles.set(0, new File(basisDir.absolutePath + "/" + Patch.BASIS_FILE_NAME + "/" + "proguard-mapping.pro"));
105 | }
106 | }
107 | }
108 |
109 | def transformClassesWithDexFor = project.tasks.findByName("transformClassesWithDexFor${variant.name.capitalize()}");
110 | if (transformClassesWithDexFor) {
111 | String assemblePatch = "assemble${variant.name.capitalize()}Patch";
112 | Logger.dim(assemblePatch);
113 | project.task(assemblePatch) << {
114 | }
115 | def assemblePatchTask = project.tasks[assemblePatch];
116 | assemblePatchTask.dependsOn transformClassesWithDexFor;
117 |
118 | DexTransform dexTransform = transformClassesWithDexFor.transform
119 | if (dexTransform) {
120 | transformClassesWithDexFor.doFirst {
121 | if (!patch.mainDexListFile.exists()) {
122 | // 记录hash值;
123 | if (!patch.hashFile.exists()) {
124 | patch.hashFile.createNewFile();
125 | } else {
126 | patch.hashFile.delete();
127 | patch.hashFile.createNewFile();
128 | }
129 | Set addParams = new HashSet<>();
130 | File fileAdtMainList = dexTransform.mainDexListFile
131 | if (fileAdtMainList != null) {
132 | addParams.add("--main-dex-list=" + fileAdtMainList.absolutePath);
133 |
134 | }
135 | addParams.add("--minimal-main-dex");
136 | //每个dex 最多40k 为后面添加留下空间.
137 | addParams.add("--set-max-idx-number=400000");
138 | addParams.add("--verbose");
139 | // 替换 AndroidBuilder
140 | MultiDexAndroidBuilder.proxyAndroidBuilder(dexTransform,
141 | addParams, patch,project.getParent().projectDir.getAbsolutePath()+"/buildSrc/libs/dx.jar")
142 | if (patch.combinedJar != null) {
143 | processJar(patch.hashFile, new File(patch.combinedJar));
144 | }
145 | if (fileAdtMainList != null) {
146 | project.copy {
147 | from fileAdtMainList
148 | into patch.basisFile
149 | }
150 | }
151 | def mapFile = new File("${project.buildDir}/outputs/mapping/${variant.dirName}/mapping.txt")
152 | if (mapFile.exists() && !patch.mappingFile.exists()) {
153 | FileUtils.copyFile(mapFile, patch.mappingFile)
154 | File mappingProguardFile = new File(patch.mappingFile.getParent(), "proguard-mapping.pro")
155 | FileUtils.writeStringToFile(mappingProguardFile, "-applymapping ${patch.mappingFile.absolutePath}\n-dontwarn **")
156 | }
157 | def rFile = new File("${project.buildDir}/generated/source/r/${variant.dirName}");
158 | File targetRFile = patch.RFile;
159 | if (rFile.exists() && targetRFile.listFiles() != null) {
160 | FileUtils.copyDirectory(rFile, targetRFile);
161 | }
162 | } else {
163 |
164 | if (basisDir == null) {
165 | dexTransform.secondaryFileInputs
166 | Set addParams = new HashSet<>();
167 | File fileAdtMainList = dexTransform.mainDexListFile
168 |
169 | addParams.add("--main-dex-list=" + fileAdtMainList.absolutePath);
170 | addParams.add("--minimal-main-dex");
171 | //每个dex 最多40k 为后面添加留下空间.
172 | addParams.add("--set-max-idx-number=400000");
173 | addParams.add("--verbose");
174 | // 替换 AndroidBuilder
175 | MultiDexAndroidBuilder.proxyAndroidBuilder(dexTransform,
176 | addParams, null,null)
177 | }
178 | }
179 |
180 | }
181 | assemblePatchTask << {
182 | if (patch.getCombinedJar() != null) {
183 | Logger.dim("开始生成插件")
184 | File fileAdtMainList = dexTransform.mainDexListFile
185 | HashRecord hashRecord = new HashRecord(patch.hashFile);
186 | DexHolder classHolder = new DexHolder(patch.dexInfoFile);
187 | Dex cl = new Dex(fileAdtMainList);
188 | classHolder.setMainClass(cl);
189 | createDex(patch, classHolder, hashRecord, new File(patch.combinedJar));
190 | }
191 | }
192 |
193 | }
194 | }
195 | }
196 | }
197 |
198 | }
199 |
200 | def createDex(Patch patch, DexHolder classHolder, HashRecord hashRecord, File jarFile) {
201 | if (jarFile) {
202 | def file = new JarFile(jarFile);
203 | DexHolder.Entity mainEntity = classHolder.getMainClassDex();
204 | Enumeration enumeration = file.entries();
205 | while (enumeration.hasMoreElements()) {
206 | JarEntry jarEntry = (JarEntry) enumeration.nextElement();
207 | String entryName = jarEntry.getName();
208 | InputStream inputStream = file.getInputStream(jarEntry);
209 | def byteArray = IOUtils.toByteArray(inputStream, jarEntry.size);
210 | def hash = DigestUtils.shaHex(byteArray);
211 | DexHolder.Entity dexEntity = classHolder.dexEntityOfClassName(entryName);
212 | if (dexEntity == null) {
213 | dexEntity = mainEntity;
214 | }
215 | File targetFile = new File(patch.getPatchPath() + File.separator
216 | + dexEntity.dexName + File.separator + entryName);
217 | IoUtils.mkdir(targetFile);
218 | FileUtils.writeByteArrayToFile(targetFile, byteArray);
219 | if (hashRecord.hasChange(entryName, hash)) {
220 | dexEntity.hasChange = true;
221 | Logger.dim("change : " + entryName)
222 | }
223 | inputStream.close();
224 | }
225 | file.close();
226 | }
227 |
228 | classHolder.dexList.values().each {
229 | if (it.hasChange) {
230 | dex(project, new File(patch.getPatchPath() + File.separator
231 | + it.dexName), patch.getPatchPath());
232 | File old = new File(patch.patchPath + File.separator + "classes.dex");
233 | File rname = new File(patch.patchPath + File.separator + it.dexName + ".dex");
234 | boolean to = old.renameTo(rname);
235 | Logger.dim(old.getAbsolutePath()+ " -> "+rname.getAbsolutePath() + " "+to);
236 | }
237 | }
238 |
239 | }
240 |
241 |
242 | public static dex(Project project, File classDir, String patchFileName) {
243 | if (classDir.listFiles().size()) {
244 | def sdkDir
245 | Properties properties = new Properties()
246 | File localProps = project.rootProject.file("local.properties")
247 | if (localProps.exists()) {
248 | properties.load(localProps.newDataInputStream())
249 | sdkDir = properties.getProperty("sdk.dir")
250 | } else {
251 | sdkDir = System.getenv("ANDROID_HOME")
252 | }
253 | if (sdkDir) {
254 | def cmdExt = Os.isFamily(Os.FAMILY_WINDOWS) ? '.bat' : ''
255 | def stdout = new ByteArrayOutputStream()
256 | project.exec {
257 | commandLine "${sdkDir}/build-tools/${project.android.buildToolsVersion}/dx${cmdExt}",
258 | '--dex',
259 | "--output=${patchFileName}",
260 | "${classDir.absolutePath}"
261 | standardOutput = stdout
262 | }
263 | println(" \"${sdkDir}/build-tools/${project.android.buildToolsVersion}/dx${cmdExt}" +
264 | " '--dex',\n" +
265 | " \"--output=${patchFileName}\",\n" +
266 | " \"${classDir.absolutePath}\"")
267 | def error = stdout.toString().trim()
268 | if (error) {
269 | println "dex error:" + error
270 | }
271 | } else {
272 | throw new InvalidUserDataException('$ANDROID_HOME is not defined')
273 | }
274 | }
275 | }
276 |
277 | public void processJar(File hashFile, File jarFile) {
278 | if (jarFile) {
279 | Logger.dim("processJar:" + jarFile.absolutePath)
280 | def file = new JarFile(jarFile);
281 | Enumeration enumeration = file.entries();
282 | while (enumeration.hasMoreElements()) {
283 | JarEntry jarEntry = (JarEntry) enumeration.nextElement();
284 | String entryName = jarEntry.getName();
285 | InputStream inputStream = file.getInputStream(jarEntry);
286 | def hash = DigestUtils.shaHex(IOUtils.toByteArray(inputStream, jarEntry.size));
287 | if (hashFile != null) {
288 | hashFile.append(HashRecord.getItemString(entryName, hash))
289 | }
290 | inputStream.close();
291 | }
292 | file.close();
293 | }
294 | }
295 | /**
296 | * 生成配置文件,给as 插件使用.
297 | * @param baseVariant
298 | */
299 | def findResPath(Project project, Config config, def variant) {
300 |
301 | //找到本身的res 靠前资源覆盖后面的资源
302 | findResPath(variant, config)
303 | if (project.configurations.hasProperty("compile")) {
304 | project.configurations.compile.dependencies.findAll {
305 | findDependencyProject(it, config)
306 | }
307 | }
308 | if (project.configurations.hasProperty("${variant.name}Compile")) {
309 | project.configurations.getByName("${variant.name}Compile").dependencies.findAll {
310 | findDependencyProject(it, config)
311 | }
312 | }
313 |
314 | }
315 |
316 | private void findDependencyProject(Dependency it, Config config) {
317 | if (it.hasProperty('dependencyProject')) {
318 | def dependenciesP = allProjectMap.get(it.name);
319 | if (dependenciesP != null) {
320 | String buildType = "release";
321 | def configuration = it.properties.get("configuration");
322 | if (configuration != null && !configuration.equals("default")) {
323 | buildType = configuration;
324 | } else {
325 | if (dependenciesP.hasProperty("android")) {
326 | def android = dependenciesP.properties.get("android");
327 | if (android.hasProperty("defaultPublishConfig")) {
328 | buildType = android.properties.get("defaultPublishConfig");
329 | }
330 | }
331 |
332 | }
333 | def android = dependenciesP.properties.get("android");
334 | if (android != null && android.hasProperty("libraryVariants")) {
335 | android.libraryVariants.all { bv ->
336 | if (bv.name.equals(buildType)) {
337 | findResPath(dependenciesP, config, bv)
338 | }
339 | }
340 | } else {
341 | //添加默认的res地址
342 | config.addResPath(dependenciesP.getProjectDir().absolutePath + "/src/main/res");
343 | }
344 | }
345 | }
346 | }
347 |
348 | static void findResPath(def variant, Config config) {
349 | for (int i = variant.sourceSets.size() - 1; i >= 0; i--) {
350 | Collection directories = variant.sourceSets.get(i).resDirectories
351 | for (int j = directories.size() - 1; j >= 0; j--) {
352 | String resPath = directories.getAt(j).absolutePath
353 | config.addResPath(resPath)
354 | }
355 | }
356 | }
357 |
358 | }
359 |
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/com/dim/bean/Config.groovy:
--------------------------------------------------------------------------------
1 | package com.dim.bean
2 |
3 | import com.dim.common.Logger;
4 | import com.google.gson.Gson;
5 |
6 | import org.apache.commons.io.FileUtils
7 |
8 | /**
9 | * Config
10 | * Created by dim on 2016-06-22.
11 | */
12 | public class Config {
13 |
14 | private String flavor;
15 | private String buildType;
16 | public String buildToolsVersion;
17 | public String compileSdkVersion;
18 | public String sdkPath;
19 | private String pushTargetFilePath = "/sdcard/hot/";
20 |
21 | private List resList = new ArrayList<>();
22 |
23 | public Config(String buildToolsVersion, String compileSdkVersion, String sdkPath,String flavor,String buildType ) {
24 | this.buildToolsVersion = buildToolsVersion;
25 | this.compileSdkVersion = compileSdkVersion;
26 | this.sdkPath = sdkPath;
27 | this.flavor = flavor;
28 | this.buildType = buildType;
29 | }
30 |
31 | public boolean addResPath(String resPath) {
32 |
33 | resPath = resPath.trim();
34 | // if (!new File(resPath).exists()) {
35 | // return false;
36 | // }
37 | if (resList.contains(resPath)) {
38 | return false;
39 | }
40 | resList.add(resPath);
41 | return true;
42 | }
43 |
44 | public void save(File file) {
45 | try {
46 |
47 | Logger.dim("save ---> "+file.toString());
48 | FileUtils.writeStringToFile(file, toString());
49 | } catch (IOException e) {
50 | e.printStackTrace();
51 | }
52 | }
53 |
54 |
55 | @Override
56 | public String toString() {
57 | return new Gson().toJson(this);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/com/dim/bean/Dex.groovy:
--------------------------------------------------------------------------------
1 | package com.dim.bean;
2 |
3 | import org.apache.commons.io.FileUtils
4 |
5 | /**
6 | * Dex
7 | * Created by dim on 2016-07-10.
8 | */
9 | public class Dex {
10 |
11 | public Set classs = new HashSet<>();
12 |
13 | public Dex(File patch) {
14 | try {
15 | String s = FileUtils.readFileToString(patch);
16 | String[] split = s.split("\n");
17 | for (String s1 : split) {
18 | if (s1 != null && s1.length() > 0) {
19 | classs.add(s1);
20 | }
21 | }
22 | } catch (IOException e) {
23 | e.printStackTrace();
24 | }
25 | }
26 |
27 | public Dex() {
28 | }
29 |
30 | public void addClassItem(String item) {
31 | classs.add(item);
32 | }
33 |
34 | // @Override
35 | // public boolean equals(Object o) {
36 | // if (this == o) return true;
37 | // if (!(o instanceof Dex)) return false;
38 | //
39 | // Dex aClass = (Dex) o;
40 | //
41 | // return classs != null ? classs.equals(aClass.classs) : aClass.classs == null;
42 | // }
43 |
44 | public Object[] getAll() {
45 | return classs.toArray();
46 | }
47 |
48 | public boolean contains(String name) {
49 | return classs.contains(name);
50 | }
51 |
52 | public void remove(String className) {
53 | classs.remove(className);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/com/dim/bean/DexHolder.groovy:
--------------------------------------------------------------------------------
1 | package com.dim.bean
2 | /**
3 | * DexHolder
4 | * Created by dim on 2016-07-10.
5 | */
6 | public class DexHolder {
7 |
8 | public Map dexList = new HashMap<>();
9 |
10 | public DexHolder(File file) {
11 | try {
12 | String line;
13 | BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
14 | Entity entity = null;
15 | while ((line = reader.readLine()) != null) {
16 | if (line.matches("classes\\d?.dex")) {
17 | entity = new Entity(line);
18 | dexList.put(line, entity);
19 | } else {
20 | if (line.length() > 0)
21 | entity.addClassItem(line);
22 | }
23 | }
24 | } catch (IOException e) {
25 | e.printStackTrace();
26 | }
27 | }
28 |
29 | public Entity getMainClassDex() {
30 | return dexList.get("classes.dex");
31 | }
32 |
33 | public void setMainClass(Dex mainClass) {
34 | Entity entity = dexList.get("classes.dex");
35 | if (!entity.equalsClass(mainClass)) {
36 | entity.hasChange = true;
37 | for (Object o : mainClass.getAll()) {
38 | Entity dexEntity = dexEntityOfClassName(o.toString());
39 | if (dexEntity == null || dexEntity.dexFile.equals("classes.dex")) {
40 | //过滤
41 | } else {
42 | dexEntity.hasChange = true;
43 | dexEntity.remove(o.toString());
44 | }
45 | }
46 | }
47 | for (Object o : mainClass.getAll()) {
48 | getMainClassDex().addClassItem(o.toString());
49 | }
50 | }
51 |
52 | public Entity dexEntityOfClassName(String classname) {
53 |
54 | for (Map.Entry entityEntry : dexList.entrySet()) {
55 | if (entityEntry.getValue().hasClass(classname)) {
56 | return entityEntry.getValue();
57 | }
58 | }
59 | return null;
60 | }
61 |
62 | public class Entity {
63 |
64 | public String dexFile;
65 | public boolean hasChange;
66 | public Dex dex;
67 | public String dexName;
68 |
69 | public Entity(String dexFile) {
70 | dex = new Dex();
71 | this.dexFile = dexFile;
72 | dexName = dexFile.substring(0, dexFile.length() - 4);
73 | }
74 |
75 | public void addClassItem(String line) {
76 | dex.addClassItem(line);
77 | }
78 |
79 | public boolean equalsClass(Dex mainClass) {
80 | return dex.equals(mainClass);
81 | }
82 |
83 | public boolean hasClass(String className) {
84 | return dex.contains(className);
85 | }
86 |
87 | public void remove(String className) {
88 | dex.remove(className);
89 | }
90 |
91 | public void setClass(Dex dex) {
92 | this.dex = dex;
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/com/dim/bean/HashRecord.groovy:
--------------------------------------------------------------------------------
1 | package com.dim.bean;
2 |
3 |
4 | /**
5 | * HashRecord
6 | * Created by dim on 2016-07-10.
7 | */
8 | public class HashRecord {
9 |
10 | public Map map = new HashMap<>();
11 |
12 | public static String getItemString(String path, String hash) {
13 | return String.format("%s:%s\n", path, hash);
14 | }
15 |
16 | public HashRecord(File file) {
17 | try {
18 | String line;
19 | BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
20 | while ((line = reader.readLine()) != null) {
21 | String[] split = line.split(":");
22 | if (split.length == 2) {
23 | map.put(split[0], split[1]);
24 | }
25 | }
26 | } catch (IOException e) {
27 | e.printStackTrace();
28 | }
29 | }
30 |
31 | public boolean hasChange(String name, String hash) {
32 | String values = map.get(name);
33 | if (values != null) {
34 | return !values.equals(hash);
35 | }
36 | return false;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/com/dim/bean/Patch.groovy:
--------------------------------------------------------------------------------
1 | package com.dim.bean
2 |
3 | import com.dim.common.IoUtils
4 |
5 | /**
6 | * PatchBean
7 | * Created by dim on 2016-06-19.
8 | */
9 | public class Patch {
10 |
11 | public static final String ROOT_NAME = "patch";
12 | public static final String PATCH_FILE_NAME = "patch";
13 | public static final String MAPPING_FILE_NAME = "mapping.txt";
14 | public static final String MAIN_DEX_LIST_FILE_NAME = "maindexlist.txt";
15 | public static final String HASH_FILE_NAME = "hash.txt";
16 | public static final String DEX_FILE_NAME = "dex.txt";
17 | public static final String BASIS_FILE_NAME = "basis";
18 |
19 | String version;
20 | String variantName;
21 | String appModuleFile;
22 | String combinedJar;
23 | String patchSerialNumber;
24 | String patchPath;
25 | boolean buildPatch;
26 |
27 |
28 | Patch(String appModuleFile, String version, String variantName, boolean buildPatch) {
29 | this.buildPatch = buildPatch
30 | this.appModuleFile = appModuleFile
31 | this.version = version
32 | this.variantName = variantName
33 | }
34 |
35 | public String getRootPath() {
36 |
37 | return appModuleFile + File.separator + ROOT_NAME;
38 | }
39 |
40 | public String getVersionFileName() {
41 | return "version_" + version;
42 | }
43 |
44 | public File getRootFile() {
45 | return new File(getRootPath());
46 | }
47 |
48 | private String getVariantFilePath() {
49 | return getRootPath() + File.separator + getVersionFileName() +
50 | File.separator + variantName;
51 | }
52 |
53 | public File getVariantFile() {
54 | File file = new File(getVariantFilePath());
55 | IoUtils.mkdir(file);
56 | return file;
57 | }
58 |
59 |
60 | public String getPatchSerialNumber() {
61 | if (patchSerialNumber == null) {
62 | patchSerialNumber = System.currentTimeMillis() + "";
63 | }
64 | return patchSerialNumber;
65 | }
66 |
67 | void setPatchSerialNumber(String patchSerialNumber) {
68 | this.patchSerialNumber = patchSerialNumber
69 | }
70 |
71 |
72 | public String getPatchPath() {
73 | if (patchPath == null) {
74 | patchPath = getVariantFilePath() + File.separator + PATCH_FILE_NAME + getPatchSerialNumber();
75 | }
76 | return patchPath;
77 | }
78 |
79 | public File getPatchFile() {
80 | File file = new File(getPatchPath());
81 | if (buildPatch)
82 | IoUtils.mkdir(file);
83 | return file;
84 | }
85 |
86 | public File getConfigFile() {
87 | File file = new File(getPatchPath() + File.separator + "config.txt");
88 | if (buildPatch)
89 | IoUtils.mkdir(file);
90 | return file;
91 | }
92 |
93 | public String getRFilePath() {
94 | return getVariantFilePath() + File.separator + BASIS_FILE_NAME + File.separator + "r"
95 | }
96 |
97 | public File getBasisFile() {
98 | return new File(getVariantFilePath() + File.separator + BASIS_FILE_NAME)
99 | }
100 |
101 | public File getRFile() {
102 | File file = new File(getRFilePath());
103 | IoUtils.mkdir(file);
104 | return file;
105 | }
106 |
107 | public File getMappingFile() {
108 | File file = new File(getVariantFilePath() + File.separator + BASIS_FILE_NAME + File.separator + MAPPING_FILE_NAME);
109 | IoUtils.mkdir(file);
110 | return file;
111 | }
112 |
113 | public File getMainDexListFile() {
114 | File file = new File(getMainDexListPath());
115 | IoUtils.mkdir(file);
116 | return file;
117 | }
118 |
119 | public String getMainDexListPath() {
120 |
121 | return getVariantFilePath() + File.separator + BASIS_FILE_NAME + File.separator + MAIN_DEX_LIST_FILE_NAME;
122 | }
123 |
124 |
125 | public File getDexInfoFile() {
126 | File file = new File(getVariantFilePath() + File.separator + BASIS_FILE_NAME + File.separator + DEX_FILE_NAME);
127 | IoUtils.mkdir(file);
128 | return file
129 | }
130 |
131 | public File getHashFile() {
132 | File file = new File(getVariantFilePath() + File.separator + BASIS_FILE_NAME + File.separator + HASH_FILE_NAME);
133 | IoUtils.mkdir(file);
134 | return file;
135 | }
136 |
137 |
138 | public String setCombinedJar(String combinedJar) {
139 | this.combinedJar = combinedJar;
140 | }
141 |
142 | String getCombinedJar() {
143 | return combinedJar
144 | }
145 |
146 | public File getPluginConfigFile() {
147 | File file = new File(getVariantFilePath() + File.separator + BASIS_FILE_NAME + File.separator + "config.json");
148 | IoUtils.mkdir(file);
149 | return file;
150 | }
151 |
152 | File getClassFile() {
153 | File file = new File(getVariantFilePath() + File.separator + BASIS_FILE_NAME + File.separator + "dex");
154 | IoUtils.mkdir(file);
155 | return file;
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/com/dim/common/IoUtils.groovy:
--------------------------------------------------------------------------------
1 | package com.dim.common;
2 |
3 |
4 | /**
5 | * Created by dim on 16/6/21.
6 | */
7 | public class IoUtils {
8 |
9 |
10 | public static void mkdir(String filePath) {
11 | mkdir(new File(filePath));
12 | }
13 |
14 | public static void mkdir(File file) {
15 | if (file.getName().contains(".")) {
16 | if (!file.getParentFile().exists()) {
17 | System.out.println(file.getParentFile().getPath()+ " mkdir");
18 |
19 | file.getParentFile().mkdirs();
20 | }
21 | } else {
22 | if (!file.exists()) {
23 | System.out.println(file.getPath() + " mkdir");
24 | file.mkdirs();
25 | }
26 | }
27 | }
28 |
29 | public static void delete(File file) {
30 | if (file.exists()) {
31 | file.delete();
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/buildSrc/src/main/groovy/com/dim/common/Logger.groovy:
--------------------------------------------------------------------------------
1 | package com.dim.common;
2 |
3 | /**
4 | * Logger
5 | * Created by dim on 2016-07-07.
6 | */
7 | public class Logger {
8 |
9 | public static void dim(String message) {
10 | System.out.println( message);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/buildSrc/src/main/resources/META-INF/gradle-plugins/tinker-imitator.properties:
--------------------------------------------------------------------------------
1 | implementation-class=com.dim.TinkerPlugin
2 |
--------------------------------------------------------------------------------
/demo/app-debug.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzz40500/Tinker_imitator/167022c62f486a3b3480c073bf2977f64aabcbd0/demo/app-debug.apk
--------------------------------------------------------------------------------
/demo/log_-20160718-0258.txt:
--------------------------------------------------------------------------------
1 | :copy: copy directory /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/basis/r-> /Users/dim/github/Tinker_imitator/app/build/generated/source/r/debug
2 | copy success
3 | command : cd /Users/dim/github/Tinker_imitator
4 | ./gradlew assembledebugPatch -P basis=/Users/dim/github/Tinker_imitator/app/patch/version_1/debug -P serial_number=-20160718-0258 --stacktrace
5 | :buildSrc:compileJava UP-TO-DATE
6 | :buildSrc:compileGroovy UP-TO-DATE
7 | :buildSrc:processResources UP-TO-DATE
8 | :buildSrc:classes UP-TO-DATE
9 | :buildSrc:jar UP-TO-DATE
10 | :buildSrc:assemble UP-TO-DATE
11 | :buildSrc:compileTestJava UP-TO-DATE
12 | :buildSrc:compileTestGroovy UP-TO-DATE
13 | :buildSrc:processTestResources UP-TO-DATE
14 | :buildSrc:testClasses UP-TO-DATE
15 | :buildSrc:test UP-TO-DATE
16 | :buildSrc:check UP-TO-DATE
17 | :buildSrc:build UP-TO-DATE
18 | Incremental java compilation is an incubating feature.
19 | save ---> /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/basis/config.json
20 | assembleDebugPatch
21 | save ---> /Users/dim/github/Tinker_imitator/app/patch/version_1/release/basis/config.json
22 | assembleReleasePatch
23 | :app:preBuild UP-TO-DATE
24 | :app:preDebugBuild UP-TO-DATE
25 | :app:checkDebugManifest
26 | :app:preReleaseBuild UP-TO-DATE
27 | :library:preBuild UP-TO-DATE
28 | :library:preReleaseBuild UP-TO-DATE
29 | :library:compileReleaseNdk UP-TO-DATE
30 | :library:compileLint
31 | :library:copyReleaseLint UP-TO-DATE
32 | :library:checkReleaseManifest
33 | :library:preDebugAndroidTestBuild UP-TO-DATE
34 | :library:preDebugBuild UP-TO-DATE
35 | :library:preDebugUnitTestBuild UP-TO-DATE
36 | :library:preReleaseUnitTestBuild UP-TO-DATE
37 | :library:prepareComAndroidSupportAnimatedVectorDrawable2340Library UP-TO-DATE
38 | :library:prepareComAndroidSupportAppcompatV72340Library UP-TO-DATE
39 | :library:prepareComAndroidSupportRecyclerviewV72340Library UP-TO-DATE
40 | :library:prepareComAndroidSupportSupportV42340Library UP-TO-DATE
41 | :library:prepareComAndroidSupportSupportVectorDrawable2340Library UP-TO-DATE
42 | :library:prepareReleaseDependencies
43 | :library:compileReleaseAidl UP-TO-DATE
44 | :library:compileReleaseRenderscript UP-TO-DATE
45 | :library:generateReleaseBuildConfig UP-TO-DATE
46 | :library:mergeReleaseShaders UP-TO-DATE
47 | :library:compileReleaseShaders UP-TO-DATE
48 | :library:generateReleaseAssets UP-TO-DATE
49 | :library:mergeReleaseAssets UP-TO-DATE
50 | :library:generateReleaseResValues UP-TO-DATE
51 | :library:generateReleaseResources UP-TO-DATE
52 | :library:mergeReleaseResources UP-TO-DATE
53 | :library:processReleaseManifest UP-TO-DATE
54 | :library:processReleaseResources UP-TO-DATE
55 | :library:generateReleaseSources UP-TO-DATE
56 | :library:incrementalReleaseJavaCompilationSafeguard UP-TO-DATE
57 | :library:compileReleaseJavaWithJavac UP-TO-DATE
58 | :library:extractReleaseAnnotations UP-TO-DATE
59 | :library:mergeReleaseProguardFiles UP-TO-DATE
60 | :library:packageReleaseRenderscript UP-TO-DATE
61 | :library:packageReleaseResources UP-TO-DATE
62 | :library:processReleaseJavaRes UP-TO-DATE
63 | :library:transformResourcesWithMergeJavaResForRelease UP-TO-DATE
64 | :library:transformClassesAndResourcesWithSyncLibJarsForRelease UP-TO-DATE
65 | :library:mergeReleaseJniLibFolders UP-TO-DATE
66 | :library:transformNative_libsWithMergeJniLibsForRelease UP-TO-DATE
67 | :library:transformNative_libsWithSyncJniLibsForRelease UP-TO-DATE
68 | :library:bundleRelease UP-TO-DATE
69 | :app:prepareComAndroidSupportAnimatedVectorDrawable2340Library UP-TO-DATE
70 | :app:prepareComAndroidSupportAppcompatV72340Library UP-TO-DATE
71 | :app:prepareComAndroidSupportDesign2340Library UP-TO-DATE
72 | :app:prepareComAndroidSupportMultidex101Library UP-TO-DATE
73 | :app:prepareComAndroidSupportRecyclerviewV72340Library UP-TO-DATE
74 | :app:prepareComAndroidSupportSupportV42340Library UP-TO-DATE
75 | :app:prepareComAndroidSupportSupportVectorDrawable2340Library UP-TO-DATE
76 | :app:prepareTinker_imitatorLibraryUnspecifiedLibrary UP-TO-DATE
77 | :app:prepareDebugDependencies
78 | :app:compileDebugAidl UP-TO-DATE
79 | :app:compileDebugRenderscript UP-TO-DATE
80 | :app:generateDebugBuildConfig UP-TO-DATE
81 | :app:mergeDebugShaders UP-TO-DATE
82 | :app:compileDebugShaders UP-TO-DATE
83 | :app:generateDebugAssets UP-TO-DATE
84 | :app:mergeDebugAssets UP-TO-DATE
85 | :app:generateDebugResValues UP-TO-DATE
86 | :app:generateDebugResources UP-TO-DATE
87 | :app:mergeDebugResources UP-TO-DATE
88 | :app:processDebugManifest UP-TO-DATE
89 | :app:processDebugResources UP-TO-DATE
90 | :app:generateDebugSources UP-TO-DATE
91 | :app:incrementalDebugJavaCompilationSafeguard UP-TO-DATE
92 | :app:compileDebugJavaWithJavac
93 | Incremental compilation of 1 classes completed in 1.887 secs.
94 | :app:processDebugJavaRes UP-TO-DATE
95 | :app:transformResourcesWithMergeJavaResForDebug UP-TO-DATE
96 | :app:transformClassesAndResourcesWithProguardForDebug
97 | ProGuard, version 5.2.1
98 | Reading input...
99 | Reading program jar [/Users/Shared/Android/sdk/extras/android/m2repository/com/android/support/support-annotations/23.4.0/support-annotations-23.4.0.jar] (filtered)
100 | Reading program jar [/Users/dim/.gradle/caches/modules-2/files-2.1/commons-io/commons-io/1.4/a8762d07e76cfde2395257a5da47ba7c1dbd3dce/commons-io-1.4.jar] (filtered)
101 | Reading program jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/exploded-aar/com.android.support/multidex/1.0.1/jars/classes.jar] (filtered)
102 | Reading program jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/exploded-aar/com.android.support/design/23.4.0/jars/classes.jar] (filtered)
103 | Reading program jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.4.0/jars/classes.jar] (filtered)
104 | Reading program jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/exploded-aar/com.android.support/animated-vector-drawable/23.4.0/jars/classes.jar] (filtered)
105 | Reading program jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/exploded-aar/com.android.support/support-vector-drawable/23.4.0/jars/classes.jar] (filtered)
106 | Reading program jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/exploded-aar/com.android.support/recyclerview-v7/23.4.0/jars/classes.jar] (filtered)
107 | Reading program jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/exploded-aar/com.android.support/support-v4/23.4.0/jars/classes.jar] (filtered)
108 | Reading program jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/exploded-aar/com.android.support/support-v4/23.4.0/jars/libs/internal_impl-23.4.0.jar] (filtered)
109 | Reading program jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/exploded-aar/Tinker_imitator/library/unspecified/jars/classes.jar] (filtered)
110 | Reading program jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/transforms/mergeJavaRes/debug/jars/2/1f/main.jar] (filtered)
111 | Reading program directory [/Users/dim/github/Tinker_imitator/app/build/intermediates/classes/debug] (filtered)
112 | Reading library jar [/Users/Shared/Android/sdk/platforms/android-23/android.jar]
113 | Reading library jar [/Users/Shared/Android/sdk/platforms/android-23/optional/org.apache.http.legacy.jar]
114 | Note: duplicate definition of library class [android.net.http.SslCertificate]
115 | Note: duplicate definition of library class [android.net.http.SslError]
116 | Note: duplicate definition of library class [android.net.http.SslCertificate$DName]
117 | Note: duplicate definition of library class [org.apache.http.conn.scheme.SocketFactory]
118 | Note: duplicate definition of library class [org.apache.http.conn.scheme.HostNameResolver]
119 | Note: duplicate definition of library class [org.apache.http.conn.ConnectTimeoutException]
120 | Note: duplicate definition of library class [org.apache.http.params.HttpParams]
121 | Initializing...
122 | Ignoring unused library classes...
123 | Original number of library classes: 3880
124 | Final number of library classes: 948
125 | Printing kept classes, fields, and methods...
126 | Shrinking...
127 | Printing usage to [/Users/dim/github/Tinker_imitator/app/build/outputs/mapping/debug/usage.txt]...
128 | Removing unused program classes and class elements...
129 | Original number of program classes: 1976
130 | Final number of program classes: 934
131 | Obfuscating...
132 | Applying mapping [/Users/dim/github/Tinker_imitator/app/patch/version_1/debug/basis/mapping.txt]
133 | Printing mapping to [/Users/dim/github/Tinker_imitator/app/build/outputs/mapping/debug/mapping.txt]...
134 | Writing output...
135 | Preparing output jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/transforms/proguard/debug/jars/3/1f/main.jar]
136 | Copying resources from program jar [/Users/Shared/Android/sdk/extras/android/m2repository/com/android/support/support-annotations/23.4.0/support-annotations-23.4.0.jar] (filtered)
137 | Copying resources from program jar [/Users/dim/.gradle/caches/modules-2/files-2.1/commons-io/commons-io/1.4/a8762d07e76cfde2395257a5da47ba7c1dbd3dce/commons-io-1.4.jar] (filtered)
138 | Copying resources from program jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/exploded-aar/com.android.support/multidex/1.0.1/jars/classes.jar] (filtered)
139 | Copying resources from program jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/exploded-aar/com.android.support/design/23.4.0/jars/classes.jar] (filtered)
140 | Copying resources from program jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.4.0/jars/classes.jar] (filtered)
141 | Copying resources from program jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/exploded-aar/com.android.support/animated-vector-drawable/23.4.0/jars/classes.jar] (filtered)
142 | Copying resources from program jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/exploded-aar/com.android.support/support-vector-drawable/23.4.0/jars/classes.jar] (filtered)
143 | Copying resources from program jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/exploded-aar/com.android.support/recyclerview-v7/23.4.0/jars/classes.jar] (filtered)
144 | Copying resources from program jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/exploded-aar/com.android.support/support-v4/23.4.0/jars/classes.jar] (filtered)
145 | Copying resources from program jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/exploded-aar/com.android.support/support-v4/23.4.0/jars/libs/internal_impl-23.4.0.jar] (filtered)
146 | Copying resources from program jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/exploded-aar/Tinker_imitator/library/unspecified/jars/classes.jar] (filtered)
147 | Copying resources from program jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/transforms/mergeJavaRes/debug/jars/2/1f/main.jar] (filtered)
148 | Copying resources from program directory [/Users/dim/github/Tinker_imitator/app/build/intermediates/classes/debug] (filtered)
149 | Printing classes to [/Users/dim/github/Tinker_imitator/app/build/outputs/mapping/debug/dump.txt]...
150 | :app:collectDebugMultiDexComponents UP-TO-DATE
151 | :app:transformClassesWithMultidexlistForDebug
152 | ProGuard, version 5.2.1
153 | Reading program jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/transforms/proguard/debug/jars/3/1f/main.jar]
154 | Reading library jar [/Users/Shared/Android/sdk/build-tools/23.0.3/lib/shrinkedAndroid.jar]
155 | Preparing output jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/multi-dex/debug/componentClasses.jar]
156 | Copying resources from program jar [/Users/dim/github/Tinker_imitator/app/build/intermediates/transforms/proguard/debug/jars/3/1f/main.jar]
157 | :app:transformClassesWithDexForDebug
158 | :app:assembleDebugPatch
159 | 开始生成插件
160 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes/android/support/annotation mkdir
161 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/a/a/a/a mkdir
162 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes/android/support/b mkdir
163 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/design/internal mkdir
164 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/design/widget mkdir
165 | change : android/support/design/widget/bp.class
166 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes/android/support/design/widget mkdir
167 | change : android/support/design/widget/bm.class
168 | change : android/support/design/widget/Snackbar.class
169 | change : android/support/design/widget/Snackbar$SnackbarLayout.class
170 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v7/view mkdir
171 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v7/view/menu mkdir
172 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes/android/support/v7/view/menu mkdir
173 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v7/a mkdir
174 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes/android/support/v7/a mkdir
175 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v7/f mkdir
176 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v7/c/a mkdir
177 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v7/widget mkdir
178 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes/android/support/v7/widget mkdir
179 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v7/e mkdir
180 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/a/a mkdir
181 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v4/f mkdir
182 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v4/h mkdir
183 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes/android/support/v4/h mkdir
184 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v4/i mkdir
185 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v4/i/a mkdir
186 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v4/i/b mkdir
187 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes/android/support/v4/i mkdir
188 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v4/b mkdir
189 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes/android/support/v4/b mkdir
190 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v4/c mkdir
191 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v4/c/a mkdir
192 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes/android/support/v4/c mkdir
193 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v4/e/a mkdir
194 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v4/a mkdir
195 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v4/d mkdir
196 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v4/d/a mkdir
197 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v4/widget mkdir
198 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v4/g mkdir
199 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/com/dim/common mkdir
200 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes/com/dim/common mkdir
201 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/com/dim/library mkdir
202 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes/com/dim/library mkdir
203 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes/META-INF/maven/commons-io/commons-io mkdir
204 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v7/b mkdir
205 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/android/support/v7/d mkdir
206 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes/com/dim/tinkerimitator mkdir
207 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2/com/dim/tinkerimitator mkdir
208 | change : com/dim/tinkerimitator/MainActivity.class
209 | "/Users/Shared/Android/sdk/build-tools/23.0.3/dx '--dex',
210 | "--output=/Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258",
211 | "/Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2"
212 | false -> true
213 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes.dex -> /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2.dex true
214 | "/Users/Shared/Android/sdk/build-tools/23.0.3/dx '--dex',
215 | "--output=/Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258",
216 | "/Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes"
217 | true -> true
218 | /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes.dex -> /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes.dex true
219 |
220 | BUILD SUCCESSFUL
221 |
222 | Total time: 55.181 secs
223 |
224 | This build could be faster, please consider using the Gradle Daemon: https://docs.gradle.org/2.10/userguide/gradle_daemon.html
225 | /usr/local/bin/bsdiff /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/basis/dex/classes.dex /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes.dex /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/patchclasses.dex
226 |
227 | /usr/local/bin/bsdiff /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/basis/dex/classes2.dex /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/classes2.dex /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/patchclasses2.dex
228 |
229 | :push: push -> /sdcard/hot/
230 | /Users/Shared/Android/sdk/platform-tools/adb -s 0915f9258bcf0f04 push /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/patchclasses.dex /sdcard/hot/
231 |
232 | :push: push -> /sdcard/hot/
233 | /Users/Shared/Android/sdk/platform-tools/adb -s 0915f9258bcf0f04 push /Users/dim/github/Tinker_imitator/app/patch/version_1/debug/patch-20160718-0258/patchclasses2.dex /sdcard/hot/
234 |
235 |
--------------------------------------------------------------------------------
/demo/patch-20160718-0258/patchclasses.dex:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzz40500/Tinker_imitator/167022c62f486a3b3480c073bf2977f64aabcbd0/demo/patch-20160718-0258/patchclasses.dex
--------------------------------------------------------------------------------
/demo/patch-20160718-0258/patchclasses2.dex:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzz40500/Tinker_imitator/167022c62f486a3b3480c073bf2977f64aabcbd0/demo/patch-20160718-0258/patchclasses2.dex
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzz40500/Tinker_imitator/167022c62f486a3b3480c073bf2977f64aabcbd0/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 28 10:00:20 PST 2015
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-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
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 Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.3"
6 |
7 | defaultConfig {
8 | minSdkVersion 15
9 | targetSdkVersion 23
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | compile fileTree(include: ['*.jar'], dir: 'libs')
23 | compile 'com.android.support:appcompat-v7:23.4.0'
24 | compile 'com.android.support:recyclerview-v7:23.4.0'
25 | compile 'commons-io:commons-io:1.4'
26 | }
27 |
--------------------------------------------------------------------------------
/library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/dim/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/library/src/androidTest/java/com/dim/library/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.dim.library;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/library/src/main/java/com/dim/common/Bsdiff.java:
--------------------------------------------------------------------------------
1 | package com.dim.common;
2 |
3 | /**
4 | * Bsdiff
5 | * Created by dim on 2016-07-12.
6 | */
7 | public class Bsdiff {
8 |
9 | static {
10 | System.loadLibrary("bsdiff");
11 | }
12 | public native static int bspatch(String old, String combine, String patch);
13 | }
14 |
--------------------------------------------------------------------------------
/library/src/main/java/com/dim/common/Logger.java:
--------------------------------------------------------------------------------
1 | package com.dim.common;
2 |
3 | import android.util.Log;
4 |
5 | /**
6 | * Logger
7 | * Created by dim on 2016-07-08.
8 | */
9 | public class Logger {
10 |
11 | private static boolean debug = true;
12 |
13 | public static boolean isDebug() {
14 | return debug;
15 | }
16 |
17 | public static void setDebug(boolean debug) {
18 | Logger.debug = debug;
19 | }
20 |
21 | public static void d(String tag, String message) {
22 | if (debug) {
23 | Log.d(tag, message);
24 | }
25 | }
26 |
27 | public static void e(String tag, String message) {
28 | if (debug) {
29 | Log.d(tag, message);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/library/src/main/java/com/dim/library/DexUtils.java:
--------------------------------------------------------------------------------
1 | package com.dim.library;
2 |
3 | import java.lang.reflect.Array;
4 | import dalvik.system.DexClassLoader;
5 | import dalvik.system.PathClassLoader;
6 |
7 | /**
8 | * Created by jixin.jia on 15/10/31.
9 | */
10 | public class DexUtils {
11 |
12 | public static void injectDexAtFirst(String dexPath, String defaultDexOptPath) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
13 | DexClassLoader dexClassLoader = new DexClassLoader(dexPath, defaultDexOptPath, dexPath, getPathClassLoader());
14 | Object baseDexElements = getDexElements(getPathList(getPathClassLoader()));
15 | Object newDexElements = getDexElements(getPathList(dexClassLoader));
16 | Object allDexElements = combineArray(newDexElements, baseDexElements);
17 | Object pathList = getPathList(getPathClassLoader());
18 | ReflectionUtils.setField(pathList, pathList.getClass(), "dexElements", allDexElements);
19 | }
20 |
21 | private static PathClassLoader getPathClassLoader() {
22 | PathClassLoader pathClassLoader = (PathClassLoader) DexUtils.class.getClassLoader();
23 | return pathClassLoader;
24 | }
25 |
26 | private static Object getDexElements(Object paramObject)
27 | throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException {
28 | return ReflectionUtils.getField(paramObject, paramObject.getClass(), "dexElements");
29 | }
30 |
31 | private static Object getPathList(Object baseDexClassLoader)
32 | throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
33 | return ReflectionUtils.getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
34 | }
35 |
36 | private static Object combineArray(Object firstArray, Object secondArray) {
37 | Class> localClass = firstArray.getClass().getComponentType();
38 | int firstArrayLength = Array.getLength(firstArray);
39 | int allLength = firstArrayLength + Array.getLength(secondArray);
40 | Object result = Array.newInstance(localClass, allLength);
41 | for (int k = 0; k < allLength; ++k) {
42 | if (k < firstArrayLength) {
43 | Array.set(result, k, Array.get(firstArray, k));
44 | } else {
45 | Array.set(result, k, Array.get(secondArray, k - firstArrayLength));
46 | }
47 | }
48 | return result;
49 | }
50 |
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/library/src/main/java/com/dim/library/NoneService.java:
--------------------------------------------------------------------------------
1 | package com.dim.library;
2 |
3 | import android.content.Intent;
4 | import android.os.IBinder;
5 | import android.support.annotation.Nullable;
6 | import android.util.Log;
7 |
8 | import com.dim.common.Logger;
9 |
10 | /**
11 | * Created by dim on 16/7/17.
12 | */
13 | public class NoneService extends android.app.Service {
14 |
15 | private static final String TAG = "Service";
16 | @Override
17 | public void onCreate() {
18 | super.onCreate();
19 | Logger.d(TAG, "onCreate: " );
20 | }
21 |
22 | @Nullable
23 | @Override
24 | public IBinder onBind(Intent intent) {
25 | return null;
26 | }
27 |
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/library/src/main/java/com/dim/library/ReflectionUtils.java:
--------------------------------------------------------------------------------
1 | package com.dim.library;
2 |
3 | import java.lang.reflect.Field;
4 |
5 | /**
6 | * Created by jixin.jia on 15/10/31.
7 | */
8 | public class ReflectionUtils {
9 |
10 | public static Object getField(Object obj, Class> cl, String field)
11 | throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
12 | Field localField = cl.getDeclaredField(field);
13 | localField.setAccessible(true);
14 | return localField.get(obj);
15 | }
16 |
17 | public static void setField(Object obj, Class> cl, String field, Object value)
18 | throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
19 | Field localField = cl.getDeclaredField(field);
20 | localField.setAccessible(true);
21 | localField.set(obj, value);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/library/src/main/java/com/dim/library/Tinker.java:
--------------------------------------------------------------------------------
1 | package com.dim.library;
2 |
3 | import android.app.Application;
4 | import android.content.ComponentCallbacks2;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.content.pm.ApplicationInfo;
8 | import android.content.pm.PackageManager;
9 | import android.content.pm.PermissionGroupInfo;
10 | import android.content.pm.PermissionInfo;
11 | import android.os.Environment;
12 | import android.support.annotation.MainThread;
13 | import android.support.annotation.RequiresPermission;
14 | import android.support.annotation.WorkerThread;
15 | import android.text.TextUtils;
16 | import android.widget.Toast;
17 |
18 | import com.dim.common.Bsdiff;
19 | import com.dim.common.Logger;
20 |
21 | import org.apache.commons.io.IOUtils;
22 |
23 | import java.io.File;
24 | import java.io.FileOutputStream;
25 | import java.io.IOException;
26 | import java.io.InputStream;
27 | import java.io.OutputStream;
28 | import java.security.Permission;
29 | import java.security.Permissions;
30 | import java.util.ArrayList;
31 | import java.util.Enumeration;
32 | import java.util.List;
33 | import java.util.jar.JarEntry;
34 | import java.util.jar.JarFile;
35 | import java.util.jar.Manifest;
36 | import java.util.zip.ZipEntry;
37 |
38 | /**
39 | * Tinker
40 | * Created by dim on 2016-07-09.
41 | */
42 | public class Tinker {
43 |
44 | private static final String DEX_OPT_DIR = "patchOpt";
45 | private static final String TAG = "Tinker";
46 | private static String sHotPath;
47 | private static String sDexPath;
48 | private static String sUnVerifyDexPath;
49 | private static Application mApplication;
50 | private static boolean debug = true;
51 | private static BackgroundPolicy mBackgroundPolicy;
52 | private static boolean needFlush = false;
53 |
54 |
55 | @MainThread
56 | public static void init(Application application) {
57 | mApplication = application;
58 | sHotPath = application.getFilesDir() + "/" + "and" + "/" + application.getPackageName() + "/hot";
59 | sDexPath = application.getFilesDir() + "/" + "and" + "/" + application.getPackageName() + "/dex";
60 | sUnVerifyDexPath = Environment.getExternalStorageDirectory() + "/hot";
61 | debug = appIsDebug(application);
62 | Logger.setDebug(debug);
63 | iniFiles();
64 | install();
65 | mountAllDex();
66 | }
67 |
68 | public static void onTrimMemory(int level) {
69 | if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN && needFlush && mBackgroundPolicy != null && mBackgroundPolicy.isReadyForFix()) {
70 | Intent intent = new Intent(mApplication, NoneService.class);
71 | mApplication.startService(intent);
72 | android.os.Process.killProcess(android.os.Process.myPid());
73 | System.exit(0);
74 | }
75 | }
76 |
77 | private static void mountAllDex() {
78 | File[] files = new File(sHotPath).listFiles();
79 | if (files != null) {
80 | for (File file : files) {
81 | mountDex(file);
82 | }
83 | }
84 | }
85 |
86 | @WorkerThread
87 | @RequiresPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
88 | public static List install() {
89 | File hotFile = new File(sUnVerifyDexPath);
90 | List result = new ArrayList<>();
91 | File[] files = hotFile.listFiles();
92 | if (files == null) {
93 | return result;
94 | }
95 | for (File file : files) {
96 | boolean install = install(file);
97 | if (install) {
98 | result.add(file.getAbsolutePath());
99 | }
100 | }
101 | return result;
102 | }
103 |
104 | @WorkerThread
105 | public static boolean install(File file) {
106 | try {
107 | if (file.getName().matches("patchclasses\\d?.dex")) {
108 | String classDex = file.getName().substring(5);
109 | File oldDex = new File(sDexPath, classDex);
110 | copyDex(classDex);
111 | File newDex = new File(sHotPath, classDex);
112 | if (oldDex.exists()) {
113 | int bspatch = Bsdiff.bspatch(oldDex.getPath(), newDex.getPath(), file.getPath());
114 | if (bspatch == 0) {
115 | Logger.d(TAG, "install success " + file);
116 | file.delete();
117 | needFlush = true;
118 | return true;
119 | }
120 | }
121 | }
122 | } finally {
123 | File dexFile = new File(sDexPath);
124 | File[] files = dexFile.listFiles();
125 | if (files != null) {
126 | for (File item : files) {
127 | item.delete();
128 | }
129 | }
130 | }
131 | return false;
132 | }
133 |
134 | private static void iniFiles() {
135 | createHotFile();
136 | }
137 |
138 | private static void createHotFile() {
139 | File hotFile = new File(Environment.getExternalStorageDirectory() + "/hot");
140 | if (!hotFile.exists()) {
141 | hotFile.mkdirs();
142 | }
143 | File anHot = new File(sHotPath);
144 | if (!anHot.exists()) {
145 | anHot.mkdirs();
146 | }
147 | }
148 |
149 | private static void copyDex(String className) {
150 | File hotFile = new File(sDexPath);
151 | if (!hotFile.exists()) {
152 | hotFile.mkdirs();
153 | }
154 | if (new File(hotFile, className).exists()) {
155 | return;
156 | }
157 | try {
158 | String sourceApkPath = getSourceApkPath(mApplication, mApplication.getPackageName());
159 | JarFile jarFile = new JarFile(sourceApkPath);
160 | Enumeration entries = jarFile.entries();
161 | ZipEntry entry = jarFile.getEntry(className);
162 | if (entry != null) {
163 | InputStream inputStream = jarFile.getInputStream(entry);
164 | OutputStream out = new FileOutputStream(new File(sDexPath, entry.getName()));
165 | IOUtils.copy(inputStream, out);
166 | }
167 | } catch (IOException e) {
168 | e.printStackTrace();
169 | }
170 | }
171 |
172 | private static String getSourceApkPath(Context context, String packageName) {
173 | if (TextUtils.isEmpty(packageName))
174 | return null;
175 | try {
176 | ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(packageName, 0);
177 | return appInfo.sourceDir;
178 | } catch (PackageManager.NameNotFoundException e) {
179 | e.printStackTrace();
180 | }
181 | return null;
182 | }
183 |
184 | private static boolean appIsDebug(Application application) {
185 |
186 | try {
187 | Class> aClass = Class.forName(application.getPackageName() + ".BuildConfig");
188 | Object debug = ReflectionUtils.getField(null, aClass, "DEBUG");
189 | return (boolean) debug;
190 | } catch (ClassNotFoundException e) {
191 | e.printStackTrace();
192 | } catch (IllegalAccessException e) {
193 | e.printStackTrace();
194 | } catch (NoSuchFieldException e) {
195 | e.printStackTrace();
196 | }
197 |
198 | return false;
199 | }
200 |
201 |
202 | private static void mountDex(File file) {
203 | File dexOptDir = new File(mApplication.getFilesDir(), DEX_OPT_DIR);
204 | dexOptDir.mkdir();
205 | try {
206 | DexUtils.injectDexAtFirst(file.getPath(), dexOptDir.getAbsolutePath());
207 | Logger.d(TAG, "loadPatch success:" + file);
208 | if (debug) {
209 | Toast.makeText(mApplication, "loadPatch success: " + file.getName(), Toast.LENGTH_SHORT).show();
210 | }
211 | } catch (Exception e) {
212 | Logger.e(TAG, "inject " + file + " failed");
213 | if (debug) {
214 | Toast.makeText(mApplication, "inject failed : " + file.getName(), Toast.LENGTH_SHORT).show();
215 | }
216 | }
217 | }
218 |
219 |
220 | public static void setBackgroundPolicy(BackgroundPolicy mBackgroundPolicy) {
221 | Tinker.mBackgroundPolicy = mBackgroundPolicy;
222 | }
223 |
224 | public interface BackgroundPolicy {
225 | boolean isReadyForFix();
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/library/src/main/jniLibs/arm64-v8a/libbsdiff.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzz40500/Tinker_imitator/167022c62f486a3b3480c073bf2977f64aabcbd0/library/src/main/jniLibs/arm64-v8a/libbsdiff.so
--------------------------------------------------------------------------------
/library/src/main/jniLibs/armeabi-v7a/libbsdiff.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzz40500/Tinker_imitator/167022c62f486a3b3480c073bf2977f64aabcbd0/library/src/main/jniLibs/armeabi-v7a/libbsdiff.so
--------------------------------------------------------------------------------
/library/src/main/jniLibs/armeabi/libbsdiff.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzz40500/Tinker_imitator/167022c62f486a3b3480c073bf2977f64aabcbd0/library/src/main/jniLibs/armeabi/libbsdiff.so
--------------------------------------------------------------------------------
/library/src/main/jniLibs/mips/libbsdiff.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzz40500/Tinker_imitator/167022c62f486a3b3480c073bf2977f64aabcbd0/library/src/main/jniLibs/mips/libbsdiff.so
--------------------------------------------------------------------------------
/library/src/main/jniLibs/mips64/libbsdiff.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzz40500/Tinker_imitator/167022c62f486a3b3480c073bf2977f64aabcbd0/library/src/main/jniLibs/mips64/libbsdiff.so
--------------------------------------------------------------------------------
/library/src/main/jniLibs/x86/libbsdiff.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzz40500/Tinker_imitator/167022c62f486a3b3480c073bf2977f64aabcbd0/library/src/main/jniLibs/x86/libbsdiff.so
--------------------------------------------------------------------------------
/library/src/main/jniLibs/x86_64/libbsdiff.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzz40500/Tinker_imitator/167022c62f486a3b3480c073bf2977f64aabcbd0/library/src/main/jniLibs/x86_64/libbsdiff.so
--------------------------------------------------------------------------------
/library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Library
3 |
4 |
--------------------------------------------------------------------------------
/library/src/test/java/com/dim/library/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.dim.library;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/plugin/Tinker-Plugin.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzz40500/Tinker_imitator/167022c62f486a3b3480c073bf2977f64aabcbd0/plugin/Tinker-Plugin.zip
--------------------------------------------------------------------------------
/screenshot/img.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zzz40500/Tinker_imitator/167022c62f486a3b3480c073bf2977f64aabcbd0/screenshot/img.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':', ':library'
--------------------------------------------------------------------------------