首页 > 编程语言 >Android插件化(三)基础之Android应用程序资源的编译和打包过程分析

Android插件化(三)基础之Android应用程序资源的编译和打包过程分析

时间:2024-09-21 12:48:31浏览次数:10  
标签:文件 插件 编译 res 应用程序 apk Android android 资源

Android插件化(三)基础之Android应用程序资源的编译和打包过程分析

Android资源加载常规思路

getResourcesForApplication
//首先,通过包名获取该包名的Resources对象
Resources res= pm.getResourcesForApplication(packageName);
//根据约定好的名字,去取资源id;
int id=res.getIdentifier("a","drawable",packageName);//根据名字取id
//根据资源id,取出资源
Drawable drawable=res.getDrawable(id)

Android Apk打包流程

  1. 打包资源文件,生成R.java文件;
  2. 处理aidl文件,生成相应java文件;
  3. 编译工程源文件,生成相应class文件;
  4. 转换所有class文件,生成classes.dex文件;
  5. 打包生成apk文件;
  6. 对apk文件进行签名;
  7. 对签名后的apk文件进行对齐处理;

在这里插入图片描述

打包过程使用的工具

名称功能介绍在操作系统中的路径源码路径
aapt(Android Asset Package Tool)Android资源打包工具${ANDROID_SDK_HOME} /build-tools/ ANDROID_VERSION/aaptframeworks\base\tools\aap
aidl(android interface definition language)Android接口描述语言,将aidl转化为.java文件的工具${ANDROID_SDK_HOME}/build-tools/ ANDROID_VERSION/aidlframeworks\base\tools\aidl
javacJava Compiler${JDK_HOME}/javac/usr/bin/javac
dex转化.class文件为Davik VM能识别的.dex文件${ANDROID_SDK_HOME}/build-tools/ ANDROID_VERSION/dx
apkbuilder生成apk包${ANDROID_SDK_HOME}/tools/apkbuildersdk\sdkmanager\libs\sdklib\ src\com\android\sdklib\build\ApkBuilderMain.java
jarsigner.jar文件的签名工具${JDK_HOME}/jarsigner或/usr/bin/jarsigner
zipalign字节码对齐工具${ANDROID_SDK_HOME}/tools/zipalign
第一步: 打包资源文件,生成R.java文件

【输入】Resource文件(就是工程中res中的文件)、Assets文件(相当于另外一种资源,这种资源Android系统并不像对res中的文件那样优化它)、AndroidManifest.xml文件(包名就是从这里读取的,因为生成R.java文件需要包名)、Android基础类库(Android.jar文件)
【工具】aapt工具
【输出】打包好的资源(bin目录中的resources.ap_文件)、R.java文件(gen目录中)
打包资源的工具aapt,大部分文本格式的XML资源文件会被编译成二进制格式的XML资源文件,除了assets和res/raw资源被原装不动地打包进APK之外,其它的资源都会被编译或者处理。 。
生成过程主要是调用了aapt源码目录下的Resource.cpp文件中的buildResource()函数,该函数首先检查AndroidManifest.xml的合法性,然后对res目录下的资源子目录进行处理,处理的函数为makeFileResource(),处理的内容包括资源文件名的合法性检查,向资源表table添加条目等,处理完后调用compileResourceFile()函数编译res与asserts目录下的资源并生成resources.arsc文件,compileResourceFile()函数位于aapt源码目录的ResourceTable.cpp文件中,该函数最后会调用parseAndAddEntry()函数生成R.java文件,完成资源编译后,接下来调用compileXmlfile()函数对res目录的子目录下的xml文件分别进行编译,这样处理过的xml文件就简单的被“加密”了,最后将所有的资源与编译生成的resorces.arsc文件以及“加密”过的AndroidManifest.xml文件打包压缩成resources.ap_文件(使用Ant工具命令行编译则会生成与build.xml中“project name”指定的属性同名的ap_文件)。
关于这一步更详细的流程可阅读http://blog.csdn.net/luoshengyang/article/details/8744683

