Android 源码分析实战 - 动态加载修复 so 库

1. 需求背景

俗话说养兵千日用兵一时,学习源码分析到底有什么用呢?我们遇到的所有问题,都能通过分析源码解决;看似无法实现的功能,都能通过源码分析找到思路......。这些都是之前无数次给大家洗脑的概念,我们来看一下实际的开发需求,我带大家来动手实战几次。之前还在有信时,我们做的是一个音频直播的项目,后面由于这一块业务一直上不去,老板要我们在里面做一个 3D 的玩法,也就是采用 Unity + Android 的开发方式。Unity 导出的资源大概有 200M 左右,直接集成到 Android 肯定会增大包体积。而包体积太大,会影响我们的安装速度和启动速度等等,这个在之前就分析过源码原理,不是我们的重点。我们的重点是需求得实现但不能增大包体积,后面我们做出来的效果是包体积只增大了 50K 。由于实现了 Unity 与 Android 项目交互的从 0 到 1,实现了 Unity 资源的动态加载,我被评为了公司的优秀员工。后来需求搞完部门就合并了,大家走的走散的散,业务起不来我们也没了激情。再后来在面试简历上也是简单的写上了一笔,迷迷糊糊就进了腾讯,幸福也是来得太突然。其实大公司也很悲催 996 压力大,只是一般人我不好意思告诉他。

2. 需求分析

Unity 导出的资源大致分为三个部分,一个部分是 jar 包 50K 左右,第二部分是 so 库 30M 左右,第三部分是 assets 资源 100M 左右。首先是 jar 包,我们写的 Activity 需要继承自 jar 里面的 UnityPlayerActivity,而且 jar 包也并不大,因此我们不做动态加载应该没问题;其次是 assets 资源,这个也是很容易处理的;难就难在 so 的动态加载,当然有的同学肯定会认为这有什么难的,不就是:

static {
  System.load("下载好的 so 目录全路径");
}  

问题是 unity 导出的 jar 包中是这样写的

static {
  (new k()).a();
  o = false;
  o = loadLibraryStatic("main");
}

protected static boolean loadLibraryStatic(String var0) {
  try {
    System.loadLibrary(var0);
    return true;
  } catch (UnsatisfiedLinkError var1) {
    com.unity3d.player.e.Log(6, "Unable to find " + var0);
    return false;
  } catch (Exception var2) {
    com.unity3d.player.e.Log(6, "Unknown error " + var2);
    return false;
  }
}

如果只是这样也还好,我们只要把这个 jar 里的源码,修改成 load 这种方案就好了,但比较坑的就是 libmain.so 在 C++ 层,还会动态的加载另外的两个 so 库文件,当时我是一晚上没睡好呀。晚上实在睡不着,我就开始翻 so 加载流程的源码。

3. 源码分析

public static void loadLibrary(String libname) {
  Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
}

synchronized void loadLibrary0(ClassLoader loader, String libname) {
  String libraryName = libname;
  if (loader != null) {
    // 通过 libname 从 ClassLoader 去找到 filename
    String filename = loader.findLibrary(libraryName);
    if (filename == null) {
      throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
        System.mapLibraryName(libraryName) + "\"");
    }
    String error = nativeLoad(filename, loader);
    if (error != null) {
      throw new UnsatisfiedLinkError(error);
    }
    return;
  }
  ...
}

@Override
public String findLibrary(String name) {
  return pathList.findLibrary(name);
}

public String findLibrary(String libraryName) {
  String fileName = System.mapLibraryName(libraryName);
  // 通过 nativeLibraryPathElements 来遍历查找返回的
  for (Element element : nativeLibraryPathElements) {
    String path = element.findNativeLibrary(fileName);
    if (path != null) {
      return path;
    }
  }
  return null;
}

源码其实还是比较简单的,最终的 so 文件是通过 DexPathList 遍历 nativeLibraryPathElements 来查找返回的,那么我们是不是可以往 nativeLibraryPathElements 的最前面插入一个 Element 呢?显然这是可以的,而且我们早在三年前的视频中就开始用这种套路,来动态加载修复 class 类了,难度系数并不大。

4. 版本适配

封装写完代码后,我开始迫不及待的炫耀了一番,转手就拿给同事去集成了:

集成效果.gif

这才意识到自己是新手上路,于是我把 Android 4.0 - 9.0 的源码统统翻了个遍。

6.0 源码
private final Element[] nativeLibraryPathElements;

static class Element {
  private final File dir;
  private final boolean isDirectory;
  private final File zip;
  private final DexFile dexFile;
  private ZipFile zipFile;

  public Element(File dir, boolean isDirectory, File zip, DexFile dexFile) {
    this.dir = dir;
    this.isDirectory = isDirectory;
    this.zip = zip;
    this.dexFile = dexFile;
  }
}

8.0 源码
private final NativeLibraryElement[] nativeLibraryPathElements;

static class NativeLibraryElement {
  private final String zipDir;
  private boolean initialized;

  public NativeLibraryElement(File dir) {
    this.path = dir;
    this.zipDir = null;
  }

  public NativeLibraryElement(File zip, String zipDir) {
    this.path = zip;
    this.zipDir = zipDir;
    if (zipDir == null) {
      throw new IllegalArgumentException();
    }
  }
}

5.0 的源码
private final File[] nativeLibraryDirectories;

很多同学在开发的过程中,可能下意识的会想到大厂的一些第三方框架,但其实很多功能他们也未必有现成的实现,其次很多东西我们未必用得上,最重要的是自己写的未必就不行。

最终效果

视频地址:https://pan.baidu.com/s/1COarOAlmOPCUlKsazho0Cw
视频密码:8lr0

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

推荐阅读更多精彩内容