说起热修复我们就不得不提类的加载器,在Android中类的加载也是通过ClassLoader来完成,就是PathClassLoader和DexClassLoader这两个Android专用的类加载器。

  • PathClassLoader 只能加载已经安装到Android系统中的apk文件(/data/app/目录),是andorid默认使用的类加载器。
  • DexClassLoader 可以加载任意目录下的dex/apk/jar/zip文件。
    这两个类都是继承自BaseDexClassLoader
  public BaseDexClassLoader(String dexPath,File optimizedDirectory,String librarySearchPath,ClassLoader parent,boolean isTrusted){          super(parent);          this.pathList = new DexPathList(this,dexPath,librarySearchPath,null,isTrusted);  }

这个构造函数做了一件事,就是通过传递进来的相关参数,初始化了一个DexPathList对象。DexPathList的构造函数,就是将参数中传递进来的程序文件(就是补丁文件)封装成Element对象,并将这些对象添加到一个Element的数组集合dexElements中去。

ClassLoader的加载机制是双亲委托机制,在这种机制下,一个Class只会被加载一次。将一个具体的类加载到内存中其实是由虚拟机完成的,对于开发者来说,我们关注的重点应该是如何去找到这个需要加载的类。

  • 在DexClassLoader的findClass方法中通过一个DexPathList对象findClass()方法来获取class
  • 在DexPathList的findClass方法中,对之前构造好dexElements数组集合进行遍历,一旦找到类名与name相同的类时,就直接返回这个class,找不到则返回null

总的来说,通过DexClassLoader查找一个类,最终就是在一个数组中查找特定值得操作特定值得操作。

综合以上所有的观点,我们很容易想到一个非常简单粗暴的热修复方案。假设现在代码中的某一类或者某几个类有bug,那么我们可以在修复完bug之后,可以将这些个类打包成一个补丁文件,然后通过这个补丁文件封装出一个Element对戏那个,并且将这个Element对象插到原有dexElements数组的最前端,这样当DexClassLoader去加载类时,优先会从我们插入的这个Element中中找到相应的类,虽然那个有bug的类还存在于数组中后面对的Element中,但由于双亲加载机制的特点,这个有bug的类已经没有机会被加载了,这个一个bug就再没有重新安装的情况下修复了。

所以一个简单的实例:

  • 获取当前app的classLoader
  • 遍历存放热修复的文件(apk,dex,jar,zip),使用DexClassLoader来加载修复好的文件
  • 获取当前app的classLoader的DexPathList的对象,同时获取DexPathList对象中的Element数组
  • 获取DexClassLoader中的DexPathList的对象,同时获取DexPathList对象中国的Element数组
  • 将获取的两个数组合并为一个新的数组
  • 将新的数组赋值给当前app的加载列中DexPathList中的Element数组
    object HotFixUtil{        //定义一个容器,用来存放所有的修复的文件          val loadedDex =  hashSetOf()        //用来过滤遍历的文件        val SUFFIXS= listOf(".apk",".jar",".zip",".dex")        //定义一个存放优化的dex文件目录        const val OPTIMIZE_DEX_DIR = "optimize_dex"        //默认加载修复的路径        const val DEFAULT_DEX = "ODEX"          fun loadFixedDex(mContext:Context){              loadFixedDex(mContext,null)          }           fun loadFixedDex(mContext:Context,path:String?){              //清空容器              loadedDex.clear()              //获取存放修复文件的文件夹              val file = if(path?.isNullOrBlank() == false) new File(path)                            else new File(mContext.filesDir,DEFAULT_DEX)               //获取文件夹下面的所有的apk/dex/zip/jar文件               val fileTree = file.walk()               fileTree.maxDepth(1)                           .filter{ it.isFile}                           .filter{it.extension in SUFFIXS}                           .forEach{                                  loadedDex.add(it)                            }               //如果容器不为空,进行容器的遍历               if(!loadedDex.isEmpty()){                    doDexInject(mContext)               }                  }          fun doDexInject(mContext:Context){              //定义完整的存放优化后的odex文件              val optimialDirPath = "${mContext.filesDir.absoluteFile}${File.separator}$OPTIMIZE_DEX_DIR"              val mFile = File(optimialDirPath)              if(!mFile.exists()){//如果不存在,则创建文件夹                    mFile.mkdirs()               }              for(dex in loadedDex){                  try{                      //获取当前的pathclassloader                       val pathClassLoader = mContext.loadClass as PathClassLoader                      //获取修复文件的加载器                      val dexClassLoader = DexClassLoader(loadedDex.absolutePath,optimialDirPath,null,pathClassLoader)                      //获取加载器对应的DexPathList对象                      val pathDexPathList = getPathList(pathClassLoader)                      val dexDexPathList = getPathList(dexClassLoader)                     //获取DexPathList中对应的Element数组                     val leftElements = getElements(dexDexPathList)                     val rightElements = getElements(pathDexPathList)                     //合并两个数组                     val elements = combineElement(leftElements,rightElements)                     //将新的数组设置到pathclassloader中                    val pathList = getPathList(pathClassLoader)                    setField(pathList,pathList.javaClass,"dexElements",elements)                  }catch(e:Exception){                      e.printStackTrace()                  }              }          }        fun getPathList(classLoader:BaseClassLoader):Any{                return getField(classLoader,Class.forName("dalvik.system.BaseDexClassLoader"),"pathList")        }        fun getField(obj:Any,clazz:Class<?>,fieldName:String):Any{              val field = clazz.getDeclaredField(fieldName)              field.isAccessible = true              return field.get(obj)         }         fun getElements(pathList:DexPathList):Any{              return getField(pathList,pathList.javaClass,"dexElements")          }        fun combineElement(leftElements:Any,rightElements:Any){              //获取数组元素的类型              val componentType = leftElements.javaClass.componentType              //获取数组的长度              int i = Array.getLength(leftElements)              int j = Array.getLength(leftElements)              //创建一个新的数组              val result = Array.newInstance(componentType,i+j)              System.arrayCopy(leftElements,0,result,0,i)              System.arrayCopy(rightElements,0,result,i,j)              return result        }    }

更多相关文章

  1. Android:ArrayAdapter 与 entries的使用
  2. Android(安卓)webview 监听是否滚动到底部
  3. Android(安卓)如何修改默认的searchable items。
  4. Android(安卓)应用跳转到指定QQ临时聊天界面
  5. 博文目录 | 杰瑞教育原创系列文章目录一览
  6. Java(Android)数据结构汇总(四)-- Map(上)
  7. 关于android WebViewClient 的方法解释
  8. Android(安卓)studio 无法创建Android项目
  9. android之PackageManager简介

随机推荐

  1. android 3.0 环境下, 用webview 打开视频
  2. android 异步任务 AsyncTask
  3. Android之MVVM架构指南(二):DataBinding
  4. 事件分发机制
  5. Dalvik 和 ART
  6. Android之使用Android-query框架开发实战
  7. Android(安卓)入门(2)-使用intent穿梭于活动
  8. Android(安卓)Screen Monitor抓取真机屏
  9. Android(安卓)实用项目链接
  10. beagleboard 启动 android 内核