├── .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 | 50 | 51 | 52 | 64 | 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 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /UNMAINTAINED.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ![Tinker_imitator.png](https://raw.githubusercontent.com/zzz40500/Tinker_imitator/master/screenshot/img.png) 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 | ![Paste_Image.png](http://upload-images.jianshu.io/upload_images/166866-f9936846f287b6a1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 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 | ![第一次运行](http://upload-images.jianshu.io/upload_images/166866-de367ac222ea7518.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 41 | 编译完成会产生几个文件: 42 | 43 | ![产生的文件.png](http://upload-images.jianshu.io/upload_images/166866-9d080c1b95d2e408.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 44 | 然后修改代码: 45 | 打补丁包: 46 | 47 | ![补丁包运行.png](http://upload-images.jianshu.io/upload_images/166866-3b7319b26baee7c7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 48 | 会有下列产物: 49 | 50 | ![patch产物.png](http://upload-images.jianshu.io/upload_images/166866-cf7b5fa7772f962c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 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 | ![Paste_Image.png](http://upload-images.jianshu.io/upload_images/166866-48ed6363e2282f7f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 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 |