一个APK是如何产生的

1:前言
在Android的世界中,App层运行的最小单元是Apk,那Apk是如何产生的,这里将从APK的结构和打包方式里做一个简单的介绍;理解了APK打包流程更加有利于我们插件化的开发,后续会开专门的章节分析主流插件化技术

2: Apk的结构
APK结构]![这里写图片描述

3:APK结构说明
APK其实就是一个ZIP文件,修改APK的文件格式为.zip,使用解压缩工具就能显示里面的内容
- AndroidManifest.xml,App的项目全局配置文件,生成的APK中该文件为二进制格式,可以使用apktool工具反编译查看原文件
- lib, native 库文件;里面存放SO文件供app层调用;lib文件夹中有针对各种硬件架构设计(CPU)的文件夹,如X86,AMR-V7a,ARM;
那我们是不是要针对各种价格写不同的文件夹尼?答案是否;在加载SO的过程中,Andriod系统首先会读取和手机硬件相关的文件夹,如果没有找到则会在兼容性文件夹中对应SO;
如一个ARM-V7A的手机,在读取SO的时候,如果在Lib/AMR-V7a文件夹中没有找到对应的SO,则会在ARM文件夹中寻找;
这点和android读取drawable文件还是很相似的。
- assert,一般用于存放音视频频,字体等文件,应用中该目录存放什么,生产APK之后还是什么,不会发生变化
- META-INF , 该文件主要用于保证APK的完整性,里面的文件结构如下
这里写图片描述
··MANIFEST.MF:这个文件保存了整个 apk 文件中所有文件的文件名 + SHA-1后的 base64 编码值,使用notepadd++打开即可查看
这里写图片描述
··RSA文件:这里之说RSA文件,但是没有指明MGame.RAS,是因为各家产生会对APK进行壳处理,修改这里的RAS名称;这个文件保存了公钥和加密方式的信息
··SF文件:这个文件与 MANIFEST.MF 的结构一样,只是其编码会被被私钥加密。使用notepadd++打开即可查看
综述:如果你的APK被别人修改了,但是没有私钥生成 CERT.SF,就无法完成校验,即安装失败
- res,资源文件,存在这个文件夹下的所有文件都会映射到Android工程的.R文件中,生成对应的ID,访问的时候直接使用资源ID即R.id.filename; 包含图片,布局,动画等,其中布局文件,动画文件等XML会被编译成二进制文件,可以使用apktool工具反编译查看原文件,图片文件可以查看
- resource.arsc,App的资源索引表,编译后的二进制资源文件;
那这里就衍生了一个问题,在drawable和drawable-hdpi目录下放了两张相同的图片,那这两张图片的索引号是一样的吗?答案:是的
我们先看R.java
这里写图片描述
每一个资源都有自己的类别,针对drawable资源,统一归于class drawable类中,在这个类中,针对每一个图片资源设置一个int值;
-资源是在应用程序运行时自动根据设备的当前配置信息进行适配的,简单的来说,先从最能符合当前设备属性的文件夹中加载资源,如果没有符合的文件夹,则从父文件夹中查找对应资源;这里不对资源适配和屏幕适配做过多解释
这里又衍生出另外一个问题:当drawable文件夹中有一个xml文件,例如button.xml
这里写图片描述
那这个button.xml会在R.java的什么类中?
答案是:class drawable
- classes.dex
首先先把Java文件编译成class文件,字节码都保存在class文件中,Dalvik虚拟机执行的是Dalvik字节码,dx工具将Java字节码转换为Dalvik字节码。
那Android系统如何根据设备配置寻找正确的资源文件?请参考如下文档
https://developer.android.com/guide/topics/resources/providing-resources.html

4:解读R.java
在上面的讲解中,aapt工具将资源文件打包成R.java
- 资源ID格式
资源ID是一个32位四字节数字,格式:PPTTEEEE。
其中PP代表package id.系统资源的package id为0x1,而app自己的资源包package id为0x7f。0x1与0x7f之间的package id都合法。
TT代表资源的类型(type);
NNNN代表这个类型下面的资源项的名称(entry);
TT 和NNNN 的取值是由aapt工具随意指定的——基本上每一种新的资源类型的数字都是从上一个数字累加的(从1开始);而每一个新的资源entry条目也是从数字1开始向上累加的。

5:以Resources.getString(id)为例讲解系统是如何拿到对应的String资源

Resources.java 资源查找不到,则抛出NotFoundException
    public String getString(int id) throws NotFoundException {
        CharSequence res = getText(id);
        if (res != null) {
            return res.toString();
        }
        throw new NotFoundException("String resource ID #0x"
                                    + Integer.toHexString(id));
    }

getString方法将调用Resources.java中的getText(id)

    public CharSequence getText(int id) throws NotFoundException {
        CharSequence res = mAssets.getResourceText(id);
        if (res != null) {
            return res;
        }
        throw new NotFoundException("String resource ID #0x"
                                    + Integer.toHexString(id));
    }

getText方法将调用AssetManager.java中的getResourceText(int resId)

    @Nullable
    final CharSequence getResourceText(@StringRes int resId) {
        synchronized (this) {
            final TypedValue outValue = mValue;
            if (getResourceValue(resId, 0, outValue, true)) {
                return outValue.coerceToString();
            }
            return null;
        }
    }

如果加载成功,就会保存在参数outValue所描述的一个TypedValue对象中

该方法将调用AssetManager.java中的getResourceValue(resId, 0, outValue, true)

    final boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
            boolean resolveRefs) {
        final int block = loadResourceValue(resId, (short) densityDpi, outValue, resolveRefs);
        if (block < 0) {
            return false;
        }
        if (outValue.type == TypedValue.TYPE_STRING) {
            outValue.string = mStringBlocks[block].get(outValue.data);
        }
        return true;
    }

