您的位置:首页 > 移动开发 > Android开发

Android 热修复原理

2016-04-06 14:52 483 查看


当安卓项目需要更新小bug时热修复是一个选择。原文:http://hanks.xyz/2016/01/05/android-myhotfix/

创建工程


├── main
│   ├── AndroidManifest.xml
│   ├── java
│   │   └── xyz
│   │       └── hanks
│   │           └── fix
│   │               ├── BugClass.java
│   │               ├── FixApplication.java
│   │               └── MainActivity.java

通过Android Studio创建一个工程.
BugClass
类是需要修复的类,
MainActivity
是主Activity,
FixApplication
是自定义的Application. 初始的MainActivity如下


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"));
}
}


/**
* Created by hanks on 16-1-2.
*/
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)中的一个类.那么代码如下:

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); // 引用 hack.dex中的Hack类
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,保证引用不会出错

/**
* 加载
* Created by hanks on 16-1-3.
*/
public class FixApplication extends Application {
@Override public void onCreate() {
super.onCreate();
try {
PathClassLoader pathClassLoader = (PathClassLoader) getClassLoader();
String hackFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() +"/hack.dex"; // hack.dex的路径
Object a = combineArray(getDexElements(getPathList(pathClassLoader)), // 原有的 dex
getDexElements(getPathList(new DexClassLoader(hackFilePath, getDir("dex", 0).getAbsolutePath(), hackFilePath, getClassLoader()))));  // 将新的dex插入到dexElements数组的前面
Object a2 = getPathList(pathClassLoader);
setField(a2, a2.getClass(), "dexElements", a); // 通过反射修改dexElements数组
pathClassLoader.loadClass("xyz.hanks.Hack");
} catch (Exception e) {
e.printStackTrace();
}
}
}


这样程序就正常运行起来了.

生成补丁包

现在
BugClass
出现bug了. 修改一下, 然后将修改后的
BugClass
导出jar包,然后通过
dx
工具转换成dex,就叫做patch.dex 吧.然后放入到sdcard目录下.

现在可以加载补丁包了.
/**
* 加载
* Created by hanks on 16-1-3.
*/
public class FixApplication extends Application {
@Override public void onCreate() {
super.onCreate();
try {
PathClassLoader pathClassLoader = (PathClassLoader) getClassLoader();
String hackFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() +"/hack.dex"; // hack.dex的路径
Object a = combineArray(getDexElements(getPathList(pathClassLoader)), // 原有的 dex
getDexElements(getPathList(new DexClassLoader(hackFilePath, getDir("dex", 0).getAbsolutePath(), hackFilePath, getClassLoader()))));  // 将新的dex插入到dexElements数组的前面
Object a2 = getPathList(pathClassLoader);
setField(a2, a2.getClass(), "dexElements", a); // 通过反射修改dexElements数组
pathClassLoader.loadClass("xyz.hanks.Hack");

// 加载补丁包
String patchFilePath = Environment.getExternalStorageDirectory()
.getAbsolutePath() + "/patch.dex";
Object a3 = combineArray(getDexElements(getPathList(pathClassLoader)), // 原有的 dex
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)


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: