Android PackageManager Service详解 (5.1源码) (一)

android基于linux,所以当设备上电后,初期的启动流程跟linux系统并无二致,uboot引导,接着载入kernel代码,加载各种驱动,结束后,启动第一个用户级进程init,init接着解析init.rc后,按照配置启动各种linux后台进程。

   整个linux系统底层已经Ready后,接着要干嘛,当然是启动android引导进程,装载android运行环境所需的代码和资源。

   Android的引导进程是Zygote,我们可以在init.rc中找到她,Zygote运行后,会创建davlik虚拟机,加载framework.jar等代码和资源;Zygote是所有android进程的父进程,这里的android进程是指需要共享dalvik虚拟机的进程。Zygote初始化结束后,会fork出她的第一个子进程system_server,即android的核心服务进程,很多轻量级的service都跑在这个进程中,这些轻量级的服务当中,包含了本章所要详细描述的PackageManager service(后续简称PMS)。

   PMS,顾名思义,一个负责安装包管理的服务,它在systemserver起来后被创建并完成初始化,它是整个android的基础,如果PMS出问题了,那整个android运行会乱掉。

  本文将会简单介绍PMS的整个实现原理,由于本人水平有限,文中肯定会存在描述不准确或者错误的地方,欢迎指正!

 

1:APK文件结构概述

  Android的安装包叫apk(android package),在介绍怎么管理它之前,肯定要先了解它的内部结构,要不在后续看PMS的相关代码时,会一头雾水。

   APK文件就是一个zip压缩文件,用解压缩软件可以看到其内部结构如下:

1) META-INF文件夹主要包含APK内所有文件的摘要清单,清单签名数据和公钥证书

2) resources.arsc和Res文件夹,resources.arsc包含APK全部资源索引数据,Res文件   夹则包含所有的资源文件

3) AndroidManifest.xml文件,包含APK的配置清单数据

4) Classes.dex文件,APK执行代码

 

1.1 资源文件相关

我们如果将APK文件解压,接着查看内部文件内容,会发现除了assets目录下的,其他所有XML文件都会被转换成二进制文件,android在打包的时候做了什么?这么做的目的是什么?

   简单描述如下:

1)  分配资源ID:

Android资源ID是一个4字节的无符号整数,最高字节表示PackageID,次高字节表示TypeID,最低两字节表示EntryID;所以,当我们拿到资源ID时,我们就知道资源所属包,以及对应的资源类型和入口ID。所有的资源ID最终都生成到R.java。

 

2)  资源索引组织管理:

   有了资源ID当然不够,资源ID对应的数据是什么?所以还需要资源索引描述表,这样才可以通过资源ID,快速的找到最匹配的资源数据;

   所有分配的资源ID都会被按照指定的数据结构重新组织并连同资源数据保存到resources.arsc中。

resources.arsc是android资源数据的核心,有了它,程序在运行时,就可以根据当前的各种环境,比如language,screensize等等来快速定位最匹配的资源;同时由于资源数据的ID化,又可以保证资源数据最大可能的复用,从而减少APK的大小。

 

3)  XML文件重新格式化:

   试想下,如果对AndroidManifest和各种布局文件,如果都是通过纯文本解析,那效率肯定是无法接受的,还有一点,文件中的各种关联数据的值如何获取?比如drawable,string等等。在上面已经描述过,resources.arsc包含了所有数据的ID和对应值,所以我们只需要根据xml内部数据,重新定义数据结构,并将xml各种值元素的key和value替换成resources.arsc中对应的id值,接着将结构化数据存储到同名文件中即可,这就是我们再在包后看到的二进制数据。

 

接着介绍程序启动后如何读取资源数据,比如我们要根据layout ID读取对应的layout文件

1) 启动后读取resources.arsc文件,得到app资源描述表数据

2) 通过资源ID的次高位字节,得到资源类型为layout,从而在app资源描述表中快速定位到layout类型的数据结构

3) 根据app当前的硬件或系统信息(language,screensize等),在layout类型的数据结构中,快速的匹配到最佳资源,从而拿到资源路径

4) 根据资源路径,获取布局文件数据

5) 根据布局文件数据,生成AttributeSet,接着根据AttributeSet的解析节点信息和对应属性ID,从app资源描述表,获取AttributeSet中当前节点下对应属性的值。

 

android默认提供了AssetManager来实现上述功能,不过我们实际开发中,更多的是使用Resource来获取资源数据,Resource基于AssetManager重新封装,提供了更加易用的接口并对指定操作做特殊处理,比如drawable,Resource会添加缓存处理等。

 