res目录有9种目录

  • –animator。这类资源以XML文件保存在res/animator目录下,用来描述属性动画。
  • –anim。这类资源以XML文件保存在res/anim目录下,用来描述补间动画。
  • –color。这类资源以XML文件保存在res/color目录下,用描述对象颜色状态选择子。
  • –drawable。这类资源以XML或者Bitmap文件保存在res/drawable目录下,用来描述可绘制对象。例如,我们可以在里面放置一些图片(.png, .9.png, .jpg, .gif),来作为程序界面视图的背景图。注意,保存在这个目录中的Bitmap文件在打包的过程中,可能会被优化的。例如,一个不需要多于256色的真彩色PNG文件可能会被转换成一个只有8位调色板的PNG面板,这样就可以无损地压缩图片,以减少图片所占用的内存资源。
  • –layout。这类资源以XML文件保存在res/layout目录下,用来描述应用程序界面布局。
  • –menu。这类资源以XML文件保存在res/menu目录下,用来描述应用程序菜单。
  • –raw。这类资源以任意格式的文件保存在res/raw目录下,它们和assets类资源一样,都是原装不动地打包在apk文件中的,不过它们会被赋予资源ID,这样我们就可以在程序中通过ID来访问它们。例如,假设在res/raw目录下有一个名称为filename的文件,并且它在编译的过程,被赋予的资源ID为R.raw.filename,那么就可以使用以下代码来访问它:Resources res = getResources(); InputStream is = res .openRawResource(R.raw.filename);
  • –values。这类资源以XML文件保存在res/values目录下,用来描述一些简单值,例如,数组、颜色、尺寸、字符串和样式值等,一般来说,这六种不同的值分别保存在名称为arrays.xml、colors.xml、dimens.xml、strings.xml和styles.xml文件中。
  • –xml。这类资源以XML文件保存在res/xml目录下,一般就是用来描述应用程序的配置信息。
第二步:处理aidl文件,生成相应的java文件。

输入】源码文件、aidl文件、framework.aidl文件
【工具】aidl工具
【输出】对应的.java文件
对于没有使用到aidl的android工程,这一步可以跳过。aidl工具解析接口定义文件并生成相应的java代码供程序调用。

第三步:编译工程源代码,生成下相应的class文件。

【输入】源码文件(包括R.java和AIDL生成的.java文件)、库文件(.jar文件)
【工具】javac工具
【输出】.class文件
这一步调用了javac编译工程src目录下所有的java源文件,生成的class文件位于工程的bin\classes目录下,上图假定编译工程源代码时程序是基于android SDK开发的,实际开发过程中,也有可能会使用android NDK来编译native代码,因此,如果可能的话,这一步还需要使用android NDK编译C/C++代码,当然,编译C/C++代码的步骤也可以提前到第一步或第二步。

第四步:转换所有的class文件,生成classes.dex文件。

【输入】 .class文件(包括Aidl生成.class文件,R生成的.class文件,源文件生成的.class文件),库文件(.jar文件)
【工具】javac工具
【输出】.dex文件
前面多次提到,android系统dalvik虚拟机的可执行文件为dex格式,程序运行所需的classes.dex文件就是在这一步生成的,使用的工具为dx,dx工具主要的工作是将java字节码转换为dalvik字节码、压缩常量池、消除冗余信息等。

第五步:打包生成apk。

【输入】打包后的资源文件、打包后类文件(.dex文件)、libs文件(包括.so文件,当然很多工程都没有这样的文件,如果你不使用C/C++开发的话)
【工具】apkbuilder工具
【输出】未签名的.apk文件
打包工具为apkbuilder,apkbuilder为一个脚本文件,实际调用的是android-sdk\tools\lib\sdklib.jar文件中的com.android.sdklib.build.ApkBuilderMain类。它的代码实现位于android系统源码的sdk\sdkmanager\libs\sdklib\src\com\android\sdklib\build\ApkBuilderMain.java文件,代码构建了一个ApkBuilder类,然后以包含resources.arsc的文件为基础生成apk文件,这个文件一般为ap_结尾,接着调用addSourceFolder()函数添加工程资源,addSourceFolder()会调用processFileForResource()函数往apk文件中添加资源,处理的内容包括res目录与asserts目录中的文件,添加完资源后调用addResourceFromJar()函数往apk文件中写入依赖库,接着调用addNativeLibraries()函数添加工程libs目录下的Native库(通过android NDK编译生成的so或bin文件),最后调用sealApk()关闭apk文件。

第六步:对apk文件进行签名。

【输入】未签名的.apk文件
【工具】jarsigner
【输出】签名的.apk文件
android的应用程序需要签名才能在android设备上安装,签名apk文件有两种情况:一种是在调试程序时进行签名,使用eclipse开发android程序时,在编译调试程序时会自己使用一个debug.keystore对apk进行签名;另一种是打包发布时对程序进行签名,这种情况下需要提供一个符合android开发文档中要求的签名文件。签名的方法也分两种:一种是使用jdk中提供的jarsigner工具签名;另一种是使用android源码中提供的signapk工具,它的代码位于android系统源码build\tools\signapk目录下。

第七步:对签名后的apk文件进行对齐处理。

【输入】签名后的.apk文件
【工具】zipalign工具
【输出】对齐后的.apk文件
这一步需要使用的工具为zipalign,它位于android-sdk\tools目录,源码位于android系统源码的build\tools\zipalign目录,它的主要工作是将spk包进行对齐处理,使spk包中的所有资源文件距离文件起始偏移为4字节整数倍,这样通过内存映射访问apk文件时速度会更快,验证apk文件是否对齐过的工作由ZipAlign.cpp文件的verify()函数完成,处理对齐的工作则由process()函数完成。

