├── .gitignore
├── .idea
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── gradle.xml
├── misc.xml
├── modules.xml
└── runConfigurations.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── cuieney
│ │ └── autofix
│ │ ├── App.java
│ │ └── MainActivity.java
│ └── res
│ ├── drawable
│ └── thumb.png
│ ├── layout
│ └── activity_main.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_round.png
│ └── thumb.jpg
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_round.png
│ └── thumb.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── buildsrc
├── .gitignore
├── build.gradle
├── mavenpush.gradle
└── src
│ └── main
│ ├── groovy
│ └── com
│ │ └── cuieney
│ │ └── autofix
│ │ ├── AutoFix.groovy
│ │ ├── AutoFixExtension.groovy
│ │ └── utils
│ │ ├── AutoUtils.groovy
│ │ ├── NuwaProcessor.groovy
│ │ └── NuwaSetUtils.groovy
│ └── resources
│ └── META-INF
│ └── gradle-plugins
│ └── com.cuieney.autofix.properties
├── fix.zip
├── fix
├── .gitignore
├── build.gradle
├── mavenpush.gradle
├── proguard-rules.pro
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ └── Auto.dex
│ ├── java
│ │ └── com
│ │ │ └── cuieney
│ │ │ └── fix
│ │ │ ├── AutoFix.java
│ │ │ ├── AutoUtils.java
│ │ │ └── DynamicApk.java
│ └── res
│ │ └── values
│ │ └── strings.xml
│ └── test
│ └── java
│ └── com
│ └── cuieney
│ └── fix
│ └── ExampleUnitTest.java
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── images
└── patchdir.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 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/.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/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
19 |
20 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## AutoFix
2 | >目前项目支持静态修复功能需要重启,集成简单,使用方便。
3 |
4 | ### 原理
5 | 这两篇文章就够了
6 |
7 | [DexClassLoader热修复的入门到放弃](https://juejin.im/post/5951d5265188250d8f602225)
8 |
9 | [手把手教你写热修复(HOTFIX)](https://juejin.im/post/595d02d5f265da6c375a90bf)
10 |
11 | ## 集成
12 | ### Get Gradle Plugin
13 | 1. 根据以下操作把代码填到你项目根目录的gradle中
14 | >classpath 'com.cuieney:autofix:1.1.1'
15 |
16 | build.gradle maybe look like this:
17 |
18 | ```
19 | buildscript {
20 | repositories {
21 | jcenter()
22 | }
23 | dependencies {
24 | classpath 'com.android.tools.build:gradle:2.3.0'
25 | classpath 'com.cuieney.autofix:gradle:1.1.7'
26 | }
27 | }
28 | ```
29 | 2. 在你的build.gradle:中添加这样的代码块
30 |
31 | >apply plugin: "com.cuieney.autofix"
32 |
33 | ### Get AutoFix SDK
34 |
35 | * gradle dependency:
36 |
37 | ```
38 | dependencies {
39 | compile 'com.cuieney.library:fix:1.1.2'
40 | }
41 | ```
42 |
43 | ### 使用步骤
44 | 1. 在你的application中添加一下代码初始化:
45 |
46 | ```
47 | @Override
48 | protected void attachBaseContext(Context base) {
49 | super.attachBaseContext(base);
50 | AutoFix.init(this);
51 | }
52 | ```
53 | 2. 如果你需要加载补丁的话可以这样,每次需需要重启:
54 |
55 | ```
56 | AutoFix.applyPatch(this,patch.jar);
57 |
58 | ```
59 |
60 | ### ProGuard
61 | * 添加到你的proguardFile文件中:
62 |
63 | >-keep class cn.cuieney.fix.** { *; }
64 |
65 | ## 补丁制作
66 | 根据下面三步即可以完成补丁的制作
67 |
68 | 1.首选你的添加`auto_fix`extension到你的build.gradle中,只在你需要制作补丁的时候才用得到(不制作补丁注释即可)
69 |
70 | ```
71 | auto_fix {
72 | lastVersion = '1'//顾名思义,上次一的版本号(就是说你当前Version是1,出现bug了,你把versioncode变成了2)然后这个lastVersion就填1
73 | }
74 |
75 | ```
76 |
77 | 2.现在你已经改好bug了,需要获取相应的patch.jar把这个补丁打到app中,只需要编译一下项目即可,buildApk这个操作也是可以的
78 |
79 | 3.获取补丁push到手机中(如果项目已上线即push到服务器)
80 |
81 |
82 | 
83 |
84 | 这个就是你的补丁对应的目录,patch.jar就是补丁包(version `2`)代表你生成补丁当前的版本号
85 |
86 | ### TODO
87 | - 增强兼容性
88 | - 支持runtime
89 | - 增加插件化跳转Act
90 |
91 | ### 补充
92 | 你会看到AutoFix SDK里面有这样一个类DynamicApk,这是一个beta版。用于插件化的工具。目前focus获取apk的资源文件,目前接口如下:
93 | - getStringFromApk
94 | - getBitmapFromApk
95 | - getDrawableFromApk
96 | - getMipmapFromApk
97 | - getLayoutFromApk
98 | - getColorFromApk
99 | - getDimenFromApk
100 |
101 | 参数都一样(Context context, String apkPath, String name),只介绍最后一个参数such as(R.drawable.thumb)这个name就是thumb
102 |
103 |
104 | #### 问题
105 | 发现bug或好的建议欢迎 [issues](https://github.com/Cuieney/AutoFix/issues) or
106 | Email
107 | ### License
108 | F**K License
109 |
110 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'com.cuieney.autofix'
3 | repositories {
4 | jcenter()
5 | }
6 | android {
7 | compileSdkVersion 25
8 | buildToolsVersion "25.0.2"
9 | defaultConfig {
10 | applicationId "com.cuieney.autofix"
11 | minSdkVersion 19
12 | targetSdkVersion 25
13 | versionCode 2
14 | versionName "1.0"
15 | }
16 |
17 |
18 |
19 | buildTypes {
20 | debug {
21 | minifyEnabled true //代码混淆处理
22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
23 | }
24 |
25 | release {
26 | minifyEnabled true
27 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
28 | }
29 | }
30 | }
31 | auto_fix {
32 | lastVersion = '1'
33 | }
34 |
35 | dependencies {
36 | compile fileTree(include: ['*.jar'], dir: 'libs')
37 | compile 'com.android.support:appcompat-v7:25.2.0'
38 | testCompile 'junit:junit:4.12'
39 | compile 'com.cuieney.library:fix:1.1.2'
40 | }
41 |
--------------------------------------------------------------------------------
/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 /Users/baidu/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 |
19 | -keep class com.cuieney.fix.** {*;}
20 | -keepattributes EnclosingMethod
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/cuieney/autofix/App.java:
--------------------------------------------------------------------------------
1 | package com.cuieney.autofix;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import android.os.Environment;
6 |
7 | import com.cuieney.fix.AutoFix;
8 |
9 | /**
10 | * Created by cuieney on 29/06/2017.
11 | */
12 |
13 | public class App extends Application {
14 | @Override
15 | protected void attachBaseContext(Context base) {
16 | super.attachBaseContext(base);
17 | AutoFix.init(this);
18 | AutoFix.applyPatch(this,Environment.getExternalStorageDirectory().getAbsolutePath()+"/patch.jar");
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/cuieney/autofix/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.cuieney.autofix;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.res.AssetManager;
6 | import android.content.res.Resources;
7 | import android.graphics.BitmapFactory;
8 | import android.os.Environment;
9 | import android.support.v7.app.AppCompatActivity;
10 | import android.os.Bundle;
11 | import android.util.Log;
12 | import android.view.View;
13 | import android.widget.ImageView;
14 | import android.widget.TextView;
15 | import android.widget.Toast;
16 |
17 | import com.cuieney.fix.DynamicApk;
18 |
19 | import java.io.File;
20 | import java.lang.reflect.Field;
21 | import java.lang.reflect.InvocationTargetException;
22 | import java.lang.reflect.Method;
23 |
24 | import dalvik.system.DexClassLoader;
25 |
26 | public class MainActivity extends Activity {
27 |
28 | private TextView test;
29 | private ImageView image;
30 | private Activity context;
31 | @Override
32 | protected void onCreate(Bundle savedInstanceState) {
33 | super.onCreate(savedInstanceState);
34 | setContentView(R.layout.activity_main);
35 | context = this;
36 |
37 | final String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/dynamic.apk";
38 |
39 | test = (TextView) findViewById(R.id.test);
40 | test.setOnClickListener(new View.OnClickListener() {
41 | @Override
42 | public void onClick(View view) {
43 | String app_name = DynamicApk.getStringFromApk(context, path, "app_name");
44 | Toast.makeText(context, app_name, Toast.LENGTH_SHORT).show();
45 | test.setTextColor(DynamicApk.getColorFromApk(context,path,"colorAccent"));
46 | image.setImageBitmap(BitmapFactory.decodeResource(DynamicApk.getResource(context,path),DynamicApk.getDrawableFromApk(context,path,"thumb")));
47 |
48 | }
49 | });
50 | image = ((ImageView) findViewById(R.id.image));
51 |
52 | image.setImageResource(R.drawable.thumb);
53 |
54 | }
55 |
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/thumb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cuieney/AutoFix/98c21f3022eb24e299788d4bee67a7cc9de3a976/app/src/main/res/drawable/thumb.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cuieney/AutoFix/98c21f3022eb24e299788d4bee67a7cc9de3a976/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cuieney/AutoFix/98c21f3022eb24e299788d4bee67a7cc9de3a976/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/thumb.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cuieney/AutoFix/98c21f3022eb24e299788d4bee67a7cc9de3a976/app/src/main/res/mipmap-hdpi/thumb.jpg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cuieney/AutoFix/98c21f3022eb24e299788d4bee67a7cc9de3a976/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cuieney/AutoFix/98c21f3022eb24e299788d4bee67a7cc9de3a976/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cuieney/AutoFix/98c21f3022eb24e299788d4bee67a7cc9de3a976/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cuieney/AutoFix/98c21f3022eb24e299788d4bee67a7cc9de3a976/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/thumb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cuieney/AutoFix/98c21f3022eb24e299788d4bee67a7cc9de3a976/app/src/main/res/mipmap-xhdpi/thumb.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cuieney/AutoFix/98c21f3022eb24e299788d4bee67a7cc9de3a976/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cuieney/AutoFix/98c21f3022eb24e299788d4bee67a7cc9de3a976/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cuieney/AutoFix/98c21f3022eb24e299788d4bee67a7cc9de3a976/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cuieney/AutoFix/98c21f3022eb24e299788d4bee67a7cc9de3a976/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/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 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | AutoFix
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.3.0'
9 | classpath 'com.cuieney.autofix:gradle:1.1.7'
10 |
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | jcenter()
19 | }
20 | }
21 |
22 | task clean(type: Delete) {
23 | delete rootProject.buildDir
24 | }
25 |
--------------------------------------------------------------------------------
/buildsrc/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/buildsrc/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | dependencies {
3 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
4 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7'
5 | classpath 'org.codehaus.groovy:groovy-all:2.4.1'
6 | classpath 'com.github.dcendents:android-maven-plugin:1.2'
7 | }
8 | repositories {
9 | mavenLocal()
10 | jcenter()
11 | }
12 | }
13 | apply plugin: 'groovy'
14 | apply plugin: 'java'
15 | sourceSets {
16 | main {
17 | groovy {
18 | srcDir 'src/main/groovy'
19 | }
20 |
21 | java {
22 | srcDir 'src/main/java'
23 | }
24 | }
25 | }
26 | repositories {
27 | jcenter()
28 | mavenCentral()
29 | }
30 | dependencies {
31 | compile localGroovy()
32 | compile gradleApi()
33 | compile 'com.android.tools.build:gradle:2.2.0'
34 | compile 'commons-io:commons-io:2.4'
35 | compile 'commons-codec:commons-codec:1.6'
36 | compile 'org.apache.commons:commons-lang3:3.4'
37 | compile 'org.ow2.asm:asm:5.0'
38 | compile 'org.javassist:javassist:3.18.1-GA'
39 | }
40 | apply from: 'mavenpush.gradle'
--------------------------------------------------------------------------------
/buildsrc/mavenpush.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.jfrog.bintray'
2 | apply plugin: 'maven-publish'
3 | apply plugin: 'groovy'
4 | apply plugin: 'maven'
5 | //def siteUrl = 'https://github.com/Cuieney/AutoFix' // 项目的主页
6 | //def gitUrl = 'https://github.com/Cuieney/AutoFix.git' // Git仓库的url
7 | //
8 | //task javadocJar(type: Jar, dependsOn: javadoc) {
9 | // classifier = 'javadoc'
10 | // from 'build/docs/javadoc'
11 | //}
12 | //
13 | //task sourcesJar(type: Jar) {
14 | // from sourceSets.main.allSource
15 | // classifier = 'sources'
16 | //}
17 | //
18 | //artifacts {
19 | // archives jar
20 | // archives javadocJar
21 | // archives sourcesJar
22 | //}
23 | ////signing {
24 | //// sign configurations.archives
25 | ////}
26 | //uploadArchives {
27 | // repositories {
28 | // mavenDeployer {
29 | // pom.project {
30 | // name 'gradle-retrolambda'
31 | // packaging 'jar'
32 | // description 'another hotfix framework'
33 | // url 'https://github.com/Cuieney/AutoFix'
34 | //
35 | // scm {
36 | // url 'https://github.com/Cuieney/AutoFix.git'
37 | // connection 'https://github.com/Cuieney/AutoFix.git'
38 | // developerConnection 'https://github.com/Cuieney/AutoFix'
39 | // }
40 | //
41 | // licenses {
42 | // license {
43 | // name 'The Apache Software License, Version 2.0'
44 | // url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
45 | // distribution 'repo'
46 | // }
47 | // }
48 | //
49 | // developers {
50 | // developer {
51 | // id 'cuieney' //填写的一些基本信息
52 | // name 'cuieney'
53 | // email 'cuieney@163.com'
54 | // }
55 | // }
56 | // }
57 | // }
58 | // }
59 | //}
60 | //
61 | //Properties properties = new Properties()
62 | ////读取properties的配置信息,当然直接把信息写到代码里也是可以的
63 | //properties.load(new File('/Users/cuieneydemacbook/Desktop/Demo/AutoFix/local.properties').newDataInputStream())
64 | //bintray {
65 | // user = properties.getProperty("bintray.user")
66 | // key = properties.getProperty("bintray.apikey")
67 | // publications = ['MyPublication']
68 | // dryRun = false //Whether to run this as dry-run, without deploying
69 | // publish = true //If version should be auto published after an upload
70 | // pkg {
71 | // userOrg="cui131425"
72 | // repo = 'mave'
73 | // name = 'AutoFixPlugin'
74 | // desc = 'another hotfix framework'
75 | // websiteUrl = siteUrl
76 | // vcsUrl = gitUrl
77 | // licenses = ["Apache-2.0"]
78 | // publish = true
79 | // version {
80 | // name = '1.1.4'
81 | // desc = 'another hotfix framework'
82 | // released = new Date()
83 | // vcsTag = '1.1.4'
84 | // attributes = ['gradle-plugin': 'autofix']
85 | // }
86 | // }
87 | //}
88 | //
89 | //
90 | ////
91 | //publishing {
92 | // publications {
93 | // MyPublication(MavenPublication) {
94 | // from components.java
95 | // groupId 'com.cuieney'
96 | // artifactId 'autofix'
97 | // version '1.1.4'
98 | // }
99 | // }
100 | //}
101 | //plugins {
102 | // id "com.jfrog.bintray" version "1.4"
103 | //}
104 |
105 | //apply plugin: 'groovy'
106 |
107 | def versionName = "1.1.7"
108 | group "com.cuieney.autofix"
109 | version versionName
110 |
111 | repositories {
112 | jcenter()
113 | }
114 |
115 | dependencies {
116 | compile gradleApi()
117 | compile "commons-io:commons-io:1.4"
118 | compile 'commons-codec:commons-codec:1.6'
119 | }
120 |
121 | apply plugin: 'maven-publish'
122 |
123 | // custom tasks for creating source/javadoc jars
124 | task sourcesJar(type: Jar, dependsOn: classes) {
125 | classifier = 'sources'
126 | from sourceSets.main.allSource
127 | }
128 |
129 | task javadocJar(type: Jar, dependsOn: javadoc) {
130 | classifier = 'javadoc'
131 | from javadoc.destinationDir
132 | }
133 |
134 | // add javadoc/source jar tasks as artifacts
135 | artifacts {
136 | archives sourcesJar, javadocJar
137 | }
138 | Properties properties = new Properties()
139 | //读取properties的配置信息,当然直接把信息写到代码里也是可以的
140 | properties.load(new File('/Users/cuieneydemacbook/Desktop/Demo/AutoFix/local.properties').newDataInputStream())
141 | bintray {
142 | user = properties.getProperty("bintray.user")
143 | key = properties.getProperty("bintray.apikey")
144 | publications = ['mavenJava']
145 | pkg {
146 | userOrg="cui131425"
147 | repo = 'mave'
148 | name = 'AutoFixPlugin'
149 | desc = 'a gradle plugin for autofix to inject some code into classes'
150 | websiteUrl = 'https://github.com/Cuieney/AutoFix'
151 | issueTrackerUrl = 'https://github.com/Cuieney/AutoFix/issues'
152 | vcsUrl = 'https://github.com/Cuieney/AutoFix'
153 | publicDownloadNumbers = true
154 | licenses = ['MIT']
155 | }
156 | }
157 |
158 | publishing {
159 | publications {
160 | mavenJava(MavenPublication) {
161 | from components.java
162 | artifact sourcesJar
163 | artifact javadocJar
164 | groupId 'com.cuieney.autofix'
165 | artifactId 'gradle'
166 | version versionName
167 | }
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/buildsrc/src/main/groovy/com/cuieney/autofix/AutoFix.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Baidu, Inc. All Rights Reserved.
3 | */
4 | package com.cuieney.autofix
5 |
6 | import com.android.SdkConstants
7 | import com.android.build.gradle.AppPlugin
8 | import com.android.build.gradle.api.ApplicationVariant
9 | import com.android.build.gradle.api.BaseVariant
10 | import com.android.build.gradle.internal.transforms.ProGuardTransform
11 | import com.cuieney.autofix.utils.AutoUtils
12 | import com.cuieney.autofix.utils.NuwaProcessor
13 | import com.cuieney.autofix.utils.NuwaSetUtils
14 | import com.google.common.collect.Maps
15 | import com.google.common.collect.Sets
16 | import org.apache.commons.codec.digest.DigestUtils
17 | import org.apache.commons.io.FileUtils
18 | import org.gradle.api.DomainObjectCollection
19 | import org.gradle.api.Plugin
20 | import org.gradle.api.Project
21 | import org.gradle.api.Task
22 | import org.gradle.api.internal.DefaultDomainObjectSet
23 | import proguard.gradle.ProGuardTask
24 |
25 | class AutoFix implements Plugin {
26 | public static final String EXTENSION_NAME = "auto_fix";
27 | private static final String MAPPING_TXT = "mapping.txt"
28 | private static final String HASH_TXT = "hash.txt"
29 |
30 | public static AutoFixExtension autoConfig
31 | @Override
32 | public void apply(Project project) {
33 | DefaultDomainObjectSet variants
34 | if (project.getPlugins().hasPlugin(AppPlugin)) {
35 | variants = project.android.applicationVariants;
36 |
37 | project.extensions.create(EXTENSION_NAME, AutoFixExtension);
38 | applyTask(project, variants);
39 | }
40 | }
41 |
42 | private void applyTask(Project project, DomainObjectCollection variants) {
43 |
44 | project.afterEvaluate {
45 |
46 | autoConfig = AutoFixExtension.getConfig(project);
47 |
48 | def includePackage = autoConfig.includePackage
49 | def excludeClass = autoConfig.excludeClass
50 |
51 | variants.all { variant ->
52 | if (!variant.getBuildType().isMinifyEnabled()) {
53 | println("不支持不开混淆的情况")
54 | return;
55 | }
56 |
57 | //获取各种task
58 | def preDexTask = project.tasks.findByName(AutoUtils.getPreDexTaskName(project, variant))
59 | def dexTask = project.tasks.findByName(AutoUtils.getDexTaskName(project, variant))
60 | println(preDexTask + " preDexTask")
61 | def manifestFile = variant.outputs.processManifest.manifestOutputFile[0]
62 |
63 |
64 | Map hashMap = applyMapping(project, variant)
65 | //创建所需要的文件包
66 | def autoFixRootDir = new File("${project.projectDir}${File.separator}autofix${File.separator}version" + variant.getVersionCode())//project/autofix/version11
67 | def outputDir = new File("${autoFixRootDir}${File.separator}${dirName}")//project/autofix/version11/debug
68 | def patchDir = new File("${outputDir}${File.separator}patch")//project/autofix/autofix/debug/patch
69 | def hashFile = new File(outputDir, "${HASH_TXT}")//project/autofix/autofix/debug/hash.txt
70 |
71 | if (!autoFixRootDir.exists()) {
72 | autoFixRootDir.mkdirs();
73 | }
74 | if (!outputDir.exists()) {
75 | outputDir.mkdirs();
76 | }
77 | if (!patchDir.exists()) {
78 | patchDir.mkdirs();
79 | }
80 |
81 | //制作patch补丁包
82 | def autoPatchTaskName = "applyAuto${variant.name.capitalize()}Patch"
83 | project.task(autoPatchTaskName) << {
84 | if (patchDir) {
85 | AutoUtils.makeDex(project, patchDir)
86 | }
87 | }
88 | def autoPatchTask = project.tasks[autoPatchTaskName]
89 |
90 | //初始化一些工作添加不需要打桩的class文件和hashfile
91 | Closure prepareClosure = {
92 | if (autoConfig.excludeClass == null) {
93 | autoConfig.excludeClass = Sets.newHashSet();
94 | }
95 | def applicationClassName = AutoUtils.getApplication(manifestFile);
96 |
97 | if (applicationClassName != null) {
98 | applicationClassName = applicationClassName.replace(".", "/") + SdkConstants.DOT_CLASS
99 | autoConfig.excludeClass.add(applicationClassName)
100 | }
101 |
102 | if (autoConfig.excludePackage == null) {
103 | autoConfig.excludePackage = Sets.newHashSet();
104 | }
105 | //添加不打桩的文件目录
106 | autoConfig.excludePackage.add("android/support/")
107 |
108 | outputDir.mkdirs()
109 | if (!hashFile.exists()) {
110 | hashFile.createNewFile()
111 | } else {
112 | hashFile.delete()
113 | hashFile.createNewFile()
114 | }
115 | }
116 |
117 |
118 | //打桩等一些工作
119 | def autoJarBeforeDex = "autoJarBeforeDex${variant.name.capitalize()}"
120 | project.task(autoJarBeforeDex) << {
121 | //获取build/intermediates/下的文件
122 | Set inputFiles = AutoUtils.getDexTaskInputFiles(project, variant, dexTask)
123 | inputFiles.each { inputFile ->
124 | def path = inputFile.absolutePath
125 | if (path.endsWith(SdkConstants.DOT_JAR)) {
126 | //对jar包进行打桩
127 | NuwaProcessor.processJar(hashFile,hashMap,inputFile, patchDir, includePackage, excludeClass)
128 | } else if (inputFile.isDirectory()) {
129 | //intermediates/classes/debug 目录下面需要打桩的class
130 | def extensions = [SdkConstants.EXT_CLASS] as String[]
131 | //过滤不需要打桩的文件class
132 | def inputClasses = FileUtils.listFiles(inputFile, extensions, true);
133 | inputClasses.each {
134 | inputClassFile ->
135 | def classPath = inputClassFile.absolutePath
136 | //过滤R文件和config文件
137 | if (classPath.endsWith(".class") && !classPath.contains("/R\$") && !classPath.endsWith("/R.class") && !classPath.endsWith("/BuildConfig.class")) {
138 | //引用nuwa而来的
139 | if (NuwaSetUtils.isIncluded(classPath, includePackage)) {
140 | if (!NuwaSetUtils.isExcluded(classPath, excludeClass)) {
141 | def bytes = NuwaProcessor.processClass(inputClassFile)
142 |
143 | if ("\\".equals(File.separator)) {
144 | classPath = classPath.split("${dirName}\\\\")[1]
145 | } else {
146 | classPath = classPath.split("${dirName}/")[1]
147 | }
148 | def hash = DigestUtils.shaHex(bytes)
149 | hashFile.append(AutoUtils.format(classPath, hash))
150 | //根据hash值来判断当前文件是否为差异文件需要做成patch吗?
151 | if (AutoUtils.notSame(hashMap,classPath, hash)) {
152 | def file = new File("${patchDir}${File.separator}${classPath}")
153 | file.getParentFile().mkdirs()
154 | if (!file.exists()) {
155 | file.createNewFile()
156 | }
157 | FileUtils.writeByteArrayToFile(file, bytes)
158 | }
159 | }
160 |
161 | }
162 | }
163 |
164 | }
165 | }
166 | }
167 | }
168 |
169 |
170 | def autoJarBeforeDexTask = project.tasks[autoJarBeforeDex]
171 |
172 | autoJarBeforeDexTask.dependsOn dexTask.taskDependencies.getDependencies(dexTask)
173 | autoJarBeforeDexTask.doFirst(prepareClosure)
174 | autoPatchTask.dependsOn autoJarBeforeDexTask
175 | dexTask.dependsOn autoPatchTask
176 | //第一个是autoJarBeforeDexTask依赖dexTask依赖的任务(就是说dexTask之前的一个任务)
177 | //第二行和第三行autoJarBeforeDexTask这个任务执行前和执行后的的任务
178 | //第三行是autoPatchTask这个任务依赖于autoJarBeforeDexTask(autoJarBeforeDexTask结束了才能到autoPatchTask)
179 |
180 |
181 | }
182 | }
183 | }
184 |
185 | private static Map applyMapping(Project project, BaseVariant variant) {
186 |
187 | Map hashMap
188 | AutoFixExtension autoConfig = AutoFixExtension.getConfig(project);
189 | if (autoConfig.lastVersion != null) {
190 |
191 | def preVersionPath = new File("${project.projectDir}${File.separator}autofix${File.separator}version" + autoConfig.lastVersion)
192 | if (preVersionPath.exists()) {
193 | def hashFile = new File("${preVersionPath}${File.separator}${variant.dirName}${File.separator}${HASH_TXT}")
194 | hashMap = AutoUtils.parseMap(hashFile)
195 | }
196 | return hashMap;
197 | }
198 | }
199 |
200 |
201 |
202 | }
203 |
--------------------------------------------------------------------------------
/buildsrc/src/main/groovy/com/cuieney/autofix/AutoFixExtension.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Baidu, Inc. All Rights Reserved.
3 | */
4 | package com.cuieney.autofix
5 |
6 | import groovy.transform.CompileStatic
7 | import org.gradle.api.Project
8 | import org.gradle.api.tasks.Input
9 | @CompileStatic
10 | class AutoFixExtension {
11 |
12 | @Input
13 | HashSet includePackage = [];
14 |
15 | @Input
16 | HashSet excludePackage = [];
17 |
18 | @Input
19 | HashSet excludeClass = [];
20 |
21 | @Input
22 | String lastVersion
23 |
24 | public static AutoFixExtension getConfig(Project project) {
25 | AutoFixExtension config =
26 | project.getExtensions().findByType(AutoFixExtension.class);
27 | if (config == null) {
28 | config = new AutoFixExtension();
29 | }
30 | return config;
31 | }
32 |
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/buildsrc/src/main/groovy/com/cuieney/autofix/utils/AutoUtils.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Baidu, Inc. All Rights Reserved.
3 | */
4 | package com.cuieney.autofix.utils
5 |
6 | import com.android.SdkConstants
7 | import com.android.build.gradle.api.BaseVariant
8 | import com.cuieney.autofix.AutoFix
9 | import com.google.common.collect.Sets
10 | import groovy.xml.Namespace
11 | import org.apache.commons.codec.digest.DigestUtils
12 | import org.apache.commons.io.FileUtils
13 | import org.apache.commons.io.IOUtils
14 | import org.apache.tools.ant.taskdefs.condition.Os
15 | import org.gradle.api.Project
16 | import org.gradle.api.Task
17 |
18 | public class AutoUtils {
19 | private static final String MAP_SEPARATOR = ":"
20 | private static final String PATCH_NAME = "patch.jar"
21 |
22 | public static boolean notSame(Map map,String name, String hash) {
23 | def notSame = false
24 | if (map) {
25 | def value = map.get(name)
26 | if (value) {
27 | if (!value.equals(hash)) {
28 | notSame = true
29 | }
30 | } else {
31 | notSame = true
32 | }
33 | }
34 | return notSame
35 | }
36 |
37 | public static Map parseMap(File hashFile) {
38 | def hashMap = [:]
39 | if (hashFile.exists()) {
40 | hashFile.eachLine {
41 | List list = it.split(MAP_SEPARATOR)
42 | if (list.size() == 2) {
43 | hashMap.put(list[0], list[1])
44 | }
45 | }
46 | } else {
47 | println "$hashFile does not exist"
48 | }
49 | return hashMap
50 | }
51 |
52 | public static format(String path, String hash) {
53 | return path + MAP_SEPARATOR + hash + "\n"
54 | }
55 |
56 |
57 | public static String getApplication(File manifestFile) {
58 | def manifest = new XmlParser().parse(manifestFile)
59 | def androidTag = new Namespace("http://schemas.android.com/apk/res/android", 'android')
60 | return manifest.application[0].attribute(androidTag.name)
61 | }
62 |
63 | private static List getFilesHash(String baseDirectoryPath, File directoryFile) {
64 | List javaFiles = new ArrayList();
65 |
66 | File[] children = directoryFile.listFiles();
67 | if (children == null) {
68 | return javaFiles;
69 | }
70 |
71 | for (final File file : children) {
72 | if (file.isDirectory()) {
73 | List tempList = getFilesHash(baseDirectoryPath, file);
74 | if (!tempList.isEmpty()) {
75 | javaFiles.addAll(tempList);
76 | }
77 | } else {
78 | InputStream is = new FileInputStream(file);
79 | def hash = DigestUtils.shaHex(IOUtils.toByteArray(is))
80 | javaFiles.add(hash)
81 |
82 | is.close()
83 | }
84 | }
85 |
86 | return javaFiles;
87 | }
88 |
89 | public static makeDex(Project project, File classDir) {
90 | println("makeDex:"+classDir)
91 | if (classDir.listFiles() != null && classDir.listFiles().size()) {
92 | StringBuilder builder = new StringBuilder();
93 | def baseDirectoryPath = classDir.getAbsolutePath() + File.separator;
94 | getFilesHash(baseDirectoryPath, classDir).each {
95 | builder.append(it)
96 | }
97 | def hash = DigestUtils.shaHex(builder.toString().bytes)
98 | def sdkDir
99 |
100 | Properties properties = new Properties()
101 | File localProps = project.rootProject.file("local.properties")
102 | if (localProps.exists()) {
103 | properties.load(localProps.newDataInputStream())
104 | sdkDir = properties.getProperty("sdk.dir")
105 | } else {
106 | sdkDir = System.getenv("ANDROID_HOME")
107 | }
108 | if (sdkDir) {
109 | def cmdExt = Os.isFamily(Os.FAMILY_WINDOWS) ? '.bat' : ''
110 | def stdout = new ByteArrayOutputStream()
111 | project.exec {
112 | commandLine "${sdkDir}${File.separator}build-tools${File.separator}${project.android.buildToolsVersion}${File.separator}dx${cmdExt}",
113 | '--dex',
114 | "--output=${new File(classDir.getParent(), PATCH_NAME).absolutePath}",
115 | "${classDir.absolutePath}"
116 | standardOutput = stdout
117 | }
118 | def error = stdout.toString().trim()
119 | if (error) {
120 | println "dex error:" + error
121 | }
122 | } else {
123 | }
124 | }else{
125 | }
126 | }
127 |
128 | static String getProcessManifestTaskName(Project project, BaseVariant variant) {
129 | return "process${variant.name.capitalize()}Manifest"
130 | }
131 |
132 | static String getProGuardTaskName(Project project, BaseVariant variant) {
133 | if (isUseTransformAPI(project)) {
134 | return "transformClassesAndResourcesWithProguardFor${variant.name.capitalize()}"
135 | } else {
136 | return "proguard${variant.name.capitalize()}"
137 | }
138 | }
139 |
140 | static String getPreDexTaskName(Project project, BaseVariant variant) {
141 | if (isUseTransformAPI(project)) {
142 | return ""
143 | } else {
144 | return "preDex${variant.name.capitalize()}"
145 | }
146 | }
147 |
148 | static String getDexTaskName(Project project, BaseVariant variant) {
149 | if (isUseTransformAPI(project)) {
150 | return "transformClassesWithDexFor${variant.name.capitalize()}"
151 | } else {
152 | return "dex${variant.name.capitalize()}"
153 | }
154 | }
155 |
156 |
157 | static Set getDexTaskInputFiles(Project project, BaseVariant variant, Task dexTask) {
158 | if (dexTask == null) {
159 | dexTask = project.tasks.findByName(getDexTaskName(project, variant));
160 | }
161 |
162 | if (isUseTransformAPI(project)) {
163 | def extensions = [SdkConstants.EXT_JAR] as String[]
164 |
165 | Set files = Sets.newHashSet();
166 |
167 | dexTask.inputs.files.files.each {
168 | if (it.exists()) {
169 | if (it.isDirectory()) {
170 | Collection jars = FileUtils.listFiles(it, extensions, true);
171 | files.addAll(jars)
172 | //intermediates/class下面对应的class文件
173 | if (it.absolutePath.toLowerCase().endsWith("intermediates${File.separator}classes${File.separator}${variant.dirName}".toLowerCase())) {
174 | files.add(it)
175 | }
176 | //jar包
177 | } else if (it.name.endsWith(SdkConstants.DOT_JAR)) {
178 | files.add(it)
179 | }
180 | }
181 | }
182 | return files
183 | } else {
184 | return dexTask.inputs.files.files;
185 | }
186 | }
187 |
188 |
189 | public static boolean isUseTransformAPI(Project project) {
190 | // println("==========gradleVersion:" + compareVersionName("1.9.0", "1.4.0"))
191 | return compareVersionName(project.gradle.gradleVersion, "1.4.0") >= 0;
192 | }
193 |
194 |
195 | private static int compareVersionName(String str1, String str2) {
196 | String[] thisParts = str1.split("-")[0].split("\\.");
197 | String[] thatParts = str2.split("-")[0].split("\\.");
198 | int length = Math.max(thisParts.length, thatParts.length);
199 | for (int i = 0; i < length; i++) {
200 | int thisPart = i < thisParts.length ?
201 | Integer.parseInt(thisParts[i]) : 0;
202 | int thatPart = i < thatParts.length ?
203 | Integer.parseInt(thatParts[i]) : 0;
204 | if (thisPart < thatPart)
205 | return -1;
206 | if (thisPart > thatPart)
207 | return 1;
208 | }
209 | return 0;
210 | }
211 |
212 | //
213 | }
--------------------------------------------------------------------------------
/buildsrc/src/main/groovy/com/cuieney/autofix/utils/NuwaProcessor.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Baidu, Inc. All Rights Reserved.
3 | */
4 | package com.cuieney.autofix.utils
5 |
6 | import org.apache.commons.codec.digest.DigestUtils
7 | import org.apache.commons.io.FileUtils
8 | import org.apache.commons.io.IOUtils
9 | import org.objectweb.asm.*
10 |
11 | import java.util.jar.JarEntry
12 | import java.util.jar.JarFile
13 | import java.util.jar.JarOutputStream
14 | import java.util.zip.ZipEntry
15 |
16 | /**
17 | * Created by jixin.jia on 15/11/10.
18 | */
19 | class NuwaProcessor {
20 |
21 |
22 | public
23 | static processJar(File hashFile,Map map,File jarFile, File patchDir, HashSet includePackage, HashSet excludeClass) {
24 | if (jarFile) {
25 |
26 | def optJar = new File(jarFile.getParent(), jarFile.name + ".opt")
27 | def file = new JarFile(jarFile);
28 | Enumeration enumeration = file.entries();
29 | JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(optJar));
30 | //遍历jar文件中的class 把需要的文件进行打桩
31 | while (enumeration.hasMoreElements()) {
32 | JarEntry jarEntry = (JarEntry) enumeration.nextElement();
33 | String entryName = jarEntry.getName();
34 | ZipEntry zipEntry = new ZipEntry(entryName);
35 |
36 | InputStream inputStream = file.getInputStream(jarEntry);
37 | jarOutputStream.putNextEntry(zipEntry);
38 | //打桩操作
39 | if (shouldProcessClassInJar(entryName, includePackage, excludeClass)) {
40 | def bytes = referHackWhenInit(inputStream);
41 |
42 | jarOutputStream.write(bytes);
43 | def hash = DigestUtils.shaHex(bytes)
44 | hashFile.append(AutoUtils.format(entryName, hash))
45 | //根据hash值来判断当前文件是否为差异文件需要做成patch吗?
46 | if (AutoUtils.notSame(map,entryName, hash)) {
47 | def entryFile = new File("${patchDir}${File.separator}${entryName}")
48 | entryFile.getParentFile().mkdirs()
49 | if (!entryFile.exists()) {
50 | entryFile.createNewFile()
51 | }
52 | FileUtils.writeByteArrayToFile(entryFile, bytes)
53 | }
54 | }
55 | else {
56 | jarOutputStream.write(IOUtils.toByteArray(inputStream));
57 | }
58 | jarOutputStream.closeEntry();
59 | }
60 |
61 | jarOutputStream.close();
62 |
63 | file.close();
64 |
65 | if (jarFile.exists()) {
66 | jarFile.delete()
67 | }
68 |
69 | org.apache.commons.io.FileUtils.copyFile(optJar,jarFile);
70 |
71 | org.apache.commons.io.FileUtils.deleteQuietly(optJar)
72 |
73 | }
74 |
75 | }
76 |
77 |
78 | //引用你的预设class 把他当成要打桩的文件
79 | public static byte[] referHackWhenInit(InputStream inputStream) {
80 | ClassReader cr = new ClassReader(inputStream);
81 | ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
82 | boolean hasHackSuccess = false;
83 | boolean isInterface = false;
84 | ClassVisitor cv = new ClassVisitor(Opcodes.ASM4, cw) {
85 | @Override
86 | void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
87 | super.visit(version, access, name, signature, superName, interfaces)
88 | //检查类型
89 | isInterface = (access & Opcodes.ACC_INTERFACE) != 0;
90 | }
91 |
92 | @Override
93 | public MethodVisitor visitMethod(int access, String name, String desc,
94 | String signature, String[] exceptions) {
95 | MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
96 | mv = new MethodVisitor(Opcodes.ASM4, mv) {
97 | @Override
98 | void visitInsn(int opcode) {
99 | if (("".equals(name) || "".equals(name)) && opcode == Opcodes.RETURN && !hasHackSuccess) {
100 | // 第一次尝试hack
101 | NuwaProcessor.hackProcess(mv)
102 | hasHackSuccess = true;
103 | }
104 | super.visitInsn(opcode);
105 | }
106 | }
107 | return mv;
108 | }
109 |
110 | };
111 | cr.accept(cv, 0);
112 | if (!hasHackSuccess && !isInterface) {
113 | // has not hack and not interface
114 | // 第二次尝试hack 这个有问题 先关闭
115 | //return addCinitAndHack(cr,cw);
116 | return cw.toByteArray();
117 | } else {
118 | return cw.toByteArray();
119 | }
120 | }
121 |
122 | /**
123 | * add clinit and do hack
124 | * @param cr
125 | * @param cw
126 | * @return
127 | */
128 | private static byte[] addCinitAndHack(ClassReader cr, ClassWriter cw) {
129 | MethodVisitor constructor = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "", "()V", null, null);
130 | constructor.visitCode();
131 | hackProcess(constructor);
132 | constructor.visitInsn(Opcodes.RETURN);
133 | // constructor.visitMaxs(1 + 2, 1);
134 | constructor.visitEnd();
135 | ClassVisitor cv = new ClassVisitor(Opcodes.ASM4, cw) {};
136 | cr.accept(cv, 0);
137 | return cw.toByteArray();
138 | }
139 |
140 | /**
141 | * hack class
142 | * @param v
143 | */
144 | public static void hackProcess(MethodVisitor v) {
145 | Label l1 = new Label();
146 | v.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Boolean", "FALSE", "Ljava/lang/Boolean;");
147 | v.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
148 | v.visitJumpInsn(Opcodes.IFEQ, l1);
149 | v.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
150 | v.visitLdcInsn(Type.getType("Lcom/cuieney/autofix/Auto;"));
151 | v.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
152 | v.visitLabel(l1);
153 | }
154 |
155 |
156 | public static boolean shouldProcessPreDexJar(String path) {
157 | return path.endsWith("classes.jar") &&
158 | !path.contains("com.android.support") &&
159 | !path.contains("/android/m2repository");
160 | }
161 |
162 | private
163 | static boolean shouldProcessClassInJar(String entryName, HashSet includePackage, HashSet excludeClass) {
164 | return entryName.endsWith(".class") &&
165 | !entryName.startsWith("com/cuieney/fix/") &&
166 | NuwaSetUtils.isIncluded(entryName, includePackage) &&
167 | !NuwaSetUtils.isExcluded(entryName, excludeClass) &&
168 | !entryName.contains("android/support/")
169 | }
170 |
171 | //通过文件的形式对file进行打桩
172 | public static byte[] processClass(File file) {
173 | println(file.getAbsolutePath() + "oye pro")
174 | def optClass = new File(file.getParent(), file.name + ".opt")
175 |
176 | FileInputStream inputStream = new FileInputStream(file);
177 | FileOutputStream outputStream = new FileOutputStream(optClass)
178 |
179 | def bytes = referHackWhenInit(inputStream);
180 | outputStream.write(bytes)
181 | inputStream.close()
182 | outputStream.close()
183 | if (file.exists()) {
184 | file.delete()
185 | }
186 | optClass.renameTo(file)
187 | return bytes
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/buildsrc/src/main/groovy/com/cuieney/autofix/utils/NuwaSetUtils.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Baidu, Inc. All Rights Reserved.
3 | */
4 | package com.cuieney.autofix.utils
5 |
6 | /**
7 | * Created by jixin.jia on 15/11/10.
8 | */
9 | class NuwaSetUtils {
10 | public static boolean isExcluded(String path, Set excludeClass) {
11 | def isExcluded = false;
12 | excludeClass.each { exclude ->
13 | if (path.endsWith(exclude)) {
14 | isExcluded = true
15 | }
16 | }
17 | return isExcluded
18 | }
19 |
20 | public static boolean isIncluded(String path, Set includePackage) {
21 | if (includePackage.size() == 0) {
22 | return true
23 | }
24 |
25 | def isIncluded = false;
26 | includePackage.each { include ->
27 | if (path.contains(include)) {
28 | isIncluded = true
29 | }
30 | }
31 | return isIncluded
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/buildsrc/src/main/resources/META-INF/gradle-plugins/com.cuieney.autofix.properties:
--------------------------------------------------------------------------------
1 | implementation-class=com.cuieney.autofix.AutoFix
--------------------------------------------------------------------------------
/fix.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cuieney/AutoFix/98c21f3022eb24e299788d4bee67a7cc9de3a976/fix.zip
--------------------------------------------------------------------------------
/fix/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/fix/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | jcenter()
4 | mavenCentral()
5 | }
6 | dependencies {
7 | classpath 'com.android.tools.build:gradle:2.3.0'
8 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1'
9 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4'
10 | }
11 | }
12 | repositories {
13 | jcenter()
14 | }
15 |
16 | apply plugin: 'com.android.library'
17 |
18 | android {
19 | compileSdkVersion 25
20 | buildToolsVersion "25.0.2"
21 |
22 | defaultConfig {
23 | minSdkVersion 15
24 | targetSdkVersion 25
25 | versionCode 1
26 | versionName "1.0"
27 |
28 | }
29 | buildTypes {
30 | release {
31 | minifyEnabled false
32 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
33 | }
34 | }
35 | }
36 |
37 | dependencies {
38 | compile fileTree(dir: 'libs', include: ['*.jar'])
39 | testCompile 'junit:junit:4.12'
40 | }
41 | apply from: 'mavenpush.gradle'
--------------------------------------------------------------------------------
/fix/mavenpush.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.github.dcendents.android-maven'
2 | apply plugin: 'com.jfrog.bintray'
3 |
4 | version = "1.1.2"
5 | def siteUrl = 'https://github.com/Cuieney/AutoFix' // 项目的主页
6 | def gitUrl = 'https://github.com/Cuieney/AutoFix.git' // Git仓库的url
7 | group = "com.cuieney.library" // Maven Group ID for the artifact,
8 | install {
9 | repositories.mavenInstaller {
10 | // This generates POM.xml with proper parameters
11 | pom {
12 | project {
13 | packaging 'aar'
14 | // Add your description here
15 | name 'another hotfix framework' //项目的描述 你可以多写一点
16 | url siteUrl
17 | // Set your license
18 | licenses {
19 | license {
20 | name 'The Apache Software License, Version 2.0'
21 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
22 | }
23 | }
24 | developers {
25 | developer {
26 | id 'cuieney' //填写的一些基本信息
27 | name 'cuieney'
28 | email 'cuieney@163.com'
29 | }
30 | }
31 | scm {
32 | connection gitUrl
33 | developerConnection gitUrl
34 | url siteUrl
35 | }
36 | }
37 | }
38 | }
39 | }
40 | task sourcesJar(type: Jar) {
41 | from android.sourceSets.main.java.srcDirs
42 | classifier = 'sources'
43 | }
44 | task javadoc(type: Javadoc) {
45 | options.encoding = 'UTF-8'
46 | source = android.sourceSets.main.java.srcDirs
47 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
48 | }
49 | task javadocJar(type: Jar, dependsOn: javadoc) {
50 | classifier = 'javadoc'
51 | from javadoc.destinationDir
52 | }
53 | artifacts {
54 | archives javadocJar
55 | archives sourcesJar
56 | }
57 | Properties properties = new Properties()
58 | //读取properties的配置信息,当然直接把信息写到代码里也是可以的
59 | properties.load(project.rootProject.file('local.properties').newDataInputStream())
60 | bintray {
61 | user = properties.getProperty("bintray.user")
62 | key = properties.getProperty("bintray.apikey")
63 | configurations = ['archives']
64 | pkg {
65 | userOrg="cui131425"
66 | repo = "mave" //这个应该是传到maven的仓库的
67 | name = "AutoFix" //发布的项目名字
68 | websiteUrl = siteUrl
69 | vcsUrl = gitUrl
70 | licenses = ["Apache-2.0"]
71 | publish = true
72 | }
73 | }
--------------------------------------------------------------------------------
/fix/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/cuieneydemacbook/Downloads/android-sdk-macosx/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 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/fix/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/fix/src/main/assets/Auto.dex:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cuieney/AutoFix/98c21f3022eb24e299788d4bee67a7cc9de3a976/fix/src/main/assets/Auto.dex
--------------------------------------------------------------------------------
/fix/src/main/java/com/cuieney/fix/AutoFix.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Baidu, Inc. All Rights Reserved.
3 | */
4 |
5 | package com.cuieney.fix;
6 |
7 | import android.content.Context;
8 | import android.content.pm.ApplicationInfo;
9 | import android.content.pm.PackageManager;
10 | import android.content.pm.PackageManager.NameNotFoundException;
11 | import android.content.res.AssetManager;
12 | import android.os.Build;
13 | import android.util.Log;
14 |
15 |
16 | import java.io.File;
17 | import java.io.FileOutputStream;
18 | import java.io.IOException;
19 | import java.io.InputStream;
20 | import java.io.OutputStream;
21 | import java.lang.reflect.Constructor;
22 | import java.lang.reflect.Field;
23 | import java.lang.reflect.InvocationTargetException;
24 | import java.lang.reflect.Method;
25 | import java.util.ArrayList;
26 | import java.util.Enumeration;
27 | import java.util.HashSet;
28 | import java.util.List;
29 | import java.util.ListIterator;
30 | import java.util.Map;
31 | import java.util.Set;
32 | import java.util.concurrent.ConcurrentHashMap;
33 | import java.util.zip.ZipFile;
34 |
35 | import dalvik.system.DexFile;
36 |
37 | /**
38 | * modify from multidex source code
39 | */
40 | public final class AutoFix {
41 |
42 | static final String TAG = "AutoFix";
43 | private static final String CODE_CACHE_NAME = "code_cache";
44 | private static final String CODE_CACHE_SECONDARY_FOLDER_NAME = "auto-dexes";
45 | private static final Set installedApk = new HashSet();
46 | private static final String DIR = "auto_opt";
47 | private static File mOptDir;
48 | private static Map> mFixedClass = new ConcurrentHashMap>();
49 |
50 |
51 | private AutoFix() {
52 | }
53 |
54 | public static void init(Context context) {
55 | initPathFromAssets(context, "Auto.dex");
56 | }
57 |
58 |
59 | /**
60 | * 从Assets里取出补丁,一般用于测试
61 | *
62 | * @param context
63 | * @param assetName
64 | */
65 | public static void initPathFromAssets(Context context, String assetName) {
66 | File dexDir = new File(context.getFilesDir(), "hotfix");
67 | dexDir.mkdir();
68 | mOptDir = new File(context.getFilesDir(), DIR);
69 | if (!mOptDir.exists() && !mOptDir.mkdirs()) {// make directory fail
70 | }
71 | String dexPath = null;
72 | try {
73 | dexPath = copyAsset(context, assetName, dexDir);
74 | } catch (IOException e) {
75 | } finally {
76 | if (dexPath != null && new File(dexPath).exists()) {
77 | applyPatch(context, dexPath);
78 | }
79 | }
80 | }
81 |
82 | /**
83 | * 从指定目录加载补丁
84 | *
85 | * @param context
86 | * @param dexPath
87 | */
88 | public static void applyPatch(Context context, String dexPath) {
89 |
90 | try {
91 | ApplicationInfo applicationInfo = getApplicationInfo(context);
92 | if (applicationInfo == null) {
93 | return;
94 | }
95 |
96 | synchronized (installedApk) {
97 | if (installedApk.contains(dexPath)) {
98 | return;
99 | }
100 | installedApk.add(dexPath);
101 |
102 | /* The patched class loader is expected to be a descendant of
103 | * dalvik.system.BaseDexClassLoader. We modify its
104 | * dalvik.system.DexPathList pathList field to append additional DEX
105 | * file entries.
106 | */
107 | ClassLoader loader;
108 | try {
109 | loader = context.getClassLoader();
110 | } catch (RuntimeException e) {
111 | /* Ignore those exceptions so that we don't break tests relying on Context like
112 | * a android.test.mock.MockContext or a android.content.ContextWrapper with a
113 | * null base Context.
114 | */
115 | Log.w(TAG, "Failure while trying to obtain Context class loader. " +
116 | "Must be running in test mode. Skip patching.", e);
117 | return;
118 | }
119 | if (loader == null) {
120 | // Note, the context class loader is null when running Robolectric tests.
121 | Log.e(TAG,
122 | "Context class loader is null. Must be running in test mode. "
123 | + "Skip patching.");
124 | return;
125 | }
126 |
127 | List files = new ArrayList();
128 | files.add(new File(dexPath));
129 | File dexDir = getDexDir(context, applicationInfo);
130 | installDexes(loader, dexDir, files);
131 | }
132 |
133 | } catch (Exception e) {
134 | e.printStackTrace();
135 | } catch (Throwable e) {
136 | e.printStackTrace();
137 | }
138 | }
139 |
140 | private static ApplicationInfo getApplicationInfo(Context context)
141 | throws NameNotFoundException {
142 | PackageManager pm;
143 | String packageName;
144 | try {
145 | pm = context.getPackageManager();
146 | packageName = context.getPackageName();
147 | } catch (RuntimeException e) {
148 | /* Ignore those exceptions so that we don't break tests relying on Context like
149 | * a android.test.mock.MockContext or a android.content.ContextWrapper with a null
150 | * base Context.
151 | */
152 | Log.w(TAG, "Failure while trying to obtain ApplicationInfo from Context. " +
153 | "Must be running in test mode. Skip patching.", e);
154 | return null;
155 | }
156 | if (pm == null || packageName == null) {
157 | // This is most likely a mock context, so just return without patching.
158 | return null;
159 | }
160 | ApplicationInfo applicationInfo =
161 | pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
162 | return applicationInfo;
163 | }
164 |
165 |
166 | private static void installDexes(ClassLoader loader, File dexDir, List files)
167 | throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException,
168 | InvocationTargetException, NoSuchMethodException, IOException, InstantiationException, ClassNotFoundException {
169 | if (!files.isEmpty()) {
170 | if (Build.VERSION.SDK_INT >= 24) {
171 | V24.install(loader, files, dexDir);
172 | } else if (Build.VERSION.SDK_INT >= 23) {
173 | V23.install(loader, files, dexDir);
174 | } else if (Build.VERSION.SDK_INT >= 19) {
175 | V19.install(loader, files, dexDir);
176 | } else if (Build.VERSION.SDK_INT >= 14) {
177 | V14.install(loader, files, dexDir);
178 | } else {
179 | V4.install(loader, files);
180 | }
181 | }
182 | }
183 |
184 |
185 | private static File getDexDir(Context context, ApplicationInfo applicationInfo)
186 | throws IOException {
187 | File cache = new File(applicationInfo.dataDir, CODE_CACHE_NAME);
188 | try {
189 | mkdirChecked(cache);
190 | } catch (IOException e) {
191 | /* If we can't emulate code_cache, then store to filesDir. This means abandoning useless
192 | * files on disk if the device ever updates to android 5+. But since this seems to
193 | * happen only on some devices running android 2, this should cause no pollution.
194 | */
195 | cache = new File(context.getFilesDir(), CODE_CACHE_NAME);
196 | mkdirChecked(cache);
197 | }
198 | File dexDir = new File(cache, CODE_CACHE_SECONDARY_FOLDER_NAME);
199 | mkdirChecked(dexDir);
200 | return dexDir;
201 | }
202 |
203 | private static void mkdirChecked(File dir) throws IOException {
204 | dir.mkdir();
205 | if (!dir.isDirectory()) {
206 | File parent = dir.getParentFile();
207 | if (parent == null) {
208 | Log.e(TAG, "Failed to create dir " + dir.getPath() + ". Parent file is null.");
209 | } else {
210 | Log.e(TAG, "Failed to create dir " + dir.getPath() +
211 | ". parent file is a dir " + parent.isDirectory() +
212 | ", a file " + parent.isFile() +
213 | ", exists " + parent.exists() +
214 | ", readable " + parent.canRead() +
215 | ", writable " + parent.canWrite());
216 | }
217 | throw new IOException("Failed to create directory " + dir.getPath());
218 | }
219 | }
220 |
221 |
222 | private static final class V23 {
223 |
224 | private static void install(ClassLoader loader, List additionalClassPathEntries,
225 | File optimizedDirectory)
226 | throws IllegalArgumentException, IllegalAccessException,
227 | NoSuchFieldException, InvocationTargetException, NoSuchMethodException, InstantiationException {
228 |
229 | Field pathListField = AutoUtils.findField(loader, "pathList");
230 | Object dexPathList = pathListField.get(loader);
231 | Field dexElement = AutoUtils.findField(dexPathList, "dexElements");
232 | Class> elementType = dexElement.getType().getComponentType();
233 | Method loadDex = AutoUtils.findMethod(dexPathList, "loadDexFile", File.class, File.class);
234 | loadDex.setAccessible(true);
235 |
236 | Object dex = loadDex.invoke(null, additionalClassPathEntries.get(0), optimizedDirectory);
237 | Constructor> constructor = elementType.getConstructor(File.class, boolean.class, File.class, DexFile.class);
238 | constructor.setAccessible(true);
239 | Object element = constructor.newInstance(new File(""), false, additionalClassPathEntries.get(0), dex);
240 |
241 | Object[] newEles = new Object[1];
242 | newEles[0] = element;
243 | AutoUtils.expandFieldArray(dexPathList, "dexElements", newEles);
244 | }
245 |
246 | }
247 |
248 | private static final class V24 {
249 |
250 | private static void install(ClassLoader loader, List additionalClassPathEntries,
251 | File optimizedDirectory)
252 | throws IllegalArgumentException, IllegalAccessException,
253 | NoSuchFieldException, InvocationTargetException, NoSuchMethodException, InstantiationException, ClassNotFoundException {
254 |
255 | Field pathListField = AutoUtils.findField(loader, "pathList");
256 | Object dexPathList = pathListField.get(loader);
257 | Field dexElement = AutoUtils.findField(dexPathList, "dexElements");
258 | Class> elementType = dexElement.getType().getComponentType();
259 | Method loadDex = AutoUtils.findMethod(dexPathList, "loadDexFile", File.class, File.class, ClassLoader.class, dexElement.getType());
260 | loadDex.setAccessible(true);
261 |
262 | Object dex = loadDex.invoke(null, additionalClassPathEntries.get(0), optimizedDirectory, loader, dexElement.get(dexPathList));
263 | Constructor> constructor = elementType.getConstructor(File.class, boolean.class, File.class, DexFile.class);
264 | constructor.setAccessible(true);
265 | Object element = constructor.newInstance(new File(""), false, additionalClassPathEntries.get(0), dex);
266 |
267 | Object[] newEles = new Object[1];
268 | newEles[0] = element;
269 | AutoUtils.expandFieldArray(dexPathList, "dexElements", newEles);
270 | }
271 |
272 | }
273 |
274 | /**
275 | * Installer for platform versions 19.
276 | */
277 | private static final class V19 {
278 |
279 | private static void install(ClassLoader loader, List additionalClassPathEntries,
280 | File optimizedDirectory)
281 | throws IllegalArgumentException, IllegalAccessException,
282 | NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
283 | /* The patched class loader is expected to be a descendant of
284 | * dalvik.system.BaseDexClassLoader. We modify its
285 | * dalvik.system.DexPathList pathList field to append additional DEX
286 | * file entries.
287 | */
288 | Field pathListField = AutoUtils.findField(loader, "pathList");
289 | Object dexPathList = pathListField.get(loader);
290 | ArrayList suppressedExceptions = new ArrayList();
291 | AutoUtils.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
292 | new ArrayList(additionalClassPathEntries), optimizedDirectory,
293 | suppressedExceptions));
294 | if (suppressedExceptions.size() > 0) {
295 | for (IOException e : suppressedExceptions) {
296 | Log.w(TAG, "Exception in makeDexElement", e);
297 | }
298 | Field suppressedExceptionsField =
299 | AutoUtils.findField(dexPathList, "dexElementsSuppressedExceptions");
300 | IOException[] dexElementsSuppressedExceptions =
301 | (IOException[]) suppressedExceptionsField.get(dexPathList);
302 |
303 | if (dexElementsSuppressedExceptions == null) {
304 | dexElementsSuppressedExceptions =
305 | suppressedExceptions.toArray(
306 | new IOException[suppressedExceptions.size()]);
307 | } else {
308 | IOException[] combined =
309 | new IOException[suppressedExceptions.size() +
310 | dexElementsSuppressedExceptions.length];
311 | suppressedExceptions.toArray(combined);
312 | System.arraycopy(dexElementsSuppressedExceptions, 0, combined,
313 | suppressedExceptions.size(), dexElementsSuppressedExceptions.length);
314 | dexElementsSuppressedExceptions = combined;
315 | }
316 |
317 | suppressedExceptionsField.set(dexPathList, dexElementsSuppressedExceptions);
318 | }
319 | }
320 |
321 | /**
322 | * A wrapper around
323 | * {@code private static final dalvik.system.DexPathList#makeDexElements}.
324 | */
325 | private static Object[] makeDexElements(
326 | Object dexPathList, ArrayList files, File optimizedDirectory,
327 | ArrayList suppressedExceptions)
328 | throws IllegalAccessException, InvocationTargetException,
329 | NoSuchMethodException {
330 | Method makeDexElements =
331 | AutoUtils.findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class,
332 | ArrayList.class);
333 |
334 | return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory,
335 | suppressedExceptions);
336 | }
337 | }
338 |
339 | /**
340 | * Installer for platform versions 14, 15, 16, 17 and 18.
341 | */
342 | private static final class V14 {
343 |
344 | private static void install(ClassLoader loader, List additionalClassPathEntries,
345 | File optimizedDirectory)
346 | throws IllegalArgumentException, IllegalAccessException,
347 | NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
348 | /* The patched class loader is expected to be a descendant of
349 | * dalvik.system.BaseDexClassLoader. We modify its
350 | * dalvik.system.DexPathList pathList field to append additional DEX
351 | * file entries.
352 | */
353 | Field pathListField = AutoUtils.findField(loader, "pathList");
354 | Object dexPathList = pathListField.get(loader);
355 | AutoUtils.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
356 | new ArrayList(additionalClassPathEntries), optimizedDirectory));
357 | }
358 |
359 | /**
360 | * A wrapper around
361 | * {@code private static final dalvik.system.DexPathList#makeDexElements}.
362 | */
363 | private static Object[] makeDexElements(
364 | Object dexPathList, ArrayList files, File optimizedDirectory)
365 | throws IllegalAccessException, InvocationTargetException,
366 | NoSuchMethodException {
367 | Method makeDexElements =
368 | AutoUtils.findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class);
369 |
370 | return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory);
371 | }
372 | }
373 |
374 | /**
375 | * Installer for platform versions 4 to 13.
376 | */
377 | private static final class V4 {
378 | private static void install(ClassLoader loader, List additionalClassPathEntries)
379 | throws IllegalArgumentException, IllegalAccessException,
380 | NoSuchFieldException, IOException {
381 | /* The patched class loader is expected to be a descendant of
382 | * dalvik.system.DexClassLoader. We modify its
383 | * fields mPaths, mFiles, mZips and mDexs to append additional DEX
384 | * file entries.
385 | */
386 | int extraSize = additionalClassPathEntries.size();
387 |
388 | Field pathField = AutoUtils.findField(loader, "path");
389 |
390 | StringBuilder path = new StringBuilder((String) pathField.get(loader));
391 | String[] extraPaths = new String[extraSize];
392 | File[] extraFiles = new File[extraSize];
393 | ZipFile[] extraZips = new ZipFile[extraSize];
394 | DexFile[] extraDexs = new DexFile[extraSize];
395 | for (ListIterator iterator = additionalClassPathEntries.listIterator();
396 | iterator.hasNext(); ) {
397 | File additionalEntry = iterator.next();
398 | String entryPath = additionalEntry.getAbsolutePath();
399 | path.append(':').append(entryPath);
400 | int index = iterator.previousIndex();
401 | extraPaths[index] = entryPath;
402 | extraFiles[index] = additionalEntry;
403 | extraZips[index] = new ZipFile(additionalEntry);
404 | extraDexs[index] = DexFile.loadDex(entryPath, entryPath + ".dex", 0);
405 | }
406 |
407 | pathField.set(loader, path.toString());
408 | AutoUtils.expandFieldArray(loader, "mPaths", extraPaths);
409 | AutoUtils.expandFieldArray(loader, "mFiles", extraFiles);
410 | AutoUtils.expandFieldArray(loader, "mZips", extraZips);
411 | AutoUtils.expandFieldArray(loader, "mDexs", extraDexs);
412 | }
413 | }
414 |
415 | public static String copyAsset(Context context, String assetName, File dir) throws IOException {
416 | File outFile = new File(dir, assetName);
417 | if (!outFile.exists()) {
418 | AssetManager assetManager = context.getAssets();
419 | InputStream in = assetManager.open(assetName);
420 | OutputStream out = new FileOutputStream(outFile);
421 | copyFile(in, out);
422 | in.close();
423 | out.close();
424 | }
425 | return outFile.getAbsolutePath();
426 | }
427 |
428 | private static void copyFile(InputStream in, OutputStream out) throws IOException {
429 | byte[] buffer = new byte[1024];
430 | int read;
431 | while ((read = in.read(buffer)) != -1) {
432 | out.write(buffer, 0, read);
433 | }
434 | }
435 |
436 |
437 |
438 | }
439 |
--------------------------------------------------------------------------------
/fix/src/main/java/com/cuieney/fix/AutoUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Baidu, Inc. All Rights Reserved.
3 | */
4 | package com.cuieney.fix;
5 |
6 | import java.io.File;
7 | import java.io.IOException;
8 | import java.lang.reflect.Array;
9 | import java.lang.reflect.Field;
10 | import java.lang.reflect.InvocationTargetException;
11 | import java.lang.reflect.Method;
12 | import java.util.ArrayList;
13 | import java.util.Arrays;
14 | import java.util.List;
15 |
16 | /**
17 | * Created by sunpengfei on 16/7/30.
18 | */
19 | public class AutoUtils {
20 | /**
21 | * Locates a given field anywhere in the class inheritance hierarchy.
22 | *
23 | * @param instance an object to search the field into.
24 | * @param name field name
25 | * @return a field object
26 | * @throws NoSuchFieldException if the field cannot be located
27 | */
28 | static Field findField(Object instance, String name) throws NoSuchFieldException {
29 | for (Class> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
30 | try {
31 | Field field = clazz.getDeclaredField(name);
32 |
33 | if (!field.isAccessible()) {
34 | field.setAccessible(true);
35 | }
36 |
37 | return field;
38 | } catch (NoSuchFieldException e) {
39 | // ignore and search next
40 | }
41 | }
42 |
43 | throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
44 | }
45 |
46 | /**
47 | * Locates a given method anywhere in the class inheritance hierarchy.
48 | *
49 | * @param instance an object to search the method into.
50 | * @param name method name
51 | * @param parameterTypes method parameter types
52 | * @return a method object
53 | * @throws NoSuchMethodException if the method cannot be located
54 | */
55 | static Method findMethod(Object instance, String name, Class>...parameterTypes) throws NoSuchMethodException {
56 | for (Class> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
57 | try {
58 | Method method = clazz.getDeclaredMethod(name, parameterTypes);
59 |
60 | if (!method.isAccessible()) {
61 | method.setAccessible(true);
62 | }
63 |
64 | return method;
65 | } catch (NoSuchMethodException e) {
66 | // ignore and search next
67 | }
68 | }
69 |
70 | throw new NoSuchMethodException("Method " + name + " with parameters " + Arrays.asList(parameterTypes)
71 | + " not found in " + instance.getClass());
72 | }
73 |
74 | /**
75 | * Replace the value of a field containing a non null array, by a new array containing the elements of the original
76 | * array plus the elements of extraElements.
77 | *
78 | * @param instance the instance whose field is to be modified.
79 | * @param fieldName the field to modify.
80 | * @param extraElements elements to append at the end of the array.
81 | */
82 | static void expandFieldArray(Object instance, String fieldName, Object[] extraElements)
83 | throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
84 | Field jlrField = findField(instance, fieldName);
85 | Object[] original = (Object[]) jlrField.get(instance);
86 | Object[] combined =
87 | (Object[]) Array.newInstance(original.getClass().getComponentType(), original.length
88 | + extraElements.length);
89 | System.arraycopy(extraElements, 0, combined, 0, extraElements.length);
90 | System.arraycopy(original, 0, combined, extraElements.length, original.length);
91 | jlrField.set(instance, combined);
92 | }
93 |
94 | public static Object[] makePathElements(Object dexPathList, ArrayList files, File optimizedDirectory,
95 | ArrayList suppressedExceptions) throws IllegalAccessException, InvocationTargetException,
96 | NoSuchMethodException {
97 | return (Object[]) findMethod(dexPathList, "makePathElements", List.class, File.class, List.class).invoke(
98 | dexPathList, files, optimizedDirectory, suppressedExceptions);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/fix/src/main/java/com/cuieney/fix/DynamicApk.java:
--------------------------------------------------------------------------------
1 | package com.cuieney.fix;
2 |
3 | import android.content.Context;
4 | import android.content.pm.ApplicationInfo;
5 | import android.content.pm.PackageInfo;
6 | import android.content.pm.PackageManager;
7 | import android.content.res.AssetManager;
8 | import android.content.res.Resources;
9 | import android.graphics.Bitmap;
10 | import android.graphics.BitmapFactory;
11 | import android.util.Log;
12 |
13 | import java.lang.reflect.Field;
14 |
15 | import dalvik.system.DexClassLoader;
16 |
17 | /**
18 | * Created by cuieney on 05/07/2017.
19 | */
20 |
21 | public class DynamicApk {
22 | private static int RES_STRING = 0;
23 | private static int RES_MIPMAP = 1;
24 | private static int RES_DRAWABLE = 2;
25 | private static int RES_LAYOUT = 3;
26 | private static int RES_COLOR = 4;
27 | private static int RES_DIMEN = 5;
28 | private static String TAG = "DynamicApk";
29 |
30 | public static void init(String apkPath) {
31 | }
32 |
33 |
34 | private static AssetManager createAssetManager(String apkPath) {
35 | try {
36 | AssetManager assetManager = AssetManager.class.newInstance();
37 | AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke(
38 | assetManager, apkPath);
39 | return assetManager;
40 | } catch (Throwable th) {
41 | th.printStackTrace();
42 | }
43 | return null;
44 | }
45 |
46 | public static Resources getResource(Context context, String apkPath) {
47 | AssetManager assetManager = createAssetManager(apkPath);
48 | return new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
49 | }
50 |
51 | private static String getTypeFromId(int type) {
52 | String resType = "";
53 | switch (type) {
54 | case 0:
55 | resType = "string";
56 | break;
57 | case 1:
58 | resType = "mipmap";
59 | break;
60 | case 2:
61 | resType = "drawable";
62 | break;
63 | case 3:
64 | resType = "layout";
65 | break;
66 | case 4:
67 | resType = "color";
68 | break;
69 | case 5:
70 | resType = "dimen";
71 | break;
72 | }
73 | return resType;
74 | }
75 |
76 | private static int getIdFromRFile(ApplicationInfo info, DexClassLoader dexLoader, int type, String name) {
77 | try {
78 | Class> aClass1 = dexLoader.loadClass(info.packageName + ".R$" + getTypeFromId(type));
79 | Field[] declaredFields = aClass1.getDeclaredFields();
80 | for (Field declaredField : declaredFields) {
81 | if (declaredField.getName().equals(name)) {
82 | return declaredField.getInt(R.string.class);
83 | }
84 | }
85 |
86 | } catch (ClassNotFoundException e) {
87 | e.printStackTrace();
88 | Log.e(TAG, "getIdFromRFile: ", e.getException());
89 | } catch (IllegalAccessException e) {
90 | e.printStackTrace();
91 | }
92 | return 0;
93 | }
94 |
95 | public static String getStringFromApk(Context context, String apk, String name) {
96 | Resources resource = getResource(context, apk);
97 | int id = getIdFromRFile(getPackageInfo(context, apk), createClassLoader(apk, context), RES_STRING, name);
98 | return resource.getString(id);
99 | }
100 |
101 | public static Bitmap getBitmapFromApk(Context context, String apk, String name) {
102 | Resources resource = getResource(context, apk);
103 | int id = getIdFromRFile(getPackageInfo(context, apk), createClassLoader(apk, context), RES_DRAWABLE, name);
104 | if (id == 0) {
105 | id = getIdFromRFile(getPackageInfo(context, apk), createClassLoader(apk, context), RES_MIPMAP, name);
106 | }
107 | return BitmapFactory.decodeResource(resource, id);
108 | }
109 |
110 | public static int getDrawableFromApk(Context context, String apk, String name) {
111 | Resources resource = getResource(context, apk);
112 | int id = getIdFromRFile(getPackageInfo(context, apk), createClassLoader(apk, context), RES_DRAWABLE, name);
113 | return id;
114 | }
115 |
116 | public static int getMipmapFromApk(Context context, String apk, String name) {
117 | Resources resource = getResource(context, apk);
118 | int id = getIdFromRFile(getPackageInfo(context, apk), createClassLoader(apk, context), RES_MIPMAP, name);
119 | return id;
120 | }
121 |
122 | public static int getLayoutFromApk(Context context, String apk, String name) {
123 | Resources resource = getResource(context, apk);
124 | int id = getIdFromRFile(getPackageInfo(context, apk), createClassLoader(apk, context), RES_LAYOUT, name);
125 | return id;
126 | }
127 |
128 | public static int getColorFromApk(Context context, String apk, String name) {
129 | Resources resource = getResource(context, apk);
130 | int id = getIdFromRFile(getPackageInfo(context, apk), createClassLoader(apk, context), RES_COLOR, name);
131 |
132 | return resource.getColor(id);
133 | }
134 |
135 | public static int getDimenFromApk(Context context, String apk, String name) {
136 | Resources resource = getResource(context, apk);
137 | int id = getIdFromRFile(getPackageInfo(context, apk), createClassLoader(apk, context), RES_DIMEN, name);
138 | return id;
139 | }
140 |
141 | private static DexClassLoader createClassLoader(String path, Context context) {
142 |
143 | DexClassLoader dexClassLoader = new DexClassLoader(path,
144 | context.getFilesDir().getAbsolutePath(),
145 | path,
146 | context.getClassLoader());
147 | return dexClassLoader;
148 | }
149 |
150 | private static ApplicationInfo getPackageInfo(Context context, String path) {
151 | PackageManager pm = context.getPackageManager();
152 | PackageInfo pi = pm.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
153 | return pi.applicationInfo;
154 | }
155 |
156 |
157 | }
158 |
--------------------------------------------------------------------------------
/fix/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | fix
3 |
4 |
--------------------------------------------------------------------------------
/fix/src/test/java/com/cuieney/fix/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.cuieney.fix;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/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 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cuieney/AutoFix/98c21f3022eb24e299788d4bee67a7cc9de3a976/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu May 11 09:54:41 CST 2017
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-3.3-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 |
--------------------------------------------------------------------------------
/images/patchdir.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cuieney/AutoFix/98c21f3022eb24e299788d4bee67a7cc9de3a976/images/patchdir.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':buildsrc', ':fix'
2 |
--------------------------------------------------------------------------------