有了前面文章的理论支持,下面动手自己写一下热修复.
创建工程 1 2 3 4 5 6 7 8 9 ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── xyz │ │ └── hanks │ │ └── fix │ │ ├── BugClass.java │ │ ├── FixApplication.java │ │ └── MainActivity.java
通过Android Studio创建一个工程. BugClass
类是需要修复的类, MainActivity
是主Activity, FixApplication
是自定义的Application. 初始的MainActivity如下
1 2 3 4 5 6 7 8 9 public class MainActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView textView = (TextView) findViewById(R.id.text); textView.setText(new BugClass ().showToast("Happy new year" )); } }
1 2 3 4 5 6 7 8 public class BugClass { public String showToast (String content) { return content; } }
现在运行程序,界面的文本显示成文字 Happy new year ,MainActivity
类会被加上 CLASS_ISPREVERIFIED 标志,因为BugClass 和 MainActivity 都属于同一个dex. 如果现在直接加载补丁包中的 BugClass 类,那么就会出现 Class ref in pre-verified class resolved to unexpected implementation
错误.
引用hack.dex,防止类加上CLASS_ISPREVERIFIED 因为我们要修复BugClass
类,而调用是在MainActivity
中,也就是说,当打上补丁包之后,MainActivity
调用的BugClass
将会是补丁包中的BugClass
(也就是来自于其他的dex),那么我就就需要防止MainActivity
被加上 CLASS_ISPREVERIFIED 标志. 那么怎么防止呢? 需要在MainActivity
中引用别的dex(hack.dex)中的一个类.那么代码如下:
1 2 3 4 5 6 7 8 9 10 public class MainActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView textView = (TextView) findViewById(R.id.text); System.out.print(Hack.class); textView.setText(new BugClass ().showToast("Happy new year" )); } }
上面代码只是简单的引用了一下 Hack.class
, 这样程序运行起来就不会把MainActivity
加上 CLASS_ISPREVERIFIED . 注意现在的代码是编译不过的. 引用我们的程序中没有Hack.class
, 要想编译通过,那么我们就得有 Hack.class
, 于是新建一个library, 然后app这个依赖与这个library, 但是注意不要使用 compile , 使用 provided 关键字,这样标示这个library这是提供引用,并不被编译到apk中(不在MainActivity的dex中).这样就解决了编译问题. 现在运行起来程序还是有错误, 因为MainActivity
引用了Hack.class
,虽然编译通过了,但是实际上是没有这个类的,所以这个时候就需要在调用Hack这个类之前,先动态加载进来.
先加载Hack.dex,保证引用不会出错 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class FixApplication extends Application { @Override public void onCreate () { super .onCreate(); try { PathClassLoader pathClassLoader = (PathClassLoader) getClassLoader(); String hackFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() +"/hack.dex" ; Object a = combineArray(getDexElements(getPathList(pathClassLoader)), getDexElements(getPathList(new DexClassLoader (hackFilePath, getDir("dex" , 0 ).getAbsolutePath(), hackFilePath, getClassLoader())))); Object a2 = getPathList(pathClassLoader); setField(a2, a2.getClass(), "dexElements" , a); pathClassLoader.loadClass("xyz.hanks.Hack" ); } catch (Exception e) { e.printStackTrace(); } } }
这样程序就正常运行起来了.
生成补丁包 现在BugClass
出现bug了. 修改一下, 然后将修改后的BugClass
导出jar包,然后通过dx
工具转换成dex,就叫做 patch.dex 吧.然后放入到sdcard目录下.
现在可以加载补丁包了.
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 public class FixApplication extends Application { @Override public void onCreate () { super .onCreate(); try { PathClassLoader pathClassLoader = (PathClassLoader) getClassLoader(); String hackFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() +"/hack.dex" ; Object a = combineArray(getDexElements(getPathList(pathClassLoader)), getDexElements(getPathList(new DexClassLoader (hackFilePath, getDir("dex" , 0 ).getAbsolutePath(), hackFilePath, getClassLoader())))); Object a2 = getPathList(pathClassLoader); setField(a2, a2.getClass(), "dexElements" , a); pathClassLoader.loadClass("xyz.hanks.Hack" ); String patchFilePath = Environment.getExternalStorageDirectory() .getAbsolutePath() + "/patch.dex" ; Object a3 = combineArray(getDexElements(getPathList(pathClassLoader)), getDexElements(getPathList(new DexClassLoader (patchFilePath, getDir("dex" , 0 ).getAbsolutePath(), patchFilePath, getClassLoader())))); Object a4 = getPathList(pathClassLoader); setField(a4, a4.getClass(), "dexElements" , a3); pathClassLoader.loadClass("xyz.hanks.fix.BugClass" ); } catch (Exception e) { e.printStackTrace(); } } }
每次修改BugClass类之后,生产补丁,放到sdcard,重启程序即可成效(不一定重启程序,目的是要在bugclass被第一次加载之前,加载补丁中的bugclass)