紧接着调用AssetManager.java中的loadResourceValue

    private native final int loadResourceValue(int ident, short density, TypedValue outValue,
            boolean resolve);

该JNI方法定义在android_util_AssetManager.cpp文件中,这里做下简单的分析

static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz,
                                                           jint ident,
                                                           jshort density,
                                                           jobject outValue,
                                                           jboolean resolve)
{
    if (outValue == NULL) {
         jniThrowNullPointerException(env, "outValue");
         return 0;
    }
    AssetManager* am = assetManagerForJavaObject(env, clazz);
    if (am == NULL) {
        return 0;
    }
    const ResTable& res(am->getResources());

    Res_value value;
    ResTable_config config;
    uint32_t typeSpecFlags;
    ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config);
    if (kThrowOnBadId) {
        if (block == BAD_INDEX) {
            jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
            return 0;
        }
    }
    uint32_t ref = ident;
    if (resolve) {
        block = res.resolveReference(&value, block, &ref, &typeSpecFlags, &config);
        if (kThrowOnBadId) {
            if (block == BAD_INDEX) {
                jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
                return 0;
            }
        }
    }
    if (block >= 0) {
        return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config);
    }

    return static_cast<jint>(block);
}

1)将Java层的AssetManager通过assetManagerForJavaObject(env, clazz)转换为C++层的AssetManager* am
2)调用am->getResources()返回ResTable的一个引用res,ResTable对象描述的是一个资源表

 Res_value value;
    ResTable_config config;
    uint32_t typeSpecFlags;

3)调用ResTable的res.getResource(ident, &value, false, density, &typeSpecFlags, &config); 获得与参数ident所对应的资源项值及其配置信息,并且保存在类型为Res_value的变量value以及类型为ResTable_config的变量config中
4)如果参数resolve的值等于true,那么就继续调用上述得到的ResTable对象的成员函数resolveReference来解析前面所得到的资源项值。
5)调用函数copyValue将上述得到的资源项值及其配置信息拷贝到参数outValue所描述的一个Java层的TypedValue对象中去,返回调用者可以获得与参数ident所对应的资源项内容。
至此,客户端已经拿到对应的资源内容了
6: 但是ResTable类时怎么和resource.arsc关联起来的?这才是真正获取资源的地方,这里做个简要说明
1)在之前的android_content_AssetManager_loadResourceValue方法中,我们创建了AssetManager* am = assetManagerForJavaObject(env, clazz);对象;这里就将android_util_AssetManager.cpp和AssetManager.cpp关联起来
2)然后调用AssetManager* am的getResources()方法返回ResTable
AssetManager.cpp中的getResources如下

const ResTable& AssetManager::getResources(bool required) const
{
    const ResTable* rt = getResTable(required);
    return *rt;
}
const ResTable* AssetManager::getResTable(bool required) const
{
    ResTable* rt = mResources;
    if (rt) {
        return rt;
    }

    // Iterate through all asset packages, collecting resources from each.

    AutoMutex _l(mLock);

    if (mResources != NULL) {
        return mResources;
    }

    if (required) {
        LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
    }

    if (mCacheMode != CACHE_OFF && !mCacheValid) {
        const_cast<AssetManager*>(this)->loadFileNameCacheLocked();
    }

    mResources = new ResTable();
    updateResourceParamsLocked();

    bool onlyEmptyResources = true;
    const size_t N = mAssetPaths.size();
    for (size_t i=0; i<N; i++) {
        bool empty = appendPathToResTable(mAssetPaths.itemAt(i));
        onlyEmptyResources = onlyEmptyResources && empty;
    }

    if (required && onlyEmptyResources) {
        ALOGW("Unable to find resources file resources.arsc");
        delete mResources;
        mResources = NULL;
    }

    return mResources;
}

从getResTable方法中,通过bool empty = appendPathToResTable(mAssetPaths.itemAt(i))将resources.arsc关联起来,经过上述的一系列操作之后,AssetManager类的成员变量mResources所指向的一个ResTable对象就包含了当前应用程序所使用的资源包的所有信息,该ResTable对象最后就会返回给调用者来使用。

7 . 从资源查找的过程来看,它们可以归结为两大类。第一类资源是不对应有文件的,而第二类资源是对应有文件的,例如,字符串资源是直接编译在resources.arsc文件中的,而界面布局资源是在APK包里面是对应的单独的文件的。如上所述,不对应文件的资源只需要执行从资源ID到资源名称的转换即可,而对应有文件的资源还需要根据资源名称来打开对应的文件。
无论是对应文件查找还是对应非文件查找,最终还是需要通过android_content_AssetManager_loadResourceValue完成查找

8 . Android打包工具简介
1)aapt: Android Asset Packaging Tool
aapt即Android Asset Packaging Tool,在SDK的build-tools目录下。该工具可以查看,创建, 更新ZIP格式的文档附件(zip, jar, apk)。也可将资源文件编译成二进制文件,尽管你可能没有直接使用过aapt工具,但是build scripts和IDE插件会使用这个工具打包apk文件构成一个Android 应用程序。

9 . Andriod APK生成过程
这里写图片描述

10 . 开发过程中android.jar存在APK中吗?
否,android.jar只是一个提供给开发者使用的API,android.jar真正才在的地方位置android系统运行环境中,即如下图
这里写图片描述
因此我们在开发的过程需要注意如下问题
1)高版本的API, 在低版本的运行库中的API; App要考虑兼容性,否则很容易crash,如低版本的运行库中的API没有包含在高版本的API
2) 低版本的API,高版本的运行库中的API; App要考虑兼容性,如方法是否废弃?原有的方法在高版本的运行库中的API是否还是原来的作用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值