热修复框架Nuwa
来源:互联网 发布:宝宝计划软件 编辑:程序博客网 时间:2024/05/21 08:38
转载请标明出处:
http://blog.csdn.net/hai_qing_xu_kong/article/details/70284239
本文出自:【顾林海的博客】
前言
当热修复框架还没出现时,我们的整个开发流程是这样的:先是开发,接着测试,如果有bug修复,当测试实在测不出问题,就打包上线,如果在线上出现问题,就需要修复Bug,并再次打包上线,由于各大平台的审核机制不同,上线的时间也是不固定,在这个阶段用户在多次打开APP并出现相同问题后就有可能卸载软件,这样的话公司就会流失部分用户,在热修复出现后,可以避免这种情况的发生,因为线上出现bug后,我们可以通过热修复来修复Bug,不用每次出现Bug都要重新上架。市面上的热修复有很多,像Nuwa、微信的Tinker以及阿里百川HotFix,这篇文章讲述Nuwa的使用以及相关的原理。
集成热修复框架Nuwa
步骤一:
工程根目录中添加:
classpath 'cn.jiajixin.nuwa:gradle:1.2.2'
最后工程根目录下的build.gradle是这样的:
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.2.0' classpath 'cn.jiajixin.nuwa:gradle:1.2.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files }}allprojects { repositories { jcenter() }}task clean(type: Delete) { delete rootProject.buildDir}
步骤二:
在app下的build.gradle添加依赖:
apply plugin: "cn.jiajixin.nuwa"dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:24.2.1' compile 'cn.jiajixin.nuwa:nuwa:1.0.0'//添加nuwa sdk}
步骤三:
在app下的build.gradle中dubug和release开启混淆
在AndroidManifest.xml中添加权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
步骤四:
创建项目的Application并添加到AndroidManifest.xml中,在Application中添加:
@Overrideprotected void attachBaseContext(Context base) { super.attachBaseContext(base); Nuwa.init(this); Nuwa.loadPatch(this, Environment.getExternalStorageDirectory().getAbsolutePath().concat("/patch.jar"));}
使用热修复框架Nuwa
ok,整体流程已经结束,现在我们编写一个有bug的app,我先在MainActivity添加以下代码:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);// findViewById(R.id.tv_show).setOnClickListener(new View.OnClickListener() {// @Override// public void onClick(View v) {// Toast.makeText(MainActivity.this, "nuwa", Toast.LENGTH_SHORT).show();// }// }); }}
这段代码中tv_show是不可点击的,我们点击run这个项目,并在安装在手机上,这时我们查看app/build/outputs文件下会出现一个nuwa的文件夹,我们看看这个文件夹下有些什么:
我们看的有两个文件,这两个文件在后面会用到,我们将整个nuwa文件夹复制到某个路径下。
接着我们修改上面MainActivity,修改内容如下:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.tv_show).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(MainActivity.this, "nuwa", Toast.LENGTH_SHORT).show(); } }); }}
上面我将注释去掉,也就是点击这个TextView会出现弹窗,我们打开android studio 的Terminal窗口,输入以下内容:
gradlew clean nuwaDebugPatch -P NuwaDir=F:/glhproject/nuwa/nuwa
上面的F:/glhproject/nuwa/nuwa就是之前我们复制的nuwa文件夹。
这时我们再查看app/build/outputs会发现多了一个叫patch.jar的东西:
在日常开发中,这个patch.jar是需要放在服务器上,通过推送或接口调用来下载这个patch.jar文件,我们这里直接复制到手机的sdcard上:
adb push app/build/outputs/nuwa/debug/patch.jar /sdcard/
最后的最后我们重启app,这样我们第二次修改的内容就生效了。
热修复框架Nuwa原理
在一头埋入nuwa源码前,我们先来扫扫盲,聊聊PathClassLoader,这货有什么用,PathClassLoader的作用就是从文件系统中加载类文件:
public class PathClassLoader extends BaseDexClassLoader { public PathClassLoader(String dexPath, ClassLoader parent) { super((String)null, (File)null, (String)null, (ClassLoader)null); throw new RuntimeException("Stub!"); } public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) { super((String)null, (File)null, (String)null, (ClassLoader)null); throw new RuntimeException("Stub!"); }}
PathClassLoader继承BaseDexClassLoader,在BaseDexClassLoader中有个findClass方法:
protected Class<?> findClass(String name) throws ClassNotFoundException { throw new RuntimeException("Stub!");}
查看findClass方法的具体实现:
@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException { List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); Class c = pathList.findClass(name, suppressedExceptions); if (c == null) { ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList); for (Throwable t : suppressedExceptions) { cnfe.addSuppressed(t); } throw cnfe; } return c;}
在findClass方法中调用了pathList对象的findClass方法,pathLit的类型是DexPathList,查看DexPathList中的findClass方法:
public Class findClass(String name, List<Throwable> suppressed) { for (Element element : dexElements) { DexFile dex = element.dexFile; if (dex != null) { Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); if (clazz != null) { return clazz; } } } if (dexElementsSuppressedExceptions != null) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); } return null;}
在findClass方法中,通过循环遍历dexElements,在循环遍历中,调用loadClassBinaryName方法来加载,加载成功就返回这个Class对象。讲到这里,我们就知道,dexElements的先后顺序是非常重要,决定着哪个dex被加载,因此要想实现热修复,就需要我们将修复后的dex文件放在dexElements前面,使得这个dex文件能先被加载,从而达到热修复。
Nuwa的原理就是利用了上面的dexElements来加载修复完的dex文件,我查看到Nuwa的源码其实比较少的:
调用 Nuwa.init(this):
public static void init(Context context) { File dexDir = new File(context.getFilesDir(), DEX_DIR); dexDir.mkdir(); String dexPath = null; try { dexPath = AssetUtils.copyAsset(context, HACK_DEX, dexDir); } catch (IOException e) { Log.e(TAG, "copy " + HACK_DEX + " failed"); e.printStackTrace(); } loadPatch(context, dexPath);}
在init方法中,创建nuwa文件,并从assets目录中拷贝一个叫hack.apk空实现的文件到nuwa文件中,在调用下面方法来进行热修复,方法内的第二个参数就是我们在上面生成的patch.jar的路径。
Nuwa.loadPatch(this, Environment.getExternalStorageDirectory().getAbsolutePath().concat("/patch.jar"));
接着调用loadPatch方法,查看此方法:
public static void loadPatch(Context context, String dexPath) { if (context == null) { Log.e(TAG, "context is null"); return; } if (!new File(dexPath).exists()) { Log.e(TAG, dexPath + " is null"); return; } File dexOptDir = new File(context.getFilesDir(), DEX_OPT_DIR); dexOptDir.mkdir(); try { DexUtils.injectDexAtFirst(dexPath, dexOptDir.getAbsolutePath()); } catch (Exception e) { Log.e(TAG, "inject " + dexPath + " failed"); e.printStackTrace(); }}
在这个方法中,先是进行两次判空,分别是context和我们存放修复后的dex文件否存在,接着创建一个nuwaopt文件夹,接着调用DexUtils中的静态方法injectDexAtFirst:
public static void injectDexAtFirst(String dexPath, String defaultDexOptPath) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException { DexClassLoader dexClassLoader = new DexClassLoader(dexPath, defaultDexOptPath, dexPath, getPathClassLoader()); Object baseDexElements = getDexElements(getPathList(getPathClassLoader())); Object newDexElements = getDexElements(getPathList(dexClassLoader)); Object allDexElements = combineArray(newDexElements, baseDexElements); Object pathList = getPathList(getPathClassLoader()); ReflectionUtils.setField(pathList, pathList.getClass(), "dexElements", allDexElements);}
获取DexClassLoader实例,DexClassLoader的作用是动态的装载class文件,并且DexClassLoader继承与BaseDexClassLoader,接着通过反射获取到DexPathList属性对象pathList,getDexElements方法中通过反射获取dexElements,上面两个baseDexElements和newDexElements分别是当前的dexElements和补丁dex的dexElements,随后将两个dexElements进行合并:
private static Object combineArray(Object firstArray, Object secondArray) { Class<?> localClass = firstArray.getClass().getComponentType(); int firstArrayLength = Array.getLength(firstArray); int allLength = firstArrayLength + Array.getLength(secondArray); Object result = Array.newInstance(localClass, allLength); for (int k = 0; k < allLength; ++k) { if (k < firstArrayLength) { Array.set(result, k, Array.get(firstArray, k)); } else { Array.set(result, k, Array.get(secondArray, k - firstArrayLength)); } } return result;}
将patch.dex放在最前面,最后加载Element数组,来完成修复。
虽然Nuwa框架有很多优点,但由于它不是即时生效,并且修复的类过大,会导致加载时间延长,以及在ART模式下,类修改了结构,会导致内存错乱,如果想解决这个问题,就需要将相关的调用类、父类、子类等都加载到patch.dex中,会导致补丁过大。
- 热修复框架Nuwa
- 热修复框架nuwa的使用
- 关于热修复框架nuwa的使用
- 关于热修复框架nuwa的使用
- 热修复框架nuwa的使用
- Nuwa热修复实现
- Nuwa热修复实现
- Nuwa(女娲)-热修复
- Android热修复-Nuwa使用
- Android热更新框架NuWa
- Nuwa热修复在项目中应用
- Android开发之nuwa热修复
- 使用Nuwa实现Android热修复
- Android 热修复之nuwa使用简略
- 基于QQ空间热修复原理开发的Nuwa框架使用步骤
- Android热更新框架Nuwa的使用
- Android 热修复使用Gradle Plugin1.5改造Nuwa插件
- Android 热修复使用Gradle Plugin1.5改造Nuwa插件
- 解决SublimeCodeIntel回车换行误打代码
- mysql主从同步安装配置
- python发送邮件
- windows / linux系统中,端口被占用解决方法
- Listener监听器
- 热修复框架Nuwa
- 18 个命令&工具帮你定位 Linux 性能问题
- SparkStreaming之HDFS操作
- Droid@screen:在PC屏幕上显示Android手机屏幕
- poj 3525 Most Distant Point from the Sea(二分+半平面交)
- 分支限界法-最少步数走出迷宫
- Python WMI获取Windows系统信息 监控系统
- Linux的使用<四> Ubuntu安装QQ
- Loadrunner中web_find和web_reg_find函数的使用与区别