下面简单介绍几个跟PMS相关的:

1) AssetManager:

  基于APK路径来初始化,接着获取AndroidManifest.xml数据

String archiveSourcePath = “***.apk”; //apk文件路径

AssetManager assmgr = new AssetManager();

int cookie = assmgr.addAssetPath(archiveSourcePath ); //将apk加入AssetManager

if (cookie != 0) {

    //设置系统配置信息,包括语言,屏幕大小等信息

assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);

//获取AndroidManifest数据

XmlResourceParser parser =

assmgr.openXmlResourceParser(cookie, “AndroidManifest.xml”);

} else {

    Slog.w(TAG, "Failed adding asset path:"+archiveSourcePath);

}

 













2) Resource:

  根据apk路径初始化AssetManager,接着生成Resources

String archiveSourcePath = “***.apk”; //apk文件路径

AssetManager assmgr = new AssetManager();

int cookie = assmgr.addAssetPath(archiveSourcePath ); //将apk加入AssetManager

if (cookie != 0) {

    //基于AssetManager初始化Resource

Resources res = new Resources(assmgr, metrics, null);

} else {

    Slog.w(TAG, "Failed adding asset path:"+archiveSourcePath);

}

 










3) AttributeSet:

  属性集合,以下代码假设AttributeSet处于AndroidManifest的根节点,

  通过Resources的obtainAttributes函数来读取Manifest中的VersionCode和VersionName值,传入属性集和要读取属性的ID数组

   1) 通过指定对应的R.attr数组读取

XmlResourceParser parser =

assmgr.openXmlResourceParser(cookie, “AndroidManifest.xml”);

 

AttributeSet attrs = parser;

TypedArray sa = res.obtainAttributes(attributes,

            new int[]{android.R.attr.versionCode, android.R.attr.versionName});

Integer versionCode = sa.getInteger(0, 0);

String versionName = sa.getString(1, 0);

   









通过R.attr数组读取,相当于把要读取的属性ID直接写入代码中,扩展性和维护性都比较差,所以建议使用第二种方式。

   2) 通过指定styleable读取

  styleable其实就是一个属性的集合,比如AndroidManifest styleable的定义如下:

    <declare-styleable name="AndroidManifest">

        <attr name="versionCode" />

        <attr name="versionName" />

        <attr name="sharedUserId" />

        <attr name="sharedUserLabel" />

        <attr name="installLocation" />

    </declare-styleable>

   








通过使用styleable,就使目标属性集合可配置,从而提高程序的扩展性

TypedArray sa = res.obtainAttributes(attrs,

           com.android.internal.R.styleable.AndroidManifest);

pkg.mVersionCode = sa.getInteger(

                com.android.internal.R.styleable.AndroidManifest_versionCode, 0);

pkg.mVersionName = sa.getNonConfigurationString(

                com.android.internal.R.styleable.AndroidManifest_versionName, 0);







1.2 APK签名相关

Android应用在打包之后,在正式发布前,需要对其签名,签名的目的主要有两个:

1)保护APK数据的完整性

2)作为APK的身份验证

下面简单介绍下签名的原理以及APK解析时的相关验证代码。

1.2.1 消息摘要,加解密,数字签名,数字证书

下面的名词解释相关内容大部分都摘自网络

1)消息摘要

  消息摘要算法是使用一个Hash函数对任意长度的输入数据进行处理,输出固定长度的数 据。输出数据称为消息摘要。无法从消息摘要倒推出消息内容。常用的消息摘要算法是 MD5 和 SHA-1。

2)加解密

公钥加密算法又称为非对称密钥加密算法,因为它包含一个公钥-私钥对,称为key pair。即 key pair = private key +public key。从功能上说,两个key作用相同,用一个key加密的消息,只能用另一个key解密,反之亦然。

3)数字签名

 数字签名就是将原始数据的信息摘要用private key加密后,和原始数据一起发送给接收方;

 接收方收到签名数据后,先使用public key进行解密,拿到信息摘要;接着用收到的原始数据生成信息摘要,并与解密后的信息摘要做对比,如果一致,数据完整性验证通过, 否则验证失败,说明数据被修改了。

  由于无法通过publickey推导出private key,通过数字签名就可以确保发送数据额完整,再则private key只有发送者知道,这样又可以确保数据是由private key的拥有者发送的。