以一个具体项目中包含的具体文件为例作图如下:
在这里插入图片描述

APK文件内容解析

android的项目经过编译和打包,形成了:

  • .dex 文件
  • resources.arsc
  • uncompiled resources
  • AndroidManifest.xml

解压一个普通的apk文件,解压出来的文件如下:

  • META-INF文件夹
  • res文件夹
  • AndroidManifest.xml
  • classes.dex
  • resources.arsc

classes.dex 是.dex文件。
resources.arsc是resources resources文件。
AndroidManifest.xml是AndroidManifest.xml文件。
res是uncompiled resources。
META-INF是签名文件夹。

META-INF其中有三个文件:

  • CERT.RSA
  • CERT.SF
  • MANIFEST.MF

MANIFEST.MF文件
版本号以及每一个文件的哈希值(BASE64)。包括资源文件。这个是对每个文件的整体进行SHA1(hash)。

Manifest-Version: 1.0
Built-By: Generated-by-ADT
Created-By: Android Gradle 2.2.0
Name: res/drawable-xhdpi-v4/abc_scrubber_control_to_pressed_mtrl_005.png
SHA1-Digest: I9s6aQ5VyOLrNo4odqSij549Oyo=
Name: res/drawable-mdpi-v4/abc_textfield_search_default_mtrl_alpha.9.png
SHA1-Digest: D6dilO+UMcglambujyMOhNbLZuY=
……

CERT.SF
这个是对每个文件的头3行进行SHA1 hash。

Signature-Version: 1.0
X-Android-APK-Signed: 2
SHA1-Digest-Manifest: QxOfCCAuQtZnHh0YRNnoxmiHT80=
Created-By: 1.0 (Android)
Name: res/drawable-xhdpi-v4/abc_scrubber_control_to_pressed_mtrl_005.png
SHA1-Digest: I9s6aQ5VyOLrNo4odqSij549Oyo=
Name: res/drawable-mdpi-v4/abc_textfield_search_default_mtrl_alpha.9.png
SHA1-Digest: D6dilO+UMcglambujyMOhNbLZuY=
……

CERT.RSA
这个文件保存了签名和公钥证书。

插件化中资源冲突解决

如果需要宿主、插件之间使用同一套资源管理器,那么我们需要将插件的资源路径添加到宿主的AssetManager中。

我们知道,apk包括代码和资源,在apk编译过程中,dex工具将代码打包成.dex文件,资源文件会由aapt工具生成对应的ID,aapt在打包的时候组织成resources.arsc文件,resources.arsc文件是用来描述资源ID和资源位置配置信息,从18个维度描述了一个资源ID的配置信息(语言、分辨率等),就是资源ID和资源的索引表。资源的ID生成是有规则的,规则:0xPPTTNNNN,由8位16进制组成,其中:
PP段:表示资源的包空间:0x01表示系统资源空间,0x7f表示应用资源空间。
TT段:表示资源类型。
NNNN段:4个16进制表示资源id,一个apk中同一类型资源从0000开始递增。
例如:

nt anim pop_dialog_in 0x7f040000
int anim pop_dialog_out 0x7f040001
int anim slide_left_in 0x7f040002
int anim slide_left_out 0x7f040003
int anim slide_right_in 0x7f040004
int anim slide_right_out 0x7f040005
int anim update_loading_progressbar_anim 0x7f040006
int array indicator_tab_icon 0x7f050001
int array indicator_tab_titlt 0x7f050000

现在问题来了,宿主apk和插件apk是独立编译出来的两个独立的apk,那么其中就有资源ID相同的情况出现,从而产生资源ID冲突。如何解决这个问题?看了一些开源框架,解决的办法就是修改资源ID的PP段,大体有两种做法:

  1. 修改aapt源码,定制aapt工具编译期间修改PP段。
    DynamicAPK的做法就是如此,定制aapt,替换google的原始aapt,在编译的时候可以传入参数修改PP段:例如传入0x05编译得到的资源的PP段就是0x05。个人觉得这个做法不是太灵活,入侵了原有的开发编译流程,不好维护。
  2. 修改aapt的产物,即,编译后期重新整理插件Apk的资源,编排ID。
    前面说过apk编译之后会生成ID以及对应的索引表resorce.arsc,那么我们能不能后期修改相关ID及索引表呢?答案是肯定的,个人比较赞同这种思路,不用入侵原有编译流程。

