[置顶] 浅谈Android的资源编译过程
AndroidAPK
由于直接粘贴复制原因,故直接上传文档至此
转载请注明出处
文档下载地址:http://download.csdn.net/detail/hao1056531028/5616263
一.APK的结构以及生成
APK是AndroidPackage的缩写,即Androidapplicationpackage文件或Android安装包。每个要安装到Android平台的应用都要被编译打包为一个单独的文件,扩展名为.apk。APK文件是用编译器编译生成的文件包,其中包含了应用的二进制代码、资源、配置文件等。通过将APK文件直接传到Android手机中执行即可安装。APK文件其实就是zip格式,但其扩展名被改为apk。在这里我们为了详细讲述Android应用程序我们将创建一个永恒的话题,它就是HelloWorld
程序,在这里我们创建的Android的HelloWorld程序的目录结构如下所示:
一个典型的APK文件通常由下列内容组成:
AndroidManifest.xml程序全局配置文件
classes.dexDalvik字节码
resources.arsc资源索引表,解压缩resources.ap_就能看到
res\该目录存放资源文件(图片,文本,xml布局)
assets\该目录可以存放一些配置文件
src\java源码文件
libs\存放应用程序所依赖的库
gen\编译器根据资源文件生成的java文件
bin\由编译器生成的apk文件和各种依赖的资源
META-INF\该目录下存放的是签名信息
首先来看一下使用Java语言编写的Android应用程序从源码到安装包的整个过程,示意图如下,其中包含编译、链接和签名等:
(1).使用aapt工具将资源文件生成R.java文件,resources.arsc和打包资源文件
(2).使用aidl工具将.aidl文件编译成.java文件
(3).使用javac工具将.java文件编译成.class文件
(4).使用dx脚本将众多.class文件转换成一个.dex文件
(5).使用apkbuilder脚本将资源文件和.dex文件生成未签名的apk安装文件
(6).使用jdk中的jarsigner对apk安装文件进行签名
上述工具都保存在android-sdk-linux中的tools/和platform-tools文件夹下面.
范例:
src/com.example.helloworldactivity:
packagecom.example.helloworldactivity;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.view.View;
importandroid.view.View.OnClickListener;
importandroid.widget.Button;
importandroid.widget.TextView;
publicclassMainActivityextendsActivity{
privatefinalstaticStringTAG="MainActivity";
privateTextViewmTextView=null;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView=(TextView)findViewById(R.id.text_view);
ButtonshowButton=(Button)findViewById(R.id.button);
showButton.setOnClickListener(newOnClickListener(){
publicvoidonClick(Viewv){
mTextView.setText(R.string.hello_world);
}
});
}
}
res/layout/activity_main.xml:
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/show"/>
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=""/>
</LinearLayout>
res/values/strings.xml:
<?xmlversion="1.0"encoding="utf-8"?>
<resources>
<stringname="app_name">HelloWorldActivity</string>
<stringname="action_settings">Settings</string>
<stringname="show">Show</string>
<stringname="hello_world">Helloworld!</string>
</resources>
AndroidManifest.xml:
<?xmlversion="1.0"encoding="utf-8"?>
<manifestxmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.helloworldactivity"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity
android:name="com.example.helloworldactivity.MainActivity"
android:label="@string/app_name">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
我们前面创建的HelloWorldActivity应用程序资源目录结构如下所示:
project
接下来,我们在HelloWorldActivity工程目录下可以使用aapt命令:
aaptp-f-m-Jmygen/-Sres/
-I~/tool/android-sdk-linux/platforms/android-17/android.jar-Aassets/
-MAndroidManifest.xml-Fhelloworldresources.apk
在mygen目录下生成一个资源ID文件R.java和在当前目录下生成一个名为helloworldresources.apk的资源包,解压缩里面内容如下所示:
被打包的APK资源文件中包含有:资源索引表文件resources.arsc,AndroidManifest.xml二进制文件和res目录下的应用程序图片资源及layout目录下的二进制activity_main.xml文件,res目录下信息如下所示:
注意:res/values目录下的字符串信息被编译进了resources.arsc资源索引文件中,而在R.java文件中仅仅保存了资源ID信息.R.java信息如下所示:
packagecom.example.helloworldactivity;
publicfinalclassR{
publicstaticfinalclassattr{
}
publicstaticfinalclassdimen{
publicstaticfinalintactivity_horizontal_margin=0x7f040000;
publicstaticfinalintactivity_vertical_margin=0x7f040001;
}
publicstaticfinalclassdrawable{
publicstaticfinalintic_launcher=0x7f020000;
}
publicstaticfinalclassid{
publicstaticfinalintbutton=0x7f070000;
publicstaticfinalinttext_view=0x7f070001;
}
publicstaticfinalclasslayout{
publicstaticfinalintactivity_main=0x7f030000;
}
publicstaticfinalclassstring{
publicstaticfinalintaction_settings=0x7f050001;
publicstaticfinalintapp_name=0x7f050000;
publicstaticfinalinthello_world=0x7f050003;
publicstaticfinalintshow=0x7f050002;
}
publicstaticfinalclassstyle{
publicstaticfinalintAppBaseTheme=0x7f060000;
publicstaticfinalintAppTheme=0x7f060001;
}
}
下面我们根据分析appt的源码详细讲述命令:
aaptp-f-m-Jmygen/-Sres/
-I~/tool/android-sdk-linux/platforms/android-17/android.jar-Aassets/
-MAndroidManifest.xml-Fhelloworldresources.apk
是如何将上述应用程序资源编译生成一个R.java文件,资源索引表文件resources.arsc,AndroidManifest.xml二进制文件和res目录下的应用程序图片资源及layout目录下的二进制activity_main.xml文件的.
appt入口函数main具体实现如下所示:
路径:frameworks/base/tools/aapt/Main.cpp
intmain(intargc,char*constargv[])
{
char*prog=argv[0];
Bundlebundle;//定义一个Bundle类存储appt命令的各种编译选项
boolwantUsage=false;
intresult=1;//pessimisticallyassumeanerror.
inttolerance=0;
/*defaulttocompression
**设置默认的压缩标准*/
bundle.setCompressionMethod(ZipEntry::kCompressDeflated);
if(argc<2){
wantUsage=true;
gotobail;
}
if(argv[1][0]=='v')
bundle.setCommand(kCommandVersion);
......
elseif(argv[1][0]=='p')//命令行选项p表示我们要打包资源
bundle.setCommand(kCommandPackage);
......
argc-=2;
argv+=2;
/*
*Pulloutflags.Wesupport"-fv"and"-f-v".
*一下while循环将各种aapt编译选项提取出来存放到bundle中*/
while(argc&&argv[0][0]=='-'){
/*flag(s)found*/
constchar*cp=argv[0]+1;
while(*cp!='\0'){
switch(*cp){
......
case'f'://如果编译出来的文件已经存在,强制覆盖
bundle.setForce(true);//bundle.mForce(bool)
break;
........
case'm'://使生成的包的目录存放在-J参数指定的目录
bundle.setMakePackageDirs(true);//bundle.mMakePackageDirs(bool)
break;
......
case'A'://assert文件夹路径
argc--;
argv++;
if(!argc){
fprintf(stderr,"ERROR:Noargumentsuppliedfor'-A'option\n");
wantUsage=true;
gotobail;
}
convertPath(argv[0]);//装换为指定OS的路径
bundle.setAssetSourceDir(argv[0]);//mAssetSourceDir(constchar*)
break;
......
case'I'://某个版本平台的android.jar的路径
argc--;
argv++;
if(!argc){
fprintf(stderr,"ERROR:Noargumentsuppliedfor'-I'option\n");
wantUsage=true;
gotobail;
}
convertPath(argv[0]);
//mPackageIncludes.add(file);android::Vector<constchar*>
bundle.addPackageInclude(argv[0]);
break;
case'F'://具体指定APK文件的输出
argc--;
argv++;
if(!argc){
fprintf(stderr,"ERROR:Noargumentsuppliedfor'-F'option\n");
wantUsage=true;
gotobail;
}
convertPath(argv[0]);
//mOutputAPKFile(constchar*)
bundle.setOutputAPKFile(argv[0]);
break;
case'J'://指定生成的R.java的输出目录
argc--;
argv++;
if(!argc){
fprintf(stderr,"ERROR:Noargumentsuppliedfor'-J'option\n");
wantUsage=true;
gotobail;
}
convertPath(argv[0]);
bundle.setRClassDir(argv[0]);//mRClassDir(constchar*)
break;
case'M'://指定AndroidManifest.xml文件路径
argc--;
argv++;
if(!argc){
fprintf(stderr,"ERROR:Noargumentsuppliedfor'-M'option\n");
wantUsage=true;
gotobail;
}
convertPath(argv[0]);
//mAndroidMainifestFile(constchar*)
bundle.setAndroidManifestFile(argv[0]);
break;
......
case'S'://res文件夹路径
argc--;
argv++;
if(!argc){
fprintf(stderr,"ERROR:Noargumentsuppliedfor'-S'option\n");
wantUsage=true;
gotobail;
}
convertPath(argv[0]);
//android::Vector<constchar*>mResourceSourceDirs;
//mResourceSourceDirs.insertAt(dir,0);
bundle.addResourceSourceDir(argv[0]);
break;
......
default:
fprintf(stderr,"ERROR:Unknownflag'-%c'\n",*cp);
wantUsage=true;
gotobail;
}
cp++;
}
argc--;
argv++;
}
/*
*We'repasttheflags.Therestallgoesstraightin.
*设置Bundle的成员变量mArgv和mArgc分别为argv,argc*/
bundle.setFileSpec(argv,argc);
/*通过handleCommand函数来处理指定命令*/
result=handleCommand(&bundle);
bail:
if(wantUsage){
usage();
result=2;
}
//printf("-->returning%d\n",result);
returnresult;
}
处理完aapt的编译选项之后,接着调用handleCommand函数来处理对应的功能:
路径:frameworks/base/tools/aapt/Main.cpp
inthandleCommand(Bundle*bundle)
{
......
switch(bundle->getCommand()){
.......
casekCommandPackage:returndoPackage(bundle);
......
default:
fprintf(stderr,"%s:requestedcommandnotyetsupported\n",gProgName);
return1;
}
}
最终打包APK的工作由函数doPackage完成,而打包一个应用程序资源的过程非常复杂,我们分如下模块一一讲解:
一.收录一个应用程序所有资源文件
路径:frameworks/base/tools/aapt/Command.cpp
/*
*Packageupanassetdirectoryandassociatedapplicationfiles.
*打包应用程序中的资源文件*/
intdoPackage(Bundle*bundle)
{
constchar*outputAPKFile;
intretVal=1;
status_terr;
sp<AaptAssets>assets;
intN;
FILE*fp;
String8dependencyFile;
......
//检查aapt打包时的参数是否都存在
N=bundle->getFileSpecCount();
if(N<1&&bundle->getResourceSourceDirs().size()==0
&&bundle->getJarFiles().size()==0
&&bundle->getAndroidManifestFile()==NULL
&&bundle->getAssetSourceDir()==NULL){
fprintf(stderr,"ERROR:noinputfiles\n");
gotobail;
}
//得到最终将资源打包输出到的APK名称
outputAPKFile=bundle->getOutputAPKFile();
//Makesurethefilenamesprovidedexistandareoftheappropriatetype.
//检查该文件是否存在不存在则创建,并确定其实常规文件
if(outputAPKFile){
FileTypetype;
type=getFileType(outputAPKFile);
if(type!=kFileTypeNonexistent&&type!=kFileTypeRegular){
fprintf(stderr,
"ERROR:outputfile'%s'existsbutisnotregularfile\n",
outputAPKFile);
gotobail;
}
}
//Loadtheassets.
//创建一个AaptAssets对象
assets=newAaptAssets();
......
/*1.调用AaptAssets类的成员函数slurpFromArgs将AndroidManifest.xml文件,
**目录assets和res下的资源目录和资源文件收录起来保存到AaptAssets中的
**成员变量中*/
err=assets->slurpFromArgs(bundle);
if(err<0){
gotobail;
}
......
}
AaptAssets的slurpFromArgs函数的具体实现如下所示:
路径:frameworks/base/tools/aapt/AaptAssets.cpp
ssize_tAaptAssets::slurpFromArgs(Bundle*bundle)
{
intcount;
inttotalCount=0;
FileTypetype;
//获取res目录的路径
constVector<constchar*>&resDirs=bundle->getResourceSourceDirs();
constsize_tdirCount=resDirs.size();
sp<AaptAssets>current=this;
//获取bundle内所保存的aapt的命令选项个数,即要完成的功能个数
constintN=bundle->getFileSpecCount();
/*
*Ifapackagemanifestwasspecified,includethatfirst.
*如果bundle中指定了AndroidManifest.xml文件,则首先包含它*/
if(bundle->getAndroidManifestFile()!=NULL){
//placeatrootofzip.
String8srcFile(bundle->getAndroidManifestFile());
/*每向AaptAssets的对象中添加一个资源文件或者一个资源目录都要新建一个
**类型AaptGroupEntry的空对象并将其添加到一个类型为SortedVector的
**AaptAssets的成员变量mGroupEntries中,在这里调用addFile函数是
**将AndroidManifest.xml文件添加到成员变量mFiles中去.
*/
addFile(srcFile.getPathLeaf(),AaptGroupEntry(),srcFile.getPathDir(),
NULL,String8());
/*每添加一个资源就加1统计一次*/
totalCount++;
}
/*
*Ifadirectoryofcustomassetswassupplied,slurp'emup.
*判断是否指定了assets文件夹,如果指定则解析它*/
if(bundle->getAssetSourceDir()){
constchar*assetDir=bundle->getAssetSourceDir();//获取目录名称
FileTypetype=getFileType(assetDir);//获取目录类型
if(type==kFileTypeNonexistent){
fprintf(stderr,"ERROR:assetdirectory'%s'doesnotexist\n",assetDir);
returnUNKNOWN_ERROR;
}
if(type!=kFileTypeDirectory){
fprintf(stderr,"ERROR:'%s'isnotadirectory\n",assetDir);
returnUNKNOWN_ERROR;
}
String8assetRoot(assetDir);
/*创建一个名为”assets”的AaptDir对象*/
sp<AaptDir>assetAaptDir=makeDir(String8(kAssetDir));
AaptGroupEntrygroup;
/*调用AaptDir的成员函数slurpFullTree收录目录“assets”下的资源文件,
**并返回资源文件个数*/
count=assetAaptDir->slurpFullTree(bundle,assetRoot,group,
String8(),mFullAssetPaths);
if(count<0){
totalCount=count;
gotobail;
}
if(count>0){
mGroupEntries.add(group);
}
/*统计资源文件总个数*/
totalCount+=count;
if(bundle->getVerbose())
printf("Found%dcustomassetfile%sin%s\n",
count,(count==1)?"":"s",assetDir);
}
/*
*Ifadirectoryofresource-specificassetswassupplied,slurp'emup.
*收录指定的res资源目录下的资源文件*/
for(size_ti=0;i<dirCount;i++){
constchar*res=resDirs[i];
if(res){
type=getFileType(res);//获取文件类型
if(type==kFileTypeNonexistent){
fprintf(stderr,"ERROR:resourcedirectory'%s'doesnotexist\n",res);
returnUNKNOWN_ERROR;
}
if(type==kFileTypeDirectory){
//如果指定了多个res资源目录文件,则为其创建多个AaptAssets
//类来分别收录这些目录中的信息,并将其设置赋值给当前
//AaptAssets对象的成员变量mOverlay
if(i>0){
sp<AaptAssets>nextOverlay=newAaptAssets();
current->setOverlay(nextOverlay);
current=nextOverlay;
current->setFullResPaths(mFullResPaths);
}
//调用成员函数slurpResourceTree来收录res目录下的资源文件
count=current->slurpResourceTree(bundle,String8(res));
if(count<0){
totalCount=count;
gotobail;
}
totalCount+=count;//统计资源文件个数
}
else{
fprintf(stderr,"ERROR:'%s'isnotadirectory\n",res);
returnUNKNOWN_ERROR;
}
}
}
/*
*Nowdoanyadditionalrawfiles.
*接着收录剩余的指定的资源文件*/
for(intarg=0;arg<N;arg++){
constchar*assetDir=bundle->getFileSpecEntry(arg);
FileTypetype=getFileType(assetDir);
if(type==kFileTypeNonexistent){
fprintf(stderr,"ERROR:inputdirectory'%s'doesnotexist\n",assetDir);
returnUNKNOWN_ERROR;
}
if(type!=kFileTypeDirectory){
fprintf(stderr,"ERROR:'%s'isnotadirectory\n",assetDir);
returnUNKNOWN_ERROR;
}
String8assetRoot(assetDir);
if(bundle->getVerbose())
printf("Processingrawdir'%s'\n",(constchar*)assetDir);
/*
*Doarecursivetraversalofsubdirtree.Wedon'tmakeany
*guaranteesaboutordering,sowe'reokaywithaninordersearch
*usingwhateverordertheOShappenstohandbacktous.
*/
count=slurpFullTree(bundle,
assetRoot,AaptGroupEntry(),String8(),mFullAssetPaths);
if(count<0){
/*failure;reporterrorandremovearchive*/
totalCount=count;
gotobail;
}
totalCount+=count;
if(bundle->getVerbose())
printf("Found%dassetfile%sin%s\n",
count,(count==1)?"":"s",assetDir);
}
count=validate();
if(count!=NO_ERROR){
totalCount=count;
gotobail;
}
count=filter(bundle);
if(count!=NO_ERROR){
totalCount=count;
gotobail;
}
bail:
returntotalCount;
}
AaptAssets的成员函数addFile用来向AaptAssets的一个对象添加一个资源文件到其成员变量mFiles中去或者添加一个资源目录到其成员变量mDirs中去,注意:每一个资源文件都封装成一个AaptFile类然后用AaptGroup将这些AaptFile对象组织起来,其具体实现如下所示:
路径:frameworks/base/tools/aapt/AaptAssets.cpp
sp<AaptFile>AaptAssets::addFile(
constString8&filePath,constAaptGroupEntry&entry,
constString8&srcDir,sp<AaptGroup>*outGroup,
constString8&resType)
{
sp<AaptDir>dir=this;//AaptAssets类继承了一个AaptDir类
sp<AaptGroup>group;
sp<AaptFile>file;
String8root,remain(filePath),partialPath;
while(remain.length()>0){
//获取remain所描述文件的工作目录,如果其仅仅指定了文件名则返回文件名,
//如果文件名前添加了路径,则返回最上层的目录名
//例如,remain=“AndroidManifest.xml”,则root=“AndroidManifest.xml”,
//remain=“”;如果remain=“/rootpath/subpath/AndroidManifest.xml”,
//则,root=“rootpath”,remain=”subpath/AndroidManifest.xml”
root=remain.walkPath(&remain);
partialPath.appendPath(root);
constString8rootStr(root);
/*在这里remain.length()返回0*/
if(remain.length()==0){//添加资源文件到mFiles中去
/*dir指向当前AaptAssets对象,其调用getFiles返回类型为
**DefaultKeyVector<String8,sp<AaptGroup>>成员变量mFiles,判断其内部
**是否包含了名称为rootStr的AaptGroup对象,并返回其位置值*/
ssize_ti=dir->getFiles().indexOfKey(rootStr);
/*如果返回的位置值>=0表示mFiles中已经包含了这个名为rootStr的
**AaptGroup对象,则将group指向该对象,否则新建一个名称为rootStr
**的AaptGroup对象并添加到mFiles中去*/
if(i>=0){
group=dir->getFiles().valueAt(i);
}else{
group=newAaptGroup(rootStr,filePath);
status_tres=dir->addFile(rootStr,group);
if(res!=NO_ERROR){
returnNULL;
}
}
//新建一个AaptFile对象指向需要添加的源文件,并将该AaptFile对象
//添加到类型为DefaultKeyedVector<AaptGroupEntry,sp<AaptFile>>的
//AaptGroup的成员变量mFiles中去
file=newAaptFile(srcDir.appendPathCopy(filePath),entry,resType);
status_tres=group->addFile(file);
if(res!=NO_ERROR){
returnNULL;
}
break;
}else{//添加资源目录到mDirs中去
/*dir指向当前AaptAssets对象,其调用getDirs返回类型为
**DefaultKeyVector<String8,sp<AaptDir>>成员变量mDirs,判断其内部
**是否包含了名称为rootStr的AaptDir对象,并返回其位置值*/
ssize_ti=dir->getDirs().indexOfKey(rootStr);
/*如果返回的位置值>=0表示mDirs中已经包含了这个名为rootStr的
**AaptDir对象,则将dir指向该对象,否则新建一个名称为rootStr
**的AaptDir对象并添加到mDirs中去*/
if(i>=0){
dir=dir->getDirs().valueAt(i);
}else{
sp<AaptDir>subdir=newAaptDir(rootStr,partialPath);
status_tres=dir->addDir(rootStr,subdir);
if(res!=NO_ERROR){
returnNULL;
}
dir=subdir;
}
}
}
/*将一个空的AaptGroupEntry对象添加到mGroupEntries中去,其是一个SortedVector
*/
mGroupEntries.add(entry);
if(outGroup)*outGroup=group;
returnfile;
}
AaptAssets的成员函数slurpFullTree将会收录路径名为srcDir目录下的所有资源文件,并将对应目录下的文件名都保存到fullResPaths中去,其具体实现如下所示:
路径:frameworks/base/tools/aapt/AaptAssets.cpp
ssize_tAaptAssets::slurpFullTree(Bundle*bundle,constString8&srcDir,
constAaptGroupEntry&kind,
constString8&resType,
sp<FilePathStore>&fullResPaths)
{
/*接着调用父类中的AaptDir的成员函数slurpFullTree收录srcDir中的
**资源文件*/
ssize_tres=AaptDir::slurpFullTree(bundle,srcDir,kind,resType,fullResPaths);
/*如果收录的资源个数>0,则将其归为一类,为这类资源文件创建一个对应
**AaptGroupEntry对象并添加到对应的成员变量mGroupEntries中去*/
if(res>0){
mGroupEntries.add(kind);
}
returnres;
}
ssize_tAaptDir::slurpFullTree(Bundle*bundle,constString8&srcDir,
constAaptGroupEntry&kind,constString8&resType,
sp<FilePathStore>&fullResPaths)
{
Vector<String8>fileNames;
{
DIR*dir=NULL;
/*首先打开将要收录的资源文件所在的源目录*/
dir=opendir(srcDir.string());
if(dir==NULL){
fprintf(stderr,"ERROR:opendir(%s):%s\n",srcDir.string(),strerror(errno));
returnUNKNOWN_ERROR;
}
/*
*Slurpthefilenamesoutofthedirectory.
*遍历srcDir目录下的每一个资源文件,将其添加到AaptAssets的成员变量
*mFullAssetPaths中,其继承了一个Vector<String8>*/
while(1){
structdirent*entry;
entry=readdir(dir);
if(entry==NULL)
break;
if(isHidden(srcDir.string(),entry->d_name))
continue;
String8name(entry->d_name);
fileNames.add(name);
//Addfullyqualifiedpathfordependencypurposes
//ifwe'recollectingthem
//按照全部路径将资源文件添加到fullResPaths中去
if(fullResPaths!=NULL){
fullResPaths->add(srcDir.appendPathCopy(name));
}
}
closedir(dir);
}
ssize_tcount=0;
/*
*Stashawaythefilesandrecursivelydescendintosubdirectories.
*递归解析srcDir下的子目录中的资源文件,指导收录完所有的
*目录中的资源文件为止*/
constsize_tN=fileNames.size();
size_ti;
for(i=0;i<N;i++){
String8pathName(srcDir);
FileTypetype;
pathName.appendPath(fileNames[i].string());
type=getFileType(pathName.string());
/*如果是资源子目录,并且其尚未收录在mDirs中,则为其创建一个
**AaptDir对象,继续递归遍历其中的资源文件及目录*/
if(type==kFileTypeDirectory){
sp<AaptDir>subdir;
boolnotAdded=false;
/*如果*/
if(mDirs.indexOfKey(fileNames[i])>=0){
subdir=mDirs.valueFor(fileNames[i]);
}else{
subdir=
newAaptDir(fileNames[i],mPath.appendPathCopy(fileNames[i]));
notAdded=true;
}
ssize_tres=subdir->slurpFullTree(bundle,pathName,kind,
resType,fullResPaths);
if(res<NO_ERROR){
returnres;
}
if(res>0&¬Added){
mDirs.add(fileNames[i],subdir);//将资源目录添加到mDirs变量中
}
count+=res;
/*如果其为一个资源文件,则为其创建一个指定的AaptFile变量
**并为其创建一个对应的AaptGroup变量,将这个AaptGroup变量添加
**到mFiles变量中,然后将AaptFile变量添加到AaptGroup中去*/
}elseif(type==kFileTypeRegular){
sp<AaptFile>file=newAaptFile(pathName,kind,resType);
status_terr=addLeafFile(fileNames[i],file);
if(err!=NO_ERROR){
returnerr;
}
count++;
}else{
if(bundle->getVerbose())
printf("(ignoringnon-file/dir'%s')\n",pathName.string());
}
}
/*返回总的资源文件个数*/
returncount;
}
AaptAssets的成员函数slurpResourceTree将会收录路径名为srcDir目录下的所有资源文件,该函数具体实现如下所示:
路径:frameworks/base/tools/aapt/AaptAssets.cpp
ssize_tAaptAssets::slurpResourceTree(Bundle*bundle,constString8&srcDir)
{
ssize_terr=0;
/*打开资源文件夹*/
DIR*dir=opendir(srcDir.string());
if(dir==NULL){
fprintf(stderr,"ERROR:opendir(%s):%s\n",srcDir.string(),strerror(errno));
returnUNKNOWN_ERROR;
}
status_tcount=0;
/*
*Runthroughthedirectory,lookingfordirsthatmatchthe
*expectedpattern.
*递归遍历对应的资源文件夹*/
while(1){
structdirent*entry=readdir(dir);
if(entry==NULL){
break;
}
if(isHidden(srcDir.string(),entry->d_name)){
continue;
}
String8subdirName(srcDir);
subdirName.appendPath(entry->d_name);
AaptGroupEntrygroup;
String8resType;
/*调用AaptGroupEntry类的initFromDirName函数来归类子目录下的资源文件
**并将对应的资源文件类型通过resType返回
**按照这样的顺序:mcc,mnc,loc,layoutsize,layoutlong,orient,den,touch,
**key,keysHidden,nav,navHidden,size,vers,uiModeType,uiModeNight,
**smallestwidthdp,widthdp,heightdp收录对应的资源文件名称将其分类
**保存到group变量中,这个函数很简单就不具体分析*/
boolb=group.initFromDirName(entry->d_name,&resType);
if(!b){
fprintf(stderr,"invalidresourcedirectoryname:%s/%s\n",srcDir.string(),
entry->d_name);
err=-1;
continue;
}
......
FileTypetype=getFileType(subdirName.string());
/*如果是一个子目录文件,则为其创建一个对应的AaptDir对象,并调用
**该对象的成员函数slurpFullTree收录该子目录下的所有资源文件*/
if(type==kFileTypeDirectory){
sp<AaptDir>dir=makeDir(resType);
ssize_tres=dir->slurpFullTree(bundle,subdirName,group,
resType,mFullResPaths);
if(res<0){
count=res;
gotobail;
}
if(res>0){
mGroupEntries.add(group);
count+=res;
}
//Onlyaddthisdirectoryifwedon'talreadyhavearesourcedir
//forthecurrenttype.Thisensuresthatweonlyaddthedironce
//forallconfigs.
//判断是否添加过对应的资源目录到成员变量mResDirs中了
//如果没有添加过则将其添加进去
sp<AaptDir>rdir=resDir(resType);
if(rdir==NULL){
mResDirs.add(dir);
}
}else{
if(bundle->getVerbose()){
fprintf(stderr,"(ignoringfile'%s')\n",subdirName.string());
}
}
}
bail:
closedir(dir);
dir=NULL;
if(err!=0){
returnerr;
}
returncount;
}
二.编译AndroidManifest.xml文件和res目录下资源文件
以上通过AaptAssets类的成员函数slurpFromArgs将一个应用程序中的所有资源文件收录完成以后,接下来就要调用buildResources函数编译AndroidManifest.xml文件以及res目录下的资源文件,这些资源文件的路径等详细信息保存在AaptAssets对象assets中,具体实现如下所示:
路径:frameworks/base/tools/aapt/Command.cpp
/*
*Packageupanassetdirectoryandassociatedapplicationfiles.
*打包应用程序中的资源文件*/
intdoPackage(Bundle*bundle)
{
constchar*outputAPKFile;
intretVal=1;
status_terr;
sp<AaptAssets>assets;
intN;
FILE*fp;
String8dependencyFile;
......
//IftheyaskedforanyfileAsthatneedtobecompiled,doso.
//编译res目录下资源文件以及AndroidManifest.xml文件
if(bundle->getResourceSourceDirs().size()||bundle->getAndroidManifestFile()){
err=buildResources(bundle,assets);
if(err!=0){
gotobail;
}
}
......
}
将文本xml资源文件编译成二进制资源文件的方法buildResources函数的具体实现如下所示,由于这个函数非常长,所以我们分开来对其一一进行解析:
1.编译AndroidManifest.xml文件
路径:frameworks/base/tools/aapt/Resource.cpp
status_tbuildResources(Bundle*bundle,constsp<AaptAssets>&assets)
{
//First,lookforapackagefiletoparse.Thisisrequiredto
//beabletogeneratetheresourceinformation.
//首先从assets中获取AndroidManifest.xml文件的信息
//AndroidManifest.xml文件信息是保存在assets的成员变量mFiles中的,
//但是其被封装成一个AaptFile类对象保存在AaptGroup对象最终再
//保存到mFiles中的
sp<AaptGroup>androidManifestFile=
assets->getFiles().valueFor(String8("AndroidManifest.xml"));
if(androidManifestFile==NULL){
fprintf(stderr,"ERROR:NoAndroidManifest.xmlfilefound.\n");
returnUNKNOWN_ERROR;
}
//调用parsePackage函数解析AndroidManifest.xml文件
status_terr=parsePackage(bundle,assets,androidManifestFile);
if(err!=NO_ERROR){
returnerr;
}
......
}
parsePackage函数的具体实现如下所示,这个解析过程我们对应着AndroidManifest.xml文件看,具体实现如下所示:
路径:frameworks/base/tools/aapt/Resource.cpp
staticstatus_tparsePackage(Bundle*bundle,constsp<AaptAssets>&assets,
constsp<AaptGroup>&grp)
{
//以下代码确保只有一个AndroidManifest.xml文件
if(grp->getFiles().size()!=1){
fprintf(stderr,"warning:MultipleAndroidManifest.xmlfilesfound,using%s\n",
grp->getFiles().valueAt(0)->getPrintableSource().string());
}
//取出存放AndroidManifest.xml文件信息的AaptFile对象
sp<AaptFile>file=grp->getFiles().valueAt(0);
//定义一个ResXMLTree对象,然后调用parseXMLResource来详细解析
//AndroidManifest.xml文件
//这个函数主要完成三个工作:
//1.收集file文件指向的xml文件中字符串资源信息.
//2.压平该file文件只想的xml文件中资源信息
//3.将前两步组织的到的资源信息最终组织到一个ResXMLTree所描述的数据结构中.
ResXMLTreeblock;
status_terr=parseXMLResource(file,&block);
if(err!=NO_ERROR){
returnerr;
}
//printXMLBlock(&block);
ResXMLTree::event_code_tcode;
while((code=block.next())!=ResXMLTree::START_TAG
&&code!=ResXMLTree::END_DOCUMENT
&&code!=ResXMLTree::BAD_DOCUMENT){
}
size_tlen;
if(code!=ResXMLTree::START_TAG){
fprintf(stderr,"%s:%d:Nostarttagfound\n",
file->getPrintableSource().string(),block.getLineNumber());
returnUNKNOWN_ERROR;
}
if(strcmp16(block.getElementName(&len),String16("manifest").string())!=0){
fprintf(stderr,"%s:%d:Invalidstarttag%s,expected<manifest>\n",
file->getPrintableSource().string(),block.getLineNumber(),
String8(block.getElementName(&len)).string());
returnUNKNOWN_ERROR;
}
ssize_tnameIndex=block.indexOfAttribute(NULL,"package");
if(nameIndex<0){
fprintf(stderr,"%s:%d:<manifest>doesnothavepackageattribute.\n",
file->getPrintableSource().string(),block.getLineNumber());
returnUNKNOWN_ERROR;
}
assets->setPackage(String8(block.getAttributeStringValue(nameIndex,&len)));
String16uses_sdk16("uses-sdk");
while((code=block.next())!=ResXMLTree::END_DOCUMENT
&&code!=ResXMLTree::BAD_DOCUMENT){
if(code==ResXMLTree::START_TAG){
if(strcmp16(block.getElementName(&len),uses_sdk16.string())==0){
ssize_tminSdkIndex=
block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE,
"minSdkVersion");
if(minSdkIndex>=0){
constuint16_t*minSdk16=
block.getAttributeStringValue(minSdkIndex,&len);
constchar*minSdk8=strdup(String8(minSdk16).string());
bundle->setManifestMinSdkVersion(minSdk8);
}
}
}
}
returnNO_ERROR;
}
parseXMLResource函数的具体实现如下所示,下面我们就一一解析其是如何解析一个XML资源文件的:
路径:frameworks/base/tools/aapt/XMLNode.cpp
status_tparseXMLResource(constsp<AaptFile>&file,ResXMLTree*outTree,
boolstripAll,boolkeepComments,
constchar**cDataTags)
{
/*接着调用XMLNode的成员函数parse来解析AndroidManifest.xml文件*/
sp<XMLNode>root=XMLNode::parse(file);
if(root==NULL){
returnUNKNOWN_ERROR;
}
root->removeWhitespace(stripAll,cDataTags);
/*新建一个AaptFile作为输出文件*/
sp<AaptFile>rsc=newAaptFile(String8(),AaptGroupEntry(),String8());
/*调用flatten函数压平AndroidManifest.xml文件,将压平后的xml文件信息按指定
**格式组织在rsc的数据缓冲区中*/
status_terr=root->flatten(rsc,!keepComments,false);
if(err!=NO_ERROR){
returnerr;
}
err=outTree->setTo(rsc->getData(),rsc->getSize(),true);
if(err!=NO_ERROR){
returnerr;
}
returnNO_ERROR;
}
(1).收集XML文本文件信息
收集AdaptFile对象file所指向的xml文本文件中信息,将其组织在一个以XMLNode对象root为根的树中
sp<XMLNode>XMLNode::parse(constsp<AaptFile>&file)
{
charbuf[16384];
/*以只读方式打开AndroidManifest.xml文件*/
intfd=open(file->getSourceFile().string(),O_RDONLY|O_BINARY);
if(fd<0){
SourcePos(file->getSourceFile(),-1).error("Unabletoopenfileforread:%s",
strerror(errno));
returnNULL;
}
/*创建一个XML文件解析器,该解析器是定义在expat库中的
**Expat是一个用C语言开发的、用来解析XML文档的开发库,它最初是开源的、
**Mozilla项目下的一个XML解析器。采用流的方式来解析XML文件,并且基于
**事件通知型来调用分析到的数据,并不需要把所有XML文件全部加载到内存里,
**这样可以分析非常大的XML文件。*/
/*1.创建一个XML分析器。*/
XML_Parserparser=XML_ParserCreateNS(NULL,1);
ParseStatestate;
state.filename=file->getPrintableSource();//制定文件名称为AndroidManifest.xml
state.parser=parser;
XML_SetUserData(parser,&state);//设置用户数据
/*2.第一个参数是那个Parser句柄,第二个和第三个参数则是整个Parser的核心,
**类型为CallBack的函数
*/
XML_SetElementHandler(parser,startElement,endElement);
/*startNamespace:解析xmlns:android开头的信息:
**参数:prefix=android,uri=android右边的属性值信息
**"http://schemas.android.com/apk/res/android"
**endNamespace-销毁ParseState中缓存的数据
**这个特殊的xmlns:android="http://schemas.android.com/apk/res/android"
**属性名和属性值会创建一个XMLNode作为根节点,其也叫做命名空间
**解析命名空间和标签元素类似,就不再赘述*/
XML_SetNamespaceDeclHandler(parser,startNamespace,endNamespace);
/*函数是设置处理一个<>和</>之间的字段的回调
**<Item>Thisisanormaltext</Item>
**那么字符串“Thisisanormaltext”就称为一个CDATA*/
XML_SetCharacterDataHandler(parser,characterData);
/*处理注释的函数*/
XML_SetCommentHandler(parser,commentData);
ssize_tlen;
booldone;
do{
len=read(fd,buf,sizeof(buf));
done=len<(ssize_t)sizeof(buf);
if(len<0){
close(fd);
returnNULL;
}
/*第二个参数是用户指定的Buffer指针,第三个是这块Buffer中实际内容的
**字节数,最后参数代表是否这块Buffer已经结束。比如要解析的XML文件太大,
**但内存比较吃紧,Buffer比较小,则可以循环读取文件,然后丢给Parser,
**在文件读取结束前,isFinal参数为FALSE,反之为TRUE。*/
if(XML_Parse(parser,buf,len,done)==XML_STATUS_ERROR){
close(fd);
returnNULL;
}
}while(!done);
XML_ParserFree(parser);//销毁一个解析器
if(state.root==NULL){
SourcePos(file->getSourceFile(),-1).error("NoXMLdatageneratedwhenparsing");
}
close(fd);
returnstate.root;
}
/*startElement解析:<>或者</>中的信息,
**参数:name为标签名,atts-从左到右将保存=两边属性名和属性值.
**endElement解析:</>中的信息*/
voidXMLCALL
XMLNode::startElement(void*userData,constchar*name,constchar**atts)
{
NOISY_PARSE(printf("StartElement:%s\n",name));
printf("StartElement:%s\n",name);
ParseState*st=(ParseState*)userData;
String16ns16,name16;
splitName(name,&ns16,&name16);
/*为每一个名称为name的标签创建一个XMLNode对象*/
sp<XMLNode>node=XMLNode::newElement(st->filename,ns16,name16);
/*设置标签开始的行号*/
node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser));
if(st->pendingComment.size()>0){
node->appendComment(st->pendingComment);
st->pendingComment=String16();
}
if(st->stack.size()>0){
/**而name子标签作为name标签XMLNode对象的一个
**子对象保存到XMLNode的成员变量mChildren(Vector)中,而ParseState
**只是用于缓存XMLNode数据信息用,缓存完成之后随即在endElement函数
**中销毁.
st->stack.itemAt(st->stack.size()-1)->addChild(node);
}else{
st->root=node;//根节点是命名空间节点
}
st->stack.push(node);//缓存
for(inti=0;atts[i];i+=2){
splitName(atts[i],&ns16,&name16);
printf("attrs:%s=%s\n",atts[i],atts[i+1]);
node->addAttribute(ns16,name16,String16(atts[i+1]));
}
}
voidXMLCALL
XMLNode::endElement(void*userData,constchar*name)
{
NOISY_PARSE(printf("EndElement:%s\n",name));
printf("EndElement:%s\n",name);
ParseState*st=(ParseState*)userData;
sp<XMLNode>node=st->stack.itemAt(st->stack.size()-1);
/*设置标签结束行号*/
node->setEndLineNumber(XML_GetCurrentLineNumber(st->parser));
if(st->pendingComment.size()>0){
node->appendComment(st->pendingComment);
st->pendingComment=String16();
}
String16ns16,name16;
splitName(name,&ns16,&name16);
st->stack.pop();
}
/*成员函数addAttribute将所有属性名和属性值的信息添加到其保存属性信息的
**成员变量mAttributes和mAttributeOrder中,每一个属性的信息会被保存在一个
**attribute_entry的结构体中,默认为每一个属性所分配的资源ID是0.
*/
status_tXMLNode::addAttribute(constString16&ns,constString16&name,
constString16&value)
{
if(getType()==TYPE_CDATA){
SourcePos(mFilename,getStartLineNumber()).error("ChildtoCDATAnode.");
returnUNKNOWN_ERROR;
}
if(ns!=RESOURCES_TOOLS_NAMESPACE){
attribute_entrye;
e.index=mNextAttributeIndex++;
e.ns=ns;
e.name=name;//属性名
e.string=value;//属性值
mAttributes.add(e);
mAttributeOrder.add(e.index,mAttributes.size()-1);
}
returnNO_ERROR;
}
(2).压平收集完字符串信息的XML文本文件
至此,解析完一个xml文本文件之后接着调用XMLNode成员函数flatten来压平该文件,下面我们一一解析这个flatten过程:
路径:frameworks/base/tools/aapt/XMLNode.cpp
status_tXMLNode::flatten(constsp<AaptFile>&dest,
boolstripComments,boolstripRawValues)const
{
/*创建一个字符串池StringPool变量strings保存属性名称字符串*/
StringPoolstrings(mUTF8);
Vector<uint32_t>resids;//保存属性名的资源ID号
//Firstcollectjustthestringsforattributenamesthathavea
//resourceIDassignedtothem.ThisensuresthattheresourceID
//arrayiscompact,andmakesiteasiertodealwithattributenames
//indifferentnamespaces(andthuswithdifferentresourceIDs).
//首先收集属性名字符串,这些字符串有一个资源ID指向它们.
//这确保资源ID数组紧凑的,并且不同于命名空间的资源ID,
//这使得处理属性名变得更简单
//注意:在这里实际上什么工作都没有做!!FUCK
collect_resid_strings(&strings,&resids);
//Nextcollectallremainibngstrings.
//真正的收集工作在这里才工作,上面什么工作都没做还递归遍历半天
collect_strings(&strings,&resids,stripComments,stripRawValues);
......
}
/*该函数从指向命名空间的rootXMLNode开始递归遍历收集每一个标签(
**一个标签中的属性信息保存在一个XMLNode变量中)的属性名和ID
*/
status_tXMLNode::collect_resid_strings(StringPool*outPool,
Vector<uint32_t>*outResIds)const
{
/*真正收集XML文件中一个标签的属性名称和其ID号的工作
**在函数collect_attr_strings中完成,这里不工作*/
collect_attr_strings(outPool,outResIds,false);
constintNC=mChildren.size();
for(inti=0;i<NC;i++){
mChildren.itemAt(i)->collect_resid_strings(outPool,outResIds);
}
returnNO_ERROR;
}
status_tXMLNode::collect_strings(StringPool*dest,Vector<uint32_t>*outResIds,
boolstripComments,boolstripRawValues)const
{
/*真正收集XML文件中一个标签的属性名称和其ID号的工作
**在函数collect_attr_strings中完成,这里工作*/
collect_attr_strings(dest,outResIds,true);
/*下列代码在收集完属性名称之后,接着将对应的其它信息按照
**一定的顺序收集保存到字符串资源池*/
inti;
if(RESOURCES_TOOLS_NAMESPACE!=mNamespaceUri){
if(mNamespacePrefix.size()>0){
dest->add(mNamespacePrefix,true);
}
if(mNamespaceUri.size()>0){
dest->add(mNamespaceUri,true);
}
}
if(mElementName.size()>0){
dest->add(mElementName,true);
}
if(!stripComments&&mComment.size()>0){
dest->add(mComment,true);
}
constintNA=mAttributes.size();
for(i=0;i<NA;i++){
constattribute_entry&ae=mAttributes.itemAt(i);
if(ae.ns.size()>0){
dest->add(ae.ns,true);
}
if(!stripRawValues||ae.needStringValue()){
dest->add(ae.string,true);
}
/*
if(ae.value.dataType==Res_value::TYPE_NULL
||ae.value.dataType==Res_value::TYPE_STRING){
dest->add(ae.string,true);
}
*/
}
if(mElementName.size()==0){
//Ifnotanelement,includetheCDATA,evenifitisempty.
dest->add(mChars,true);
}
constintNC=mChildren.size();
for(i=0;i<NC;i++){
mChildren.itemAt(i)->collect_strings(dest,outResIds,
stripComments,stripRawValues);
}
returnNO_ERROR;
}
/*收集XML文件中一个标签中的属性名称及其ID分别保存到字符串资源池和ID池中*/
status_tXMLNode::collect_attr_strings(StringPool*outPool,
Vector<uint32_t>*outResIds,boolallAttrs)const{
/*获取属性个数*/
constintNA=mAttributes.size();
for(inti=0;i<NA;i++){
/*每一个属性使用一个attribute_entry结构体来表示*/
constattribute_entry&attr=mAttributes.itemAt(i);
//获取资源属性ID,默认的资源属性ID为0
//故,在这里该函数什么工作都没有做
uint32_tid=attr.nameResId;
if(id||allAttrs){
//SeeifwehavealreadyassignedthisresourceIDtoapooled
//string...
//检测在字符串池中是否已经收集到了指定属性名称的字符串
constVector<size_t>*indices=outPool->offsetsForString(attr.name);
ssize_tidx=-1;
if(indices!=NULL){
constintNJ=indices->size();
constsize_tNR=outResIds->size();
for(intj=0;j<NJ;j++){
size_tstrIdx=indices->itemAt(j);
if(strIdx>=NR){
if(id==0){
//Wedon'tneedtoassignaresourceIDforthisone.
idx=strIdx;
break;
}
//Justignorestringsthatareoutofrangeof
//thecurrentlyassignedresourceIDs...weadd
//stringsasweassignthefirstID.
}elseif(outResIds->itemAt(strIdx)==id){
idx=strIdx;
break;
}
}
}
if(idx<0){
//尚未将指定的属性名添加到字符串资源池中,如果add函数后面跟随
//着描述字符串属性的entry_style_span的Vector则将字符串属性一并
//加入,并返回其在字符串资源池中位置
idx=outPool->add(attr.name);
//判断是否为属性名分配过资源ID
if(id!=0){
/*确保属性名资源ID与属性名对应*/
while((ssize_t)outResIds->size()<=idx){
outResIds->add(0);
}
//替换原有资源ID
outResIds->replaceAt(id,idx);
}
}
attr.namePoolIdx=idx;
}
}
returnNO_ERROR;
}
为收集到的字符串信息分配字符串资源池并将收集到的信息按照指定格式组织起来,在介绍字符串缓冲块之前,我们首先了解下StringPool的几个重要的成员变量的含义:
//Thefollowingdatastructuresrepresenttheactualstructures
//thatwillbegeneratedforthefinalstringpool.
//Rawarrayofuniquestrings,insomearbitraryorder.Thisisthe
//actualstringsthatappearinthefinalstringpool,intheorder
//thattheywillbewritten.
//mEntries用于保存属性字符串信息,每一个字符串都会被封装成一个
//structentry,这个结构体保存了这个字符串的各种配置属性,每次将
//一个字符串信息add到StringPool中时都要将其封装成一个entry
//然后add到mEntries中
//注意:每一个entry结构体的indices这个Vector中保存着这个字符串在
//mValues中的位置值
Vector<entry>mEntries;
//ArrayofindicesintomEntries,intheordertheywere
//addedtothepool.ThiscanbedifferentthanmEntries
//ifthesamestringwasaddedmultipletimes(itwillappear
//onceinmEntries,withmultipleoccurrencesinthisarray).
//Thisisthelookuparraythatwillbewrittenforfinding
//thestringforeachoffset/positioninthestringpool.
//mEntryArray中保存着某一个字符串在mEntries中的位置
Vector<size_t>mEntryArray;
//Optionalstylespaninformationassociatedwitheachindexof
//mEntryArray.
//mEntryStyleArray中保存着mValues中的字符串的样式,其跟
//mValues中的字符串是一一对应的
Vector<entry_style>mEntryStyleArray;
//Thefollowingdatastructuresareusedforbook-keepingasthe
//stringpoolisconstructed.
//Uniquesetofallthestringsaddedtothepool,mappedto
//thefirstindexofmEntryArraywherethevaluewasadded.
//mValues保存着一个字符串的值以及其在mValues中的位置值
DefaultKeyedVector<String16,ssize_t>mValues;
下面我们详细介绍是如何为一个StringPool创建一个StringBlock来组织存储在其中的字符串信息的,具体实现如下所示:
路径:frameworks/base/tools/aapt/XMLNode.cpp
status_tXMLNode::flatten(constsp<AaptFile>&dest,
boolstripComments,boolstripRawValues)const
{
StringPoolstrings(mUTF8);
Vector<uint32_t>resids;
......
/*收集到的属性信息都保存在strings所指向的字符串资源池
**现在为这些字符串资源池中的属性信息分配字符串块*/
sp<AaptFile>stringPool=strings.createStringBlock();
......
}
路径:frameworks/base/tools/aapt/StringPool.cpp
sp<AaptFile>StringPool::createStringBlock()
{
/*首先创建一个匿名AaptFile类对象*/
sp<AaptFile>pool=newAaptFile(String8(),AaptGroupEntry(),
String8());
/*调用writeStringBlock来创建字符串块*/
status_terr=writeStringBlock(pool);
returnerr==NO_ERROR?pool:NULL;
}
status_tStringPool::writeStringBlock(constsp<AaptFile>&pool)
{
......
//Firstweneedtoaddallstylespannamestothestringpool.
//Wedothisnow(insteadofwhenthespanisadded)sothatthese
//willappearattheendofthepool,notdisruptingtheorder
//ourclientplacedtheirownstringsinit.
/*计算存储字符串样式的Vector大小及其设置一些成员变量*/
constsize_tSTYLES=mEntryStyleArray.size();
size_ti;
for(i=0;i<STYLES;i++){
entry_style&style=mEntryStyleArray.editItemAt(i);
constsize_tN=style.spans.size();
for(size_ti=0;i<N;i++){
entry_style_span&span=style.spans.editItemAt(i);
ssize_tidx=add(span.name,true);
if(idx<0){
fprintf(stderr,"Erroraddingspanforstyletag'%s'\n",
String8(span.name).string());
returnidx;
}
span.span.name.index=(uint32_t)idx;
}
}
/*计算mEntryArray的大小*/
constsize_tENTRIES=mEntryArray.size();
//Nowbuildthepoolofuniquestrings.
/*计算mEntries的大小,也就是字符串个数*/
constsize_tSTRINGS=mEntries.size();
/*计算出预分配内存缓冲区大小*/
constsize_tpreSize=sizeof(ResStringPool_header)//header
+(sizeof(uint32_t)*ENTRIES)//字符串偏移数组
+(sizeof(uint32_t)*STYLES);//格式偏移数组
/*调用AaptFile的成员函数editData函数分配指定大小的内存缓冲区*/
if(pool->editData(preSize)==NULL){
fprintf(stderr,"ERROR:Outofmemoryforstringpool\n");
returnNO_MEMORY;
}
/*判断字符格式*/
constsize_tcharSize=mUTF8?sizeof(uint8_t):sizeof(char16_t);
/*下列代码详细计算没一个字符串大小并为其分配内存空间并将StringPool中的
**字符串按照二进制格式拷贝到StringBlock中为其分配的空间中*/
size_tstrPos=0;
for(i=0;i<STRINGS;i++){
entry&ent=mEntries.editItemAt(i);
constsize_tstrSize=(ent.value.size());
constsize_tlenSize=strSize>(size_t)(1<<((charSize*8)-1))-1?
charSize*2:charSize;
String8encStr;
if(mUTF8){
encStr=String8(ent.value);
}
constsize_tencSize=mUTF8?encStr.size():0;
constsize_tencLenSize=mUTF8?
(encSize>(size_t)(1<<((charSize*8)-1))-1?
charSize*2:charSize):0;
ent.offset=strPos;//计算字符串偏移量,以文件开头偏移量为0,以此类推
constsize_ttotalSize=lenSize+encLenSize+
((mUTF8?encSize:strSize)+1)*charSize;
void*dat=(void*)pool->editData(preSize+strPos+totalSize);
if(dat==NULL){
fprintf(stderr,"ERROR:Outofmemoryforstringpool\n");
returnNO_MEMORY;
}
dat=(uint8_t*)dat+preSize+strPos;
if(mUTF8){
uint8_t*strings=(uint8_t*)dat;
ENCODE_LENGTH(strings,sizeof(uint8_t),strSize)
ENCODE_LENGTH(strings,sizeof(uint8_t),encSize)
strncpy((char*)strings,encStr,encSize+1);
}else{
uint16_t*strings=(uint16_t*)dat;
ENCODE_LENGTH(strings,sizeof(uint16_t),strSize)
strcpy16_htod(strings,ent.value);
}
strPos+=totalSize;
}
//Padendingstringpositionuptoauint32_tboundary.
//将StringBlock内存对齐
if(strPos&0x3){
size_tpadPos=((strPos+3)&~0x3);
uint8_t*dat=(uint8_t*)pool->editData(preSize+padPos);
if(dat==NULL){
fprintf(stderr,"ERROR:Outofmemorypaddingstringpool\n");
returnNO_MEMORY;
}
memset(dat+preSize+strPos,0,padPos-strPos);
strPos=padPos;
}
//Buildthepoolofstylespans.
//在随后的StringBlock中将字符串格式按照二进制格式存储起来
size_tstyPos=strPos;
for(i=0;i<STYLES;i++){
entry_style&ent=mEntryStyleArray.editItemAt(i);
constsize_tN=ent.spans.size();
constsize_ttotalSize=(N*sizeof(ResStringPool_span))
+sizeof(ResStringPool_ref);
ent.offset=styPos-strPos;
uint8_t*dat=(uint8_t*)pool->editData(preSize+styPos+totalSize);
if(dat==NULL){
fprintf(stderr,"ERROR:Outofmemoryforstringstyles\n");
returnNO_MEMORY;
}
/*字符串样式在StringBlock中是组装成一个ResStringPoll_span类型存储起来*/
ResStringPool_span*span=(ResStringPool_span*)(dat+preSize+styPos);
for(size_ti=0;i<N;i++){
span->name.index=htodl(ent.spans[i].span.name.index);
span->firstChar=htodl(ent.spans[i].span.firstChar);
span->lastChar=htodl(ent.spans[i].span.lastChar);
span++;
}
/*写入每个字符串样式结尾符*/
span->name.index=htodl(ResStringPool_span::END);
styPos+=totalSize;
}
/*在StringBlock中写入样式结束符号*/
if(STYLES>0){
//Addfullterminatorattheend(whenreadingwevalidatethat
//theendofthepoolisfullyterminatedtosimplifyerror
//checking).
size_textra=sizeof(ResStringPool_span)-sizeof(ResStringPool_ref);
uint8_t*dat=(uint8_t*)pool->editData(preSize+styPos+extra);
if(dat==NULL){
fprintf(stderr,"ERROR:Outofmemoryforstringstyles\n");
returnNO_MEMORY;
}
uint32_t*p=(uint32_t*)(dat+preSize+styPos);
while(extra>0){
*p++=htodl(ResStringPool_span::END);
extra-=sizeof(uint32_t);
}
styPos+=extra;
}
//Writeheader.
//字符串缓冲区开始位置写入一个记录字符串缓冲区信息的ResStringPool_header
ResStringPool_header*header=
(ResStringPool_header*)pool->padData(sizeof(uint32_t));
if(header==NULL){
fprintf(stderr,"ERROR:Outofmemoryforstringpool\n");
returnNO_MEMORY;
}
memset(header,0,sizeof(*header));
header->header.type=htods(RES_STRING_POOL_TYPE);
header->header.headerSize=htods(sizeof(*header));
header->header.size=htodl(pool->getSize());
header->stringCount=htodl(ENTRIES);
header->styleCount=htodl(STYLES);
if(mUTF8){
header->flags|=htodl(ResStringPool_header::UTF8_FLAG);
}
header->stringsStart=htodl(preSize);
header->stylesStart=htodl(STYLES>0?(preSize+strPos):0);
//Writestringindexarray.
//写入字符串偏移数组
uint32_t*index=(uint32_t*)(header+1);
for(i=0;i<ENTRIES;i++){
entry&ent=mEntries.editItemAt(mEntryArray[i]);
*index++=htodl(ent.offset);
}
//Writestyleindexarray.
//写入字符串样式偏移数组写入
for(i=0;i<STYLES;i++){
*index++=htodl(mEntryStyleArray[i].offset);
}
returnNO_ERROR;
}
至此,我们就将收集在StringPool中的xml文本文件的信息,按照指定组织方式以二进制的存储方式存储在一块内存块中,接下来我们继续分析xml文本文件是如何被flatten的,其具体实现如下所示:
路径:frameworks/base/tools/aapt/XMLNode.cpp
status_tXMLNode::flatten(constsp<AaptFile>&dest,
boolstripComments,boolstripRawValues)const
{
StringPoolstrings(mUTF8);
Vector<uint32_t>resids;
......
/*收集到的属性信息都保存在strings所指向的字符串资源池
**现在为这些字符串资源池中的属性信息分配字符串块
*/
sp<AaptFile>stringPool=strings.createStringBlock();
/*接着创建一个ResXMLTree_header*/
ResXMLTree_headerheader;
memset(&header,0,sizeof(header));
header.header.type=htods(RES_XML_TYPE);
header.header.headerSize=htods(sizeof(header));
constsize_tbasePos=dest->getSize();
/*在匿名AdaptFile对象dest中先写入一个header对象用于记录信息,接着
**将我们上面组织好的二进制xml字符串信息内存缓冲块中数据写入这个
**匿名AdaptFile对象dest中的缓冲区去*/
dest->writeData(&header,sizeof(header));
dest->writeData(stringPool->getData(),stringPool->getSize());
//IfwehaveresourceIDs,writethem.
//如果已经分配了资源ID则先写入一个记录资源ID信息的ResChunk_header
//头,然后将资源ID的信息写入,但是这里尚未对任何资源分配资源ID.
if(resids.size()>0){
constsize_tresIdsPos=dest->getSize();
constsize_tresIdsSize=
sizeof(ResChunk_header)+(sizeof(uint32_t)*resids.size());
ResChunk_header*idsHeader=(ResChunk_header*)
(((constuint8_t*)dest->editData(resIdsPos+resIdsSize))+resIdsPos);
idsHeader->type=htods(RES_XML_RESOURCE_MAP_TYPE);
idsHeader->headerSize=htods(sizeof(*idsHeader));
idsHeader->size=htodl(resIdsSize);
uint32_t*ids=(uint32_t*)(idsHeader+1);
for(size_ti=0;i<resids.size();i++){
*ids++=htodl(resids[i]);
}
}
/*调用flatten_node函数继续组织收集到的xml文件中的信息*/
flatten_node(strings,dest,stripComments,stripRawValues);
/*最后,再写入一个ResXMLTree_header标记写入工作完成并记录上次写入这类
**header到刚刚创建的header之间写入的数据信息
*/
void*data=dest->editData();
ResXMLTree_header*hd=(ResXMLTree_header*)(((uint8_t*)data)+basePos);
size_tsize=dest->getSize()-basePos;
hd->header.size=htodl(dest->getSize()-basePos);
returnNO_ERROR;
}
status_tXMLNode::flatten_node(constStringPool&strings,constsp<AaptFile>&dest,
boolstripComments,boolstripRawValues)const
{
ResXMLTree_nodenode;
ResXMLTree_cdataExtcdataExt;
ResXMLTree_namespaceExtnamespaceExt;
ResXMLTree_attrExtattrExt;
constvoid*extData=NULL;
size_textSize=0;
ResXMLTree_attributeattr;
boolwriteCurrentNode=true;
/*NA和NC分别记录属性个数和子标签个数*/
constsize_tNA=mAttributes.size();
constsize_tNC=mChildren.size();
size_ti;
constString16id16("id");
constString16class16("class");
constString16style16("style");
consttypetype=getType();//获取当前处理的XMLNode所记录信息的类型
/*初始化一个node变量和attr变量*/
memset(&node,0,sizeof(node));
memset(&attr,0,sizeof(attr));
node.header.headerSize=htods(sizeof(node));
node.lineNumber=htodl(getStartLineNumber());
if(!stripComments){
/*返回注释字符串在StringPool的成员变量mValues中的位置*/
node.comment.index=htodl(
mComment.size()>0?strings.offsetForString(mComment):-1);
}else{
node.comment.index=htodl((uint32_t)-1);
}
if(type==TYPE_ELEMENT){
/*设置该node类型*/
node.header.type=htods(RES_XML_START_ELEMENT_TYPE);
/*使用attrExt记录一个attribute额外的信息*/
extData=&attrExt;
extSize=sizeof(attrExt);
memset(&attrExt,0,sizeof(attrExt));
if(mNamespaceUri.size()>0){
attrExt.ns.index=htodl(strings.offsetForString(mNamespaceUri));
}else{
attrExt.ns.index=htodl((uint32_t)-1);
}
attrExt.name.index=htodl(strings.offsetForString(mElementName));
attrExt.attributeStart=htods(sizeof(attrExt));
attrExt.attributeSize=htods(sizeof(attr));
attrExt.attributeCount=htods(NA);
attrExt.idIndex=htods(0);
attrExt.classIndex=htods(0);
attrExt.styleIndex=htods(0);
for(i=0;i<NA;i++){
ssize_tidx=mAttributeOrder.valueAt(i);
constattribute_entry&ae=mAttributes.itemAt(idx);
if(ae.ns.size()==0){
if(ae.name==id16){
attrExt.idIndex=htods(i+1);
}elseif(ae.name==class16){
attrExt.classIndex=htods(i+1);
}elseif(ae.name==style16){
attrExt.styleIndex=htods(i+1);
}
}
}
}elseif(type==TYPE_NAMESPACE){
if(mNamespaceUri==RESOURCES_TOOLS_NAMESPACE){
writeCurrentNode=false;
}else{
node.header.type=htods(RES_XML_START_NAMESPACE_TYPE);
extData=&namespaceExt;
extSize=sizeof(namespaceExt);
memset(&namespaceExt,0,sizeof(namespaceExt));
if(mNamespacePrefix.size()>0){
namespaceExt.prefix.index=
htodl(strings.offsetForString(mNamespacePrefix));
}else{
namespaceExt.prefix.index=htodl((uint32_t)-1);
}
namespaceExt.prefix.index=
htodl(strings.offsetForString(mNamespacePrefix));
namespaceExt.uri.index=htodl(strings.offsetForString(mNamespaceUri));
}
LOG_ALWAYS_FATAL_IF(NA!=0,"Namespacenodescan'thaveattributes!");
}elseif(type==TYPE_CDATA){
node.header.type=htods(RES_XML_CDATA_TYPE);
extData=&cdataExt;
extSize=sizeof(cdataExt);
memset(&cdataExt,0,sizeof(cdataExt));
cdataExt.data.index=htodl(strings.offsetForString(mChars));
cdataExt.typedData.size=htods(sizeof(cdataExt.typedData));
cdataExt.typedData.res0=0;
cdataExt.typedData.dataType=mCharsValue.dataType;
cdataExt.typedData.data=htodl(mCharsValue.data);
LOG_ALWAYS_FATAL_IF(NA!=0,"CDATAnodescan'thaveattributes!");
}
node.header.size=htodl(sizeof(node)+extSize+(sizeof(attr)*NA));
/*初始化完成后将这个node和extData写入dest所记录的缓冲区中*/
if(writeCurrentNode){
dest->writeData(&node,sizeof(node));
if(extSize>0){
dest->writeData(extData,extSize);
}
}
/*将一个标签的没一个属性创建一个ResXMLAttribute变量然后按照指定的顺序
**组织在dest所描述的缓冲区中,注意:字符串信息被替换成其在StringPool
**中成员变量中的位置值*/
for(i=0;i<NA;i++){
ssize_tidx=mAttributeOrder.valueAt(i);
constattribute_entry&ae=mAttributes.itemAt(idx);
if(ae.ns.size()>0){
attr.ns.index=htodl(strings.offsetForString(ae.ns));
}else{
attr.ns.index=htodl((uint32_t)-1);
}
attr.name.index=htodl(ae.namePoolIdx);
if(!stripRawValues||ae.needStringValue()){
attr.rawValue.index=htodl(strings.offsetForString(ae.string));
}else{
attr.rawValue.index=htodl((uint32_t)-1);
}
attr.typedValue.size=htods(sizeof(attr.typedValue));
if(ae.value.dataType==Res_value::TYPE_NULL
||ae.value.dataType==Res_value::TYPE_STRING){
attr.typedValue.res0=0;
attr.typedValue.dataType=Res_value::TYPE_STRING;
attr.typedValue.data=htodl(strings.offsetForString(ae.string));
}else{
attr.typedValue.res0=0;
attr.typedValue.dataType=ae.value.dataType;
attr.typedValue.data=htodl(ae.value.data);
}
dest->writeData(&attr,sizeof(attr));
}
for(i=0;i<NC;i++){
status_terr=mChildren.itemAt(i)->flatten_node(strings,dest,
stripComments,stripRawValues);
if(err!=NO_ERROR){
returnerr;
}
}
/*写入标记数据写入完成header*/
if(type==TYPE_ELEMENT){
ResXMLTree_endElementExtendElementExt;
memset(&endElementExt,0,sizeof(endElementExt));
node.header.type=htods(RES_XML_END_ELEMENT_TYPE);
node.header.size=htodl(sizeof(node)+sizeof(endElementExt));
node.lineNumber=htodl(getEndLineNumber());
node.comment.index=htodl((uint32_t)-1);
endElementExt.ns.index=attrExt.ns.index;
endElementExt.name.index=attrExt.name.index;
dest->writeData(&node,sizeof(node));
dest->writeData(&endElementExt,sizeof(endElementExt));
}elseif(type==TYPE_NAMESPACE){
if(writeCurrentNode){
node.header.type=htods(RES_XML_END_NAMESPACE_TYPE);
node.lineNumber=htodl(getEndLineNumber());
node.comment.index=htodl((uint32_t)-1);
node.header.size=htodl(sizeof(node)+extSize);
dest->writeData(&node,sizeof(node));
dest->writeData(extData,extSize);
}
}
returnNO_ERROR;
}
综上,我们在flatten函数中将各种属性信息组织成如下方式并保存在一个AdaptFile对象中:
ResXMLTree_header--标记开始 |
ResStringPool_header--记录StringPool中各种信息 header->header.type=htods(RES_STRING_POOL_TYPE);//记录类型信息 header->header.headerSize=htods(sizeof(*header));//记录header大小 header->header.size=htodl(pool->getSize());//记录StringPool数据总量大小 header->stringCount=htodl(ENTRIES);//记录字符串条数 header->styleCount=htodl(STYLES);//记录格式个数 if(mUTF8){ header->flags|=htodl(ResStringPool_header::UTF8_FLAG);//字符类型标记 } header->stringsStart=htodl(preSize);//字符缓冲区起始位置 header->stylesStart=htodl(STYLES>0?(preSize+strPos):0);//字符格式起始位置 |
字符串偏移数组 |
字符串格式偏移数组 |
Xml文本文件中收集到的所有字符串信息 |
描述上述字符串的格式信息,每种格式由一个ResStringPool_span组织,每个ResStringPool_span结构体采用ResStringPool_ref将其设置为ResStringPool_span::END作为结束 |
ResXMLTree_node--使用header标记xml文件中一个标签开始信息,区分标签中包含的数据类型: RES_XML_START_ELEMENT_TYPE--用于记录属性信息的 RES_XML_START_NAMESPACE_TYPE--用于记录命名空间信息的 RES_XML_CDATA_TYPE--用于记录CDATA_TYPE类型数据信息的 上述三种类型数据对应使用如下三种类型记录一个标签中其对应信息: ResXMLTree_attrExt ResXMLTree_namespaceExt ResXMLTree_cdataExt |
将每一个属性字符串信息组织成一个ResXMLTree_attribute对象 |
ResXMLTree_node--使用header标记xml文件中一个标签结束信息 |
ResXMLTree_endElementExt--标记结束 |
如果有资源ID信息,则将资源ID信息存储在这里 |
ResXMLTree_header--标记结束 |
表格中蓝色模块为字符串资源池;紫色模块为由ResXML*的各种数据结构组织起来各种XML文本文件中数据,其中字符串信息都使用偏移量替换,我私自将其定为字符串资源数据结构组织区.
注意:每个资源数据模块使用一个ResChunk_header作为区分,比如字符串资源池使用ResStringPool_header标记开始以及记录信息,ResStringPool_header的第一个成员变量就是一个ResChunk_header;而字符串资源数据结构组织区以ResXMLTree_node标记开始以及记录信息,其第一个成员变量也是一个ResChunk_header
(3).将压平的字符串资源信息组织到ResXMLTree所描述的数据结构中
至此,我们在完成压平xml文本文件的工作之后,返回到parseXMLResource函数中调用ResXMLTree对象outTree的setTo成员函数将保存在AdaptFile对象rsc中的数据组织到outTree中.setTo函数的具体实现如下:
路径:frameworks/base/libs/androidfw/ResourcesTypes.cpp
status_tResXMLTree::setTo(constvoid*data,size_tsize,boolcopyData)
{
if(!data||!size){
return(mError=BAD_TYPE);
}
uninit();//初始化各种变量
mEventCode=START_DOCUMENT;
//在这里需要拷贝数据,故将按照上述组织方式组织在AdaptFile对象rsc中
//数据缓冲区中的数据拷贝到ResXMLTree成员变量mOwnedData所指向
//的数据缓冲区中,而rsc数据缓冲区中由mData指向,其内容如上述表格所示
if(copyData){
mOwnedData=malloc(size);
if(mOwnedData==NULL){
return(mError=NO_MEMORY);
}
memcpy(mOwnedData,data,size);
data=mOwnedData;
}
/*按照上述表格所描述的数据缓冲区内容逐一解析
**首先取出头:ResXMLTree_header*/
mHeader=(constResXMLTree_header*)data;
mSize=dtohl(mHeader->header.size);
if(dtohs(mHeader->header.headerSize)>mSize||mSize>size){
restart();
returnmError;
}
//将mDataEnd指向缓冲区末尾
mDataEnd=((constuint8_t*)mHeader)+mSize;
/*初始化*/
mStrings.uninit();
mRootNode=NULL;
mResIds=NULL;
mNumResIds=0;
//Firstlookforacoupleinterestingchunks:thestringblock
//andfirstXMLnode.
//取出数据缓冲区中的第一个ResChunk_header,也就是保存在
//ResStringPool_header中的header,如上述表格所示
constResChunk_header*chunk=
(constResChunk_header*)(((constuint8_t*)mHeader)+
dtohs(mHeader->header.headerSize));
constResChunk_header*lastChunk=chunk;
/*这里while循环逐一取出标记各个资源数据模块的ResChunk_header,
**在这里我们有两个上述描述两个数据模块*/
while(((constuint8_t*)chunk)<(mDataEnd-sizeof(ResChunk_header))&&
((constuint8_t*)chunk)<(mDataEnd-dtohl(chunk->size))){
status_terr=validate_chunk(chunk,sizeof(ResChunk_header),mDataEnd,"XML");
if(err!=NO_ERROR){
mError=err;
gotodone;
}
constuint16_ttype=dtohs(chunk->type);
constsize_tsize=dtohl(chunk->size);
//如果取出的资源类型是字符串则将其组织存储在成员变量mStrings中
//在这里会将上述保存在字符串资源池中的内容保存到mStrings中去
//mString是一个ResStringPool,最终资源池中数据就保存到其里面
if(type==RES_STRING_POOL_TYPE){
mStrings.setTo(chunk,size);
}elseif(type==RES_XML_RESOURCE_MAP_TYPE){
mResIds=(constuint32_t*)
(((constuint8_t*)chunk)+dtohs(chunk->headerSize));
mNumResIds=
(dtohl(chunk->size)-dtohs(chunk->headerSize))/sizeof(uint32_t);
/*这里保存字符串资源数据结构组织区*/
}elseif(type>=RES_XML_FIRST_CHUNK_TYPE
&&type<=RES_XML_LAST_CHUNK_TYPE){
if(validateNode((constResXMLTree_node*)chunk)!=NO_ERROR){
mError=BAD_TYPE;
gotodone;
}
mCurNode=(constResXMLTree_node*)lastChunk;
if(nextNode()==BAD_DOCUMENT){
mError=BAD_TYPE;
gotodone;
}
//用mRootNode保存数据结构区根节点数据
mRootNode=mCurNode;
mRootExt=mCurExt;
mRootCode=mEventCode;
break;
}else{
XML_NOISY(printf("Skippingunknownchunk!\n"));
}
lastChunk=chunk;
chunk=(constResChunk_header*)
(((constuint8_t*)chunk)+size);
}
if(mRootNode==NULL){
ALOGW("BadXMLblock:norootelementnodefound\n");
mError=BAD_TYPE;
gotodone;
}
mError=mStrings.getError();
done:
restart();
returnmError;
}
至此,我们在调用parseXMLResources函数解析完了AndroidManifest.xml文件,接下来我们返回到parsePackage函数中继续分析其后续工作过程,具体实现如下所示:
路径:frameworks/base/tools/aapt/Resource.cpp
staticstatus_tparsePackage(Bundle*bundle,constsp<AaptAssets>&assets,
constsp<AaptGroup>&grp)
{
//以下代码确保只有一个AndroidManifest.xml文件
if(grp->getFiles().size()!=1){
fprintf(stderr,"warning:MultipleAndroidManifest.xmlfilesfound,using%s\n",
grp->getFiles().valueAt(0)->getPrintableSource().string());
}
//取出存放AndroidManifest.xml文件信息的AaptFile对象
sp<AaptFile>file=grp->getFiles().valueAt(0);
//定义一个ResXMLTree对象,然后调用parseXMLResource来详细解析
//AndroidManifest.xml文件
//这个函数主要完成三个工作:
//1.收集file文件指向的xml文件中字符串资源信息.
//2.压平该file文件只想的xml文件中资源信息
//3.将前两步组织的到的资源信息最终组织到一个ResXMLTree所描述的数据结构中.
ResXMLTreeblock;
status_terr=parseXMLResource(file,&block);
if(err!=NO_ERROR){
returnerr;
}
//printXMLBlock(&block);
ResXMLTree::event_code_tcode;
/*下列while循环找到起开始位置*/
while((code=block.next())!=ResXMLTree::START_TAG
&&code!=ResXMLTree::END_DOCUMENT
&&code!=ResXMLTree::BAD_DOCUMENT){
}
size_tlen;
if(code!=ResXMLTree::START_TAG){
fprintf(stderr,"%s:%d:Nostarttagfound\n",
file->getPrintableSource().string(),block.getLineNumber());
returnUNKNOWN_ERROR;
}
//首先找到manifest标签
if(strcmp16(block.getElementName(&len),String16("manifest").string())!=0){
fprintf(stderr,"%s:%d:Invalidstarttag%s,expected<manifest>\n",
file->getPrintableSource().string(),block.getLineNumber(),
String8(block.getElementName(&len)).string());
returnUNKNOWN_ERROR;
}
//再找到pacakge标签属性所在block中的索引位置
ssize_tnameIndex=block.indexOfAttribute(NULL,"package");
if(nameIndex<0){
fprintf(stderr,"%s:%d:<manifest>doesnothavepackageattribute.\n",
file->getPrintableSource().string(),block.getLineNumber());
returnUNKNOWN_ERROR;
}
//获取正在编译的资源的包名,并设置将其保存到assets的成员变量
//mPackage中
assets->setPackage(String8(block.getAttributeStringValue(nameIndex,&len)));
String16uses_sdk16("uses-sdk");
/*找到SDK版本并设置minSdkVersion*/
while((code=block.next())!=ResXMLTree::END_DOCUMENT
&&code!=ResXMLTree::BAD_DOCUMENT){
if(code==ResXMLTree::START_TAG){
if(strcmp16(block.getElementName(&len),uses_sdk16.string())==0){
ssize_tminSdkIndex=
block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE,
"minSdkVersion");
if(minSdkIndex>=0){
constuint16_t*minSdk16=
block.getAttributeStringValue(minSdkIndex,&len);
constchar*minSdk8=strdup(String8(minSdk16).string());
bundle->setManifestMinSdkVersion(minSdk8);
}
}
}
}
returnNO_ERROR;
}
至此,我们就编译完成了AndroidManifest.xml文件接下来返回到buildResources函数继续分析后面的build工作,具体实现如下所示:
2.创建一个ResourcesTable收集当前应用程序编译所依赖的资源
(1).首先收集当前编译应用程序所依赖的系统资源包android.jar信息
路径:frameworks/base/tools/aapt/Resource.cpp
status_tbuildResources(Bundle*bundle,constsp<AaptAssets>&assets)
{
......
/*根据包名创建一个对应的ResourceTable*/
ResourceTabletable(bundle,String16(assets->getPackage()));
/*调用ResourceTable的成员函数addIncludedResources添加其所引用的android.jar
**包的路径信息*/
err=table.addIncludedResources(bundle,assets);
if(err!=NO_ERROR){
returnerr;
}
......
}
ResourceTable的成员函数addIncludedResources具体实现如下所示:
路径:frameworks/base/tools/aapt/ResourceTable.cpp
status_tResourceTable::addIncludedResources(Bundle*bundle,
constsp<AaptAssets>&assets)
{
/*调用AaptAssets的成员函数buildIncludedResources将当前包所依赖系统的
**android.jar包路径信息添加到assets的成员变量mIncludedAssets中*/
status_terr=assets->buildIncludedResources(bundle);
if(err!=NO_ERROR){
returnerr;
}
//Forfuturereferencetoincludedresources.
mAssets=assets;//将ResourceTable类的成员变量mAssets指向assets
/*接着调用AaptAssets的成员函数getIncludedResources获取一个
**ResTable对象用于描述当前APK所引用的android.jar包中的资源信息*/
constResTable&incl=assets->getIncludedResources();
//Retrieveallthepackages.
//恢复所有保存在ResTable中的包
/*Android系统定义了一套通用资源,这些资源可以被应用程序引用。例如,
**我们在XML布局文件中指定一个LinearLayout的android:orientation属性的值为
**“vertical”时,这个“vertical”实际上就是在系统资源包里面定义的一个值。
**从上面的分析就可以看出,我们在编译一个Android应用程序的资源的时候,
**至少会涉及到两个包,其中一个被引用的系统资源包,另外一个就是当前正在
**编译的应用程序资源包。每一个包都可以定义自己的资源,同时它也可以引用
**其它包的资源。那么,一个包是通过什么方式来引用其它包的资源的呢?这就是
**我们熟悉的资源ID了。资源ID是一个4字节的无符号整数,其中,最高字节表示
**PackageID,次高字节表示TypeID,最低两字节表示EntryID。
*/
constsize_tN=incl.getBasePackageCount();
for(size_tphase=0;phase<2;phase++){
for(size_ti=0;i<N;i++){
String16name(incl.getBasePackageName(i));
uint32_tid=incl.getBasePackageId(i);
//Firsttimethrough:onlyaddbasepackages(id
//isnot0);secondtimethroughaddtheother
//packages.
if(phase!=0){
if(id!=0){
//Skipbasepackages--alreadyone.
id=0;
}else{
//Assignadynamicid.
id=mNextPackageId;
}
}elseif(id!=0){
if(id==127){
if(mHaveAppPackage){
returnUNKNOWN_ERROR;
}
mHaveAppPackage=true;
}
if(mNextPackageId>id){
fprintf(stderr,"IncludedbasepackageID%dalreadyinuse!\n",id);
returnUNKNOWN_ERROR;
}
}
if(id!=0){
sp<Package>p=newPackage(name,id);
mPackages.add(name,p);
mOrderedPackages.add(p);
if(id>=mNextPackageId){
mNextPackageId=id+1;
}
}
}
}
//Everyresourcetablealwayshasonefirstentry,thebagattributes.
constSourcePosunknown(String8("????"),0);
sp<Type>attr=getType(mAssetsPackage,String16("attr"),unknown);
returnNO_ERROR;
}
AaptAssets的成员函数buildIncludedResources函数的具体实现如下所示:
路径:frameworks/base/tools/aapt/AaptAssets.cpp
status_tAaptAssets::buildIncludedResources(Bundle*bundle)
{
if(!mHaveIncludedAssets){
//Addinallincludes.
//首先获取我们使用-I选项所制定的android.jar包路径信息
constVector<constchar*>&incl=bundle->getPackageIncludes();
constsize_tN=incl.size();
/*将指定的所有android.jar的路径信息添加到当前对象成员变量
**mIncludedAssets中,mIncludedAssets的成员变量是一个AssetManager
**对象*/
for(size_ti=0;i<N;i++){
if(bundle->getVerbose())
printf("Includingresourcesfrompackage:%s\n",incl[i]);
/*最终调用AssetManager对象的addAssetPath将路径添加到其成员变量
**mAssetPaths中*/
if(!mIncludedAssets.addAssetPath(String8(incl[i]),NULL)){
fprintf(stderr,"ERROR:Assetpackageinclude'%s'notfound.\n",
incl[i]);
returnUNKNOWN_ERROR;
}
}
mHaveIncludedAssets=true;
}
returnNO_ERROR;
}
mIncludedAssets是一个AssetManager类,其成员函数getResources的具体实现如下所示:
路径:frameworks/base/libs/androidfw/AssetManager.cpp
boolAssetManager::addAssetPath(constString8&path,void**cookie)
{
AutoMutex_l(mLock);
asset_pathap;
/*利用现有信息初始化一个asset_path对象*/
String8realPath(path);
if(kAppZipName){
realPath.appendPath(kAppZipName);
}
ap.type=::getFileType(realPath.string());
if(ap.type==kFileTypeRegular){
ap.path=realPath;
}else{
ap.path=path;
ap.type=::getFileType(path.string());
if(ap.type!=kFileTypeDirectory&&ap.type!=kFileTypeRegular){
ALOGW("Assetpath%sisneitheradirectorynorfile(type=%d).",
path.string(),(int)ap.type);
returnfalse;
}
}
//Skipifwehaveitalready.
//检测是否添加过该文件
for(size_ti=0;i<mAssetPaths.size();i++){
if(mAssetPaths[i].path==ap.path){
if(cookie){
*cookie=(void*)(i+1);
}
returntrue;
}
}
mAssetPaths.add(ap);//将文件信息添加到mAssetPaths中去
//newpathsarealwaysaddedattheend
if(cookie){
*cookie=(void*)mAssetPaths.size();
}
//addoverlaypackagesfor/system/framework;appsarehandledbythe
//(Java)packagemanager
//判断是否是/system/framework开头的系统资源.
........
returntrue;
}
AaptAssets的成员函数getIncludedResources具体实现如下所示:
路径:frameworks/base/tools/aapt/AaptAssets.cpp
constResTable&AaptAssets::getIncludedResources()const
{
returnmIncludedAssets.getResources(false);
}
mIncludedAssets是一个AssetManager类,其成员函数getResources的具体实现如下所示:
路径:frameworks/base/libs/androidfw/AssetManager.cpp
constResTable&AssetManager::getResources(boolrequired)const
{
constResTable*rt=getResTable(required);
return*rt;
}
/*getResTable函数是一个Singleton*/
constResTable*AssetManager::getResTable(boolrequired)const
{
/*如果已经为AssetManager类的成员变量mResources分配了空间,则直接返回它*/
ResTable*rt=mResources;
if(rt){
returnrt;
}
//Iteratethroughallassetpackages,collectingresourcesfromeach.
AutoMutex_l(mLock);
if(mResources!=NULL){
returnmResources;
}
/*加载一些chache文件*/
if(mCacheMode!=CACHE_OFF&&!mCacheValid)
const_cast<AssetManager*>(this)->loadFileNameCacheLocked();
/*逐个扫描所包含的android.jar文件路径*/
constsize_tN=mAssetPaths.size();
for(size_ti=0;i<N;i++){
Asset*ass=NULL;
ResTable*sharedRes=NULL;
boolshared=true;
constasset_path&ap=mAssetPaths.itemAt(i);//取出路径
//打开idmap,在这里并未创建过idmap故,返回idmap指向NULL
Asset*idmap=openIdmapLocked(ap);
/*判断ap所指向的文件类型*/
if(ap.type!=kFileTypeDirectory){
/*如果ap所指向的文件类型不是目录文件*/
if(i==0){
//Thefirstitemistypicallytheframeworkresources,
//whichwewanttoavoidparsingeverytime.
//为第一个指向的android.jar包创建一个对应的SharedZip
//对象,并将其成员变量mResourceTable返回
sharedRes=const_cast<AssetManager*>(this)->
mZipSet.getZipResourceTable(ap.path);
}
//在这里返回NULL,表示尚未为该android.jar创建过一个ResTable对象
if(sharedRes==NULL){
/*返回对应android.jar包的SharedZip中的mResourceTableAsset对象
*/
ass=const_cast<AssetManager*>(this)->
mZipSet.getZipResourceTableAsset(ap.path);
//在这里返回的ass对象为NULL,表示尚未为该android.jar创建过一个
//Asset对象
if(ass==NULL){
ass=const_cast<AssetManager*>(this)->
openNonAssetInPathLocked("resources.arsc",
Asset::ACCESS_BUFFER,
ap);
if(ass!=NULL&&ass!=kExcludedAsset){
/*到这里我们就为一个android.jar包创建了一个Asset对象
**并将其保存到与之对应的SharedZip类对象的
**mResourceTableAsset中*/
ass=const_cast<AssetManager*>(this)->
mZipSet.setZipResourceTableAsset(ap.path,ass);
}
}
/*为android.jar包创建一个与之对应的ResTable类对象,并将其保存
**到与之对应的SharedZip的成员变量mResourceTable中*/
if(i==0&&ass!=NULL){
//Ifthisisthefirstresourcetableintheasset
//manager,thenwearegoingtocacheitsothatwe
//canquicklycopyitoutforothers.
sharedRes=newResTable();
sharedRes->add(ass,(void*)(i+1),false,idmap);
sharedRes=const_cast<AssetManager*>(this)->
mZipSet.setZipResourceTable(ap.path,sharedRes);
}
}
}else{//如果ap指向的是一个目录文件,则执行如下代码
Asset*ass=const_cast<AssetManager*>(this)->
openNonAssetInPathLocked("resources.arsc",
Asset::ACCESS_BUFFER,
ap);
shared=false;
}
/*完成了为一个android.jar包创建一个与之对应的Asset和ResTable对象之后,
**则新建一个ResTable对象用于初始化mResources成员变量*/
if((ass!=NULL||sharedRes!=NULL)&&ass!=kExcludedAsset){
if(rt==NULL){
mResources=rt=newResTable();
/*更新mResources的参数*/
updateResourceParamsLocked();
}
if(sharedRes!=NULL){
ALOGV("Copyingexistingresourcesfor%s",ap.path.string());
rt->add(sharedRes);
}else{
ALOGV("Parsingresourcesfor%s",ap.path.string());
rt->add(ass,(void*)(i+1),!shared,idmap);
}
if(!shared){
deleteass;
}
}
if(idmap!=NULL){
deleteidmap;
}
}
/*创建一个ResTable类对象,由mResources和rt指向*/
if(!rt){
mResources=rt=newResTable();
}
returnrt;
}
/*以下为SharedZip类成员函数的具体实现如下所示:*/
AssetManager::SharedZip::SharedZip(constString8&path,time_tmodWhen)
:mPath(path),mZipFile(NULL),mModWhen(modWhen),
mResourceTableAsset(NULL),mResourceTable(NULL)
{
mZipFile=newZipFileRO;//创建一个ZipFileRO对象
/*调用mZipFile成员函数open初始化其成员变量*/
if(mZipFile->open(mPath.string())!=NO_ERROR){
deletemZipFile;
mZipFile=NULL;
}
}
/*get方法用于获取一个SharedZip对象,该方法同样实现为Singleton模式*/
sp<AssetManager::SharedZip>AssetManager::SharedZip::get(constString8&path)
{
AutoMutex_l(gLock);
time_tmodWhen=getFileModDate(path);
sp<SharedZip>zip=gOpen.valueFor(path).promote();
if(zip!=NULL&&zip->mModWhen==modWhen){
returnzip;
}
zip=newSharedZip(path,modWhen);
gOpen.add(path,zip);
returnzip;
}
Asset*AssetManager::SharedZip::getResourceTableAsset()
{
returnmResourceTableAsset;
}
ResTable*AssetManager::SharedZip::getResourceTable()
{
returnmResourceTable;
}
/*以下为AssetManager::ZipSet中个成员函数的实现,具体实现如下所示*/
Asset*AssetManager::ZipSet::getZipResourceTableAsset(constString8&path)
{
intidx=getIndex(path);
sp<SharedZip>zip=mZipFile[idx];
if(zip==NULL){
zip=SharedZip::get(path);
mZipFile.editItemAt(idx)=zip;
}
//返回SharedZip的成员变量mResourceTableAsset(Asset*)
returnzip->getResourceTableAsset();
}
ResTable*AssetManager::ZipSet::getZipResourceTable(constString8&path)
{
intidx=getIndex(path);//获取其在mZipPath中的索引值
//获取在mZipFile中的对应索引所指向的SharedZip对象
sp<SharedZip>zip=mZipFile[idx];
//如果尚未为对应的android.jar包创建一个SharedZip对象则为其创建一个新的
//对象并将其保存到mZipFile中去
if(zip==NULL){
/*get方法用于创建一个新的SharedZip对象*/
zip=SharedZip::get(path);
mZipFile.editItemAt(idx)=zip;
}
//将SharedZip对象zip的成员变量mResourceTable(ResTable*)返回,这里为NULL
returnzip->getResourceTable();
}
intAssetManager::ZipSet::getIndex(constString8&zip)const
{
/*检测是否在ZipSet的成员变量mZipPath中是否包含当前的
**android.jar包*/
constsize_tN=mZipPath.size();
for(size_ti=0;i<N;i++){
if(mZipPath[i]==zip){
returni;
}
}
/*将当前android.jar包添加到其成员变量mZipPath中,并将mZipFile末尾元素
**设置为NULL*/
mZipPath.add(zip);
mZipFile.add(NULL);
returnmZipPath.size()-1;//将mZipPath最后一个元素索引返回给调用者
}
在上面的SharedZip类构造函数中,我们会创建一个与android.jar对应的SharedZip对象时会为其成员变量mZipFile创建一个ZipFileRO对象,并调用ZipFileRO对象的成员函数open进行初始化工作,具体实现如下所示:
路径:frameworks/native/include/utils/ZipFileRO.h
classZipFileRO{
public:
ZipFileRO()
:mFd(-1),mFileName(NULL),mFileLength(-1),
mDirectoryMap(NULL),
mNumEntries(-1),mDirectoryOffset(-1),
mHashTableSize(-1),mHashTable(NULL)
{}
......
/*
*Openanarchive.
*/
status_topen(constchar*zipFileName);
......
};
其成员函数open的实现如下所示:
路径:frameworks/native/libs/utils/ZipFileRO.cpp
/*
*Openthespecifiedfileread-only.Wememory-maptheentirethingand
*closethefilebeforereturning.
*以只读方式打开一个zip文件,使用ZipFileRO类对象来初始化该类对象
*/
status_tZipFileRO::open(constchar*zipFileName)
{
intfd=-1;
assert(mDirectoryMap==NULL);
/*
*Openandmapthespecifiedfile.
*以只读方式打开该zip文件*/
fd=::open(zipFileName,O_RDONLY|O_BINARY);
if(fd<0){
ALOGW("Unabletoopenzip'%s':%s\n",zipFileName,strerror(errno));
returnNAME_NOT_FOUND;
}
/*计算该zip文件大小*/
mFileLength=lseek64(fd,0,SEEK_END);
if(mFileLength<kEOCDLen){
TEMP_FAILURE_RETRY(close(fd));
returnUNKNOWN_ERROR;
}
if(mFileName!=NULL){
free(mFileName);
}
/*初始化一个ZipFileRO类对象的成员变量mFileName和mFd*/
mFileName=strdup(zipFileName);
mFd=fd;
/*以下两个函数主要用于解析一个ZIP文件,与ZIP压缩相关,我们就不详细
**分析*/
/*
*FindtheCentralDirectoryandstoreitssizeandnumberofentries.
*mapCentralDirectory函数用于找到核心文件夹以及存储它的大小和
*entry的个数
*压缩源文件数据区+压缩源文件目录区+压缩源文件目录结束标志
*在这个数据区中每一个压缩的源文件/目录都是一条记录,记录的格式如下:
*[文件头+文件数据+数据描述符]*/
if(!mapCentralDirectory()){
gotobail;
}
/*
*VerifyCentralDirectoryandcreatedatastructuresforfastaccess.
*验证核心目录并为快速访问创建数据结构*/
if(!parseZipArchive()){
gotobail;
}
returnOK;
bail:
free(mFileName);
mFileName=NULL;
TEMP_FAILURE_RETRY(close(fd));
returnUNKNOWN_ERROR;
}
至此,我们就为我们使用-I选项指定的android.jar包创建了一个SharedZip类对象,下面我们就使用openNonAssetInPathLock函数来为该android.jar包创建一个Asset类对象,其具体实现如下所示:
路径:frameworks/base/libs/androidfw/AssetManager.cpp
/*
*Openanon-assetfileasifitwereanasset,searchingforitinthe
*specifiedapp.
*
*PassinaNULLvaluesfor"appName"ifthecommonappdirectoryshould
*beused.
*
*如果尚未为我们使用-I选项指定的android.jar包创建过一个Asset对象和
*和ResTable对象,那么我们调用openNonAssetInPathLocked函数为其创建一个
**Asset类对象*/
Asset*AssetManager::openNonAssetInPathLocked(constchar*fileName,
AccessModemode,constasset_path&ap)
{
Asset*pAsset=NULL;
/*lookatthefilesystemondisk*/
if(ap.type==kFileTypeDirectory){
......
/*lookinsidethezipfile*/
}else{
String8path(fileName);
/*checktheappropriateZipfile*/
ZipFileRO*pZip;
ZipEntryROentry;
/*返回与fileName对应的SharedZip对象的成员变量mZipFile*/
pZip=getZipFileLocked(ap);
if(pZip!=NULL){
//printf("GOTzip,checkingNA'%s'\n",(constchar*)path);
/*寻找一个与android.jar包对应的ZipEntryRO项目条目*/
entry=pZip->findEntryByName(path.string());
if(entry!=NULL){
/*使用一个ZIP压缩包的ZIPEntryRO项目条目创建一个新的
**Asset对象,如果这个条目没有被压缩,我们可能想要创建
**或者共享一片共享内存*/
pAsset=openAssetFromZipLocked(pZip,entry,mode,path);
}
}
if(pAsset!=NULL){
/*createa"source"name,fordebug/display
**将pAsset的成员变量mAssetSource设置为android.jar:/resources.arsc
*/
pAsset->setAssetSource(
createZipSourceNameLocked(ZipSet::getPathName(
ap.path.string()),String8(""),String8(fileName)));
}
}
returnpAsset;
}
(2).收集当前应用程序包中的资源文件信息添加到ResourceTable中
至此,我们就将收集在AaptAsset中的资源保存到了一个ResourceTable对象中了,下面我们继续分析buildResource的过程,具体实现如下所示:
路径:frameworks/base/tools/aapt/Resource.cpp
#defineASSIGN_IT(n)\
do{\
ssize_tindex=resources->indexOfKey(String8(#n));\
if(index>=0){\
n##s=resources->valueAt(index);\
}\
}while(0)
status_tbuildResources(Bundle*bundle,constsp<AaptAssets>&assets)
{
//StandardflagsforcompiledXMLandoptionalUTF-8encoding
//设置编译XML文件的选项为标准和UTF-8的编码方式
intxmlFlags=XML_COMPILE_STANDARD_RESOURCE;
/*OnlyenableUTF-8ifthecallerofaaptdidn'tspecifically
*requestUTF-16encodingandtheparametersofthispackage
*allowUTF-8tobeused.
*/
if(!bundle->getUTF16StringsOption()){
xmlFlags|=XML_COMPILE_UTF8;
}
//resType->leafName->group
KeyedVector<String8,sp<ResourceTypeSet>>*resources=
newKeyedVector<String8,sp<ResourceTypeSet>>;
/*调用collect_files将前面收集到assets中的各类资源文件重新收集到resources中来*/
collect_files(assets,resources);
/*定义收集各类资源文件的容器*/
sp<ResourceTypeSet>drawables;
sp<ResourceTypeSet>layouts;
sp<ResourceTypeSet>anims;
sp<ResourceTypeSet>animators;
sp<ResourceTypeSet>interpolators;
sp<ResourceTypeSet>xmls;
sp<ResourceTypeSet>raws;
sp<ResourceTypeSet>colors;
sp<ResourceTypeSet>menus;
sp<ResourceTypeSet>mipmaps;
/*将保存到resources中的各类文件的Set保存到我们上述定义的Set中去*/
ASSIGN_IT(drawable);
ASSIGN_IT(layout);
ASSIGN_IT(anim);
ASSIGN_IT(animator);
ASSIGN_IT(interpolator);
ASSIGN_IT(xml);
ASSIGN_IT(raw);
ASSIGN_IT(color);
ASSIGN_IT(menu);
ASSIGN_IT(mipmap);
//设置assets的资源为resources中保存的
assets->setResources(resources);
//nowgothroughanyresourceoverlaysandcollecttheirfiles
/*判断当前应用程序是否有overlay的资源,有的话将assets中保存
**的资源设置为overlay中的*/
sp<AaptAssets>current=assets->getOverlay();
while(current.get()){
KeyedVector<String8,sp<ResourceTypeSet>>*resources=
newKeyedVector<String8,sp<ResourceTypeSet>>;
current->setResources(resources);
collect_files(current,resources);
current=current->getOverlay();
}
//applytheoverlayfilestothebaseset
//如果有overlay资源则使用overlay资源替换现有资源
if(!applyFileOverlay(bundle,assets,&drawables,"drawable")||
!applyFileOverlay(bundle,assets,&layouts,"layout")||
!applyFileOverlay(bundle,assets,&anims,"anim")||
!applyFileOverlay(bundle,assets,&animators,"animator")||
!applyFileOverlay(bundle,assets,&interpolators,"interpolator")||
!applyFileOverlay(bundle,assets,&xmls,"xml")||
!applyFileOverlay(bundle,assets,&raws,"raw")||
!applyFileOverlay(bundle,assets,&colors,"color")||
!applyFileOverlay(bundle,assets,&menus,"menu")||
!applyFileOverlay(bundle,assets,&mipmaps,"mipmap")){
returnUNKNOWN_ERROR;
}
boolhasErrors=false;
//如果当前应用程序有drawables资源,则首先调用preProcessImages函数预处理
//图像,然后调用makeFileResources函数处理drawables中的资源
if(drawables!=NULL){
if(bundle->getOutputAPKFile()!=NULL){
err=preProcessImages(bundle,assets,drawables,"drawable");
}
if(err==NO_ERROR){
err=makeFileResources(bundle,assets,&table,drawables,"drawable");
if(err!=NO_ERROR){
hasErrors=true;
}
}else{
hasErrors=true;
}
}
......
//compileresources
current=assets;
while(current.get()){
KeyedVector<String8,sp<ResourceTypeSet>>*resources=
current->getResources();
ssize_tindex=resources->indexOfKey(String8("values"));
if(index>=0){
ResourceDirIteratorit(resources->valueAt(index),String8("values"));
ssize_tres;
while((res=it.next())==NO_ERROR){
sp<AaptFile>file=it.getFile();
res=compileResourceFile(bundle,assets,file,it.getParams(),
(current!=assets),&table);
if(res!=NO_ERROR){
hasErrors=true;
}
}
}
current=current->getOverlay();
}
......
}
按类别将AaptAssets类对象中收集的资源保存到ResourceTypeSet为元素的Vector中具体实现如下所示:
路径:frameworks/base/tools/aapt/Resource.cpp
/*按类别将保存在assets中的资源文件进行归类处理*/
staticvoidcollect_files(constsp<AaptDir>&dir,
KeyedVector<String8,sp<ResourceTypeSet>>*resources)
{
constDefaultKeyedVector<String8,sp<AaptGroup>>&groups=dir->getFiles();
intN=groups.size();//获取资源文件夹下的资源文件个数,逐个扫描
for(inti=0;i<N;i++){
String8leafName=groups.keyAt(i);
constsp<AaptGroup>&group=groups.valueAt(i);
constDefaultKeyedVector<AaptGroupEntry,sp<AaptFile>>&files
=group->getFiles();
if(files.size()==0){
continue;
}
/*按照资源文件的类型新建一个对应的ResourceTypeSet容器保存各类
**文件,最终保存到resource中去*/
String8resType=files.valueAt(0)->getResourceType();
ssize_tindex=resources->indexOfKey(resType);
/*如果index小于0表示还未为resType所描述的类型资源文件创建一个对应的
**Set对象,于是为其新建一个*/
if(index<0){
sp<ResourceTypeSet>set=newResourceTypeSet();
set->add(leafName,group);
resources->add(resType,set);
}else{
sp<ResourceTypeSet>set=resources->valueAt(index);
index=set->indexOfKey(leafName);
if(index<0){
set->add(leafName,group);
}else{
sp<AaptGroup>existingGroup=set->valueAt(index);
for(size_tj=0;j<files.size();j++){
status_terr=existingGroup->addFile(files.valueAt(j));
}
}
}
}
}
staticvoidcollect_files(constsp<AaptAssets>&ass,
KeyedVector<String8,sp<ResourceTypeSet>>*resources)
{
constVector<sp<AaptDir>>&dirs=ass->resDirs();
intN=dirs.size();
/*逐个收集assets资源文件夹下的各类资源文件*/
for(inti=0;i<N;i++){
sp<AaptDir>d=dirs.itemAt(i);
collect_files(d,resources);
//don'ttrytoincludetheresdir
//不打算包含资源文件夹本身
ass->removeDir(d->getLeaf());
}
}
/*预处理图像,目前只支持处理png格式图像*/
staticstatus_tpreProcessImages(constBundle*bundle,constsp<AaptAssets>&assets,
constsp<ResourceTypeSet>&set,constchar*type)
{
volatileboolhasErrors=false;
ssize_tres=NO_ERROR;
if(bundle->getUseCrunchCache()==false){
/*创建一个工作队列来预处理图像*/
WorkQueuewq(MAX_THREADS,false);
ResourceDirIteratorit(set,String8(type));
while((res=it.next())==NO_ERROR){
/*创建一个工作单元线程然后马上使其工作预处理图像*/
PreProcessImageWorkUnit*w=newPreProcessImageWorkUnit(
bundle,assets,it.getFile(),&hasErrors);
status_tstatus=wq.schedule(w);
if(status){
fprintf(stderr,"preProcessImagesfailed:schedule()returned%d\n",status);
hasErrors=true;
deletew;
break;
}
}
status_tstatus=wq.finish();//处理完后释放资源
if(status){
fprintf(stderr,"preProcessImagesfailed:finish()returned%d\n",status);
hasErrors=true;
}
}
return(hasErrors||(res<NO_ERROR))?UNKNOWN_ERROR:NO_ERROR;
}
/*这是用于预处理图像的工作单元*/
classPreProcessImageWorkUnit:publicWorkQueue::WorkUnit{
public:
PreProcessImageWorkUnit(constBundle*bundle,constsp<AaptAssets>&assets,
constsp<AaptFile>&file,volatilebool*hasErrors):
mBundle(bundle),mAssets(assets),mFile(file),mHasErrors(hasErrors){
}
//真正的处理工作是在run方法中的preProcessImage中进行
virtualboolrun(){
status_tstatus=preProcessImage(mBundle,mAssets,mFile,NULL);
if(status){
*mHasErrors=true;
}
returntrue;//continueevenifthereareerrors
}
private:
constBundle*mBundle;
sp<AaptAssets>mAssets;
sp<AaptFile>mFile;
volatilebool*mHasErrors;
};
/*调用png图像处理库对png图像进行打包处理*/
status_tpreProcessImage(constBundle*bundle,constsp<AaptAssets>&assets,
constsp<AaptFile>&file,String8*outNewLeafName)
{
String8ext(file->getPath().getPathExtension());//获取文件扩展名
//WecurrentlyonlyprocessPNGimages.我们仅仅处理png格式的图像
if(strcmp(ext.string(),".png")!=0){
returnNO_ERROR;
}
String8printableName(file->getPrintableSource());//获取文件名
if(bundle->getVerbose()){
printf("Processingimage:%s\n",printableName.string());
}
png_structpread_ptr=NULL;
png_infopread_info=NULL;
FILE*fp;
image_infoimageInfo;
png_structpwrite_ptr=NULL;
png_infopwrite_info=NULL;
status_terror=UNKNOWN_ERROR;
constsize_tnameLen=file->getPath().length();
/*以只读方式打开该文件*/
fp=fopen(file->getSourceFile().string(),"rb");
if(fp==NULL){
fprintf(stderr,"%s:ERROR:UnabletoopenPNGfile\n",printableName.string());
gotobail;
}
/*以下使用png库打包处理png格式的图像*/
read_ptr=png_create_read_struct(PNG_LIBPNG_VER_STRING,
0,(png_error_ptr)NULL,(png_error_ptr)NULL);
if(!read_ptr){
gotobail;
}
read_info=png_create_info_struct(read_ptr);
if(!read_info){
gotobail;
}
if(setjmp(png_jmpbuf(read_ptr))){
gotobail;
}
png_init_io(read_ptr,fp);
read_png(printableName.string(),read_ptr,read_info,&imageInfo);
if(nameLen>6){
constchar*name=file->getPath().string();
if(name[nameLen-5]=='9'&&name[nameLen-6]=='.'){
if(do_9patch(printableName.string(),&imageInfo)!=NO_ERROR){
gotobail;
}
}
}
write_ptr=png_create_write_struct(PNG_LIBPNG_VER_STRING,
0,(png_error_ptr)NULL,(png_error_ptr)NULL);
if(!write_ptr)
{
gotobail;
}
write_info=png_create_info_struct(write_ptr);
if(!write_info)
{
gotobail;
}
png_set_write_fn(write_ptr,(void*)file.get(),
png_write_aapt_file,png_flush_aapt_file);
if(setjmp(png_jmpbuf(write_ptr)))
{
gotobail;
}
write_png(printableName.string(),write_ptr,write_info,imageInfo,
bundle->getGrayscaleTolerance());
error=NO_ERROR;
if(bundle->getVerbose()){
fseek(fp,0,SEEK_END);
size_toldSize=(size_t)ftell(fp);
size_tnewSize=file->getSize();
floatfactor=((float)newSize)/oldSize;
intpercent=(int)(factor*100);
}
bail:
if(read_ptr){
png_destroy_read_struct(&read_ptr,&read_info,(png_infopp)NULL);
}
if(fp){
fclose(fp);
}
if(write_ptr){
png_destroy_write_struct(&write_ptr,&write_info);
}
if(error!=NO_ERROR){
fprintf(stderr,"ERROR:FailureprocessingPNGimage%s\n",
file->getPrintableSource().string());
}
returnerror;
}
staticstatus_tmakeFileResources(Bundle*bundle,constsp<AaptAssets>&assets,
ResourceTable*table,
constsp<ResourceTypeSet>&set,
constchar*resType)
{
String8type8(resType);
String16type16(resType);
boolhasErrors=false;
ResourceDirIteratorit(set,String8(resType));
ssize_tres;
/*下列for循环逐个取出set中保存的文件进行处理*/
while((res=it.next())==NO_ERROR){
if(bundle->getVerbose()){
printf("(newresourceid%sfrom%s)\n",
it.getBaseName().string(),it.getFile()->getPrintableSource().string());
}
String16baseName(it.getBaseName());
constchar16_t*str=baseName.string();
constchar16_t*constend=str+baseName.size();
/*判断命名是否规范*/
while(str<end){
if(!((*str>='a'&&*str<='z')
||(*str>='0'&&*str<='9')
||*str=='_'||*str=='.')){
fprintf(stderr,"%s:Invalidfilename:mustcontainonly[a-z0-9_.]\n",
it.getPath().string());
hasErrors=true;
}
str++;
}
String8resPath=it.getPath();//获取资源文件名称
resPath.convertToResPath();
/*将一个资源文件封装为一个Entry添加到ResourceTable中去*/
table->addEntry(SourcePos(it.getPath(),0),String16(assets->getPackage()),
type16,
baseName,
String16(resPath),
NULL,
&it.getParams());
/*将该资源文件信息添加到AaptAsset对象assets中去*/
assets->addResource(it.getLeafName(),resPath,it.getFile(),type8);
}
returnhasErrors?UNKNOWN_ERROR:NO_ERROR;
}
将一个名称为name,类型为type,所属包为package的资源文件组织成一个Entry具体实现如下所示:
路径:frameworks/base/tools/aapt/ResourceTable.cpp
status_tResourceTable::addEntry(constSourcePos&sourcePos,
constString16&package,
constString16&type,
constString16&name,
constString16&value,
constVector<StringPool::entry_style_span>*style,
constResTable_config*params,
constbooldoSetIndex,
constint32_tformat,
constbooloverwrite)
{
//Checkforaddingentriesinotherpackages...fornowwedo
//nothing.Weneedtodotherightthingheretosupportskinning.
uint32_trid=mAssets->getIncludedResources()
.identifierForName(name.string(),name.size(),
type.string(),type.size(),
package.string(),package.size());
if(rid!=0){
returnNO_ERROR;
}
/*Entry类用来描述一个资源项,它的重要成员变量的含义如下所示:
--mName:表示资源名称。
--mItem:表示资源数据,用一个Item对象来描述。
Item类用来描述一个资源项数据,它的重要成员变量的含义如下所示:
--value:表示资源项的原始值,它是一个字符串。
--parsedValue:表示资源项原始值经过解析后得到的结构化的资源值,使用一个Res_Value对象来描述。例如,一个整数类型的资源项的原始值为“12345”,经过解析后,就得到一个大小为12345的整数类型的资源项。*/
/*首先调用getEntry函数获取一个描述名称为name的资源文件的Entry类对象
**其所在包为package,其类型使用type描述*/
sp<Entry>e=getEntry(package,type,name,sourcePos,overwrite,
params,doSetIndex);
if(e==NULL){
returnUNKNOWN_ERROR;
}
status_terr=e->setItem(sourcePos,value,style,format,overwrite);
if(err==NO_ERROR){
mNumLocal++;
}
returnerr;
}
sp<ResourceTable::Entry>ResourceTable::getEntry(constString16&package,
constString16&type,
constString16&name,
constSourcePos&sourcePos,
booloverlay,
constResTable_config*config,
booldoSetIndex)
{
/*Type类用来描述一个资源类型,它的重要成员变量的含义如下所示:
--mName:表示资源类型名称。
--mConfigs:表示包含的资源配置项列表,每一个配置项列表都包含了一系列同名的资源,使用一个ConfigList来描述。例如,假设有main.xml和sub.xml两个layout类型的资源,那么main.xml和sub.xml都分别对应有一个ConfigList。
--mOrderedConfigs:和mConfigs一样,也是表示包含的资源配置项,不过它们是以EntryID从小到大的顺序保存在一个Vector里面的,而mConfigs是以EntryName来Key的DefaultKeyedVector。
--mUniqueConfigs:表示包含的不同资源配置信息的个数。我们可以将mConfigs和mOrderedConfigs看作是按照名称的不同来划分资源项,而将mUniqueConfigs看作是按照配置信息的不同来划分资源项。
*/
/*ConfigList用来描述一个资源配置项列表,它的重要成员变量的含义如下所示:
--mName:表示资源项名称,也称为EntryName。
--mEntries:表示包含的资源项,每一个资源项都用一个Entry对象来描述,并且以一个对应的ConfigDescription为Key保存在一个DefaultKeyedVector中。例如,假设有一个名称为icon.png的drawable资源,有三种不同的配置,分别是ldpi、mdpi和hdpi,那么以icon.png为名称的资源就对应有三个项。
*/
/*调用getType函数来获取一个Type对象t用来描述资源类型*/
sp<Type>t=getType(package,type,sourcePos,doSetIndex);
if(t==NULL){
returnNULL;
}
/*从获取对应的Type对象中取出名称为name的Entry的对象用来描述名称为
**name的资源文件*/
returnt->getEntry(
name,sourcePos,config,doSetIndex,overlay,mBundle->getAutoAddOverlay());
}
sp<ResourceTable::Type>ResourceTable::getType(constString16&package,
constString16&type,
constSourcePos&sourcePos,
booldoSetIndex)
{
/*Package类用来描述一个包,这个包可以是一个被引用的包,即一个预先编译好的包,也可以是一个正在编译的包,它的重要成员变量的含义如下所示:
--mName:表示包的名称。
--mTypes:表示包含的资源的类型,每一个类型都用一个Type对象来描述。资源的类型就是指animimator、anim、color、drawable、layout、menu和values等。
--mOrderedTypes:和mTypes一样,也是表示包含的资源的类型,不过它们是TypeID从小到大的顺序保存在一个Vector里面的,而mTypes是一个以TypeName为Key的DefaultKeyedVector。
*/
/*获取一个Package对象用来描述当前正在编译的应用程序包*/
sp<Package>p=getPackage(package);
if(p==NULL){
returnNULL;
}
/*从获取的Package中获取其成员变量mTypes中名称为type的一个Type对象
**如果名称为type的Type对象不存在则新建一个并添加到mTypes成员变量中
*/
returnp->getType(type,sourcePos,doSetIndex);
}
sp<ResourceTable::Package>ResourceTable::getPackage(constString16&package)
{
/*通过包名判断是否已经为当前正在编译的应用程序创建过一个与其
**名称相对应的包名,如果创建过则直接从mPackages中取出,若
**没有则根据不同情况创建一个新的包并添加到mPackages中去*/
sp<Package>p=mPackages.valueFor(package);
if(p==NULL){
if(mBundle->getIsOverlayPackage()){
p=newPackage(package,0x00);
}elseif(mIsAppPackage){
if(mHaveAppPackage){
returnNULL;
}
mHaveAppPackage=true;
p=newPackage(package,127);
}else{
p=newPackage(package,mNextPackageId);
}
mPackages.add(package,p);
mOrderedPackages.add(p);
mNextPackageId++;
}
returnp;
}
上述获取了一个描述名称为name资源文件的Entry对象之后,把其相关信息组织成一个Item对象然后添加到Entry中,其具体实现如下所示:
status_tResourceTable::Entry::setItem(constSourcePos&sourcePos,
constString16&value,
constVector<StringPool::entry_style_span>*style,
int32_tformat,
constbooloverwrite)
{
Itemitem(sourcePos,false,value,style);//新建一个Item对象
if(mType==TYPE_BAG){
constItem&item(mBag.valueAt(0));
sourcePos.error("Resourceentry%sisalreadydefinedasabag.\n"
"%s:%d:Originallydefinedhere.\n",
String8(mName).string(),
item.sourcePos.file.string(),item.sourcePos.line);
returnUNKNOWN_ERROR;
}
if((mType!=TYPE_UNKNOWN)&&(overwrite==false)){
sourcePos.error("Resourceentry%sisalreadydefined.\n"
"%s:%d:Originallydefinedhere.\n",
String8(mName).string(),
mItem.sourcePos.file.string(),mItem.sourcePos.line);
returnUNKNOWN_ERROR;
}
mType=TYPE_ITEM;//指定类型
mItem=item;
mItemFormat=format;
returnNO_ERROR;
}
至此,我们分析如何将收集到一个AaptAsset中的资源文件信息分类重新由函数makeFileResources组织到一个ResourceTable对象中去,这些资源文件的信息最终组织在Package,Type,Entry,Item中,Package代表当前编译APK的包信息,Type类保存资源类型信息,Entry代表保存资源文件,Item保存文件中属性信息.Package包含Type,Type包含Entry,Entry包含Item.
baseName=ic_launcher
beforeconvertresPath=res/drawable-hdpi/ic_launcher.png
Addingentryleft:file=res/drawable-hdpi/ic_launcher.png,line=0,type=drawable,value=res/drawable-hdpi/ic_launcher.png
baseName=ic_launcher
beforeconvertresPath=res/drawable-mdpi/ic_launcher.png
Addingentryleft:file=res/drawable-mdpi/ic_launcher.png,line=0,type=drawable,value=res/drawable-mdpi/ic_launcher.png
baseName=ic_launcher
beforeconvertresPath=res/drawable-xhdpi/ic_launcher.png
Addingentryleft:file=res/drawable-xhdpi/ic_launcher.png,line=0,type=drawable,
value=res/drawable-xhdpi/ic_launcher.png
baseName=ic_launcher
beforeconvertresPath=res/drawable-xxhdpi/ic_launcher.png
Addingentryleft:file=res/drawable-xxhdpi/ic_launcher.png,line=0,type=drawable,value=res/drawable-xxhdpi/ic_launcher.png
baseName=activity_main
beforeconvertresPath=res/layout/activity_main.xml
Addingentryleft:file=res/layout/activity_main.xml,line=0,type=layout,
value=res/layout/activity_main.xml
Addingentryleft:file=res/values/dimens.xml,line=4,type=dimen,value=16dp
Addingentryleft:file=res/values/dimens.xml,line=5,type=dimen,value=16dp
Addingentryleft:file=res/values-sw720dp-land/dimens.xml,line=7,type=dimen,
value=128dp
Addingentryleft:file=res/values/strings.xml,line=4,type=string,value=HelloWorldActivity
Addingentryleft:file=res/values/strings.xml,line=5,type=string,value=Settings
Addingentryleft:file=res/values/strings.xml,line=6,type=string,value=Show
Addingentryleft:file=res/values/strings.xml,line=7,type=string,value=Helloworld!
Addingentryleft:file=res/layout/activity_main.xml,line=7,type=id,value=false
Addingentryleft:file=res/layout/activity_main.xml,line=13,type=id,value=false
对于drawable,mipmap,layout,anim,animator,iterpolator,xml,raw,color,menu中的资源文件,我们由函数makeFileResources就能将其组织成Package,Type,Entry,Item这样的数据结构形式最终保存到一个ResourceTable中去,而对于values中的资源文件却是由一个独立函数compileResourceFile进行组织的,该函数所要实现的功能如下所示:
原型:
status_tcompileResourceFile(Bundle*bundle,
constsp<AaptAssets>&assets,
constsp<AaptFile>&in,
constResTable_config&defParams,
constbooloverwrite,
ResourceTable*outTable)
A.调用函数parseXMLResource解析values目录下的每一个资源文件in,将解析的信息首先保存到一个类型为ResXMLTree的数据结构block中.
B.按照如下类别对block中的每一个资源文件信息详细解析:
//Top-leveltag.
constString16resources16("resources");
//Identifierdeclarationtags.
constString16declare_styleable16("declare-styleable");
constString16attr16("attr");
//Datacreationorganizationaltags.
constString16string16("string");
constString16drawable16("drawable");
constString16color16("color");
constString16bool16("bool");
constString16integer16("integer");
constString16dimen16("dimen");
constString16fraction16("fraction");
constString16style16("style");
constString16plurals16("plurals");
constString16array16("array");
constString16string_array16("string-array");
constString16integer_array16("integer-array");
constString16public16("public");
constString16public_padding16("public-padding");
constString16private_symbols16("private-symbols");
constString16java_symbol16("java-symbol");
constString16add_resource16("add-resource");
constString16skip16("skip");
constString16eat_comment16("eat-comment");
//Datacreationtags.
constString16bag16("bag");
constString16item16("item");
//Attributetypeconstants.
constString16enum16("enum");
//pluralvalues
constString16other16("other");
constString16quantityOther16("^other");
constString16zero16("zero");
constString16quantityZero16("^zero");
constString16one16("one");
constString16quantityOne16("^one");
constString16two16("two");
constString16quantityTwo16("^two");
constString16few16("few");
constString16quantityFew16("^few");
constString16many16("many");
constString16quantityMany16("^many");
//usefulattributenamesandspecialvalues
constString16name16("name");
constString16translatable16("translatable");
constString16formatted16("formatted");
constString16false16("false");
将解析完的信息保存在一个临时变量中通过调用outTable对应的类型信息的成员函数添加到ResourceTable对象outTable中去,至此我们就就将values文件夹下资源文件信息收集到了一个ResourceTable中去了.
综述,上面的工作我们将当前正在编译的应用程序所依赖的所有资源文件信息(包括系统android.jar中的和应用程序自身的被收集到一个AaptAsset类对象中的)都收集到了一个ResourceTable对象中去了,接下来buildResources函数的工作是为这些资源文件中的各种属性分配资源ID,具体实现如下所示:
3.为资源文件中Bag资源分配资源ID
在继续编译其它非values的资源之前,我们需要给之前收集到的Bag资源分配资源ID,因为它们可能会被其它非values类资源引用到
路径:frameworks/base/tools/aapt/Resource.cpp
status_tbuildResources(Bundle*bundle,constsp<AaptAssets>&assets)
{
......
//-----------------------------------------------------------
//AssignmentofresourceIDsandinitialgenerationofresourcetable.
//-----------------------------------------------------------
//分配资源ID和初始化生成资源表
if(table.hasResources()){
//首先创建一个名为resources.arsc的资源表文件
sp<AaptFile>resFile(getResourceFile(assets));
if(resFile==NULL){
fprintf(stderr,"Error:unabletogenerateentryforresourcedata\n");
returnUNKNOWN_ERROR;
}
/*调用ResourceTable类的成员函数assignResourceIds分配资源ID信息*/
err=table.assignResourceIds();
if(err<NO_ERROR){
returnerr;
}
}
......
}
ResourceTable分配资源ID的成员函数assignResourceIds的具体实现如下所示:
路径:frameworks/base/tools/aapt/ResourceTable.cpp
status_tResourceTable::assignResourceIds()
{
constsize_tN=mOrderedPackages.size();
size_tpi;
status_tfirstError=NO_ERROR;
//Firstgenerateallbagattributesandassignindices.
//首先取出当前编译应用程序资源所依赖的的包个数,并分别为包中的资源分配资源
//ID,在这里这两个包分别是:android.jar和com.example.helloworldactivity.
for(pi=0;pi<N;pi++){
sp<Package>p=mOrderedPackages.itemAt(pi);
if(p==NULL||p->getTypes().size()==0){
//Empty,skip!
continue;
}
/*如果为Package对象p中的Type设定了public属性id,那么调用
**applyPublicTypeOrder函数将p中成员变量mOrderedTypes中的Type按照id
**由小到大的顺序排列
**
**例如,我们在values/public.xml中如下定义:
**<?xmlversion="1.0"encoding="utf-8"?>
**<resources>
<publictype="string"name="show"id="0x7f030001"/>
<publictype="style"name="AppTheme"id="0x7f040001"/>
**</resources>
**那么type为string和style的在mOrderedTypes中的位置是在2,3
**位置处,就是将3和4进行减1操作而,第0,1两个位置保留.
*/
status_terr=p->applyPublicTypeOrder();
if(err!=NO_ERROR&&firstError==NO_ERROR){
firstError=err;
}
//Generateattributes...
/*按照Type-->ConfigList-->Entry的顺序依次将所有的Entry调用函数
**generateAttributes生成一个属性信息*/
constsize_tN=p->getOrderedTypes().size();
size_tti;
for(ti=0;ti<N;ti++){
sp<Type>t=p->getOrderedTypes().itemAt(ti);
if(t==NULL){
continue;
}
constsize_tN=t->getOrderedConfigs().size();
for(size_tci=0;ci<N;ci++){
sp<ConfigList>c=t->getOrderedConfigs().itemAt(ci);
if(c==NULL){
continue;
}
constsize_tN=c->getEntries().size();
for(size_tei=0;ei<N;ei++){
sp<Entry>e=c->getEntries().valueAt(ei);
if(e==NULL){
continue;
}
/*generateAttributes函数用于将保存到mBag中的信息取出,如果
**其是一个id属性,并且在table中没有对应的bag或者entry则
**创建一个entry添加进table中*/
status_terr=e->generateAttributes(this,p->getName());
if(err!=NO_ERROR&&firstError==NO_ERROR){
firstError=err;
}
}
}
}
constSourcePosunknown(String8("????"),0);
sp<Type>attr=p->getType(String16("attr"),unknown);
//Assignindices...
for(ti=0;ti<N;ti++){
sp<Type>t=p->getOrderedTypes().itemAt(ti);
if(t==NULL){
continue;
}
//类似的,我们如果为某类Type对象指定了public的IDS信息,我们就同上
//将Type中的ConfigList对象按照id值从小到大排列在mOrderedConfigs
//中去
err=t->applyPublicEntryOrder();
if(err!=NO_ERROR&&firstError==NO_ERROR){
firstError=err;
}
constsize_tN=t->getOrderedConfigs().size();
t->setIndex(ti+1);//为每一种Type设定索引值
/*为当前Type中的ConfigList设定索引值*/
for(size_tei=0;ei<N;ei++){
sp<ConfigList>c=t->getOrderedConfigs().itemAt(ei);
if(c==NULL){
continue;
}
c->setEntryIndex(ei);
}
}
//AssignresourceIDstokeysinbags...
//按照Package-->ConfigList-->Entry处理顺序
//逐个取出每一个资源属性调用Entry的assignResourceIds为其分配属性ID
for(ti=0;ti<N;ti++){
sp<Type>t=p->getOrderedTypes().itemAt(ti);
if(t==NULL){
continue;
}
constsize_tN=t->getOrderedConfigs().size();
for(size_tci=0;ci<N;ci++){
sp<ConfigList>c=t->getOrderedConfigs().itemAt(ci);
//printf("Orderedconfig#%d:%p\n",ci,c.get());
constsize_tN=c->getEntries().size();
for(size_tei=0;ei<N;ei++){
sp<Entry>e=c->getEntries().valueAt(ei);
if(e==NULL){
continue;
}
status_terr=e->assignResourceIds(this,p->getName());
if(err!=NO_ERROR&&firstError==NO_ERROR){
firstError=err;
}
}
}
}
}
returnfirstError;
}
status_tResourceTable::Entry::assignResourceIds(ResourceTable*table,
constString16&package)
{
boolhasErrors=false;
/*Type为values的资源除了是string之外,还有其它很多类型的资源,其中
**有一些比较特殊,如bag、style、plurals和array类的资源。这些资源会给
**自己定义一些专用的值,这些带有专用值的资源就统称为Bag资源。
**例如,Android系统提供的android:orientation属性的取值范围
**为{“vertical”、“horizontal”},就相当于是定义了vertical和horizontal
**两个Bag。在继续编译其它非values的资源之前,我们需要给之前收集到的Bag
**资源分配资源ID
*/
if(mType==TYPE_BAG){
constchar*errorMsg;
constString16style16("style");
constString16attr16("attr");
constString16id16("id");
mParentId=0;
/*在当前正在编译的应用程序中,Bag类型的资源在values/styles.xml:
**AppBaseTheme其mParent分别为:android:Theme.Light,
**android:Theme.Holo.Light,android:Theme.Holo.Light.DarkActionBar
**AppTheme其mParent为AppBaseTheme,在这里只为父类Bag资源分配
**资源ID*/
if(mParent.size()>0){
mParentId=table->getResId(mParent,&style16,NULL,&errorMsg);
if(mParentId==0){
mPos.error("Errorretrievingparentforitem:%s'%s'.\n",
errorMsg,String8(mParent).string());
hasErrors=true;
}
}
constsize_tN=mBag.size();
for(size_ti=0;i<N;i++){
constString16&key=mBag.keyAt(i);
Item&it=mBag.editValueAt(i);
it.bagKeyId=table->getResId(key,
it.isId?&id16:&attr16,NULL,&errorMsg);
if(it.bagKeyId==0){
it.sourcePos.error("Error:%s:%s'%s'.\n",errorMsg,
String8(it.isId?id16:attr16).string(),
String8(key).string());
hasErrors=true;
}
}
}
returnhasErrors?UNKNOWN_ERROR:NO_ERROR;
}
uint32_tResourceTable::getResId(constString16&package,
constString16&type,
constString16&name,
boolonlyPublic)const
{
sp<Package>p=mPackages.valueFor(package);
if(p==NULL)return0;
........
sp<Type>t=p->getTypes().valueFor(type);
if(t==NULL)return0;
sp<ConfigList>c=t->getConfigs().valueFor(name);
if(c==NULL)return0;
int32_tei=c->getEntryIndex();
if(ei<0)return0;
/*最终通过getResId分配到一个资源ID*/
returngetResId(p,t,ei);
}
inlineuint32_tResourceTable::getResId(constsp<Package>&p,
constsp<Type>&t,
uint32_tnameId)
{
returnmakeResId(p->getAssignedId(),t->getIndex(),nameId);
}
/*创建资源ID函数
**PackageID相当于是一个命名空间,限定资源的来源。Android系统当前定义了两个资源命令空间,其中一个系统资源命令空间,它的PackageID等于0x01,另外一个是应用程序资源命令空间,它的PackageID等于0x7f。所有位于[0x01,0x7f]之间的PackageID都是合法的,而在这个范围之外的都是非法的PackageID。前面提到的系统资源包package-export.apk的PackageID就等于0x01,而我们在应用程序中定义的资源的PackageID的值都等于0x7f,这一点可以通过生成的R.java文件来验证。
TypeID是指资源的类型ID。资源的类型有animator、anim、color、drawable、layout、menu、raw、string和xml等等若干种,每一种都会被赋予一个ID。
EntryID是指每一个资源在其所属的资源类型中所出现的次序。注意,不同类型的资源的EntryID有可能是相同的,但是由于它们的类型不同,我们仍然可以通过其资源ID来区别开来。
*/
staticinlineuint32_tmakeResId(uint32_tpackageId,
uint32_ttypeId,
uint32_tnameId)
{
/*使用PackageID|typeID|nameID就为名为name的
**资源属性创建了一个资源ID号*/
returnnameId|(typeId<<16)|(packageId<<24);
}
4.编译XML文件
在前面的所有工作我们都是为编译一个XML文件做准备的,接下来我们将要在buildResources函数中调用compileXmlFile编译一个xml文件.具体实现如下所示:
路径:frameworks/base/tools/aapt/Resource.cpp
status_tbuildResources(Bundle*bundle,constsp<AaptAssets>&assets)
{
......
//------------------------------------------------------
//Finally,wecannowwecancompileXMLfiles,whichmayreference
//resources.
//------------------------------------------------------
//最后我们将要编译XML文件,这样我们就能引用资源
if(layouts!=NULL){
ResourceDirIteratorit(layouts,String8("layout"));
while((err=it.next())==NO_ERROR){
String8src=it.getFile()->getPrintableSource();
err=compileXmlFile(assets,it.getFile(),&table,xmlFlags);
if(err==NO_ERROR){
ResXMLTreeblock;
/*将编译后的信息组织到ResXMLTree中去*/
block.setTo(it.getFile()->getData(),it.getFile()->getSize(),true);
/*检验分配的ID是否正确*/
checkForIds(src,block);
}else{
hasErrors=true;
}
}
if(err<NO_ERROR){
hasErrors=true;
}
err=NO_ERROR;
}
//对于anim,animator,interpolator,xml,color,menu,drawable中的xml文件
//都是通过compileXmlFile函数进行编译的.
......
/*取出AndroidManifest.xml文件*/
constsp<AaptFile>manifestFile(androidManifestFile->getFiles().valueAt(0));
String8manifestPath(manifestFile->getPrintableSource());
//Generatefinalcompiledmanifestfile.
//清空manifestFile所指向的AndroidManfiest.xml的信息,然后重新解析
manifestFile->clearData();
sp<XMLNode>manifestTree=XMLNode::parse(manifestFile);
if(manifestTree==NULL){
returnUNKNOWN_ERROR;
}
//检测是否AndroidManifest.xml中是否有overlay资源,如果有就将现有资源替换
err=massageManifest(bundle,manifestTree);
if(err<NO_ERROR){
returnerr;
}
//编译AndroidManifest.xml文件
err=compileXmlFile(assets,manifestTree,manifestFile,&table);
if(err<NO_ERROR){
returnerr;
}
........
}
路径:frameworks/base/tools/aapt/ResourceTable.cpp
status_tcompileXmlFile(constsp<AaptAssets>&assets,
constsp<AaptFile>&target,
ResourceTable*table,
intoptions)
{
/*首先调用XMLNode的成员函数解析target指定的xml文件收集其属性信息
**到root所指向的数据结构中*/
sp<XMLNode>root=XMLNode::parse(target);
if(root==NULL){
returnUNKNOWN_ERROR;
}
/*调用重载的compileXmlFile函数编译XML文件*/
returncompileXmlFile(assets,root,target,table,options);
}
status_tcompileXmlFile(constsp<AaptAssets>&assets,
constsp<XMLNode>&root,
constsp<AaptFile>&target,
ResourceTable*table,
intoptions)
{
/*首先去除空格*/
if((options&XML_COMPILE_STRIP_WHITESPACE)!=0){
root->removeWhitespace(true,NULL);
}elseif((options&XML_COMPILE_COMPACT_WHITESPACE)!=0){
root->removeWhitespace(false,NULL);
}
/*设定编码格式*/
if((options&XML_COMPILE_UTF8)!=0){
root->setUTF8(true);
}
boolhasErrors=false;
/*如果尚未对解析到root数据结构中的属性分配资源ID则调用
**root的成员函数分配资源id,给属性分配资源ID原理类似于上
**述给Bag资源分配ID*/
if((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS)!=0){
status_terr=root->assignResourceIds(assets,table);
if(err!=NO_ERROR){
hasErrors=true;
}
}
/*parseValues函数用于获取当前资源属性所在的行号等信息将其保存到table
**中,并将字符串资源信息替换成对应的类型值*/
status_terr=root->parseValues(assets,table);
if(err!=NO_ERROR){
hasErrors=true;
}
if(hasErrors){
returnUNKNOWN_ERROR;
}
/*压平XML文件,组织格式我们已经在上面详细分析过*/
err=root->flatten(target,
(options&XML_COMPILE_STRIP_COMMENTS)!=0,
(options&XML_COMPILE_STRIP_RAW_VALUES)!=0);
if(err!=NO_ERROR){
returnerr;
}
target->setCompressionMethod(ZipEntry::kCompressDeflated);
returnerr;
}
5.生成资源符号和生成资源索引表
这里生成资源符号为后面生成R.java文件做好准备的。从前面的操作可以知道,所有收集到的资源项都按照类型来保存在一个资源表中,即保存在一个ResourceTable对象。因此,Android资源打包工具aapt只要遍历每一个Package里面的每一个Type,然后取出每一个Entry的名称,并且根据这个Entry在自己的Type里面出现的次序来计算得到它的资源ID,那么就可以生成一个资源符号了,这个资源符号由名称以及资源ID所组成。
例如,对于strings.xml文件中名称为“show”的Entry来说,它是一个类型为string的资源项,假设它出现的次序为第3,那么它的资源符号就等于R.string.show,对应的资源ID就为0x7f050002,其中,高字节0x7f表示PackageID,次高字节0x05表示string的TypeID,而低两字节0x02就表示“show”是第三个出现的字符串。
经过前面的工作我们就获得了所有资源的信息并保存到了ResourceTable对象table中去,接着就要调用ResourceTable的成员函数flatten来生成资源索引表resources.arsc
路径:frameworks/base/tools/aapt/Resource.cpp
status_tbuildResources(Bundle*bundle,constsp<AaptAssets>&assets)
{
......
//--------------------------------------------------------
//Generatethefinalresourcetable.
//Re-flattenbecausewemayhaveaddednewresourceIDs
//--------------------------------------------------------
ResTablefinalResTable;
sp<AaptFile>resFile;
if(table.hasResources()){
/*生成资源符号表*/
sp<AaptSymbols>symbols=assets->getSymbolsFor(String8("R"));
err=table.addSymbols(symbols);
if(err<NO_ERROR){
returnerr;
}
/*生成资源索引表*/
resFile=getResourceFile(assets);
if(resFile==NULL){
fprintf(stderr,"Error:unabletogenerateentryforresourcedata\n");
returnUNKNOWN_ERROR;
}
err=table.flatten(bundle,resFile);
if(err<NO_ERROR){
returnerr;
}
if(bundle->getPublicOutputFile()){
FILE*fp=fopen(bundle->getPublicOutputFile(),"w+");
if(fp==NULL){
returnUNKNOWN_ERROR;
}
if(bundle->getVerbose()){
}
table.writePublicDefinitions(String16(assets->getPackage()),fp);
fclose(fp);
}
//Readresourcesbackin,
finalResTable.add(resFile->getData(),resFile->getSize(),NULL);
......
}
}
ResourceTable的flatten函数用于生成资源索引表resources.arsc,其具体实现如下所示,我们一一解析这个函数的具体实现:
1).收集类型字符串,资源项名称字符串和资源项值字符串
注意,这些字符串不是按Package来收集的,也就是说,当前所有参与编译的Package的资源项值字符串都会被统一收集在一起。
路径:frameworks/base/tools/aapt/Resource.cpp
status_tResourceTable::flatten(Bundle*bundle,constsp<AaptFile>&dest)
{
......
constsize_tN=mOrderedPackages.size();
size_tpi;
conststaticString16mipmap16("mipmap");
booluseUTF8=!bundle->getUTF16StringsOption();
//Iteratethroughalldata,collectingallvalues(strings,
//references,etc).
/*创建一个字符串资源池用于保存资源项值字符串*/
StringPoolvalueStrings(useUTF8);
Vector<sp<Entry>>allEntries;
for(pi=0;pi<N;pi++){
sp<Package>p=mOrderedPackages.itemAt(pi);
if(p->getTypes().size()==0){
//Empty,skip!
continue;
}
/*创建一个字符串资源池用于保存资源类型字符串*/
StringPooltypeStrings(useUTF8);
/*创建一个字符串资源池用于保存资源项名称字符串*/
StringPoolkeyStrings(useUTF8);
constsize_tN=p->getOrderedTypes().size();
for(size_tti=0;ti<N;ti++){
sp<Type>t=p->getOrderedTypes().itemAt(ti);
if(t==NULL){
typeStrings.add(String16("<empty>"),false);
continue;
}
constString16typeName(t->getName());
typeStrings.add(typeName,false);//收集资源类型
//Thisisahacktotweakthesortingorderofthefinalstrings,
//toputstuffthatisgenerallynotlanguage-specificfirst.
String8configTypeName(typeName);
if(configTypeName=="drawable"||configTypeName=="layout"
||configTypeName=="color"||configTypeName=="anim"
||configTypeName=="interpolator"
||configTypeName=="animator"
||configTypeName=="xml"||configTypeName=="menu"
||configTypeName=="mipmap"||configTypeName=="raw"){
configTypeName="1complex";
}else{
configTypeName="2value";
}
constboolfilterable=(typeName!=mipmap16);
constsize_tN=t->getOrderedConfigs().size();
for(size_tci=0;ci<N;ci++){
sp<ConfigList>c=t->getOrderedConfigs().itemAt(ci);
if(c==NULL){
continue;
}
constsize_tN=c->getEntries().size();
for(size_tei=0;ei<N;ei++){
ConfigDescriptionconfig=c->getEntries().keyAt(ei);
if(filterable&&!filter.match(config)){
continue;
}
sp<Entry>e=c->getEntries().valueAt(ei);
if(e==NULL){
continue;
}
/*收集资源项名称字符串*/
e->setNameIndex(keyStrings.add(e->getName(),true));
//Ifthisentryhasnovaluesforotherconfigs,
//andisthedefaultconfig,thenitisspecial.Otherwise
//wewanttoadditwiththeconfiginfo.
ConfigDescription*valueConfig=NULL;
if(N!=1||config==nullConfig){
valueConfig=&config;
}
/*收集资源项值,并将字符串类型的值转换成特定的类型*/
status_terr=e->prepareFlatten(&valueStrings,this,
&configTypeName,&config);
if(err!=NO_ERROR){
returnerr;
}
allEntries.add(e);
}
}
}
/*将上述收集到的信息添加到Package中去*/
p->setTypeStrings(typeStrings.createStringBlock());
p->setKeyStrings(keyStrings.createStringBlock());
}
......
}
2).生成Package数据块
参与编译的每一个Package的资源项元信息都写在一块独立的数据上,这个数据块使用一个类型为ResTable_package的头部来描述。
路径:frameworks/base/tools/aapt/Resource.cpp
status_tResourceTable::flatten(Bundle*bundle,constsp<AaptFile>&dest)
{
......
//Nowbuildthearrayofpackagechunks.
Vector<sp<AaptFile>>flatPackages;
for(pi=0;pi<N;pi++){
/*取出一个Package*/
sp<Package>p=mOrderedPackages.itemAt(pi);
if(p->getTypes().size()==0){
//Empty,skip!
continue;
}
/*获取类型资源字符串的个数*/
constsize_tN=p->getTypeStrings().size();
constsize_tbaseSize=sizeof(ResTable_package);//计算头部大小
//Startthepackagedata.
//创建一个data的匿名AaptFile类来分配存储数据的空间
sp<AaptFile>data=newAaptFile(String8(),AaptGroupEntry(),String8());
/*分配一个ResTable_package的头部大小数据空间*/
ResTable_package*header=(ResTable_package*)data->editData(baseSize);
if(header==NULL){
fprintf(stderr,"ERROR:outofmemorycreatingResTable_package\n");
returnNO_MEMORY;
}
/*初始化这个头部*/
memset(header,0,sizeof(*header));
header->header.type=htods(RES_TABLE_PACKAGE_TYPE);
header->header.headerSize=htods(sizeof(*header));
header->id=htodl(p->getAssignedId());
strcpy16_htod(header->name,p->getName().string());
//Writethestringblocks.
//写入字符串类型字符串资源池中的数据
constsize_ttypeStringsStart=data->getSize();
sp<AaptFile>strFile=p->getTypeStringsData();
ssize_tamt=data->writeData(strFile->getData(),strFile->getSize());
strAmt+=amt;
if(amt<0){
returnamt;
}
//写入字符串名称字符串资源池中的数据
constsize_tkeyStringsStart=data->getSize();
strFile=p->getKeyStringsData();
amt=data->writeData(strFile->getData(),strFile->getSize());
strAmt+=amt;
if(amt<0){
returnamt;
}
//Buildthetypechunksinsideofthispackage.
//在Package内部构建一个typeSpec块
for(size_tti=0;ti<N;ti++){
//Retrievetheminthesameorderasthetypestringblock.
size_tlen;
//获取类型名称
String16typeName(p->getTypeStrings().stringAt(ti,&len));
sp<Type>t=p->getTypes().valueFor(typeName);
constboolfilterable=(typeName!=mipmap16);
constsize_tN=t!=NULL?t->getOrderedConfigs().size():0;
//FirstwritethetypeSpecchunk,containinginformationabout
//eachresourceentryinthistype.
//首先写入typeSpec块,其中包含了关于在这个类型中的每个资源entry
{
/*为这个typeSpec块分配空间*/
constsize_ttypeSpecSize=
sizeof(ResTable_typeSpec)+sizeof(uint32_t)*N;
constsize_ttypeSpecStart=data->getSize();
ResTable_typeSpec*tsHeader=(ResTable_typeSpec*)
(((uint8_t*)data->editData(typeSpecStart+typeSpecSize))+
typeSpecStart);
if(tsHeader==NULL){
returnNO_MEMORY;
}
/*在这个typeSpec块前面创建一个类型规范数据块ResTable_typeSpec
**类型规范数据块用来描述资源项的配置差异性。通过这个差异性描述,
**我们就可以知道每一个资源项的配置状况。知道了一个资源项的配置
**状况之后,Android资源管理框架在检测到设备的配置信息发生变化
**之后,就可以知道是否需要重新加载该资源项。类型规范数据块是按
**照类型来组织的,也就是说,每一种类型都对应有一个类型规范数据
**块。*/
memset(tsHeader,0,sizeof(*tsHeader));
tsHeader->header.type=htods(RES_TABLE_TYPE_SPEC_TYPE);
tsHeader->header.headerSize=htods(sizeof(*tsHeader));
tsHeader->header.size=htodl(typeSpecSize);
tsHeader->id=ti+1;
tsHeader->entryCount=htodl(N);
/*ResTable_typeSpec后面紧跟着的是一个大小为entryCount的uint32_t数组,每一个数组元素,即每一个uint32_t,都是用来描述一个资源项的配置差异性的。例如,名称为icon的drawable资源项有三种不同的
屏幕配置ldpi、mdpi和hdpi,于是用来描述它的配置差异性的uint32_t的第CONFIG_DENSITY位就等于1,而其余位都等于0。又如,名称为main_activity的layout资源项只有一种配置default,于是用来描述它的配置差异性的uint32_t的值就等于0。此外,如果一个资源项是导出的,即它的资源ID是通过public.xml来固定的,那么用来描述它的配置差异性的uint32_t的第ResTable_typeSpec::SPEC_PUBLIC位也会被设置为1。
*/
uint32_t*typeSpecFlags=(uint32_t*)
(((uint8_t*)data->editData())
+typeSpecStart+sizeof(ResTable_typeSpec));
memset(typeSpecFlags,0,sizeof(uint32_t)*N);
for(size_tei=0;ei<N;ei++){
sp<ConfigList>cl=t->getOrderedConfigs().itemAt(ei);
/*对应每一个ConfigList对象的位置处写入一个
**RestTable_typeSpec::SPEC_PUBLIC标记*/
if(cl->getPublic()){
typeSpecFlags[ei]|=htodl(ResTable_typeSpec::SPEC_PUBLIC);
}
constsize_tCN=cl->getEntries().size();
for(size_tci=0;ci<CN;ci++){
if(filterable&&!filter.match(cl->getEntries().keyAt(ci))){
continue;
}
for(size_tcj=ci+1;cj<CN;cj++){
if(filterable&&!filter.match(cl->getEntries().keyAt(cj))){
continue;
}
typeSpecFlags[ei]|=htodl(
cl->getEntries().keyAt(ci).diff(cl->getEntries().keyAt(cj)));
}
}
}
}
//Weneedtowriteonetypechunkforeachconfigurationfor
//whichwehaveentriesinthistype.
constsize_tNC=t->getUniqueConfigs().size();
constsize_ttypeSize=sizeof(ResTable_type)+sizeof(uint32_t)*N;
for(size_tci=0;ci<NC;ci++){
ConfigDescriptionconfig=t->getUniqueConfigs().itemAt(ci);
if(filterable&&!filter.match(config)){
continue;
}
constsize_ttypeStart=data->getSize();
/*类型资源项数据块用来描述资源项的具体信息,这样我们就可以知道
**每一个资源项名称、值和配置等信息。类型资源项数据同样是按照
**类型和配置来组织的,也就是说,一个具有N个配置的类型一共对应
**有N个类型资源项数据块。
**类型资源项数据块的头部是用一个ResTable_type来定义的
*/
ResTable_type*tHeader=(ResTable_type*)
(((uint8_t*)data->editData(typeStart+typeSize))+typeStart);
if(tHeader==NULL){
fprintf(stderr,"ERROR:outofmemorycreatingResTable_type\n");
returnNO_MEMORY;
}
memset(tHeader,0,sizeof(*tHeader));
tHeader->header.type=htods(RES_TABLE_TYPE_TYPE);
tHeader->header.headerSize=htods(sizeof(*tHeader));
tHeader->id=ti+1;
tHeader->entryCount=htodl(N);
tHeader->entriesStart=htodl(typeSize);
tHeader->config=config;
//Buildtheentriesinsideofthistype.
for(size_tei=0;ei<N;ei++){
sp<ConfigList>cl=t->getOrderedConfigs().itemAt(ei);
sp<Entry>e=cl->getEntries().valueFor(config);
//Settheoffsetforthisentryinitstype.
/*ResTable_type紧跟着的是一个大小为entryCount的uint32_t
**数组,每一个数组元素,即每一个uint32_t,都是用来描述一个
**资源项数据块的偏移位置。紧跟在这个uint32_t数组后面的是
**一个大小为entryCount的ResTable_entry数组,每一个数组
**元素,即每一个ResTable_entry,都是用来描述一个资源项的
**具体信息。*/
uint32_t*index=(uint32_t*)
(((uint8_t*)data->editData())
+typeStart+sizeof(ResTable_type));
if(e!=NULL){
index[ei]=htodl(data->getSize()-typeStart-typeSize);
//Createtheentry.
ssize_tamt=e->flatten(bundle,data,cl->getPublic());
if(amt<0){
returnamt;
}
}else{
index[ei]=htodl(ResTable_type::NO_ENTRY);
}
}
//Fillintherestofthetypeinformation.
tHeader=(ResTable_type*)
(((uint8_t*)data->editData())+typeStart);
tHeader->header.size=htodl(data->getSize()-typeStart);
}
}
//Fillintherestofthepackageinformation.
//将描述Package的ResTable_package类型的header各数据项填满
header=(ResTable_package*)data->editData();
header->header.size=htodl(data->getSize());
header->typeStrings=htodl(typeStringsStart);
header->lastPublicType=htodl(p->getTypeStrings().size());
header->keyStrings=htodl(keyStringsStart);
header->lastPublicKey=htodl(p->getKeyStrings().size());
flatPackages.add(data);
}
//Andnowwriteoutthefinalchunks.
constsize_tdataStart=dest->getSize();
/*资源索引表头部使用一个ResTable_header来表示*/
{
//blah
ResTable_headerheader;
memset(&header,0,sizeof(header));
header.header.type=htods(RES_TABLE_TYPE);
header.header.headerSize=htods(sizeof(header));
header.packageCount=htodl(flatPackages.size());
status_terr=dest->writeData(&header,sizeof(header));
if(err!=NO_ERROR){
fprintf(stderr,"ERROR:outofmemorycreatingResTable_header\n");
returnerr;
}
}
ssize_tstrStart=dest->getSize();
/*我们已经将所有的资源项的值字符串都收集起来了,因此,这里直接它们写入到
**资源索引表去就可以了。注意,这个字符串资源池包含了在所有的资源包里面所
**定义的资源项的值字符串,并且是紧跟在资源索引表头部的后面。*/
err=valueStrings.writeStringBlock(dest);
if(err!=NO_ERROR){
returnerr;
}
ssize_tamt=(dest->getSize()-strStart);
strAmt+=amt;
/*我们已经所有的Package数据块都收集起来了,因此,这里直接将它们写入到
**资源索引表去就可以了。这些Package数据块是依次写入到资源索引表去的,
**并且是紧跟在资源项的值字符串资源池的后面。*/
for(pi=0;pi<flatPackages.size();pi++){
err=dest->writeData(flatPackages[pi]->getData(),
flatPackages[pi]->getSize());
if(err!=NO_ERROR){
returnerr;
}
}
ResTable_header*header=(ResTable_header*)
(((uint8_t*)dest->getData())+dataStart);
header->header.size=htodl(dest->getSize()-dataStart);
returnNO_ERROR;
}
/*组织各数据项的数据*/
ssize_tResourceTable::Entry::flatten(Bundle*bundle,
constsp<AaptFile>&data,boolisPublic)
{
size_tamt=0;
ResTable_entryheader;
memset(&header,0,sizeof(header));
header.size=htods(sizeof(header));
consttypety=this!=NULL?mType:TYPE_ITEM;
if(this!=NULL){
if(ty==TYPE_BAG){
header.flags|=htods(header.FLAG_COMPLEX);
}
if(isPublic){
header.flags|=htods(header.FLAG_PUBLIC);
}
header.key.index=htodl(mNameIndex);
}
/*接下来我们就分两种情况来讨论资源项信息写入到资源索引表的过程。
首先看一个普通的资源项,即一个非Bag资源项的写入过程。每一个资源项的
数据都是用一个Item来描述的。在这个Item中,有一个类型为Res_value的
成员变量parsedValue,它表示一个资源项经过解析后得到值。
*/
if(ty!=TYPE_BAG){
/*写入一个类型为ResTable_entry的header*/
status_terr=data->writeData(&header,sizeof(header));
if(err!=NO_ERROR){
fprintf(stderr,"ERROR:outofmemorycreatingResTable_entry\n");
returnerr;
}
constItem&it=mItem;
Res_valuepar;
memset(&par,0,sizeof(par));
par.size=htods(it.parsedValue.size);
par.dataType=it.parsedValue.dataType;
par.res0=it.parsedValue.res0;
par.data=htodl(it.parsedValue.data);
err=data->writeData(&par,it.parsedValue.size);
if(err!=NO_ERROR){
fprintf(stderr,"ERROR:outofmemorycreatingRes_value\n");
returnerr;
}
amt+=it.parsedValue.size;
/*以下是Bag资源的写入过程*/
}else{
size_tN=mBag.size();
size_ti;
//Createcorrectorderingofitems.
KeyedVector<uint32_t,constItem*>items;
for(i=0;i<N;i++){
constItem&it=mBag.valueAt(i);
items.add(it.bagKeyId,&it);
}
N=items.size();
/*紧跟在ResTable_entry后面的是一个ResTable_map_entry,用来描述后面要
**写入到的ResTable_map的信息。假设一个Bag资源项有N个bag,那么
**在ResTable_map_entry就有N个ResTable_map
*/
ResTable_map_entrymapHeader;
memcpy(&mapHeader,&header,sizeof(header));
mapHeader.size=htods(sizeof(mapHeader));
mapHeader.parent.ident=htodl(mParentId);
mapHeader.count=htodl(N);
status_terr=data->writeData(&mapHeader,sizeof(mapHeader));
if(err!=NO_ERROR){
fprintf(stderr,"ERROR:outofmemorycreatingResTable_entry\n");
returnerr;
}
for(i=0;i<N;i++){
constItem&it=*items.valueAt(i);
ResTable_mapmap;
map.name.ident=htodl(it.bagKeyId);
map.value.size=htods(it.parsedValue.size);
map.value.dataType=it.parsedValue.dataType;
map.value.res0=it.parsedValue.res0;
map.value.data=htodl(it.parsedValue.data);
err=data->writeData(&map,sizeof(map));
if(err!=NO_ERROR){
fprintf(stderr,"ERROR:outofmemorycreatingRes_value\n");
returnerr;
}
amt+=sizeof(map);
}
}
returnamt;
}
6.再编译AndroidManifest.xml文件
完成了上述所有工作后,我们在buildResources函数中将再次编译AndroidManifest.xml文件,应用程序的所有资源项就编译完成了,这时候就开始将应用程序的配置文件AndroidManifest.xml也编译成二进制格式的Xml文件。之所以要在应用程序的所有资源项都编译完成之后,再编译应用程序的配置文件,是因为后者可能会引用到前者,其跟普通xml文件的编译过程类似,在此我们就不再赘述。
到这里,我们就分析完成了调用函数buildResources编译AndroidManifest.xml文件和res文件夹下面的资源文件的过程,接下来我们返回到doPackage函数中,分析后续的工作:
三.将上述编译完成的资源生成R.java文件和APK
路径:frameworks/base/tools/aapt/Command.cpp
/*
*Packageupanassetdirectoryandassociatedapplicationfiles.
*/
intdoPackage(Bundle*bundle)
{
.......
//Atthispointwe'vereadeverythingandprocessedeverything.Fromhere
//onoutit'sjustwritingoutputfiles.
//我们在此已经完成了所有的准备工作,现在我们要将编译保存在缓存中的数据
//输出到输出文件中去
if(SourcePos::hasErrors()){
gotobail;
}
//UpdatesymbolswithinformationaboutwhichonesareneededasJavasymbols.
//更新资源符号
assets->applyJavaSymbols();
if(SourcePos::hasErrors()){
gotobail;
}
//Ifwe'vebeenaskedtogenerateadependencyfile,dothathere
//在这里生成依赖文件,就是在指定的APK名称后添加.d
if(bundle->getGenDependencies()){
//Ifthisisthepackagingstep,generatethedependencyfilenextto
//theoutputapk(e.g.bin/resources.ap_.d)
if(outputAPKFile){
dependencyFile=String8(outputAPKFile);
//Addthe.dextensiontothedependencyfile.
dependencyFile.append(".d");
}else{
//ElseifthisistheR.javadependencygenerationstep,
//generatethedependencyfileintheR.javapackagesubdirectory
//e.g.gen/com/foo/app/R.java.d
dependencyFile=String8(bundle->getRClassDir());
dependencyFile.appendPath("R.java.d");
}
//Makesurewehaveacleandependencyfiletostartwith
fp=fopen(dependencyFile,"w");
fclose(fp);
}
//WriteoutR.javaconstants
//生成R.java文件
if(!assets->havePrivateSymbols()){
if(bundle->getCustomPackage()==NULL){
//WritetheR.javafileintotheappropriateclassdirectory
//e.g.gen/com/foo/app/R.java
err=writeResourceSymbols(bundle,assets,assets->getPackage(),true);
}else{
constString8customPkg(bundle->getCustomPackage());
err=writeResourceSymbols(bundle,assets,customPkg,true);
}
if(err<0){
gotobail;
}
//Ifwehavelibraryfiles,we'regoingtowriteourR.javafileinto
//theappropriateclassdirectoryforthoselibrariesaswell.
//e.g.gen/com/foo/app/lib/R.java
if(bundle->getExtraPackages()!=NULL){
//Splitoncolon
String8libs(bundle->getExtraPackages());
char*packageString=strtok(libs.lockBuffer(libs.length()),":");
while(packageString!=NULL){
//WritetheR.javafileoutwiththecorrectpackagename
err=writeResourceSymbols(bundle,assets,String8(packageString),true);
if(err<0){
gotobail;
}
packageString=strtok(NULL,":");
}
libs.unlockBuffer();
}
}else{
err=writeResourceSymbols(bundle,assets,assets->getPackage(),false);
if(err<0){
gotobail;
}
err=writeResourceSymbols(bundle,
assets,assets->getSymbolsPrivatePackage(),true);
if(err<0){
gotobail;
}
}
//WriteouttheProGuardfile
//后续工作是打包APK文件
err=writeProguardFile(bundle,assets);
if(err<0){
gotobail;
}
//Writetheapk
if(outputAPKFile){
err=writeAPK(bundle,assets,String8(outputAPKFile));
if(err!=NO_ERROR){
fprintf(stderr,"ERROR:packagingof'%s'failed\n",outputAPKFile);
gotobail;
}
}
//Ifwe'vebeenaskedtogenerateadependencyfile,weneedtofinishuphere.
//thewriteResourceSymbolsandwriteAPKfunctionshavealreadywrittenthetarget
//halfofthedependencyfile,nowweneedtowritetheprerequisites.(filesthat
//theR.javafileor.ap_filedependon)
if(bundle->getGenDependencies()){
//NowthatwriteResourceSymbolsorwriteAPKhastakencareofwriting
//thetargetstoourdependencyfile,we'llwritetheprereqs
fp=fopen(dependencyFile,"a+");
fprintf(fp,":");
boolincludeRaw=(outputAPKFile!=NULL);
err=writeDependencyPreReqs(bundle,assets,fp,includeRaw);
//AlsomanuallyaddtheAndroidManifesetsinceit'snotunderres/orassets/
//andthereforewasnotaddedtoourpathstoresduringslurping
fprintf(fp,"%s\\\n",bundle->getAndroidManifestFile());
fclose(fp);
}
retVal=0;
bail:
if(SourcePos::hasErrors()){
SourcePos::printErrors(stderr);
}
returnretVal;
}
/***************END******2013/6/19*************LeeMH****************/
更多相关文章
- 关于安装Android(安卓)Studio的一些问题的解决方法
- Android里的媒体库
- android开发问题解决日志
- 如何将Eclipse中的项目迁移到Android(安卓)Studio 中
- 推荐一个android学习网站
- android保存文件到手机内存
- Android(安卓)数据存储(二) 文件的使用
- Android(安卓)数据保存
- 【Android每周专题】触摸屏手势