4)数字证书

  数字签名存在一个问题,那就是数据接收方如何获取publickey?预先把public key给接 收方或者直接和数字签名一起发送给接收方,这么做在通常情况下,肯定是没问题的,但是 在如下两种情况下,会存在安全性问题:

  1:预先把publickey 给接收方,如果有人在接收方不知情的情况下,更换了public key,然后再截取发送过来的数据并替换成自己的,这样,接收方和发送方在不知情的情况下,数据已经被人调包了。

  2:publickey连同数字签名一起发送给接收方,如果有人截取发送的数据,将数据,数字签名和public key都替换掉,这样也会产生数据调包的问题。

  所以,要解决上面安全性问题,核心就是保证发送者的publickey不被调换。

  数字证书因此而生,数字证书是指由权威机构CA认证发行的,主要包含拥有者的相关信 息和public key;所以上述发送者,只需要去CA,提供自己的相关信息和publickey做认证加密,通过后,CA会给其一本数字证书;后续发送者发送数据时,将数据用private key加密后,随同数字证书一起发出,就ok了。

1.2.2 APK签名介绍

APK在使用SignApk进行签名后,会在META-INF目录下生成如下文件:

1) MANIFEST.MF保存APK包内除了META-INF目录下文件以外其他文件的SHA-1摘
  要数据。

2) CERT.SF保存MANIFEST.MF文件的SHA-1摘要数据和MANIFEST.MF里头每一项
  的SHA-1摘要数据。

3) CERT.RSA保存有CERT.SF的数字签名和公钥证书

 

APK需要使用SignApk来进行签名,在签名前,要使用keytool生成.keystore密码仓库,接着通过aliasname和password从密码仓库中提取公私钥对供SignApk签名时使用。

 

SignApk工具代码对应系统源码路径build/tools/signapk/SignApk.java,由于main函数比较长,这里就不全部贴出,重点介绍下几个代码段

 

1)    生成MANIFEST.MF

//遍历APK内部文件,获取SHA-1摘要数据并base64编码

Manifest manifest = addDigestsToManifest(inputJar, hashes);

// MANIFEST.MF

JarEntry je = new JarEntry(JarFile.MANIFEST_NAME);

je.setTime(timestamp);

outputJar.putNextEntry(je);

manifest.write(outputJar);

 

2)    生成CERT.SF

je = new JarEntry(numKeys == 1 ? CERT_SF_NAME :

(String.format(CERT_SF_MULTI_NAME, k)));

je.setTime(timestamp);

outputJar.putNextEntry(je);

ByteArrayOutputStream baos = new ByteArrayOutputStream();

writeSignatureFile(manifest, baos, getDigestAlgorithm(publicKey[k]));

byte[] signedData = baos.toByteArray();

outputJar.write(signedData);

writeSignatureFile负责将manifest数据项重新生成SHA-1摘要并写到baos输出流,最终baos数据写入CERT.SF

 

3)    生成CERT.RSA

4)    /** Sign data and write the digital signature to 'out'. */

5)        private static void writeSignatureBlock(

6)            CMSTypedData data, X509Certificate publicKey, PrivateKey privateKey,

7)            OutputStream out)

8)            throws IOException,

9)                   CertificateEncodingException,

10)                OperatorCreationException,

11)                CMSException {

12)         ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>(1);

13)         certList.add(publicKey);

14)         JcaCertStore certs = new JcaCertStore(certList);

15)  

16)         CMSSignedDataGenerator gen = new CMSSignedDataGenerator();

17)         ContentSigner signer = new JcaContentSignerBuilder(getSignatureAlgorithm(publicKey))

18)             .setProvider(sBouncyCastleProvider)

19)             .build(privateKey);

20)         gen.addSignerInfoGenerator(

21)             new JcaSignerInfoGeneratorBuilder(

22)                 new JcaDigestCalculatorProviderBuilder()

23)                 .setProvider(sBouncyCastleProvider)

24)                 .build())

25)             .setDirectSignature(true)

26)             .build(signer, publicKey));

27)         gen.addCertificates(certs);

28)         CMSSignedData sigData = gen.generate(data, false);

29)  

30)         ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded());

31)         DEROutputStream dos = new DEROutputStream(out);

32)         dos.writeObject(asn1.readObject());

33)     }

重点看标红的两个函数,addSignerInfoGenerator将CERT.SF数据用私钥加密后存入CERT.RSA中;addCertificates则将公钥证书保存到CERT.RSA中。

 

到这里,很多人可以会有疑问,为什么要产生CERT.SF这个摘要文件,好像没啥必要啊,直接用MANIFEST.MF不就可以了;细细的思考下,好像的确是这么回事,但是我们还是要相信Google这么做是有原因的,有可能是为了APK校验时效率更高?异或是为了留个口子,便于后续扩展,这里就不做深究,咱们继续往下走

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值