Android中apk的构建过程

构建apk

如图 所示,典型 Android 应用模块的构建流程通常依循下列步骤:

编译器将您的源代码转换成 DEX(Dalvik Executable) 文件(其中包括 Android 设备上运行的字节码),将所有其他内容转换成已编译资源。

APK 打包器将 DEX 文件和已编译资源合并成单个 APK。 不过,必须先签署 APK,才能将应用安装并部署到 Android 设备上。* APK 打包器使用调试或发布密钥库签署您的 APK:

如果您构建的是调试版本的应用(即专用于测试和分析的应用),打包器会使用调试密钥库签署您的应用。 Android Studio 自动使用调试密钥库配置新项目。

如果您构建的是打算向外发布的发布版本应用,打包器会使用发布密钥库签署您的应用。 要创建发布密钥库,请阅读在 Android Studio 中签署您的应用。

在生成最终 APK 之前,打包器会使用 zipalign 工具对应用进行优化,减少其在设备上运行时占用的内存。

构建流程结束时,将获得应用的调试 APK 或发布 APK,您可使用它们进行部署、测试,或向外部用户发布。

Android中的类加载机制

1 Android中的ClassLoader

Java中的ClassLoader是加载class文件,而Android中的虚拟机无论是dvm还是art都只能识别dex文件。因此Java中的ClassLoader在Android中不适用。Android中的java.lang.ClassLoader这个类也不同于Java中的java.lang.ClassLoader。

Android中的ClassLoader类型也可分为系统ClassLoader和自定义ClassLoader。其中系统ClassLoader包括3种分别是

BootClassLoader: Android系统启动时会使用BootClassLoader来预加载常用类,与Java中的Bootstrap ClassLoader不同的是,它并不是由C/C++代码实现,而是由Java实现的。BootClassLoader是ClassLoader的一个内部类。

PathClassLoader,全名是dalvik/system.PathClassLoader,可以加载已经安装的Apk,也就是/data/app/package 下的apk文件,也可以加载/vendor/lib, /system/lib下的nativeLibrary。

DexClassLoader,全名是dalvik/system.DexClassLoader,可以加载一个未安装的apk文件。

PathClassLoader和DexClasLoader都是继承自 dalviksystem.BaseDexClassLoader,它们的类加载逻辑全部写在BaseDexClassLoader中。

Android中的ClassLoader

2 ClassLoader源码分析

在Android中我们主要关心的是PathClassLoader和DexClassLoader。

PathClassLoader用来操作本地文件系统中的文件和目录的集合。并不会加载来源于网络中的类。Android采用这个类加载器一般是用于加载系统类和它自己的应用类。这个应用类放置在data/data/包名下。

看一下PathClassLoader的源码,只有2个构造方法:

package dalvik.system;

public class PathClassLoader extends BaseDexClassLoader {

public PathClassLoader(String dexPath, ClassLoader parent) {

super(dexPath, null, null, parent);

}

public PathClassLoader(String dexPath, String libraryPath,

ClassLoader parent) {

super(dexPath, null, libraryPath, parent);

}

}

DexClassLoader可以加载一个未安装的APK,也可以加载其它包含dex文件的JAR/ZIP类型的文件。DexClassLoader需要一个对应用私有且可读写的文件夹来缓存优化后的class文件。而且一定要注意不要把优化后的文件存放到外部存储上,避免使自己的应用遭受代码注入攻击。看一下它的源码,只有1个构造方法:

package dalvik.system;

import java.io.File;

public class DexClassLoader extends BaseDexClassLoader {

public DexClassLoader(String dexPath, String optimizedDirectory,

String libraryPath, ClassLoader parent) {

super(dexPath, new File(optimizedDirectory), libraryPath, parent);

}

}

可以看到,PathClassLoader和DexClassLoader除了构造方法传参不同,其它的逻辑都是一样的。要注意的是DexClassLoader构造方法第2个参数指的是dex优化缓存路径,这个值是不能为空的。而PathClassLoader对应的dex优化缓存路径为null是因为Android系统自己决定了缓存路径。

