Android 动手实现热修复

有了前面文章的理论支持,下面动手自己写一下热修复.

创建工程

├── 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)

文章出自 http://hanks.xyz

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值