插件可能是 Apk 也可能是 so 格式,不管哪一种,都不会生成 R.id ,从而没办法使用。这个问题有好几种解决方案。一种是是重写 Context 的 getAsset 、 getResource 之类的方法,偷换概念,让插件读取插件里的资源,但缺点就是宿主和插件的资源 id 会冲突,需要重写 AAPT 。另一种是重写 AMS中保存的插件列表,从而让宿主和插件分别去加载各自的资源而不会冲突。第三种方法,就是打包后,执行一个脚本,修改生成包中资源id。

标签:文件,插件,编译,res,应用程序,apk,Android,android,资源
From: https://blog.csdn.net/sjw890821sjw/article/details/142326690

相关文章

  • Android设备亮屏熄屏控制CEC设备同步断电上电
    相关文件:release/frameworks/base/services/core/java/com/android/server/hdmi/HdmiControlService.javaAndroid设备亮屏熄屏时会发送相关广播,亮屏广播:“android.intent.action.SCREEN_ON”,熄屏广播:“android.intent.action.SCREEN_OFF”。HdmiControlService.java接收到......
  • android raw中的文件引用不到
    如果在Android项目中遇到了无法引用res/raw目录下文件的问题,这通常是因为以下几个原因:文件命名问题:确保文件名不包含任何特殊字符,并且是全小写。这是因为资源文件名在R类中会被转换成常量名,而常量名只能包含字母数字字符和下划线,并且是全小写。文件路径问题:确认文件是否确实位......
  • Nodejs 揭秘:单线程魔法背后的真相以及它如何为高性能应用程序提供动力
    Node.js有时被称为“单线程”,这个词对于习惯了Java或.NET等多线程环境的开发人员来说可能会令人困惑,甚至令人畏惧。然而,Node.js如何处理作业的真相远比这个简单术语所暗示的复杂和强大。在这篇博客中,我们将了解Node.js的架构、单线程意味着什么,以及Node.js如何通过其独......
  • AI无人直播插件常用功能的代码分享!
    在当前的AI技术浪潮中,AI无人直播插件已经成为许多商家和内容创作者提升直播效率、降低成本的得力助手。这类插件通过集成先进的AI技术,实现了直播内容的自动化生成、智能互动、数据分析等功能,以下,我将分享五个AI无人直播插件的常用功能及其简化后的代码示例。1、自动内容生成自动内......
  • Android 他人开源库自定义imageview实现图片圆角,操作简单
    Android他人开源库自定义imageview实现图片圆角,操作简单效果图:1.导入依赖dependencies{implementation'io.github.FlyJingFish:ShapeImageView:1.5.6'}2.ShapeImageView示例<com.flyjingfish.shapeimageviewlib.ShapeImageViewandroid:id="@+id/i......
  • Nuxt Kit 中的插件:创建与使用
    title:NuxtKit中的插件:创建与使用date:2024/9/19updated:2024/9/19author:cmdragonexcerpt:摘要:本文介绍了在Nuxt3框架中使用NuxtKit创建和管理插件的方法,包括使用addPlugin注册插件、创建插件文件、在Vue组件中使用插件,以及使用addPluginTemplate创建......
  • 传统WAF无法全面应对Web应用程序攻击,Web安全网关成为新首选
    互联网时代,HTTP协议基本统治了整个互联网,web应用成为当下主流。随着企业数字化转型地不断深入,越来越多的企业业务应用系统被部署到互联网平台上。Web应用程序成为企业信息系统中最常见的应用程序之一,同时,也是最容易受到攻击的应用程序之一。据Gartner调查统计,2022年全球Web......
  • WordPress中最佳播客插件:入门级指南
    近年来,播客在全球范围内迅速普及,成为人们获取信息和娱乐的重要途径。对于想在WordPress网站上添加播客功能的用户来说,选择合适的插件非常重要。本文将为大家介绍几款适合用户入门级WordPress播客插件,让你轻松实现播客功能。1.PodcastPlayer简介PodcastPlayer是一款简单易用的插......
  • 解决帝国CMS插件404的10种方法,轻松修复网站错误!
    解决帝国CMS插件404错误可以通过多种方法来实现,以下是根据提供的信息整理出的十种方法,帮助你轻松修复网站错误:1.检查插件文件是否存在问题描述:插件文件可能由于某种原因丢失或未正确安装。解决方法:确认插件文件存在于帝国CMS的指定目录中。如果文件丢失,重新下载或安装插件。......
  • 使用Termux把Android手机变成SSH服务器
    Termux是一款能够为Android手机提供Linux环境的应用。它最大的特点就是无需root,而且自带pkg包管理软件,可以很方便的其他linux应用。安装Termux可以在GooglePlayStore或F-Droid上搜索Termux来安装,目前的版本是0.47。值得一提的是,在豌豆荚上,这个应用的名字似乎变成了高......