接下来我们看一下BaseDexClassLoader这个类:

dexPath,指的是在Androdi包含类和资源的jar/apk类型的文件集合,指的是包含dex文件。多个文件用“:”分隔开,用代码就是File.pathSeparator。

optimizedDirectory,指的是odex优化文件存放的路径,可以为null,那么就采用默认的系统路径。

libraryPath,指的是native库文件存放目录,也是以“:”分隔。

parent,parent类加载器

可以看到,在BaseDexClassLoader类中初始化了DexPathList这个类的对象。这个类的作用是存放指明包含dex文件、native库和优化目录。

# dalvik.system.BaseDexClassLoader

public BaseDexClassLoader(String dexPath, File optimizedDirectory,

String libraryPath, ClassLoader parent) {

super(parent);

this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);

}

dalvik.system.DexPathList封装了dex路径,是一个final类,而且访问权限是包权限,也就是说外界不可继承,也不可访问这个类。

BaseDexClassLoader在其构造方法中初始化了DexPathList对象,我们来看一下DexPathList的源码,我们需要重点关注一下它的成员变量dexElements,它是一个Element[]数组,是包含dex的文件集合。Element是DexPathList的一个静态内部类。DexPathList的构造方法有4个参数。从其构造方法中也可以看到传递过来的classLoade对象和dexPath不能为null,否则就抛出空指针异常。

# dalvik.system.DexPathList

private final Element[] dexElements;

public DexPathList(ClassLoader definingContext, String dexPath,

String libraryPath, File optimizedDirectory) {

if (definingContext == null) {

throw new NullPointerException("definingContext == null");

}

if (dexPath == null) {

throw new NullPointerException("dexPath == null");

}

if (optimizedDirectory != null) {

if (!optimizedDirectory.exists()) {

throw new IllegalArgumentException(

"optimizedDirectory doesn't exist: "

+ optimizedDirectory);

}

// 如果文件不是可读可写的也会抛出异常

if (!(optimizedDirectory.canRead()

&& optimizedDirectory.canWrite())) {

throw new IllegalArgumentException(

"optimizedDirectory not readable/writable: "

+ optimizedDirectory);

}

}

this.definingContext = definingContext;

ArrayList suppressedExceptions = new ArrayList();

// 通过makeDexElements方法来获取Element数组

// splitDexPath(dexPath)方法是用来把我们之前按照“:”分隔的路径转为File集合。

this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,

suppressedExceptions);

if (suppressedExceptions.size() > 0) {

this.dexElementsSuppressedExceptions =

suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);

} else {

dexElementsSuppressedExceptions = null;

}

this.nativeLibraryDirectories = splitLibraryPath(libraryPath);

}

加载一个dex文件调用的是loadDexFile()方法。

# dalvik.system.DexPathList

private static DexFile loadDexFile(File file, File optimizedDirectory)

throws IOException {

// 如果缓存存放目录为null就直接创建一个DexFile对象返回

if (optimizedDirectory == null) {

return new DexFile(file);

} else {

// 根据缓存存放目录和文件名得到一个优化后的缓存文件路径

String optimizedPath = optimizedPathFor(file, optimizedDirectory);

// 调用DexFile的loadDex()方法来获取DexFile对象。

return DexFile.loadDex(file.getPath(), optimizedPath, 0);

}

}

DexFile的loadDex()方法如下,内部也做了一些调用。抛开这些细节来讲,它的作用就是加载DexFile文件,而且会把优化后的dex文件缓存到对应目录。

分析到这,我们可以小结一下:在BaseDexClassLoader对象构造方法内,创建了PathDexList对象。而在PathDexList构造方法内部,通过调用一系列方法,把直接包含或者间接包含dex的文件解压缩并缓存优化后的dex文件,通过PathDexList的成员变量Element[] dexElements来指向这个文件。

到此我们就分析完了BaseDexClassLoader的构造方法。

