如何将HTML5程序pycharm 打包应用程序成Android应用

用phonegap打包html5程序(手机app开发html5 juqery moblie phonegap系列二)_百度文库
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
用phonegap打包html5程序(手机app开发html5 juqery moblie phonegap系列二)
&&手机app开发html5 juqery moblie phonegap系列文章第二篇,教你如何用phonegap创建一个html5工程,并导入到eclipseADT运行成功。
阅读已结束,下载文档到电脑
想免费下载本文?
定制HR最喜欢的简历
下载文档到电脑,方便使用
还剩2页未读,继续阅读
定制HR最喜欢的简历
你可能喜欢利用HTML5开发Android笔记
资源来自于 杨丰盛老师成都场的PPT分享 一个很简明的demo 可以作为入门基础
学习的过程中做了点笔记 整理如下 虽然内容比较简单 但是数量还是比较多的 所以分了3篇
(上)包括Android设备多分辨率的问题,Android中构建HTML5应用程序基础
资源来自于 杨丰盛老师成都场的PPT分享 一个很简明的demo 可以作为入门基础
学习的过程中做了点笔记 整理如下 虽然内容比较简单 但是数量还是比较多的 所以分了3篇
(上)包括Android设备多分辨率的问题,Android中构建HTML5应用程序基础
(中)包括Android与JS之间的互动,Android处理JS的警告对话框等,Android中的调试
(下)包括本地储存在Android中的应用,地理位置的应用,离线应用的构建
Android设备多分辨率的问题
Android浏览器默认预览模式浏览 会缩小页面 WebView中则会以原始大小显示
Android浏览器和WebView默认为mdpi。hdpi相当于mdpi的1.5倍 ldpi相当于0.75倍
三种解决方式:1 viewport属性 2 CSS控制 3 JS控制
1 viewport属性放在HTML的&meta&中
style="font-size: x-"
name=”viewport” content=”width=device-width,user-scalable=no”
meta中viewport的属性如下
style="font-size: x-"
name="viewport"
height = [pixel_value | device-height] ,
width = [pixel_value | device-width ] ,
initial-scale = float_value ,
minimum-scale = float_value ,
maximum-scale = float_value ,
user-scalable = [yes | no] ,
target-densitydpi = [dpi_value | device-dpi |
high-dpi | medium-dpi | low-dpi]
2 CSS控制设备密度
为每种密度创建独立的样式表(注意其中的webkit-device-pixel-ratio 3个数值对应3种分辨率)
rel="stylesheet" media="screen and (-webkit-device-pixel-ratio: 1.5)" href="hdpi.css"
rel="stylesheet" media="screen and (-webkit-device-pixel-ratio: 1.0)" href="mdpi.css"
rel="stylesheet" media="screen and (-webkit-device-pixel-ratio: 0.75)" href="ldpi.css"
在一个样式表中,指定不同的样式
style="white-space:"
background:url(medium-density-image.png);
@media screen and (-webkit-device-pixel-ratio: 1.5) {
// CSS for high-density screens
background:url(high-density-image.png);
@media screen and (-webkit-device-pixel-ratio: 0.75) {
// CSS for low-density screens
background:url(low-density-image.png);
name="viewport" content="target-densitydpi=device-dpi, width=device-width"
Android浏览器和WebView支持查询当前设别密度的DOM特性
window.devicePixelRatio 同样值有3个(0.75,1,1.5对应3种分辨率)
JS中查询设备密度的方法
if (window.devicePixelRatio == 1.5) {
alert("This is a high-density screen");
} else if (window.devicePixelRation == 0.75) {
alert("This is a low-density screen");
● Android中构建HTML5应用
使用WebView控件 与其他控件的使用方法相同 在layout中使用一个&WebView&标签
WebView不包括导航栏,地址栏等完整浏览器功能,只用于显示一个网页
在WebView中加载Web页面,使用loadUrl()
WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.loadUrl("");
注意在manifest文件中加入访问互联网的权限:
android:name="android.permission.INTERNET"
在Android中点击一个链接,默认是调用应用程序来启动,因此WebView需要代为处理这个动作 通过WebViewClient
webView.setWebViewClient(new WebViewClient(){
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
这个WebViewClient对象是可以自己扩展的,例如
private class MyWebViewClient extends WebViewClient {
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (Uri.parse(url).getHost().equals("")) {
return false;
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
return true;
WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.setWebViewClient(new MyWebViewClient());
另外出于用户习惯上的考虑 需要将WebView表现得更像一个浏览器,也就是需要可以回退历史记录
因此需要覆盖系统的回退键 goBack,goForward可向前向后浏览历史页面
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack() {
myWebView.goBack();
return true;
return super.onKeyDown(keyCode, event);
Android与JS之间的互相调用交互
WebView默认是禁用JavaScript的,在启用后,就可以在两者间建立接口进行调用
WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
(这里的webSetting用处非常大 可以开启很多设置 在之后的本地存储,地理位置等之中都会使用到)
1 在JS中调用Android的函数方法
首先 需要在Android程序中建立接口
final class InJavaScript {
public void runOnAndroidJavaScript(final String str) {
handler.post(new Runnable() {
public void run() {
TextView show = (TextView) findViewById(R.id.textview);
show.setText(str);
webView.addJavascriptInterface(new InJavaScript(), "injs");
在JavaScript中调用
function sendToAndroid(){
var str = "Cookie call the Android method from js";
window.injs.runOnAndroidJavaScript(str);
2 在Android中调用JS的方法
在JS中的方法:
function getFromAndroid(str){
document.getElementById("android").innerHTML=
在Android调用该方法
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
public void onClick(View arg0) {
webView.loadUrl("javascript:getFromAndroid('Cookie call the js function from Android')");
3 Android中处理JS的警告,对话框等
在Android中处理JS的警告,对话框等需要对WebView设置WebChromeClient对象
webView.setWebChromeClient(new WebChromeClient(){
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
Builder builder = new Builder(MainActivity.this);
builder.setTitle("Alert");
builder.setMessage(message);
builder.setPositiveButton(android.R.string.ok,
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.confirm();
builder.setCancelable(false);
builder.create();
builder.show();
return true;
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
Builder builder = new Builder(MainActivity.this);
builder.setTitle("confirm");
builder.setMessage(message);
builder.setPositiveButton(android.R.string.ok,
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.confirm();
builder.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.cancel();
builder.setCancelable(false);
builder.create();
builder.show();
return true;
public void onProgressChanged(WebView view, int newProgress) {
MainActivity.this.getWindow().setFeatureInt(Window.FEATURE_PROGRESS, newProgress * 100);
super.onProgressChanged(view, newProgress);
public void onReceivedTitle(WebView view, String title) {
MainActivity.this.setTitle(title);
super.onReceivedTitle(view, title);
● Android中的调试
通过JS代码输出log信息
Js代码: console.log("Hello World");
Log信息: Console: Hello World http:
在WebChromeClient中实现onConsoleMesaage()回调方法,让其在LogCat中打印信息
WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.setWebChromeClient(new WebChromeClient() {
public void onConsoleMessage(String message, int lineNumber, String sourceID) {
Log.d("MyApplication", message + " -- From line "
+ lineNumber + " of "
+ sourceID);
WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.setWebChromeClient(new WebChromeClient() {
public boolean onConsoleMessage(ConsoleMessage cm) {
Log.d("MyApplication", cm.message() + " -- From line "
+ cm.lineNumber() + " of "
+ cm.sourceId() );
return true;
*ConsoleMessage 还包括一个 MessageLevel 表示控制台传递信息类型。 您可以用messageLevel()查询信息级别,以确定信息的严重程度,然后使用适当的Log方法或采取其他适当的措施。
TML5本地存储在Android中的应用
HTML5提供了2种客户端存储数据新方法:
localStorage 没有时间限制
sessionStorage 针对一个Session的数据存储
&script type="text/javascript"&
localStorage.lastname="Smith";
document.write(localStorage.lastname);
&script type="text/javascript"&
sessionStorage.lastname="Smith";
document.write(sessionStorage.lastname);
WebStorage的API:
localStorage.clear();
localStorage.setItem(“yarin”,“yangfegnsheng”);
localStorage.getItem(“yarin”);
localStorage.key(0);
localStorage.removeItem(“yarin”);
注意一定要在设置中开启哦
setDomStorageEnabled(true)
在Android中进行操作
webSettings.setDatabaseEnabled(true);
String dir = this.getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
webSettings.setDatabasePath(dir);
webSettings.setDomStorageEnabled(true);
public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota,
long estimatedSize, long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
quotaUpdater.updateQuota(estimatedSize * 2);
在JS中按常规进行数据库操作
function initDatabase() {
if (!window.openDatabase) {
alert('Databases are not supported by your browser');
var shortName = 'YARINDB';
var version = '1.0';
var displayName = 'yarin db';
var maxSize = 100000;
YARINDB = openDatabase(shortName, version, displayName, maxSize);
createTables();
selectAll();
} catch(e) {
if (e == 2) {
console.log("Invalid database version.");
console.log("Unknown error "+ e +".");
function createTables(){
YARINDB.transaction(
function (transaction) {
transaction.executeSql('CREATE TABLE IF NOT EXISTS yarin(id INTEGER NOT NULL PRIMARY KEY, name TEXT NOT NULL,desc TEXT NOT NULL);', [], nullDataHandler, errorHandler);
insertData();
function insertData(){
YARINDB.transaction(
function (transaction) {
var data = ['1','yarin yang','I am yarin'];
transaction.executeSql("INSERT INTO yarin(id, name, desc) VALUES (?, ?, ?)", [data[0], data[1], data[2]]);
function errorHandler(transaction, error){
if (error.code==1){
console.log('Oops.
Error was '+error.message+' (Code '+error.code+')');
return false;
function nullDataHandler(){
console.log("SQL Query Succeeded");
function selectAll(){
YARINDB.transaction(
function (transaction) {
transaction.executeSql("SELECT * FROM", [], dataSelectHandler, errorHandler);
function dataSelectHandler(transaction, results){
for (var i=0; i&results.rows. i++) {
var row = results.rows.item(i);
var newFeature = new Object();
newFeature.name
= row['name'];
newFeature.decs = row['desc'];
document.getElementById("name").innerHTML="name:"+newFeature.
document.getElementById("desc").innerHTML="desc:"+newFeature.
function updateData(){
YARINDB.transaction(
function (transaction) {
var data = ['fengsheng yang','I am fengsheng'];
transaction.executeSql("UPDATE yarin SET name=?, desc=? WHERE id = 1", [data[0], data[1]]);
selectAll();
function ddeleteTables(){
YARINDB.transaction(
function (transaction) {
transaction.executeSql("DROP TABLE", [], nullDataHandler, errorHandler);
console.log("Table 'page_settings' has been dropped.");
注意onLoad中的初始化工作
function initLocalStorage(){
if (window.localStorage) {
textarea.addEventListener("keyup", function() {
window.localStorage["value"] = this.
window.localStorage["time"] = new Date().getTime();
}, false);
alert("LocalStorage are not supported in this browser.");
window.onload = function() {
initDatabase();
initLocalStorage();
● HTML5地理位置服务在Android中的应用
webSettings.setGeolocationEnabled(true);
webSettings.setGeolocationDatabasePath(dir);
public void onGeolocationPermissionsShowPrompt(String origin,
GeolocationPermissions.Callback callback) {
callback.invoke(origin, true, false);
super.onGeolocationPermissionsShowPrompt(origin, callback);
在Manifest中添加权限
android:name="android.permission.ACCESS_FINE_LOCATION"
android:name="android.permission.ACCESS_COARSE_LOCATION"
HTML5中 通过navigator.geolocation对象获取地理位置信息
常用的navigator.geolocation对象有以下三种方法:
navigator.geolocation.getCurrentPosition(success_callback_function, error_callback_function, position_options)
navigator.geolocation.watchPosition(success_callback_function, error_callback_function, position_options)
navigator.geolocation.clearWatch(watch_position_id)
其中success_callback_function为成功之后处理的函数,error_callback_function为失败之后返回的处理函数,参数position_options是配置项
在JS中的代码
function get_location() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(show_map,handle_error,{enableHighAccuracy:false,maximumAge:1000,timeout:15000});
alert("Your browser does not support HTML5 geoLocation");
function show_map(position) {
var latitude = position.coords.
var longitude = position.coords.
var city = position.coords.
document.getElementById("Latitude").innerHTML="latitude:"+
document.getElementById("Longitude").innerHTML="longitude:"+
document.getElementById("City").innerHTML="city:"+
function handle_error(err) {
switch (err.code) {
alert("permission denied");
alert("the network is down or the position satellites can't be contacted");
alert("time out");
alert("unknown error");
其中position对象包含很多数据 error代码及选项 可以查看文档
● 构建HTML5离线应用
需要提供一个cache manifest文件,理出所有需要在离线状态下使用的资源
Manifest代码
CACHE MANIFEST
images/sound-icon.png
images/background.png
clock.html
style/default.css
/files/projects /projects
在html标签中声明 &html manifest="clock.manifest"&
HTML5离线应用更新缓存机制
分为手动更新和自动更新2种
自动更新:
在cache manifest文件本身发生变化时更新缓存 资源文件发生变化不会触发更新
手动更新:
使用window.applicationCache
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
window.applicationCache.update();
在线状态检测
HTML5 提供了两种检测是否在线的方式:navigator.online(true/false) 和 online/offline事件。
在Android中构建离线应用
webSettingssetAppCacheEnabled(true);
String dir = this.getApplicationContext().getDir("cache", Context.MODE_PRIVATE).getPath();
webSettings.setAppCachePath(dir);
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
webSettings.setAppCacheMaxSize(1024*1024*8);
public void onReachedMaxAppCacheSize(long spaceNeeded,
long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
quotaUpdater.updateQuota(spaceNeeded * 2);
版权声明:本文内容由互联网用户自发贡献,本社区不拥有所有权,也不承担相关法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至: 进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
用云栖社区APP,舒服~
【云栖快讯】哪个编程语言最热门?各个专业领域的技术趋势是什么?如何才能更快速的踏上技术进阶之路……云栖社区2017中国开发者大调查火热进行!答卷可抽奖,红轴机械键盘、天猫精灵,丰富好礼大概率抽取&&
阿里云移动APP解决方案,助力开发者轻松应对移动app中随时可能出现的用户数量的爆发式增长、复杂的移动安全挑战等...
以阿里云成熟的商业化云服务为基础,为游戏开发者、运营企业提供专属集群、尊享VIP服务、专项扶持基金、及多场景多类...
移动测试(Mobile Testing)是为广大企业客户和移动开发者提供真机测试服务的云平台,拥有大量热门机型,...
为您提供简单高效、处理能力可弹性伸缩的计算服务,帮助您快速构建更稳定、安全的应用,提升运维效率,降低 IT 成本...
安全技术百问
Loading...博客分类:
我们知道,在一个APK文件中,除了有代码文件之外,还有很多资源文件。这些资源文件是通过Android资源打包工具aapt(Android Asset Package Tool)打包到APK文件里面的。在打包之前,大部分文本格式的XML资源文件还会被编译成二进制格式的XML资源文件。在本文中,我们就详细分析 XML资源文件的编译和打包过程,为后面深入了解Android系统的资源管理框架打下坚实的基础。
在前面一 文中提到,只有那些类型为res/animator、res/anim、res/color、res/drawable(非Bitmap文件,即 非.png、.9.png、.jpg、.gif文件)、res/layout、res/menu、res/values和res/xml的资源文件均会从 文本格式的XML文件编译成二进制格式的XML文件,如图1所示:
图1 Android应用程序资源的编译和打包过程
这些XML资源文件之所要从文本格式编译成二进制格式,是因为:
1. 二进制格式的XML文件占用空间更小。这是由于所有XML元素的标签、属性名称、属性值和内容所涉及到的字符串都会被统一收集到一个字符串资源池中去,并 且会去重。有了这个字符串资源池,原来使用字符串的地方就会被替换成一个索引到字符串资源池的整数值,从而可以减少文件的大小。
2. 二进制格式的XML文件解析速度更快。这是由于二进制格式的XML元素里面不再包含有字符串值,因此就避免了进行字符串解析,从而提高速度。
将XML资源文件从文本格式编译成二进制格式解决了空间占用以及解析效率的问题,但是对于Android资源管理框架来说,这只是完成了其中的一部分工作。Android资源管理框架的另外一个重要任务就是要根据资源ID来快速找到对应的资源。
在前面一 文中提到,为了使得一个应用程序能够在运行时同时支持不同的大小和密度的屏幕,以及支持国际化,即支持不同的国家地区和语言,Android应用程序资源 的组织方式有18个维度,每一个维度都代表一个配置信息,从而可以使得应用程序能够根据设备的当前配置信息来找到最匹配的资源来展现在UI上,从而提高用 户体验。
由于Android应用程序资源的组织方式可以达到18个维度,因此就要求Android资源管理框架能够快速定位最匹配设备当前配置信息的资源来展现在 UI上,否则的话,就会影响用户体验。为了支持Android资源管理框架快速定位最匹配资源,Android资源打包工具aapt在编译和打包资源的过 程中,会执行以下两个额外的操作:
1. 赋予每一个非assets资源一个ID值,这些ID值以常量的形式定义在一个R.java文件中。
2. 生成一个resources.arsc文件,用来描述那些具有ID值的资源的配置信息,它的内容就相当于是一个资源索引表。
有了资源ID以及资源索引表之后,Android资源管理框架就可以迅速将根据设备当前配置信息来定位最匹配的资源了。接下来我们在分析Android应 用程序资源的编译和打包过程中,就主要关注XML资源的编译过程、资源ID文件R.java的生成过程以及资源索引表文件resources.arsc的 生成过程。
Android资源打包工具在编译应用程序资源之前,会创建一个资源表。这个资源表使用一个ResourceTable对象来描述,当应用程序资源编 译完成之后,它就会包含所有资源的信息。有了这个资源表之后, Android资源打包工具就可以根据它的内容来生成资源索引表文件 resources.arsc了。
接下来,我们就通过ResourceTable类的实现来先大概了解资源表里面都有些什么东西,如图2所示:
图2 ResourceTable的实现
ResourceTable类用来总体描述一个资源表,它的重要成员变量的含义如下所示:
--mAssetsPackage:表示当前正在编译的资源的包名称。
--mPackages:表示当前正在编译的资源包,每一个包都用一个Package对象来描述。例如,一般我们在编译应用程序资源时,都会引用系统预先编译好的资源包,这样当前正在编译的资源包除了目标应用程序资源包之外,就还有预先编译好的系统资源包。
--mOrderedPackages:和mPackages一样,也是表示当前正在编译的资源包,不过它们是以Package ID从小到大的顺序保存在一个Vector里面的,而mPackages是一个以Package Name为Key的DefaultKeyedVector。
--mAssets:表示当前编译的资源目录,它指向的是一个AaptAssets对象。
Package类用来描述一个包,这个包可以是一个被引用的包,即一个预先编译好的包,也可以是一个正在编译的包,它的重要成员变量的含义如下所示:
--mName:表示包的名称。
--mTypes:表示包含的资源的类型,每一个类型都用一个Type对象来描述。资源的类型就是指animimator、anim、color、drawable、layout、menu和values等。
--mOrderedTypes:和mTypes一样,也是表示包含的资源的类型,不过它们是Type ID从小到大的顺序保存在一个Vector里面的,而mTypes是一个以Type Name为Key的DefaultKeyedVector。
Type类用来描述一个资源类型,它的重要成员变量的含义如下所示:
--mName:表示资源类型名称。
--mConfigs:表示包含的资源配置项列表,每一个配置项列表都包含了一系列同名的资源,使用一个ConfigList来描述。例如,假设有main.xml和sub.xml两个layout类型的资源,那么main.xml和sub.xml都分别对应有一个ConfigList。
--mOrderedConfigs:和mConfigs一样,也是表示包含的资源配置项,不过它们是以Entry ID从小到大的顺序保存在一个Vector里面的,而mConfigs是以Entry Name来Key的DefaultKeyedVector。
--mUniqueConfigs:表示包含的不同资源配置信息的个数。我们可以将mConfigs和mOrderedConfigs看作是按照名称的不同来划分资源项,而将mUniqueConfigs看作是按照配置信息的不同来划分资源项。
ConfigList用来描述一个资源配置项列表,它的重要成员变量的含义如下所示:
--mName:表示资源项名称,也称为Entry Name。
--mEntries: 表示包含的资源项,每一个资源项都用一个Entry对象来描述,并且以一个对应的ConfigDescription为Key保存在一个 DefaultKeyedVector中。例如,假设有一个名称为icon.png的drawable资源,有三种不同的配置,分别是ldpi、mdpi 和hdpi,那么以icon.png为名称的资源就对应有三个项。
Entry类用来描述一个资源项,它的重要成员变量的含义如下所示:
--mName:表示资源名称。
--mItem:表示资源数据,用一个Item对象来描述。
Item类用来描述一个资源项数据,它的重要成员变量的含义如下所示:
--value:表示资源项的原始值,它是一个字符串。
--parsedValue:表示资源项原始值经过解析后得到的结构化的资源值,使用一个Res_Value对象来描述。例如,一个整数类型的资源项的原始值为“12345”,经过解析后,就得到一个大小为12345的整数类型的资源项。
ConfigDescription类是从ResTable_config类继承下来的,用来描述一个资源配置信息。ResTable_config 类的成员变量imsi、locale、screenType、input、screenSize、version和screenConfig对应的实际上 就是在前面一文提到的18个资源维度。
前面提到,当前正在编译的资源目录是使用一个AaptAssets对象来描述的,它的实现如图3所示:
图3 AaptAssets类的实现
AaptAssets类的重要成员变量的含义如下所示:
--mPackage:表示当前正在编译的资源的包名称。
--mRes:表示所包含的资源类型集,每一个资源类型都使用一个ResourceTypeSet来描述,并且以Type Name为Key保存在一个KeyedVector中。
--mHaveIncludedAssets:表示是否有引用包。
--mIncludedAssets:指向的是一个AssetManager,用来解析引用包。引用包都是一些预编译好的资源包,它们需要通过AssetManager来解析。事实上,Android应用程序在运行的过程中,也是通过AssetManager来解析资源的。
--mOverlay: 表示当前正在编译的资源的重叠包。重叠包是什么概念呢?假设我们正在编译的是Package-1,这时候我们可以设置另外一个Package-2,用来告 诉aapt,如果Package-2定义有和Package-1一样的资源,那么就用定义在Package-2的资源来替换掉定义在Package-1的 资源。通过这种Overlay机制,我们就可以对资源进行定制,而又不失一般性。
ResourceTypeSet类实际上描述的是一个类型为AaptGroup的KeyedVector,并且这个KeyedVector是以 AaptGroup Name为Key的。AaptGroup类描述的是一组同名的资源,类似于前面所描述的ConfigList,它有一个重要的成员变量mFiles,里面 保存的就是一系列同名的资源文件。每一个资源文件都是用一个AaptFile对象来描述的,并且以一个AaptGroupEntry为Key保存在一个 DefaultKeyedVector中。
AaptFile类的重要成员变量的含义如下所示:
--mPath:表示资源文件路径。
--mGroupEntry:表示资源文件对应的配置信息,使用一个AaptGroupEntry对象来描述。
--mResourceType:表示资源类型名称。
--mData:表示资源文件编译后得到的二进制数据。
--mDataSize:表示资源文件编译后得到的二进制数据的大小。
AaptGroupEntry类的作用类似前面所描述的ResTable_config,它的成员变量mcc、mnc、locale、vendor、 screenLayoutSize、screenLayoutLong、orientation、uiModeType、uiModeNight、 density、tounscreen、keysHidden、keyboard、navHidden、navigation、screenSize和 version对应的实际上就是在前面一文提到的18个资源维度。
了解了ResourceTable类和AaptAssets类的实现之后,我们就可以开始分析Android资源打包工具的执行过程了,如图4所示:
图4 Android资源打包工具的执行过程
假设我们当前要编译的应用程序资源目录结构如下所示:
--AndroidManifest.xml
--drawable-ldpi
--icon.png
--drawable-mdpi
--icon.png
--drawable-hdpi
--icon.png
--main.xml
--strings.xml
接下来,我们就按照图4所示的步骤来分析上述应用程序资源的编译和打包过程。
一. 解析AndroidManifest.xml
解析AndroidManifest.xml是为了获得要编译资源的应用程序的包名称。我们知道,在AndroidManifest.xml文件中,manifest标签的package属性的值描述的就是应用程序的包名称。有了这个包名称之后,就可以创建资源表了,即创建一个ResourceTable对象。
二. 添加被引用资源包
Android系统定义了一套通用资源,这些资源可以被应用程序引用。例如,我们在XML布局文件中指定一个LinearLayout的android:orientation属性的值为“vertical”时,这个“vertical”实际上就是在系统资源包里面定义的一个值。
在Android源代码工程环境中,Android系统提供的资源经过编译后,就位于out/target/common/obj/APPS/framework-res_intermediates/package-export.apk文件中,因此,在Android源代码工程环境中编译的应用程序资源,都会引用到这个package-export.apk。
从上面的分析就可以看出,我们在编译一个Android应用程序的资源的时候,至少会涉及到两个包,其中一个被引用的系统资源包,另外一个就是当前正在编 译的应用程序资源包。每一个包都可以定义自己的资源,同时它也可以引用其它包的资源。那么,一个包是通过什么方式来引用其它包的资源的呢?这就是我们熟悉 的资源ID了。资源ID是一个4字节的无符号整数,其中,最高字节表示Package ID,次高字节表示Type ID,最低两字节表示Entry ID。
Package ID相当于是一个命名空间,限定资源的来源。Android系统当前定义了两个资源命令空间,其中一个系统资源命令空间,它的Package ID等于0x01,另外一个是应用程序资源命令空间,它的Package ID等于0x7f。所有位于[0x01, 0x7f]之间的Package ID都是合法的,而在这个范围之外的都是非法的Package ID。前面提到的系统资源包package-export.apk的Package ID就等于0x01,而我们在应用程序中定义的资源的Package ID的值都等于0x7f,这一点可以通过生成的R.java文件来验证。
Type ID是指资源的类型ID。资源的类型有animator、anim、color、drawable、layout、menu、raw、string和xml等等若干种,每一种都会被赋予一个ID。
Entry ID是指每一个资源在其所属的资源类型中所出现的次序。注意,不同类型的资源的Entry ID有可能是相同的,但是由于它们的类型不同,我们仍然可以通过其资源ID来区别开来。
关于资源ID的更多描述,以及资源的引用关系,可以参考frameworks/base/libs/utils目录下的README文件。
三. 收集资源文件
在编译应用程序资源之前,Android资源打包工具aapt会创建一个AaptAssets对象,用来收集当前需要编译的资源文件。这些需要编译的资源文件就保存在AaptAssets类的成员变量mRes中,如下所示:
class AaptAssets : public AaptDir
KeyedVector&String8, sp&ResourceTypeSet& &* mR
AaptAssets类定义在文件frameworks/base/tools/aapt/AaptAssets.h中。
AaptAssets类的成员变量mRes是一个类型为ResourceTypeSet的KeyedVector,这个KeyedVector 的Key就是资源的类型名称。由此就可知,收集到资源文件是按照类型来保存的。例如,对于我们在这篇文章中要用到的例子,一共有三种类型的资源,分别是 drawable、layout和values,于是,就对应有三个ResourceTypeSet。
从前面的图3可以看出,ResourceTypeSet类本身描述的也是一个KeyedVector,不过它里面保存的是一系列有着相同文件名的AaptGroup。例如,对于我们在这篇文章中要用到的例子:
1. 类型为drawable的ResourceTypeSet只有一个AaptGroup,它的名称为icon.png。这个AaptGroup包含了三个文件,分别是res/drawable-ldpi/icon.png、res/drawable-mdpi/icon.png和res/drawable-hdpi/icon.png。每一个文件都用一个AaptFile来描述,并且都对应有一个AaptGroupEntry。每一个AaptGroupEntry描述的都是不同的资源配置信息,即它们所描述的屏幕密度分别是ldpi、mdpi和hdpi。
2. 类型为layout的ResourceTypeSet有两个AaptGroup,它们的名称分别为main.xml和sub.xml。这两个AaptGroup都是只包含了一个AaptFile,分别是res/layout/main.xml和res/layout/sub.xml。这两个AaptFile同样是分别对应有一个AaptGroupEntry,不过这两个AaptGroupEntry描述的资源配置信息都是属于default的。
3. 类型为values的ResourceTypeSet只有一个AaptGroup,它的名称为strings.xml。这个AaptGroup只包含了一个AaptFile,即res/values/strings.xml。这个AaptFile也对应有一个AaptGroupEntry,这个AaptGroupEntry描述的资源配置信息也是属于default的。
四. 将收集到的资源增加到资源表
前面收集到的资源只是保存在一个AaptAssets对象中,这一步需要将这些资源同时增加到一个资源表中去,即增加到前面所创建的一个ResourceTable对象中去,因为最后我们需要根据这个ResourceTable来生成资源索引表,即生成resources.arsc文件。
注意,这一步收集到资源表的资源是不包括values类型的资源的。类型为values的资源比较特殊,它们要经过编译之后,才会添加到资源表中去。这个过程我们后面再描述。
从前面的图2可以看出,在ResourceTable类中,每一个资源都是分别用一个Entry对象来描述的,这些Entry分别按照Pacakge、Type和ConfigList来分类保存。例如,对于我们在这篇文章中要用到的例子,假设它的包名为“shy.luo.activity”,那么在ResourceTable类的成员变量mPackages和mOrderedPackages中,就会分别保存有一个名称为“shy.luo.activity”的Package,如下所示:
class ResourceTable : public ResTable::Accessor
DefaultKeyedVector&String16, sp&Package& & mP
Vector&sp&Package& & mOrderedP
ResourceTable类定义在文件frameworks/base/tools/aapt/ResourceTable.h中。
在这个名称为“shy.luo.activity”的Package中,分别包含有drawable和layout两种类型的资源,每一种类型使用一个Type对象来描述,其中:
1. 类型为drawable的Type包含有一个ConfigList。这个ConfigList的名称为icon.png,包含有三个Entry,分别为res/drawable-ldip/icon.png、res/drawable-mdip/icon.png和res/drawable-hdip/icon.png。每一个Entry都对应有一个ConfigDescription,用来描述不同的资源配置信息,即分别用来描述ldpi、mdpi和hdpi三种不同的屏幕密度。
2. 类型为layout的Type包含有两个ConfigList。这两个ConfigList的名称分别为main.xml和sub.xml。名称为main.xml的ConfigList包含有一个Entry,即res/layout/main.xml。名称为sub.xml的ConfigList包含有一个Entry,即res/layout/sub/xml。
上述得到的五个Entry分别对应有五个Item,它们的对应关系以及内容如下图5所示:
图5 收集到的drawable和layout资源项列表
五. 编译values类资源
类型为values的资源描述的都是一些简单的值,如数组、颜色、尺寸、字符串和样式值等,这些资源是在编译的过程中进行收集的。接下来,我们就以字符串的编译过程来进行说明。
在这篇文章中要用到的例子中,包含有一个strings.xml的文件,它的内容如下所示:
version="1.0" encoding="utf-8"
name="app_name"Activity
name="sub_activity"Sub Activity
name="start_in_process"Start sub-activity in process
name="start_in_new_process"Start sub-activity in new process
name="finish"Finish activity
这个文件经过编译之后,资源表就多了一个名称为string的Type,这个Type有五个ConfigList。这五个ConfigList的名称分别 为“app_name”、“sub_activity”、“start_in_process”、“start_in_new_process”和 “finish”,每一个ConfigList又分别含有一个Entry。
上述得到的五个Entry分别对应有五个Item,它们的对应关系以及内容如图6所示:
图6 收集到的string资源项列表
六. 给Bag资源分配ID
类型为values的资源除了是string之外,还有其它很多类型的资源,其中有一些比较特殊,如bag、style、plurals和array类的 资源。这些资源会给自己定义一些专用的值,这些带有专用值的资源就统称为Bag资源。例如,Android系统提供的 android:orientation属性的取值范围为{“vertical”、“horizontal”},就相当于是定义了vertical和 horizontal两个Bag。
在继续编译其它非values的资源之前,我们需要给之前收集到的Bag资源分配资源ID,因为它们可能会被其它非values类资源引用到。假设在res/values目录下,有一个attrs.xml文件,它的内容如下所示:
version="1.0" encoding="utf-8"
name="custom_orientation"
name="custom_vertical" value="0"
name="custom_horizontal" value="1"
这个文件定义了一个名称为“custom_orientation”的属性,它是一个枚举类型的属性,可以取值为“custom_vertical”或者 “custom_horizontal”。Android资源打包工具aapt在编译这个文件的时候,就会生成以下三个Entry,如图7所示:
图7 收集到的Bag资源项列表
上述三个Entry均为Bag资源项,其中,custom_vertical(id类型资源)和custom_horizontal( id类型资源)是custom_orientation(attr类型资源)的两个bag,我们可以将custom_vertical和 custom_horizontal看成是custom_orientation的两个元数据,用来描述custom_orientation的取值范 围。实际上,custom_orientation还有一个内部元数据,用来描述它的类型。这个内部元数据也是通过一个bag来表示的,这个bag的名称 和值分别为“^type”和TYPE_ENUM,用来表示它描述的是一个枚举类型的属性。注意,所有名称以“^”开头的bag都是表示一个内部元数据。
对于Bag资源来说,这一步需要给它们的元数据项分配资源ID,也就是给它们的bag分配资源ID。例如,对于上述的 custom_orientation来说,我们需要给它的^type、custom_vertical和custom_horizontal分配资源 ID,其中,^type分配到的是attr类型的资源ID,而custom_vertical和custom_horizontal分配到的是id类型的 资源ID。
七. 编译Xml资源文件
前面的六步操作为编译Xml资源文件准备好了所有的素材,因此,现在就开始要编译Xml资源文件了。除了values类型的资源文件,其它所有的Xml资源文件都需要编译。这里我们只挑layout类型的资源文件来说明Xml资源文件的编译过程,也就是这篇文章中要用到的例子中的main.xml文件,它的内容如下所示:
version="1.0" encoding="utf-8"
xmlns:android="/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:id="@+id/button_start_in_process"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/start_in_process"
android:id="@+id/button_start_in_new_process"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/start_in_new_process"
Xml资源文件main.xml的编译过程如图8所示:
图8 Xml资源文件的编译过程
1. 解析Xml文件
解析Xml文件是为了可以在内存中用一系列树形结构的XMLNode来表示它。XMLNode类的定义在文件frameworks/base/tools/aapt/XMLNode.h中,如下所示:
class XMLNode : public RefBase
String16 mElementN
Vector&sp&XMLNode& & mC
Vector&attribute_entry& mA
String16 mC
每一个XMLNode都表示一个Xml元素,其中:
--mElementName,表示Xml元素标签。
--mChars,表示Xml元素的文本内容。
--mAttributes,表示Xml元素的属性列表。
--mChildren,表示Xml元素的子元素。
Xml文件解析完成之后,就可以得到一个用来描述根节点的XMLNode,接下来就可以通过这个根节点来完成其它的编译操作。
2. 赋予属性名称资源ID
这一步实际上就是给每一个Xml元素的属性名称都赋予资源ID。例如,对于main.xml文件的根节点LinearLayout来说,就是要分别给它的 属性名称“android:orientation”、“android:layout_width”、“android:layout_height” 和“android:gravity”赋予一个资源ID。注意,上述这些属性都是在系统资源包里面定义的,因此,Android资源打包工具首先是要在系 统资源包里面找到这些名称所对应的资源ID,然后才能赋给main.xml文件的根节点LinearLayout。
对于系统资源包来说,“android:orientation”、“android:layout_width”、 “android:layout_height”和“android:gravity”等这些属性名称是它定义的一系列Bag资源,在它被编译的时候,就 已经分配好资源ID了,就如上面的第六步操作所示。
每一个Xml文件都是从根节点开始给属性名称赋予资源ID,然后再给递归给每一个子节点的属性名称赋予资源ID,直到每一个节点的属性名称都获得了资源ID为止。
3. 解析属性值
上一步是对Xml元素的属性的名称进行解析,这一步是对Xml元素的属性的值进行解析。例如,对于对于main.xml文件的根节点 LinearLayout来说,前面我们已经给它的属性android:orientation的名称赋予了一个资源ID,这里就要给它的值 “vertical”进行解析。
前面提到,android:orientation是在系统资源包定义的一个Bag资源,这个Bag资源分配有资源ID,而且会指定有元数据,也就是它可 以取哪些值。对于android:orientation来说,它的合法取值就为“horizontal”或者“vertical”。在系统资源包 中,“horizontal”或者“vertical”也同样是一个Bag资源,它们的值分别被定义为0和1。
Android资源打包工具是如何找到main.xml文件的根节点LinearLayout的属性android:orientation的字符串值 “vertical”所对应的整数值1的呢?假设在上一步中,从系统资源包找到“android:orientation”的资源ID为 0x,那么Android资源打包工具就会通过这个资源ID找到它的元数据,也就是两个名称分别为“horizontal”和 “vertical”的bag,接着就根据字符串匹配到名称“vertical”的bag,最后就可以将这个bag的值1作为解析结果了。
注意,对于引用类型的属性值,要进行一些额外的处理。例如,对于main.xml文件的第一个Button节点的android:id属性值 “@+id/button_start_in_process”,其中,“@”表示后面描述的属性是引用类型的,“+”表示如果该引用不存在,那么就新建 一个,“id”表示引用的资源类型是id,“button_start_in_process”表示引用的名称。实际上,在"id"前面,还可以指定一个 包名,例如,将main.xml文件的第一个Button节点的android:id属性值指定为“@+ [package:]id/button_start_in_process” 。如果没有指定包名的话,那么就会默认在当前编译的包里面查找button_start_in_process这个引用。由于前面指有“+”符号,因此, 如果在指定的包里面找不到button_start_in_process这个引用的话,那么就会在该包里面创建一个新的。无论 button_start_in_process在指定的包里面原来就存在的,还是新建的,最终Android资源打包工具都是将它的资源ID作为解析结 果。
在我们这个情景中,在解析main.xml文件的两个Button节点的android:id属性值 “@+id/button_start_in_process”和“@+id/button_start_in_new_process”时,当前正在编 译的资源包没有包含有相应的引用的,因此,Android资源打包工具就会在当前正在编译的资源包里面增加两个类型为id的Entry,如图9所示:
图9 增加两个类型为id的资源项
此外,对于main.xml文件的两个Button节点的android:text属性值“@string/start_in_process”和 “@string/start_in_new_process”,它们分别表示引用的是当前正在编译的资源包的名称分别为 “start_in_process”和“start_in_new_process”的string资源。这两个string资源在前面的第五步操作中 已经编译过了,因此,这里就可以直接获得它们的资源ID。
注意,一个资源项一旦创建之后,要获得它的资源ID是很容易的,因为它的Package ID、Type ID和Entry ID都是已知的。
4. 压平Xml文件
经过前面的三步操作之后,所需要的基本材料都已经准备好了,接下来就可以对Xml文件的内容进行扁平化处理了,实际上就是将Xml文件从文本格式转换为二进制格式,这个过程如图10所示:
图10 压平Xml文件
将Xml文件从文本格式转换为二进制格式可以划分为六个步骤,接下来我们就详细分析每一个步骤。
Step 1. 收集有资源ID的属性的名称字符串
这一步除了收集那些具有资源ID的Xml元素属性的名称字符串之外,还会将对应的资源ID收集起来放在一个数组中。这里收集到的属性名称字符串保存在一个字符串资源池中,它们与收集到的资源ID数组是一一对应的。
对于main.xml文件来说,具有资源ID的Xml元素属性的名称字符串有“orientation”、“layout_width”、 “layout_height”、“gravity”、“id”和“text”,假设它们对应的资源ID分别为0x、 0x、0x、0x010100af、0x和0x0101014f,那么最终得到的字符串资源池的前6个 位置和资源ID数组的对应关系如图11所示:
图11 属性名称字符串与属性资源ID的对应关系
Step 2. 收集其它字符串
这一步收集的是Xml文件中的其它所有字符串。由于在前面的Step 1中,那些具有资源ID的Xml元素属性的名称字符串已经被收集过了,因此,它们在一步中不会被重复收集。对于main.xml文件来说,这一步收集到的字符串如图12所示:
图12 其它字符串
其中,“android”是android命名空间前缀,“/apk/res/android”是 android命名空间uri,“LinearLayout”是LinearLayout元素的标签,“Button”是Button元素的标签。
Step 3. 写入Xml文件头
最终编译出来的Xml二进制文件是一系列的chunk组成的,每一个chunk都有一个头部,用来描述chunk的元信息。同时,整个Xml二进制文件又可以看成一块总的chunk,它有一个类型为ResXMLTree_header的头部。
ResXMLTree_header定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:
struct ResChunk_header
uint16_t headerS
struct ResXMLTree_header
struct ResChunk_
ResXMLTree_header内嵌有一个类型为ResChunk_header的头部。事实上,每一种头部类型都会内嵌有一个类型为 ResChunk_header的基础头部,并且这个ResChunk_header都是作为第一个成员变量出现的。这样在解析二进制Xml文件的时候, 只需要读出前面大小为sizeof(ResChunk_header)的数据块,并且通过识别其中的type值,就可以知道实际正在处理的chunk的具 体类型。
对于ResXMLTree_header头部来说,内嵌在它里面的ResChunk_header的成员变量的值如下所示:
--type:等于RES_XML_TYPE,描述这是一个Xml文件头部。
--headerSize:等于sizeof(ResXMLTree_header),表示头部的大小。
--size:等于整个二进制Xml文件的大小,包括头部headerSize的大小。
Step 4. 写入字符串资源池
原来定义在Xml文件中的字符串已经在Step 1和Step 2中收集完毕,因此,这里就可以将它们写入到最终收集到二进制格式的Xml文件中去。注意,写入的字符串是严格按照它们在字符串资源池中的顺序写入的。例 如,对于main.xml来说,依次写入的字符串为“orientation”、“layout_width”、“layout_height”、 “gravity”、“id”、"text"、"android"、“/apk/res /android”、“LinearLayout”和“Button”。之所以要严格按照这个顺序来写入,是因为接下来要将前面Step 1收集到的资源ID数组也写入到二进制格式的Xml文件中去,并且要保持这个资源ID数组与字符串资源池前六个字符串的对应关系。
写入的字符串池chunk同样也是具有一个头部的,这个头部的类型为ResStringPool_header,它定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:
struct ResStringPool_header
struct ResChunk_
uint32_t stringC
uint32_t styleC
SORTED_FLAG = 1&&0,
UTF8_FLAG = 1&&8
uint32_t stringsS
uint32_t stylesS
内嵌在ResStringPool_header里面的ResChunk_header的成员变量的值如下所示:
--type:等于RES_STRING_POOL_TYPE,描述这是一个字符串资源池。
--headerSize:等于sizeof(ResStringPool_header),表示头部的大小。
--size:整个字符串chunk的大小,包括头部headerSize的大小。
ResStringPool_header的其余成员变量的值如下所示:
--stringCount:等于字符串的数量。
--styleCount:等于字符串的样式的数量。
--flags:等于0、SORTED_FLAG、UTF8_FLAG或者它们的组合值,用来描述字符串资源串的属性,例如,SORTED_FLAG位等于1表示字符串是经过排序的,而UTF8_FLAG位等于1表示字符串是使用UTF8编码的,否则就是UTF16编码的。
--stringsStart:等于字符串内容块相对于其头部的距离。
--stylesStart:等于字符串样式块相对于其头部的距离。
无论是UTF8,还是UTF16的字符串编码,每一个字符串的前面都有2个字节表示其长度,而且后面以一个NULL字符结束。对于UTF8编码的字符串来 说,NULL字符使用一个字节的0x00来表示,而对于UTF16编码的字符串来说,NULL字符使用两个字节的0x0000来表示。
如果一个字符串的长度超过32767,那么就会使用更多的字节来表示。假设字符串的长度超过32767,那么前两个字节的最高位就会等于0,表示接下来的两个字节仍然是用来表示字符串长度的,并且前两个字表示高16位,而后两个字节表示低16位。
除了ResStringPool_header头部、字符串内容块和字符串样式内容块之外,还有两个偏移数组,分别是字符串偏移数组和字符串样式偏移数 组,这两个偏移数组的大小就分别等于字符串的数量stringCount和styleCount的值,而每一个元素都是一个无符号整数。整个字符中资源池 的组成就如图13所示:
图13 字符串资源池结构
注意,字符串偏移数组和字符串样式偏移数组的值分别是相对于stringStart和styleStart而言的。在解析二进制Xml文件的时候,通过这 两个偏移数组以及stringsStart和stylesStart的值就可以迅速地定位到第i个字符串。
接下来,我们就重点说说什么是字符串样式。假设有一个字符串资源池,它有五个字符串,分别是"apple"、“banana”、“orange”、 “&b&man&/b&&i&go&/i&”和“pear”。注意到第四个字符串 “&b&man&/b&&i&go&/i&”,它实际表示的是一个字符串“mango”,不过它的前三 个字符“man”通过b标签来描述为粗体的,而后两个字符通过i标签来描述为斜体的。这样实际上在整个字符串资源池中,包含了七个字符串,分别 是"apple"、“banana”、“orange”、“mango”、“pear”、“b”和“i”,其中,第四个字符串“mango”来有两个 sytle,第一个style表示第1到第3个字符是粗体的,第二个style表示第4到第5个字符是斜体的。
字符串与其样式描述是一一对应的,也变是说,如果第i个字符串是带有样式描述的,那么它的样式描述就位于样式内容块第i个位置上。以上面的字符串资源池为 例,由于第4个字符中带有样式描述,为了保持字符串与样式描述的一一对应关系,那么也需要假设前面3个字符串也带有样式描述的,不过需要将这3个字符串的 样式描述的个数设置为0。也就是说,在这种情况下,字符串的个数等于7,而样式描述的个数等于4,其中,第1到第3个字符串的样式描述的个数等于0,而第 4个字符串的样式描述的个数等于2。
假设一个字符串有N个样式描述,那么它在样式内容块中,就对应有N个ResStringPool_span,以及一个 ResStringPool_ref,其中,N个ResStringPool_span位于前面,用来描述每一个样式,而 ResStringPool_ref表示一个结束占位符。例如,对于上述的“mango”字符串来说,它就对应有2个 ResStringPool_span,以及1个ResStringPool_ref,而对于"apple"、“banana”和“orange”这三个 字符串来说,它们对应有0个ResStringPool_span,但是对应有1个ResStringPool_ref,最后三个字符串“pear”、 “b”和"i"对应有0个ResStringPool_span和0个ResStringPool_ref。
ResStringPool_span和ResStringPool_ref定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:
struct ResStringPool_ref
struct ResStringPool_span
END = 0xFFFFFFFF
ResStringPool_
uint32_t firstChar, lastC
由于ResStringPool_ref在这里出现的作用就是充当样式描述结束占位符,因此,它唯一的成员变量index的取值就固定为ResStringPool_span::END。
再来看ResStringPool_span是如何表示一个样式描述的。以字符串“mango”的第一个样式描述为例,对应的ResStringPool_span的各个成员变量的取值为:
--name:等于字符串“b”在字符串资源池中的位置。
--firstChar:等于0,即指向字符“m”。
--lastChar:等于2,即指向字符"n"。
综合起来就是表示字符串“man”是粗体的。
再以字符串“mango”的第二个样式描述为例,对应的ResStringPool_span的各个成员变量的取值为:
--name:等于字符串“i”在字符串资源池中的位置。
--firstChar:等于3,即指向字符“g”。
--lastChar:等于4,即指向字符“o”。
综合起来就是表示字符串“go”是斜体的。
另外有一个地方需要注意的是,字符串样式内容的最后会有8个字节,每4个字节都被填充为ResStringPool_span::END,用来表达字符串样式内容结束符。这个结束符可以在解析过程中用作错误验证。
Step 5. 写入资源ID
在前面的Step 1中,我们把属性的资源ID都收集起来了。这些收集起来的资源ID会作为一个单独的chunk写入到最终的二进制Xml文件中去。这个chunk位于字符 串资源池的后面,它的头部使用ResChunk_header来描述。这个ResChunk_header的各个成员变量的取值如下所示:
--type:等于RES_XML_RESOURCE_MAP_TYPE,表示这是一个从字符串资源池到资源ID的映射头部。
--headerSize:等于sizeof(ResChunk_header),表示头部大小。
--size:等于headerSize的大小再加上sizeof(uint32_t) * count,其中,count为收集到的资源ID的个数。
以main.xml为例,字符串资源池的第一个字符串为“orientation”,而在资源ID这个chunk中记录的第一个数据为0x,那么就表示属性名称字符串“orientation”对应的资源ID为0x。
Step 6. 压平Xml文件
压平Xml文件其实就是指将里面的各个Xml元素中的字符串都替换掉。这些字符串要么是被替换成到字符串资源池的一个索引,要么是替换成一个具有类型的其它值。我们以main.xml为例来说这个压平的过程。
首先被压平的是一个表示命名空间的Xml Node。这个Xml Node用两个ResXMLTree_node和两个ResXMLTree_namespaceExt来表示,如图14所示:
图14 命名空间chunk块
ResXMLTree_node和ResXMLTree_namespaceExt定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:
struct ResXMLTree_node
struct ResChunk_
uint32_t lineN
struct ResStringPool_
struct ResXMLTree_namespaceExt
struct ResStringPool_
struct ResStringPool_
对于main.xml文件来说,在它的命名空间chunk中,内嵌在第一个ResXMLTree_node里面的ResChunk_header的各个成员变量的取值如下所示:
--type:等于RES_XML_START_NAMESPACE_TYPE,表示命名空间开始标签的头部。
--headerSize:等于sizeof(ResXMLTree_node),表示头部的大小。
--size:等于sizeof(ResXMLTree_node) + sizeof(ResXMLTree_namespaceExt)。
第一个ResXMLTree_node的其余成员变量的取值如下所示:
--lineNumber:等于命名空间开始标签在原来文本格式的Xml文件出现的行号。
--comment:等于命名空间的注释在字符池资源池的索引。
内嵌在第二个ResXMLTree_node里面的ResChunk_header的各个成员变量的取值如下所示:
--type:等于RES_XML_END_NAMESPACE_TYPE,表示命名空间结束标签的头部。
--headerSize:等于sizeof(ResXMLTree_node),表示头部的大小。
--size:等于sizeof(ResXMLTree_node) + sizeof(ResXMLTree_namespaceExt)。
第二个ResXMLTree_node的其余成员变量的取值如下所示:
--lineNumber:等于命名空间结束标签在原来文本格式的Xml文件出现的行号。
--comment:等于0xffffffff,即-1。
两个ResXMLTree_namespaceExt的内容都是一样的,它们的成员变量的取值如下所示:
--prefix:等于字符串“android”在字符串资源池中的索引。
--uri:等于字符串“/apk/res/android”在字符串资源池中的索引。
接下来被压平的是标签为LinearLayout的Xml Node。这个Xml Node由两个ResXMLTree_node、一个ResXMLTree_attrExt、一个ResXMLTree_endElementExt和四 个ResXMLTree_attribute来表示,如图15所示:
图15 标签为LinearLayout的Xml元素chunk
ResXMLTree_attrExt、ResXMLTree_attribute和ResXMLTree_endElementExt定义在文件 frameworks/base/include/utils/ResourceTypes.h中,如下所示:
struct ResXMLTree_attrExt
struct ResStringPool_
struct ResStringPool_
uint16_t attributeS
uint16_t attributeS
uint16_t attributeC
uint16_t idI
uint16_t classI
uint16_t styleI
struct ResXMLTree_attribute
struct ResStringPool_
struct ResStringPool_
struct ResStringPool_ref rawV
struct Res_value typedV
struct ResXMLTree_endElementExt
struct ResStringPool_
struct ResStringPool_
内嵌在第一个ResXMLTree_node里面的ResChunk_header的各个成员变量的取值如下所示:
--type:等于RES_XML_START_ELEMENT_TYPE,表示LinearLayout开始标签的头部。
--headerSize:等于sizeof(ResXMLTree_node),表示头部的大小。
--size:等于sizeof(ResXMLTree_node) + sizeof(ResXMLTree_attrExt) + sizeof(ResXMLTree_attribute) * 4。
第一个ResXMLTree_node的其余成员变量的取值如下所示:
--lineNumber:等于LinearLayout开始标签在原来文本格式的Xml文件出现的行号。
--comment:等于LinearLayout标签的注释在字符池资源池的索引。
ResXMLTree_attrExt的各个成员变量的取值如下所示:
--ns:等于LinearLayout元素的命令空间在字符池资源池的索引,没有指定则等于-1。
--name:等于字符串“LinearLayout”在字符池资源池的索引。
--attributeStart:等于sizeof(ResXMLTree_attrExt),表示LinearLayout的属性chunk相对type值为RES_XML_START_ELEMENT_TYPE的ResXMLTree_node头部的位置。
--attributeSize:等于sizeof(ResXMLTree_attribute),表示每一个属性占据的chunk大小。
--attributeCount:等于4,表示有4个属性chunk。
--idIndex:如果LinearLayout元素有一个名称为“id”的属性,那么就将它出现在属性列表中的位置再加上1的值记录在idIndex中,否则的话,idIndex的值就等于0。
--classIndex:如果LinearLayout元素有一个名称为“class”的属性,那么就将它出现在属性列表中的位置再加上1的值记录在classIndex中,否则的话,classIndex的值就等于0。
--styleIndex:如果LinearLayout元素有一个名称为“style”的属性,那么就将它出现在属性列表中的位置再加上1的值记录在styleIndex中,否则的话,styleIndex的值就等于0。
LinearLayout元素有四个属性,每一个属性都对应一个ResXMLTree_attribute,接下来我们就以名称为“orientation”的属性为例,来说明它的各个成员变量的取值,如下所示:
--ns:等于属性orientation的命令空间在字符池资源池的索引,没有指定则等于-1。
--name:等于属性名称字符串“orientation”在字符池资源池的索引。
--rawValue:等于属性orientation的原始值“vertical”在字符池资源池的索引,这是可选的,如果不用保留,它的值就等于-1。
名称为“orientation”的ResXMLTree_attribute的成员变量typedValue是一个Res_value,它定义在文 件frameworks/base/include/utils/ResourceTypes.h中,如下所示:
struct Res_value
uint8_t res0;
TYPE_NULL = 0x00,
TYPE_REFERENCE = 0x01,
TYPE_ATTRIBUTE = 0x02,
TYPE_STRING = 0x03,
TYPE_FLOAT = 0x04,
TYPE_DIMENSION = 0x05,
TYPE_FRACTION = 0x06,
TYPE_FIRST_INT = 0x10,
TYPE_INT_DEC = 0x10,
TYPE_INT_HEX = 0x11,
TYPE_INT_BOOLEAN = 0x12,
TYPE_FIRST_COLOR_INT = 0x1c,
TYPE_INT_COLOR_ARGB8 = 0x1c,
TYPE_INT_COLOR_ARGB8 = 0x1c,
TYPE_INT_COLOR_RGB8 = 0x1d,
TYPE_INT_COLOR_ARGB4 = 0x1e,
TYPE_INT_COLOR_RGB4 = 0x1f,
TYPE_LAST_COLOR_INT = 0x1f,
TYPE_LAST_INT = 0x1f
uint8_t dataT
COMPLEX_UNIT_SHIFT = 0,
COMPLEX_UNIT_MASK = 0xf,
COMPLEX_UNIT_PX = 0,
COMPLEX_UNIT_DIP = 1,
COMPLEX_UNIT_SP = 2,
COMPLEX_UNIT_PT = 3,
COMPLEX_UNIT_IN = 4,
COMPLEX_UNIT_MM = 5,
COMPLEX_UNIT_FRACTION = 0,
COMPLEX_UNIT_FRACTION_PARENT = 1,
COMPLEX_RADIX_SHIFT = 4,
COMPLEX_RADIX_MASK = 0x3,
COMPLEX_RADIX_23p0 = 0,
COMPLEX_RADIX_16p7 = 1,
COMPLEX_RADIX_8p15 = 2,
COMPLEX_RADIX_0p23 = 3,
COMPLEX_MANTISSA_SHIFT = 8,
COMPLEX_MANTISSA_MASK = 0xffffff
void copyFrom_dtoh(const Res_value& src);
一个属性的值经过解析之后,也就是经过前面编译Xml资源的第3个操作之后,就用一个Res_value来表示。 例如,对于名称为 “orientation”的属性的值“vertical”来说,经过解析之后,它就会用一个Res_value来表示,这个Res_value的各个成 员变量的值如下所示:
--size:等于sizeof(Res_value)。
--res0:等于0,保留给以后用。
--dataType:等于TYPE_INT_DEC,表示数据类型,即这是一个十进制形式的整数值。
--data:等于1,参考前面编译Xml资源的第3个操作。
更多的数据类型,请参考Res_value定义里面的注释。从这里我们就可以看出,在解析二进制格式的Xml文件的过程中,当我们知道一个属性的名称在字 符串资源池的索引之后,就可以通过这个索引在字符串资源池中找到对应的属性名称字符中,同时,通过这个索引还可以在资源ID的那块chunk中找到对应的 属性资源ID,而有了这个属性资源ID之后,我们就可以进一步地验证该属性的取值是否正确等操作。
内嵌在第二个ResXMLTree_node里面的ResChunk_header的各个成员变量的取值如下所示:
--type:等于RES_XML_END_ELEMENT_TYPE,表示LinearLayout结束标签的头部。
--headerSize:等于sizeof(ResXMLTree_node),表示头部的大小。
--size:等于sizeof(ResXMLTree_node) + sizeof(ResXMLTree_endElementExt) 。
第二个ResXMLTree_node的其余成员变量的取值如下所示:
--lineNumber:等于LinearLayout结束标签在原来文本格式的Xml文件出现的行号。
--comment:等于-1。
ResXMLTree_endElementExt的各个成员变量的取值如下所示:
--ns:等于LinearLayout元素的命令空间在字符池资源池的索引,没有指定则等于-1。
--name:等于字符串“LinearLayout”在字符池资源池的索引。
注意,位于名称为“gravity”的ResXMLTree_attribute和第二个ResXMLTree_node之间的chunk是用来写入 LinearLayout元素的两个子元素Button的内容的。这两个Button与LinearLayout一样,都是以相同的结构递归写入到最终的 二进制Xml文件中去的,区别只在于结构内容的不同,以及属性个数的不同。
例如,对于第一个Button的属性id来说,它所对应的ResXMLTree_attribute的成员变量typedValue所指向的一个Res_value的各个成员变量的值如下所示:
--size:等于sizeof(Res_value)。
--res0:等于0,保留给以后用。
--dataType:等于TYPE_REFERENCE,表示数据类型,即这是一个引用值。
--data:等于分配给名称为“button_start_in_process”的ID的资源ID值,它的Package ID等于0x7f,而Type ID等于0x04,Entry ID取决于它的出现次序。
又如,对于第一个Button的属性text来说,它所对应的ResXMLTree_attribute的成员变量typedValue所指向的一个Res_value的各个成员变量的值如下所示:
--size:等于sizeof(Res_value)。
--res0:等于0,保留给以后用。
--dataType:等于TYPE_REFERENCE,表示数据类型,即这是一个引用值。
--data:等于分配给名称“start_in_process”的字符串的资源ID值,它的Package ID等于0x7f,而Type ID等于0x05,Entry ID取决于它的出现次序。
对于一个Xml文件来说,它除了有命名空间和普通标签类型的Node之外,还有一些称为CDATA类型的Node,例如,假设一个Xml文件,它的一个Item标签的内容如下所示:
This is a normal text
那么字符串“This is a normal text”就称为一个CDATA,它在二进制Xml文件中用一个ResXMLTree_node和一个ResXMLTree_cdataExt来描述,如图16所示:
图16 CDATA类型的Xml Node的二进制表示
ResXMLTree_cdataExt定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:
struct ResXMLTree_cdataExt
struct ResStringPool_
struct Res_value typedD
内嵌在上面的ResXMLTree_node的ResChunk_header的各个成员变量的取值如下所示:
--type:等于RES_XML_CDATA_TYPE,表示CDATA头部。
--headerSize:等于sizeof(ResXMLTree_node),表示头部的大小。
--size:等于sizeof(ResXMLTree_node) + sizeof(ResXMLTree_cdataExt) 。
上面的ResXMLTree_node的其余成员变量的取值如下所示:
--lineNumber:等于字符串“This is a normal text”在原来文本格式的Xml文件出现的行号。
--comment:等于字符串“This is a normal text”的注释,如果没有则等于-1。
下面的ResXMLTree_cdataExt的成员变量data等于字符串“This is a normal text”在字符串资源池的索引,另外一个成员变量typedData所指向的一个Res_value的各个成员变量的值如下所示:
--size:等于sizeof(Res_value)。
--res0:等于0,保留给以后用。
--dataType:等于TYPE_NULL,表示没有包含数据,数据已经包含在ResXMLTree_cdataExt的成员变量data中。
--data:等于0,由于dataType等于TYPE_NULL,这个值是没有意义的。
至此,一个Xml文件就从按照图8以及图10的步骤从文本格式编译成二进制格式了。当所有的Xml文件都编译完成之后,接下来就开始生成资源符号了。
八. 生成资源符号
这里生成资源符号为后面生成R.java文件做好准备的。从前面的操作可以知道,所有收集到的资源项都按照类型来保存在一个资源表中,即保存在一个 ResourceTable对象。因此,Android资源打包工具aapt只要遍历每一个Package里面的每一个Type,然后取出每一个 Entry的名称,并且根据这个Entry在自己的Type里面出现的次序来计算得到它的资源ID,那么就可以生成一个资源符号了,这个资源符号由名称以 及资源ID所组成。
例如,对于strings.xml文件中名称为“start_in_process”的Entry来说,它是一个类型为string的资源项,假设它出现 的次序为第3,那么它的资源符号就等于R.string.start_in_process,对应的资源ID就为0x7f050002,其中,高字节 0x7f表示Package ID,次高字节0x05表示string的Type ID,而低两字节0x02就表示“start_in_process”是第三个出现的字符串。
九. 生成资源索引表
我们首先总结一下,经过上述八个操作之后,所获得的资源列表如图17所示:
图17 收集到的所有资源项
有了这些资源项之后,Android资源打包工具aapt就可以按照下面的流程来生成资源索引表resources.arsc了,如图18所示:
图18 资源索引表的生成过程
接下来,我们就以图17所示的资源项来说图18所示的资源索引表生成过程。
1. 收集类型字符串
在图17所示的资源项中,一共有4种类型的资源,分别是drawable、layout、string和id,于是对应的类型字符串就为“drawable”、“layout”、“string”和“id”。
注意,这些字符串是按Package来收集的,也就是说,当前被编译的应用程序资源有几个Package,就有几组对应的类型字符串,每一个组类型字符串都保存在其所属的Package中。
2. 收集资源项名称字符串
在图17所示的资源项中,一共有12个资源项,每一个资源项的名称分别为“icon”、“icon”、“icon”、“main”、“sub”、 “app_name”、“sub_activity”、“start_in_process”、“start_in_new_process”、 “finish”、“button_start_in_process”和“button_start_in_new_process”,于是收集到的资 源项名称字符串就为“icon”、“main”、“sub”、“app_name”、“sub_activity”、 “start_in_process”、“start_in_new_process”、“finish”、 “button_start_in_process”和“button_start_in_new_process”。
注意,这些字符串同样是按Package来收集的,也就是说,当前被编译的应用程序资源有几个Package,就有几组对应的资源项名称字符串,每一个组资源项名称字符串都保存在其所属的Package中。
3. 收集资源项值字符串
在图17所示的资源项中,一共有12个资源项,但是只有10项是具有值字符串的,它们分别是“res/drawable-ldpi/icon.png”、 “res/drawable-mdpi/icon.png”、“res/drawable-hdpi/icon.png”、“res/layout /main.xml”、“res/layout/sub.xml”、“Activity”、“Sub Activity”、“Start sub-activity in process”、“Start sub-activity in new process”和“Finish activity”。
注意,这些字符串不是按Package来收集的,也就是说,当前所有参与编译的Package的资源项值字符串都会被统一收集在一起。
4. 生成Package数据块
参与编译的每一个Package的资源项元信息都写在一块独立的数据上,这个数据块使用一个类型为ResTable_package的头部来描述。每一个Package的资源项元信息数据块的生成过程如图19所示:
图19 Package资源项元信息数据块的生成过程
这个生成过程可以分为5个步骤,接下来我们就以图17所示的资源项来说详细分析每一个步骤。
Step 1. 写入Package资源项元信息数据块头部
Package资源项元信息数据块头部是用一个ResTable_package来定义的。ResTable_package定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:
struct ResTable_package
struct ResChunk_
char16_t name[128];
uint32_t typeS
uint32_t lastPublicT
uint32_t keyS
uint32_t lastPublicK
嵌入在ResTable_package内部的ResChunk_header的各个成员变量的取值如下所示:
--type:等于RES_TABLE_PACKAGE_TYPE,表示这是一个Package资源项元信息数据块头部。
--headerSize:等于sizeof(ResTable_package),表示头部大小。
--size:等于sizeof(ResTable_package) + 类型字符串资源池大小 + 资源项名称字符串资源池大小 + 类型规范数据块大小 + 数据项信息数据块大小。
ResTable_package的其它成员变量的取值如下所示:
--id:等于Package ID。
--name:等于Package Name。
--typeStrings:等于类型字符串资源池相对头部的偏移位置。
--lastPublicType:等于最后一个导出的Public类型字符串在类型字符串资源池中的索引,目前这个值设置为类型字符串资源池的大小。
--keyStrings:等于资源项名称字符串相对头部的偏移位置。
--lastPublicKey:等于最后一个导出的Public资源项名称字符串在资源项名称字符串资源池中的索引,目前这个值设置为资源项名称字符串资源池的大小。
我们可以通过图20来清楚地看到一个Package资源项元信息数据块的结构:
图20 Package资源项元信息数据块结构
在Android资源中,有一种资源类型称为Public,它们一般是定义在res/values/public.xml文件中,形式如下所示:
version="1.0" encoding="utf-8"
type="string" name="string3" id="0x7f040001"
这个public.xml用来告诉Android资源打包工具aapt,将类型为string的资源string3的ID固定为0x7f040001。为 什么需要将某一个资源项的ID固定下来呢?一般来说,当我们将自己定义的资源导出来给第三方应用程序使用时,为了保证以后修改这些导出资源时,仍然保证第 三方应用程序的兼容性,就需要给那些导出资源一个固定的资源ID。
每当Android资源打包工具aapt重新编译被修改过的资源时,都会重新给这些资源赋予ID,这就可能会造成同一个资源项在两次不同的编译中被赋予不 同的ID。这种情况就会给第三方应用程序程序带来麻烦,因为后者一般是假设一个ID对应的永远是同一个资源的。因此,当我们将自己定义的资源导出来给第三 方应用程序使用时,就需要通过public.xml文件将导出来的资源的ID固定下来。
我们用一个例子来说public.xml文件的作用,考虑下面这个strings.xml文件:
version="1.0" encoding="utf-8"
name="string1"String 1
name="string3"String 3
假设Android资源打包工具aapt为字符串资源项string1和string3分配到的资源ID如下所示:
public final class R {
public static final class string {
public static final int string1=0x7f040000;
public static final int string3=0x7f040001;
这时候第三方应用程序就会认为0x7f040001引用的永远是字符串“String 3”。
假设将来的某一天,我们需要在strings.xml文件中增加一个新的字符串,如下所示:
version="1.0" encoding="utf-8"
name="string1"String 1
name="string2"String 2
name="string3"String 3
如果没有上述的public.xml文件,那么Android资源打包工具aapt为字符串资源项string1、 string2和string3分配的资源ID就会如下所示:
public final class R {
public static final class string {
public static final int string1=0x7f040000;
public static final int string2=0x7f040001;
public static final int string3=0x7f040002;
这就完蛋了,这时候第三方应用程序通过0x7f040001引用到的字符串变成了“String 2”。
如果我们使用上述的public.xml文件将字符串“String 3”固定为0x7f040001,那么Android资源打包工具aapt为字符串资源项string1、 string2和string3分配的资源ID就会如下所示:
public final class R {
public static final class string {
public static final int string1=0x7f040000;
public static final int string2=0x7f040002;
public static final int string3=0x7f040001;
这样第三方应用程序通过0x7f040001引用到的字符串仍然是“String 3”。
注意,我们在开发应用程序时,一般是不需要用到public.xml文件的,因为我们的资源基本上都是在内部使用的,不会导出来给第三方应用程序使用。只 在内部使用的资源,不管它的ID如何变化,我们都可以通过R.java文件定义的常量来正确地引用它们。只有系统定义的资源包才会使用到 public.xml文件,因为它定义的资源是需要提供给第三方应用程序使用的。
Step 2. 写入类型字符串资源池
在前面的第1个操作中,我们已经将每一个Package用到的类型字符串收集起来了,因此,这里就可以直接将它们写入到Package资源项元信息数据块头部后面的那个数据块去。
Step 3. 写入资源项名称字符串资源池
在前面的第2个操作中,我们已经将每一个Package用到的资源项名称字符串收集起来了,这里就可以直接将它们写入到类型字符串资源池后面的那个数据块去。
Step 4. 写入类型规范数据块
类型规范数据块用来描述资源项的配置差异性。通过这个差异性描述,我们就可以知道每一个资源项的配置状况。知道了一个资源项的配置状况之 后,Android资源管理框架在检测到设备的配置信息发生变化之后,就可以知道是否需要重新加载该资源项。类型规范数据块是按照类型来组织的,也就是 说,每一种类型都对应有一个类型规范数据块。
类型规范数据块的头部是用一个ResTable_typeSpec来定义的。ResTable_typeSpec定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:
struct ResTable_typeSpec
struct ResChunk_
uint8_t res0;
uint16_t res1;
uint32_t entryC
SPEC_PUBLIC = 0x
嵌入在ResTable_typeSpec里面的ResChunk_header的各个成员变量的取值如下所示:
--type:等于RES_TABLE_TYPE_SPEC_TYPE,用来描述一个类型规范头部。
--headerSize:等于sizeof(ResTable_typeSpec),表示头部的大小。
--size:等于sizeof(ResTable_typeSpec) + sizeof(uint32_t) * entryCount,其中,entryCount表示本类型的资源项个数。
ResTable_typeSpec的其它成员变量的取值如下所示:
--id:表示资源的Type ID。
--res0:等于0,保留以后使用。
--res1:等于0,保留以后使用。
--entryCount:等于本类型的资源项个数,注意,这里是指名称相同的资源项的个数。
ResTable_typeSpec后面紧跟着的是一个大小为entryCount的uint32_t数组,每一个数组元数,即每一个 uint32_t,都是用来描述一个资源项的配置差异性的。例如,在图17中,名称为icon的drawable资源项有三种不同的屏幕配置ldpi、 mdpi和hdpi,于是用来描述它的配置差异性的uint32_t的第CONFIG_DENSITY位就等于1,而其余位都等于0。又如,在图17中, 名称为main的layout资源项只有一种配置default,于是用来描述它的配置差异性的uint32_t的值就等于0。此外,如果一个资源项是导 出的,即它的资源ID是通过public.xml来固定的,那么用来描述它的配置差异性的uint32_t的第 ResTable_typeSpec::SPEC_PUBLIC位也会被设置为1。
在图17中,一共有4种不同类型的资源项,它们所对应的4个类型规范数据块如图21至图24所示:
图21 类型为drawable的规范数据块
图22 类型为layout的规范数据块
图23 类型为string的规范数据块
图24 类型为id的规范数据块
从图21到图24就可以看出,类型为drawable的资源项icon在设备的屏幕密度发生变化之后,Android资源管理框架需要重新对它进行加载, 以便获得更合适的资源项,而其它资源项无论设备配置信息发生何种变化,它们都不需要重新加载,因为它们只有一种配置。
Step 5. 写入类型资源项数据块
类型资源项数据块用来描述资源项的具体信息, 这样我们就可以知道每一个资源项名称、值和配置等信息。类型资源项数据同样是按照类型和配置来组织的,也就是说,一个具有N个配置的类型一共对应有N个类型资源项数据块。
类型资源项数据块的头部是用一个ResTable_type来定义的。ResTable_type定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:
struct ResTable_type
struct ResChunk_
NO_ENTRY = 0xFFFFFFFF
uint8_t res0;
uint16_t res1;
uint32_t entryC
uint32_t entriesS
嵌入在ResTable_type里面的ResChunk_header的各个成员变量的取值如下所示:
--type:等于RES_TABLE_TYPE_TYPE,用来描述一个类型资源项头部。
--headerSize:等于sizeof(ResTable_type),表示头部的大小。
--size:等于sizeof(ResTable_type) + sizeof(uint32_t) * entryCount,其中,entryCount表示本类型的资源项个数。
ResTable_type的其它成员变量的取值如下所示:
--id:表示资源的Type ID。
--res0:等于0,保留以后使用。
--res1:等于0,保留以后使用。
--entryCount:等于本类型的资源项个数,注意,这里是指名称相同的资源项的个数。
--entriesStart:等于资源项数据块相对头部的偏移值。
--config:指向一个ResTable_config,用来描述配置信息,它的定义可以参考图2的类图。
ResTable_type紧跟着的是一个大小为entryCount的uint32_t数组,每一个数组元数,即每一个uint32_t,都是用来 描述一个资源项数据块的偏移位置。紧跟在这个uint32_t数组后面的是一个大小为entryCount的ResTable_entry数组,每一个数 组元素,即每一个ResTable_entry,都是用来描述一个资源项的具体信息。
在图17中,一共有4种不同类型的资源项,其中,类型为drawable的资源有1个资源项以及3种不同的配置,类型为layout的资源有2个资源 项以及1种配置,类型为string的资源有5个资源项以及1种配置,类型为id的资源有2个资源项以及1种配置,这样一共就对应有3 + 1 + 1 + 1个类型资源项数据块,如图25至图图30所示:
图25 类型为drawable和配置为ldpi的资源项数据块
图26 类型为drawable和配置为mdpi的资源项数据块
图27 类型为drawable和配置为hdpi的资源项数据块
图28 类型为layout和配置为default的资源项数据块
图29 类型为string和配置为default的资源项数据块
图30 类型为id和配置为default的资源项数据块
注意,ResTable_type后面的uint32_t数组和ResTable_entry数组的大小不一定是相等的,考虑下面的资源目录:
--drawable-ldpi
--icon.png
--drawable-mdpi
--icon.png
--logo.png
--drawable-hdpi
--logo.png
那么最终得到类型为drawable和配置为ldpi的资源项数据块如图31所示:
图31 大小不等的uint32_t数组和ResTable_entry数组的资源项数据块
由于不存在类型为drawable、配置为ldpi,并且名称为logo的资源项,因此,在图31中,ResTable_type后面的uint32_t数组和ResTable_entry数组的大小是不相等的,并且没有相应的ResTable_entry的uint32_t数组元素的值会被设置为ResTable_type::NO_ENTRY。
每一个资源项数据都是通过一个ResTable_entry来定义的。ResTable_entry定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:
struct ResTable_entry
FLAG_COMPLEX = 0x0001,
FLAG_PUBLIC = 0x0002
struct ResStringPool_
ResTable_entry的各个成员变量的取值如下所示:
--size:等于sizeof(ResTable_entry),表示资源项头部大小。
--flags:资源项标志位。如果是一个Bag资源项,那么FLAG_COMPLEX位就等于1,并且在ResTable_entry后面跟有一个ResTable_map数组,否则的话,在ResTable_entry后面跟的是一个Res_value。如果是一个可以被引用的资源项,那么FLAG_PUBLIC位就等于1。
--key:资源项名称在资源项名称字符串资源池的索引。
接下来我们就分两种情况来讨论资源项信息写入到资源索引表的过程。
首先看一个普通的资源项,即一个非Bag资源项的写入过程。从图2可以知道,每一个资源项的数据都是用一个Item来描述的。在这个Item中,有一个类型为Res_value的成员变量parsedValue,它表示一个资源项经过解析后得到值。
前面在分析Xml资源文件的编译过程时,我们已经介绍过Res_value的定义了。假设当前要写入的资源项是类型为layout的main,从图1

我要回帖

更多关于 应用程序打包工具 的文章

 

随机推荐