热修复

说起热修复我们就不得不提类的加载器,在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<String>(".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
        }
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349

推荐阅读更多精彩内容