类加载是按需加载,也就是说当明确需要使用class文件的时候才会加载。我们来看一下在Android中ClassLoader的loadeClass()方法。

与在Java中的loadClass()方法主要流程是类似的,不过因为Android中BootClassLoader是用Java代码写的,所以可以直接当作系统类加载器的parent类加载器。在Android中如果parent类加载器找不到类,最终还是会调用ClassLoader对象自己的findClass()方法。这个与在Java中逻辑是一样的。

我们可以去看一下BaseDexClassLoader类的findClass()方法。

# dalvik.system.BaseDexClassLoader

@Override

protected Class> findClass(String name) throws ClassNotFoundException {

List suppressedExceptions = new ArrayList();

// 调用DexPathList对象的findClass()方法

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;

}

可以看到,实际上BaseDexClassLoader调用的是其成员变量DexPathList pathList的findClass()方法。

# dalvik.system.DexPathList

public Class findClass(String name, List suppressed) {

// 遍历Element

for (Element element : dexElements) {

// 获取DexFile,然后调用DexFile对象的loadClassBinaryName()方法来加载Class文件。

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;

}

从上面的代码中我们也可以看到,实际上DexPathList最终还是遍历其自身的Element[]数组,获取DexFile对象来加载Class文件。我们之前讲DexPathList构造方法内是调用其makeDexElements()方法来创建Element[]数组的,而且也提到了如果zip文件或者dex文件二者之一不为null,就把元素添加进来,而添加进来的zip存在不为null也不包含dex文件的可能。从上面的代码中也可以看到,获取Class的时候跟这个zip文件没什么关系,调用的是dex文件对应的DexFile的方法来获取Class。

数组的遍历是有序的,假设有两个dex文件存放了二进制名称相同的Class,类加载器肯定就会加载在放在数组前面的dex文件中的Class。现在很多热修复技术就是把修复的dex文件放在DexPathList中Element[]数组的前面,这样就实现了修复后的Class抢先加载了,达到了修改bug的目的。

Android加载一个Class是调用DexFile的defineClass()方法。而不是调用ClassLoader的defineClass()方法。这一点与Java不同,毕竟Android虚拟机加载的dex文件,而不是class文件。

总结

Android中的类加载器是BootClassLoader、PathClassLoader、DexClassLoader,其中BootClassLoader是虚拟机加载系统类需要用到的,PathClassLoader是App加载自身dex文件中的类用到的,DexClassLoader可以加载直接或间接包含dex文件的文件,如APK等。

PathClassLoader和DexClassLoader都继承自BaseDexClassLoader,它的一个DexPathList类型的成员变量pathList很重要。DexPathList中有一个Element类型的数组dexElements,这个数组中存放了包含dex文件(对应的是DexFile)的元素。BaseDexClassLoader加载一个类,最后调用的是DexFile的方法进行加载的。

无论是热修复还是插件化技术中都利用了类加载机制,所以深入理解Android中的类加载机制对于理解这些技术的原理很有帮助

android classloader异常,Android中ClassLoader类加载机制相关推荐

  1. android classloader的功能和工作模式,Android中ClassLoader和java中ClassLoader有什么关系和不同...

    一.Java中的ClassLoader是什么? 当写好一个Java应用程序,程序都是由若干个.class类文件组织而成的,当程序在运行时,即会调用该程序的一个入口函数来调用系统的相关功能,而这些功能都 ...

  2. 20、java中的类加载机制

    1.类加载机制是什么? 类加载机制指的就是jvm将类的信息动态添加到内存并使用的一种机制. 2.那么类加载的具体流程是什么呢? 一般说类加载只有三步:加载.连接和初始化,其中连接包括验证.准备和解析, ...

  3. Java中的类加载机制

    目录 类加载器介绍 JVM类加载过程 类加载器介绍 首先类的加载是由类加载器完成的,类加载器包括:根加载器(Bootstrap).拓展加载器(Extension).系统加载器(System)和用户自定 ...

  4. android视频播放异常,Android 播放视频常见问题小结

    在android 开发中常见到视频播放的问题,在常规的视频中 有直接用videoView + MediaController 或者 mediaController + serfercie holder ...

  5. android 添加异常,android – 坏标记异常 – 无法添加窗口(Marshmallow – 浮动工具栏)...

    我一直在努力寻找这个例外的原因有一段时间,我从来没有能够重现自己,但我的一些客户正在体验它.它只发生在 Android 6.0.1上,由于SDK本身的崩溃发生,很难弄清楚它是如何发生的. 关于这个问题 ...

  6. 性能优化——Android热修复技术,类加载机制详解

    一.背景 热修复技术慢慢的成为Android开发必不可少的技术,也是成为一名高级程序员必不可少的技能之一.那么什么是热修复技术呢? 当app上线之后,发现了一个严重的bug,需要紧急修复,按照以往的惯 ...

  7. 【重难点】【JVM 01】OOM 出现的原因、方法区、类加载机制、JVM 中的对象

    [重难点][JVM 01]OOM 出现的原因.方法区.类加载机制.JVM 中的对象 文章目录 [重难点][JVM 01]OOM 出现的原因.方法区.类加载机制.JVM 中的对象 一.OOM 出现的原因 ...

  8. 【胖虎的逆向之路】01——动态加载和类加载机制详解

    胖虎的逆向之路 01--动态加载和类加载机制详解 一.前言 二.类的加载器 1. 双亲委派模式 2. Android 中的类加载机制 1)Android 基本类的预加载 2)Android类加载器层级 ...

  9. jvm运行时类加载机制_JVM体系结构:JVM类加载器和运行时数据区

    jvm运行时类加载机制 各位读者好! 在JVM系列的上一篇文章中,开发人员了解了Java虚拟机(JVM)及其体系结构. 本教程将帮助开发人员正确回答以下主题的问题: ClassLoader子系统 运行 ...

