首先定义一个功能类,类里定义一个test方法
public class UserInfoManager { public UserInfoManager(){ } public void test(Context context) { Toast.makeText(context,"我是"+UserInfoManager.class.getName()+"",Toast.LENGTH_LONG).show(); } }
然后定义一个针对改功能使用的接口类
public class Manager { private UserInfoManager userInfoManager; public Manager() { userInfoManager = new UserInfoManager(); } public void test(Context context) { if (userInfoManager != null) { userInfoManager.test(context); } } }
在Activity中的按钮点击事件中使用如下方法调用
manager = new Manager(); manager.test(this);
执行后可以看到如下弹框
假如这个是一个线上APP的一段逻辑,这一段业务逻辑有问题,我们现在需要更改他;热修复的要求是直接替换这个类,用户只需要更新自己需要的类就可以完成替换;在这个要求下,我们首先需要想到的是在这一段逻辑中加入可更新类对象的方法,利用反射可以这样实现
实现一个新类继承自UserInfoManager
public class UserInfoManagerFix extends UserInfoManager { public UserInfoManagerFix(){ } @Override public void test(Context context) { Toast.makeText(context,"我是"+UserInfoManagerFix.class.getName()+"",Toast.LENGTH_LONG).show(); } }
然后更新按钮点击的实现方式,利用反射替换UserInfoManager为UserInfoManagerFix
manager = new Manager(); //替换补丁类 try { //new一个补丁 userInfoManagerFix = new UserInfoManagerFix(); //获取需要替换的对象字段 Field mUserInfoManagerA = manager.getClass().getDeclaredField("userInfoManager"); mUserInfoManagerA.setAccessible(true); //核心,替换类引用 mUserInfoManagerA.set(manager, userInfoManagerFix); //再次执行方法 manager.test(this); } catch (Exception e) { e.printStackTrace(); }
执行后,可以看到如下
是不是已经执行的是补丁类的方法
我们需要的是将这个类打包成补丁进行网络下载后加载,我简单的将其打包成jar,选择Build->Make Project-,完成后在
app\build\intermediates\javac\debug\classes\包名\下找到类名.class文件,然后利用jar 命令转化成一个jar。
我转化后放在assets下,如图
然后更改下点击事件
manager = new Manager(); //复制补丁到指定路径,模拟网络下载 String hotFixLibPath = getExternalCacheDir().getAbsolutePath() + File.separator + "UserInfoManagerFix.jar"; try { InputStream inputStream = getAssets().open("UserInfoManagerFix.jar"); File file = new File(hotFixLibPath); file.deleteOnExit(); file.createNewFile(); FileOutputStream outputStream = new FileOutputStream(file); int read = -1; while ((read = inputStream.read()) > 0) { outputStream.write(read); } outputStream.flush(); outputStream.close(); } catch (Exception e) { } //利用DexClassLoader加载补丁,然后替换 String hotFixOutPath = getExternalCacheDir().getAbsolutePath(); DexClassLoader classLoader = new DexClassLoader(hotFixLibPath, hotFixOutPath, null, getClassLoader()); try { Class hotFixCls = classLoader.loadClass("com.example.hotfix.managerfix.UserInfoManagerFix"); Object hotFixObj = hotFixCls.newInstance(); //找到字段进行替换 Field mUserInfoManagerA = manager.getClass().getDeclaredField("userInfoManager"); mUserInfoManagerA.setAccessible(true); mUserInfoManagerA.set(manager, hotFixObj); //再次执行方法 manager.test(this); } catch (Exception e) { e.printStackTrace(); }
执行点击
可以看到执行的是jar包中的方法。整个jar包也就2Kb
如果真的有这么一个需求,那么更新2kb的jar包总比更新整个APP体验好的多。
现在基本实现了一个简单的,可以进行热修复目的小Demo。
插件化和虚拟化双开APP的原理基本也如上。其核心过程都是更改系统原有的一些流程,因此需要从安卓源码进行分析;并且如果需要兼容不同版本系统,还需要在针对不同代码写不用的hook过程。