最新文章

  1. 每张脸值5美元,谷歌花钱买数据强化刷脸,还把隔空操控手机变成现实
  2. pelee yuface 手势模型
  3. 访问ASP.NET临时文件夹的权限问题
  4. JAVA面试整理之——JAVA基础
  5. 好用的浏览器_“遇见”一个好用的浏览器,功能非常强大到无法想象
  6. 七、功能性组件与事件逻辑(IVX 快速开发教程)
  7. 解决Div自适应高度的方法(转)
  8. python中head_Python(Head First)学习笔记:二
  9. mysql 的hash和b tree_mysql索引hash索引和b-tree索引的区别
  10. Ural 1043 Cover the Arc
  11. response.setHeader()的用法 (转别人转的)
  12. ubuntuQQ怎末安装
  13. [转帖]身份证前两位是怎么来的
  14. html5跳转页面接收参数,HTML页面跳转及参数传递问题
  15. python怎么换背景颜色_更换python默认编辑器背景色的操作方法
  16. 中移物联网入门记录(1)
  17. Redmi K30 Pro 标准版更换相机后魔改为变焦版过程
  18. android-更新UI的几种方式
  19. selenium 的显示等待与隐式等待
  20. APP下载链接在微信被屏蔽了 无法打开的解决方案

热门文章

  1. ML:MLOps系列讲解之系列知识解读全貌
  2. Py之imblearn:imblearn/imbalanced-learn库的简介、安装、使用方法之详细攻略
  3. Interview:算法岗位面试—10.31下午上海某银行总部公司(二面,四大行之一)之项目简介、比赛介绍、某个比赛的过程
  4. 成功解决TypeError: Singleton array array('data_input/xgboost/data_RentListingInquries/RentListingInqurie
  5. DayDayUp:《复仇者联盟4:终局之战》娱乐闲谈——当灭霸碰上一个处女座的程序猿
  6. Dataset之COCO数据集:COCO数据集的简介、下载、使用方法之详细攻略
  7. 学习Python3:20171031
  8. 《疯狂java讲义》6
  9. Python封装发送信息到钉钉群
  10. openstack搭建之-nova配置(10)