apk安装包介绍(下载安装,存储的位置,路径,可以对里面的文件进行修改吗)

Android中apk文件的结构解析

APK是AndroidPackage的缩写,即Android安装包(apk)。APK是类似Symbian Sis或Sisx的文件格式。通过将APK文件直接传到Android模拟器或Android手机中执行即可安装。apk文件和sis一样,把android sdk编译的工程打包成一个安装程序文件,格式为apk。 APK文件其实是zip格式,但后缀名被修改为apk,可以通过winrar等解压工具进行解压缩,进而进行汉化。

APK文件的结构如下图:

- META-INF
- res
   - anim
   - color
   - drawable
   - drawable-hdpi
   - drawable-land
   - drawable-land-hdpi
   - drawable-mdpi
   - drawable-port
   - drawable-port-hdpi
   - layout
   - layout-land
   - layout-port
   - xml
- AndroidManifest.xml
- classes.dex
- resources.arsc

1、Manifest文件

AndroidManifest.xml是每个应用都必须定义和包含的,它描述了应用的名字、版本、权限、引用的库文件等信息,如要把apk上传到Google Market上,也要对这个xml做一些配置。网上已有很多资料,在此就不多做介绍了。

在apk中的AndroidManifest.xml是经过压缩的,可以通过AXMLPrinter2工具解开,具体命令为:

java -jar AXMLPrinter2.jar AndroidManifest.xml。

2、META-INF目录

META-INF目录下存放的是签名信息,用来保证apk包的完整性和系统的安全。

在eclipse编译生成一个apk包时,会对所有要打包的文件做一个校验计算,并把计算结果放在META-INF目录下。而在Android手机上安装apk包时,应用管理器会按照同样的算法对包里的文件做校验,如果校验结果与META-INF下的内容不一致,系统就不会安装这个apk。这就保证了apk包里的文件不能被随意替换。比如拿到一个apk包后,如果想要替换里面的一幅图片,一段代码, 或一段版权信息,想直接解压缩、替换再重新打包,基本是不可能的。如此一来就给病毒感染和恶意修改增加了难度,有助于保护系 统的安全。

软件修改后需要将里面的证书文件删除(***.RSA、***.SF、***.MF三个文件),否则软件无法安装。

3、classes.dex文件

在Android系统中,dex文件是可以直接在Dalvik虚拟机中加载运行的文件。通过ADT,经过复杂的编译,可以把java源代码转换为dex文 件。 那么这个文件的格式是什么样的呢?为什么Android不直接使用class文件,而采用这个不一样文件呢?其实它是针对嵌入式系统优化的结 果,Dalvik虚拟机的指令码并不是标准的Java虚拟机指令码,而是使用了自己独有的一套指令集。如果有自己的编译系统,可以不生成class文件, 直接生成dex文件。dex文件中共用了很多类名称、常量字符串,使它的体积比较小,运行效率也比较高。但归根到底,Dalvik还是基于寄存器的虚拟机 的一个实现。

文件头(File Header)

Dex文件头主要包括校验和以及其他结构的偏移地址和长度信息。

字段名称偏移值长度描述
magic 0x0 8 'Magic'值,即魔数字段,格式如”dex/n035/0”,其中的035表示结构的版本。
checksum 0x8 4 校验码。
signature 0xC 20 SHA-1签名。
file_size 0x20 4 Dex文件的总长度。
header_size 0x24 4 文件头长度,009版本=0x5C,035版本=0x70。
endian_tag 0x28 4 标识字节顺序的常量,根据这个常量可以判断文件是否交换了字节顺序,缺省情况下=0x78563412。
link_size 0x2C 4 连接段的大小,如果为0就表示是静态连接。
link_off 0x30 4 连接段的开始位置,从本文件头开始算起。如果连接段的大小为0,这里也是0。
map_off 0x34 4 map数据基地址。
string_ids_size 0x38 4 字符串列表的字符串个数。
string_ids_off 0x3C 4 字符串列表表基地址。
type_ids_size 0x40 4 类型列表里类型个数。
type_ids_off 0x44 4 类型列表基地址。
proto_ids_size 0x48 4 原型列表里原型个数。
proto_ids_off 0x4C 4 原型列表基地址。
field_ids_size 0x50 4 字段列表里字段个数。
field_ids_off 0x54 4 字段列表基地址。
method_ids_size 0x58 4 方法列表里方法个数。
method_ids_off 0x5C 4 方法列表基地址。
class_defs_size 0x60 4 类定义类表中类的个数。
class_defs_off 0x64 4 类定义列表基地址。
data_size 0x68 4 数据段的大小,必须以4字节对齐。
data_off 0x6C 4 数据段基地址

魔数字段

魔数字段,主要就是Dex文件的标识符,它占用4个字节,在目前的源码里是 “dex ”,它的作用主要是用来标识dex文件的,比如有一个文件也以dex为后缀名,仅此并不会被认为是Davlik虚拟机运行的文件,还要判断这 四个字节。另外Davlik虚拟机也有优化的Dex,也是通过个字段来区分的,当它是优化的Dex文件时,它的值就变成”dey ”了。根据这四个字 节,就可以识别不同类型的Dex文件了。

跟在“dex ”后面的是版本字段,主要用来标识Dex文件的版本。目前支持的版本号为“035”,不管是否优化的版本,都是使用这个版本号。

检验码字段

主要用来检查从这个字段开始到文件结尾,这段数据是否完整,有没有人修改过,或者传送过程中是否有出错等等。通常用来检查数据是否完整的算法,有 CRC32、有SHA128等,但这里采用并不是这两类,而采用一个比较特别的算法,叫做adler32,这是在开源zlib里常用的算法,用来检查文件 是否完整性。该算法由MarkAdler发明,其可靠程度跟CRC32差不多,不过还是弱一点点,但它有一个很好的优点,就是使用软件来计算检验码时比较 CRC32要快很多。可见Android系统,就算法上就已经为移动设备进行优化了。

SHA-1签名字段

dex文件头里,前面已经有了面有一个4字节的检验字段码了,为什么还会有SHA-1签名字段呢?不是重复了吗?可是仔细考虑一下,这样设计自有道理。因 为dex文件一般都不是很小,简单的应用程序都有几十K,这么多数据使用一个4字节的检验码,重复的机率还是有的,也就是说当文件里的数据修改了,还是很 有可能检验不出来的。这时检验码就失去了作用,需要使用更加强大的检验码,这就是SHA-1。SHA-1校验码有20个字节,比前面的检验码多了16个字 节,几乎不会不同的文件计算出来的检验是一样的。设计两个检验码的目的,就是先使用第一个检验码进行快速检查,这样可以先把简单出错的dex文件丢掉了, 接着再使用第二个复杂的检验码进行复杂计算,验证文件是否完整,这样确保执行的文件完整和安全。

SHA(Secure Hash Algorithm, 安全散列算法)是美国国家安全局设计,美国国家标准与技术研究院发布的一系列密码散列函数。SHA-1看起来和MD5算法很像,也许是Ron Rivest在SHA-1的设计中起了一定的作用。SHA-1的内部比MD5更强,其摘要比MD5的16字节长4个字节,这个算法成功经受了密码分析专家 的攻击,也因而受到密码学界的广泛推崇。这个算法在目前网络上的签名,BT软件里就有大量使用,比如在BT里要计算是否同一个种子时,就是利用文件的签名 来判断的。同一份8G的电影从几千BT用户那里下载,也不会出现错误的数据,导致电影不播放。

map_off字段

这个字段主要保存map开始位置,就是从文件头开始到map数据的长度,通过这个索引就可以找到map数据。map的数据结构如下:

名称大小说明
size 4字节 map里项的个数
list 变长 每一项定义为12字节,项的个数由上面项大小决定。

4、res目录

res目录存放资源文件。

res/anim/

XML文件,它们被编译进逐帧动画(frame by frameanimation)或补间动画(tweenedanimation)对象

res/drawable/

.png、.9.png、.jpg文件,它们被编译进以下的Drawable资源子类型中:

要获得这种类型的一个资源,可以使用Resource.getDrawable(id)

位图文件

9-patches(可变尺寸的位图)

为了获取资源类型,使用mContext.getResources().getDrawable(R.drawable.imageId)

注意:放在这里的图像资源可能会被aapt工具自动地进行无损压缩优化。比如,一个真彩色但并不需要256色的PNG可能会被转换为一个带调色板的8位PNG。这使得同等质量的图片占用更少的资源。所以我们得意识到这些放在该目录下的二进制图像在生成时可能会发生变化。如果你想读取一个图像位流并转换成一个位图(bitmap),请把图像文件放在res/raw/目录下,这样可以避免被自动优化。

res/layout/

被编译为屏幕布局(或屏幕的一部分)的XML文件。参见布局声明(Declaring Layout)

res/values/

可以被编译成很多种类型的资源的XML文件。

注意:
不像其他的res/文件夹,它可以保存任意数量的文件,这些文件保存了要创建资源的描述,而不是资源本身。XML元素类型控制这些资源应该放在R类的什么地方。


尽管这个文件夹里的文件可以任意命名,不过下面使一些比较典型的文件(文件命名的惯例是将元素类型包含在该名称之中):


array.xml 定义数组


colors.xml 定义colordrawable和颜色的字符串值(color stringvalues)。使用Resource.getDrawable()和Resources.getColor()分别获得这些资源。


dimens.xml定义尺寸值(dimensionvalue)。使用Resources.getDimension()获得这些资源。


strings.xml定义字符串(string)值。使用Resources.getString()或者Resources.getText()获取这些资源。getText()会保留在UI字符串上应用的丰富的文本样式。


styles.xml 定义样式(style)对象。

res/xml/

任意的XML文件,在运行时可以通过调用Resources.getXML()读取。

res/raw/

assets/

直接复制到设备中的任意文件。它们无需编译,添加到你的应用程序编译产生的压缩文件中。要使用这些资源,可以调用Resources.openRawResource(),参数是资源的ID,即R.raw.somefilename

assets下也可以放置任意文件,使用Activity.this.getAssets().open("519.txt");打开


*res/raw和assets的相同点:

1.两者目录下的文件在打包后会原封不动的保存在apk包中,不会被编译成二进制。
  *res/raw和assets的不同点:
1.res/raw中的文件会被映射到R.java文件中,访问的时候直接使用资源ID即R.id.filename;assets文件夹下的文件不会被映射到R.java中,访问的时候需要AssetManager类。
2.res/raw不可以有目录结构,而assets则可以有目录结构,也就是assets目录下可以再建立文件夹


5、resources.arsc

编译后的二进制资源文件。通常本地化、汉化资源存储在该文件文件中。

apk下载安装,存储的位置,路径,可以对里面的文件进行修改吗?

luo_text 发布于 2013/09/22 10:14
 
阅读 25K+
 
 收藏 2
 
 答案 2
Android jQuery PhoneGap
如题,当我下载了apk后,我安装了。
那么这个apk是怎么运行的?根据classes.dex文件嘛?
我现在想直接将这个apk里面的某个文件进行修改,又可行吗?
能告诉下原理吗?谢谢大神哈。
最后就是想要获取下这个apk的路径,在手机上的位置。
 
 
收藏 (2)
举报
 
0
 
 
Liberxue
Liberxue 

来自华为内部资料

 PackageInstaller 原理简述

应用安装是智能机的主要特点,即用户可以把各种应用(如游戏等)安装到手机上,并可以对其进行卸载等管理操作。APK是Android Package的缩写,即Android安装包。APK是类似Symbian Sis或Sisx的文件格式。通过将APK文件直接传到Android模拟器或Android手机中执行即可安装。

Android应用安装有如下四种方式

1.        系统应用安装――开机时完成,没有安装界面

2.        网络下载应用安装――通过market应用完成,没有安装界面

3.        ADB工具安装――没有安装界面。

4.        第三方应用安装――通过SD卡里的APK文件安装,有安装界面,由packageinstaller.apk应用处理安装及卸载过程的界面。

应用安装的流程及路径
应用安装涉及到如下几个目录:

system/app
 系统自带的应用程序,无法删除
 
data/app
 用户程序安装的目录,有删除权限。

安装时把apk文件复制到此目录
 
data/data
 存放应用程序的数据
 
Data/dalvik-cache
 将apk中的dex文件安装到dalvik-cache目录下(dex文件是dalvik虚拟机的可执行文件,其大小约为原始apk文件大小的四分之一)
 

       安装过程:复制APK安装包到data/app目录下,解压并扫描安装包,把dex文件(Dalvik字节码)保存到dalvik-cache目录,并data/data目录下创建对应的应用数据目录。

       卸载过程:删除安装过程中在上述三个目录下创建的文件及目录。


 

一、系统应用安装:
PackageManagerService处理各种应用的安装,卸载,管理等工作,开机时由systemServer启动此服务

(源文件路径:androidframeworksaseservicesjavacomandroidserverPackageManagerService.java)

PackageManagerService服务启动的流程:

1. 首先扫描安装“systemframework”目录下的jar包

1. scanDirLI(mFrameworkDir,PackageParser.PARSE_IS_SYSTEM,

                    scanMode | SCAN_NO_DEX);


2.第二步扫描安装“systemapp”目录下的各个系统应用

scanDirLI(mSystemAppDir,PackageParser.PARSE_IS_SYSTEM, scanMode);


3.第三步扫描“dataapp”目录,即用户安装的第三方应用

scanDirLI(mAppInstallDir, 0, scanMode);


4.第四步扫描" dataapp-private"目录,即安装DRM保护的APK文件(目前没有遇到过此类的应用)。

scanDirLI(mDrmAppPrivateInstallDir,0, scanMode | SCAN_FORWARD_LOCKED);

安装应用的过程

1.scanDirLI(Filedir, int flags, int scanMode) 遍历安装指定目录下的文件

2.scanPackageLI(FilescanFile,

            File destCodeFile, FiledestResourceFile, int parseFlags,

            int scanMode)                安装package文件

3.scanPackageLI(

        File scanFile, File destCodeFile, FiledestResourceFile,

        PackageParser.Package pkg, intparseFlags, int scanMode)

通过解析安装包parsePackage获取到安装包的信息结构

4.mInstaller.install(pkgName,pkg.applicationInfo.uid,

              pkg.applicationInfo.uid);   实现文件复制的安装过程

(源文件路径:frameworksasecmdsinstalldinstalld.install)


二、从market上下载应用:
Google Market应用需要使用gmail账户登录才可以使用,选择某一应用后,开始下载安装包,此过程中,在手机的信号区有进度条提示,下载完成后,会自动调用Packagemanager的接口安装,调用接口如下:

public voidinstallPackage(final Uri packageURI, final IPackageInstallObserver observer,final int flags)

final Uri packageURI:文件下载完成后保存的路径

final IPackageInstallObserver observer:处理返回的安装结果

final int flags:安装的参数,从market上下载的应用,安装参数为-r (replace)

installPackage接口函数的安装过程:

1.public voidinstallPackage(

            final Uri packageURI, final IPackageInstallObserverobserver, final int flags,

            final String installerPackageName)

final StringinstallerPackageName:安装完成后此名称保存在settings里,一般为null,不是关键参数

2.FiletmpPackageFile = copyTempInstallFile(packageURI, res);

把apk文件复制到临时目录下的临时文件

3.private voidinstallPackageLI(Uri pPackageURI,

            int pFlags, boolean newInstall,String installerPackageName,

           File tmpPackageFile, PackageInstalledInfo res)

解析临时文件,获取应用包名pkgName = PackageParser.parsePackageName(

                   tmpPackageFile.getAbsolutePath(), 0);

4.判断如果带有参数INSTALL_REPLACE_EXISTING,则调用replacePackageLI(pkgName,

                        tmpPackageFile,

                        destFilePath,destPackageFile, destResourceFile,

                        pkg, forwardLocked,newInstall, installerPackageName,

                        res)

5.如果没有,则调用installNewPackageLI(pkgName,

                        tmpPackageFile,

                        destFilePath,destPackageFile, destResourceFile,

                        pkg,forwardLocked, newInstall, installerPackageName,

                        res);

6.privatePackageParser.Package scanPackageLI(

        File scanFile, File destCodeFile, FiledestResourceFile,

        PackageParser.Package pkg, intparseFlags, int scanMode)

scanPackageLI以后的流程,与开机时的应用安装流程相同。

三、从ADB工具安装
Android Debug Bridge (adb) 是SDK自带的管理设备的工具,通过ADB命令行的方式也可以为手机或模拟器安装应用,其入口函数源文件为pm.java

(源文件路径:androidframeworksasecmdspmsrccomandroidcommandspmpm.java)

ADB命令行的形式为adb install <path_to_apk> ,还可以带安装参数如:"-l""-r" "-i" "-t"

函数runInstall()中判断参数

"-l"――INSTALL_FORWARD_LOCK

 "-r"——INSTALL_REPLACE_EXISTING 

"-i" ——installerPackageName

"-t"——INSTALL_ALLOW_TEST

我们常用的参数为-r,表示覆盖安装手机上已安装的同名应用。从market上下载的应用,也是直接传入这个参数安装的。

runInstall与market调用同样的接口完成应用安装。

public voidinstallPackage(android.net.Uri packageURI,android.content.pm.IPackageInstallObserver observer, int flags,java.lang.String installerPackageName)

四、第三方应用安装――通过SD卡里的APK文件安装
把APK安装包保存在SD卡中,从手机里访问SD卡中的APK安装包,点击就可以启动安装界面,系统应用Packageinstaller.apk处理这种方式下的安装及卸载界面流程,如下图:


PackageInstallerActivity负责解析包,判断是否是可用的Apk文件

创建临时安装文件/data/data/com.android.packageinstaller/files/ApiDemos.apk

并启动安装确认界面startInstallConfirm,列出解析得到的该应用基本信息。如果手机上已安装有同名应用,则需要用户确认是否要替换安装。

确认安装后,启动InstallAppProgress,调用安装接口完成安装。

pm.installPackage(mPackageURI,observer, installFlags);

其它:
1. PackageManagerService.java的内部类AppDirObserver实现了监听app目录的功能:当把某个APK拖到app目录下时,可以直接调用scanPackageLI完成安装。

2.手机数据区目录“data/system/packages.xml”文件中,包含了手机上所有已安装应用的基本信息,如安装路径,申请的permission等信息。


 


 

http://wenku.baidu.com/view/3a64c3c6bb4cf7ec4afed05f.html

APK文件结构详解

http://www.ophonesdn.com/article/show/38

 评论 (2)   举报
Liberxue
Liberxue 
@luo_text 可以发编译的 但是不知道你是否能处理好 容易乱码 网站apk文件发编译好多方法 但是签名不好处理 goodluck
 回复  举报
luo_text
luo_text 
你好,谢谢你的这份资料,但是可以再说说,这个apk文件内的东西我可以修改吗?
 回复  举报
 
0
 
 
luo_text
luo_text 

引用来自“cvsyun便利云”的答案

来自华为内部资料

 PackageInstaller 原理简述

应用安装是智能机的主要特点,即用户可以把各种应用(如游戏等)安装到手机上,并可以对其进行卸载等管理操作。APK是Android Package的缩写,即Android安装包。APK是类似Symbian Sis或Sisx的文件格式。通过将APK文件直接传到Android模拟器或Android手机中执行即可安装。

Android应用安装有如下四种方式

1.        系统应用安装――开机时完成,没有安装界面

2.        网络下载应用安装――通过market应用完成,没有安装界面

3.        ADB工具安装――没有安装界面。

4.        第三方应用安装――通过SD卡里的APK文件安装,有安装界面,由packageinstaller.apk应用处理安装及卸载过程的界面。

应用安装的流程及路径
应用安装涉及到如下几个目录:

system/app
 系统自带的应用程序,无法删除
 
data/app
 用户程序安装的目录,有删除权限。

安装时把apk文件复制到此目录
 
data/data
 存放应用程序的数据
 
Data/dalvik-cache
 将apk中的dex文件安装到dalvik-cache目录下(dex文件是dalvik虚拟机的可执行文件,其大小约为原始apk文件大小的四分之一)
 

       安装过程:复制APK安装包到data/app目录下,解压并扫描安装包,把dex文件(Dalvik字节码)保存到dalvik-cache目录,并data/data目录下创建对应的应用数据目录。

       卸载过程:删除安装过程中在上述三个目录下创建的文件及目录。


 

一、系统应用安装:
PackageManagerService处理各种应用的安装,卸载,管理等工作,开机时由systemServer启动此服务

(源文件路径:androidframeworksaseservicesjavacomandroidserverPackageManagerService.java)

PackageManagerService服务启动的流程:

1. 首先扫描安装“systemframework”目录下的jar包

1. scanDirLI(mFrameworkDir,PackageParser.PARSE_IS_SYSTEM,

                    scanMode | SCAN_NO_DEX);


2.第二步扫描安装“systemapp”目录下的各个系统应用

scanDirLI(mSystemAppDir,PackageParser.PARSE_IS_SYSTEM, scanMode);


3.第三步扫描“dataapp”目录,即用户安装的第三方应用

scanDirLI(mAppInstallDir, 0, scanMode);


4.第四步扫描" dataapp-private"目录,即安装DRM保护的APK文件(目前没有遇到过此类的应用)。

scanDirLI(mDrmAppPrivateInstallDir,0, scanMode | SCAN_FORWARD_LOCKED);

安装应用的过程

1.scanDirLI(Filedir, int flags, int scanMode) 遍历安装指定目录下的文件

2.scanPackageLI(FilescanFile,

            File destCodeFile, FiledestResourceFile, int parseFlags,

            int scanMode)                安装package文件

3.scanPackageLI(

        File scanFile, File destCodeFile, FiledestResourceFile,

        PackageParser.Package pkg, intparseFlags, int scanMode)

通过解析安装包parsePackage获取到安装包的信息结构

4.mInstaller.install(pkgName,pkg.applicationInfo.uid,

              pkg.applicationInfo.uid);   实现文件复制的安装过程

(源文件路径:frameworksasecmdsinstalldinstalld.install)


二、从market上下载应用:
Google Market应用需要使用gmail账户登录才可以使用,选择某一应用后,开始下载安装包,此过程中,在手机的信号区有进度条提示,下载完成后,会自动调用Packagemanager的接口安装,调用接口如下:

public voidinstallPackage(final Uri packageURI, final IPackageInstallObserver observer,final int flags)

final Uri packageURI:文件下载完成后保存的路径

final IPackageInstallObserver observer:处理返回的安装结果

final int flags:安装的参数,从market上下载的应用,安装参数为-r (replace)

installPackage接口函数的安装过程:

1.public voidinstallPackage(

            final Uri packageURI, final IPackageInstallObserverobserver, final int flags,

            final String installerPackageName)

final StringinstallerPackageName:安装完成后此名称保存在settings里,一般为null,不是关键参数

2.FiletmpPackageFile = copyTempInstallFile(packageURI, res);

把apk文件复制到临时目录下的临时文件

3.private voidinstallPackageLI(Uri pPackageURI,

            int pFlags, boolean newInstall,String installerPackageName,

           File tmpPackageFile, PackageInstalledInfo res)

解析临时文件,获取应用包名pkgName = PackageParser.parsePackageName(

                   tmpPackageFile.getAbsolutePath(), 0);

4.判断如果带有参数INSTALL_REPLACE_EXISTING,则调用replacePackageLI(pkgName,

                        tmpPackageFile,

                        destFilePath,destPackageFile, destResourceFile,

                        pkg, forwardLocked,newInstall, installerPackageName,

                        res)

5.如果没有,则调用installNewPackageLI(pkgName,

                        tmpPackageFile,

                        destFilePath,destPackageFile, destResourceFile,

                        pkg,forwardLocked, newInstall, installerPackageName,

                        res);

6.privatePackageParser.Package scanPackageLI(

        File scanFile, File destCodeFile, FiledestResourceFile,

        PackageParser.Package pkg, intparseFlags, int scanMode)

scanPackageLI以后的流程,与开机时的应用安装流程相同。

三、从ADB工具安装
Android Debug Bridge (adb) 是SDK自带的管理设备的工具,通过ADB命令行的方式也可以为手机或模拟器安装应用,其入口函数源文件为pm.java

(源文件路径:androidframeworksasecmdspmsrccomandroidcommandspmpm.java)

ADB命令行的形式为adb install <path_to_apk> ,还可以带安装参数如:"-l""-r" "-i" "-t"

函数runInstall()中判断参数

"-l"――INSTALL_FORWARD_LOCK

 "-r"——INSTALL_REPLACE_EXISTING 

"-i" ——installerPackageName

"-t"——INSTALL_ALLOW_TEST

我们常用的参数为-r,表示覆盖安装手机上已安装的同名应用。从market上下载的应用,也是直接传入这个参数安装的。

runInstall与market调用同样的接口完成应用安装。

public voidinstallPackage(android.net.Uri packageURI,android.content.pm.IPackageInstallObserver observer, int flags,java.lang.String installerPackageName)

四、第三方应用安装――通过SD卡里的APK文件安装
把APK安装包保存在SD卡中,从手机里访问SD卡中的APK安装包,点击就可以启动安装界面,系统应用Packageinstaller.apk处理这种方式下的安装及卸载界面流程,如下图:


PackageInstallerActivity负责解析包,判断是否是可用的Apk文件

创建临时安装文件/data/data/com.android.packageinstaller/files/ApiDemos.apk

并启动安装确认界面startInstallConfirm,列出解析得到的该应用基本信息。如果手机上已安装有同名应用,则需要用户确认是否要替换安装。

确认安装后,启动InstallAppProgress,调用安装接口完成安装。

pm.installPackage(mPackageURI,observer, installFlags);

其它:
1. PackageManagerService.java的内部类AppDirObserver实现了监听app目录的功能:当把某个APK拖到app目录下时,可以直接调用scanPackageLI完成安装。

2.手机数据区目录“data/system/packages.xml”文件中,包含了手机上所有已安装应用的基本信息,如安装路径,申请的permission等信息。


 


 

http://wenku.baidu.com/view/3a64c3c6bb4cf7ec4afed05f.html

APK文件结构详解

http://www.ophonesdn.com/article/show/38

就是这样的,我想问下就是apk安装后, 将apk中的dex文件安装到dalvik-cache目录下(dex文件是dalvik虚拟机的可执行文件,其大小约为原始apk文件大小的四分之一)。那么这个文件夹里面包含的类、配置文件、其他的文件。这个文件夹在什么位置?里面的内容在手机这边可以修改的吗?而不是在编译这个阶段修改。
转发:https://www.oschina.net/question/1249832_126587

解决APK下载到Cache目录安装提示“解析安装包失败”的问题

下载的APK在使用下面代码安装的时候提示解析安装包失败,APK本身没有问题,使用手机助手或者adb install 都可以正常安装。
 /**
     * 安装应用程序
     */
    public void installAPK(String path) {
        File apkfile = new File(path);
        if (!apkfile.exists())
            return;
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true); //表明不是未知来源
        intent.setDataAndType(Uri.parse("file://" + apkfile.toString()),
                "application/vnd.android.package-archive");
        ((Activity)context).startActivityForResult(intent, 0);
    }
截取的Log如下
从日志里可以看出来是PermissonDenied,failed to open, Unable to read。测试后发现文件存储在程序Cache目录下才会出现这个问题,存储在SD卡并不报错, 所以在installApk方法中加一个chmod方法更改文件的访问权限, 问题解决!
 /**
     * 安装应用程序
     */
    public void installAPK(String path) {
        File apkfile = new File(path);
        if (!apkfile.exists())
            return;
        chmod("777", path); //更改文件权限
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true); //表明不是未知来源
        intent.setDataAndType(Uri.parse("file://" + apkfile.toString()),
                "application/vnd.android.package-archive");
        ((Activity)context).startActivityForResult(intent, 0);
    }
/**
    * 获取权限
    *
    * @param permission
    *            权限
    * @param path
    *            路径
    */
    public  void chmod(String permission, String path) {
    try {
    String command = "chmod " + permission + " " + path;
    Runtime runtime = Runtime.getRuntime();
    runtime.exec(command);
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
 
转发:https://blog.csdn.net/qust_lizhijun/article/details/54880201

APK安装流程详解11——普通应用安装简介

本片文章的主要内容如下:
  • 1、概述
  • 2、Android应用程序的几种安装方式
  • 3、应用安装涉及到的目录
  • 4、安装流程概述
  • 5、PackageInstaller.apk与PackageManger
  • 6、普通的APK安装方式的界面
  • 7、PackageInstallerActivity类的安装流程
  • 8、InstallAppProgress类的安装流程
  • 9、InstallAppProgress中涉及到PackageManager的三个方法

一、 概述

众所周知,Android应用最终是打包成.apk格式(其实就是一个压缩包),然后安装至手机并运行的。其中APK是Android Package的缩写。

Android系统在启动的过程中,会启动一个引用程序管理服务PackageManagerService,这个服务负责扫描系统中特定的目录,找到里面的应用程序文件,以.apk为后缀的文件,然后对这些文件进行解析,得到引用程序的相关信息,完成应用程序的安装过程。应用程序管理服务PackageManagerService安装应用程序的过程,其实就是解析应用程序配置文件的AndroidManifest.xml的过程,并从里面得到应用程序的相关信息,例如得到引用程序的组件Activity、Service、Receiver和Content Provider等信息,有了这些信息后,通过ActivityManagerService这个服务,我们就可以在系统中正常地使用这些应用程序了。

二、Android应用程序的几种安装方式

Android上应用安装可以分为以下几种方式:

  • 1、系统安装:开机的时候,没有安装界面
  • 2、adb 命令安装:通过abd命令行安装,没有安装界面
  • 3、应用市场安装,这个要视应用的权限,有系统的权限无安装界面(例如MUI的小米应用商店)
  • 4、第三方安装,有安装界面,通过packageinstaller.apk来处理安装及卸载的过程的界面

三、应用安装涉及到的目录

  • /system/app:系统自带的应用程序,获得adb root 权限才能删除
  • /data/app:用户程序安装的目录。安装时把apk文件复制到此目录
  • /data/data:存放应用程序的数据
  • /data/dalvik-cache:将apk中的dex文件安装到dalvik-cache目录下(dex文件是dalvik虚拟机的可执行文件,当然,ART-Android Runtime的可执行文件格式为.oat,启动ART时,系统会执行dex文件转换至oat文件)
  • /data/system:该目录下的packages.xml文件。类似于Window的注册表,这个文件是解析apk时由writeLP()创建的,里面记录了系统的permissons,以及每个apk的name,codePath,flag,ts,version,userid等信息,这些信息主要通过apk的AndroidManifest解析获取,解析完apk后将更新信息写入这个文件并保存到flash,下次开机的时候直接从里面读取相关信息并添加到内存相关列表中。当有apk升级,安装或删除时会更新这个文件。 -/data/system/package.xml与/data/system/package.list:packages.list指定了应用的默认存储位置/data/data/com.xxx.xxx;package.xml中包含了该应用申请的权限、签名和代码所在的位置等信息系,并且两者都有同一个userld。之所以每个应用都要一个userId,是因为Android在系统设计上把每个应用当做Linux系统上的一个用户对待,这样就可以利用已有的Linux用户管理机制来设计Android应用,比如应用目录,应用权限,应用进程管理等。

四、安装流程概述

apk的大体流程如下:

  • 第一步:拷贝文件到指定的目录: 在Android系统中,apk安装文件是会被保存起来的,默认情况下,用户安装的apk首先会被拷贝到/data/app目录下,/data/app目录是用户有权限访问的目录,在安装apk的时候会自动选择该目录存放用户安装的文件,而系统出场的apk文件则被放到了/system分区下,包括/system/app,/system/vendor/app,以及/system/priv-app等等,该分区只有ROOT权限的用户才能访问,这也就是为什么在没有Root手机之前,我们没法删除系统出场的app的原因了。
  • 第二步:解压缩apk,宝贝文件,创建应用的数据目录 为了加快app的启动速度,apk在安装的时候,会首先将app的可执行文件dex拷贝到/data/dalvik-cache目录,缓存起来。然后,在/data/data/目录下创建应用程序的数据目录(以应用的包名命名),存放在应用的相关数据,如数据库、xml文件、cache、二进制的so动态库等。
  • 第三步:解析apk的AndroidManifest.xml文件

Android系统中,也有一个类似注册表的东西,用来记录当前所有安装的应用的基本信息,每次系统安装或者卸载了任何apk文件,都会更新这个文件。这个文件位于如下目录:/data/system/packages.xml。系统在安装这个apk的过程中,会解析apk的AndroidManifest.xml文件,提取出这个apk的重要信息写入到packages.xml文件中,这些信息包括:权限、应用包名、APK的安装位置、版本、userID等等。由此,我们就知道了为什么一些应用市场和软件管理类的app能够很清楚地知道当前手机所安装的所有app,以及这些app的详细信息了。另外一件事就是Linux的用户Id和用户组Id,以便他们可以获得合适的运行权限。以上都是由PackageServcieManager完成的,后面我们会重点介绍PackageServiceManager。

  • 第四步:显示快捷方式 如果这些应用程序在PackageManagerService服务注册好了,如果我们想要在Android桌米上看到这些应用程序,还需要有一个Home应用程序,负责从PackageManagerService服务中把这些安装好的应用程序取出来,并以友好的方式在桌面上展现出来,例如以快捷图标的形式。在Android系统中,负责把系统中已经安装的应用程序在桌面中展现出来的Home应用就是Launcher了。

五、PackageInstaller.apk与PackageManger

PackageInstaller.apk地址

PackageInstaller/AndroidManifest.xml.png

(一)、PackageInstaller概述

PackagInstaller是安卓上默认的应用程序,用它来安装普通文件。PackageInstaller提供了用户界面来管理应用或者包文件。PackageInstaller调用一个叫做InstallAppProgress的activity来获取用户发出的指令。InstallAppProgress会请求Package Manager服务,然后通过installed来安装包文件。

installed这个守护进程的首要角色就是获取来自Package Manager服务的请求,而该请求是通过Linux套接字/dev/socket/installed获得的。installed使用管理员权限执行一系列步骤来安装APK。

(二)、PackageInstaller内容解析

PackageInstaller的结构如下:

PackageInstaller结构1.png

PackageInstaller结构2.png

这里面重点介绍以下两个类

  • PackageInstallerActivity:主要是检查各种权限,展示被安装应用的向相关信息,最后跳转到实际安装应用的InstallAppProgress
  • InstallAppProgress:也是进行了一系列的操作,最终把安装交给了PackageManager.installPackageWithVerificationAndEncryption(mPackageURI, observer, installFlags, installerPackageName, verificationParams, null);

下面我们就来看看

六、普通的APK安装方式的界面

普通的APK安装方式 一般是经过下面的两个界面的

image.png

上面的两个界面分别是PackageInstallerActivity和InstallAppProgress

七、PackageInstallerActivity类的安装流程

(一)、PackageInstallerActivity类

/*
 * This activity is launched when a new application is installed via side loading
 * The package is first parsed and the user is notified of parse errors via a dialog.
 * If the package is successfully parsed, the user is notified to turn on the install unknown
 * applications setting. A memory check is made at this point and the user is notified of out
 * of memory conditions if any. If the package is already existing on the device,
 * a confirmation dialog (to replace the existing package) is presented to the user.
 * Based on the user response the package is then installed by launching InstallAppConfirm
 * sub activity. All state transitions are handled in this activity
 */
public class PackageInstallerActivity extends Activity implements OnCancelListener, OnClickListener {
   ...
   ...
}

我们是知道PackageInstallerActivity是一个Activity并且实现了OnCancelListener和OnClickListener接口,下面我们来看一下注释。

当通过渠道安装一个应用程序的时候,会启动这个Activity。如果在首次解析这个安装包的时候出现解析错误,会通过对话框的形式告诉用户。如果首次解析安装包的时候,成功解析了,则会通知用户去打开"安装未知应用程序设置"。在启动Activity的时候会进行内存检查,如果内存不足会通知用户。如果这个应用程序已经在这个设备安装过了,则会向用户弹出一个对话框询问用户是否"替换现有应用程序的安装包"。基于用户的回应,然后通过InstallAppConfirm的子Activity来安装应用程序。在这Activity中处理所有状态的转换。

大家平时写Activity一般都是先在onCreate方法里面做一些初始化的操作,那我们来看下PackageInstallerActivity的onCreate里面都做了什么?

(二)、PackageInstallerActivity类的onCreate()方法

代码在PackageInstallerActivity.java 439行

    PackageManager mPm;
    UserManager mUserManager;
    PackageInstaller mInstaller;
    PackageInfo mPkgInfo;
    ApplicationInfo mSourceInfo;

    @Override
    protected void onCreate(Bundle icicle) {
        super.onCreate(icicle);
         //第一步
        // 一个PackageManager对象,具体用来执行安装操作
        mPm = getPackageManager();
        // PackageInstaller对象,在该对象中包含了安装APK的基本信息
        mInstaller = mPm.getPackageInstaller();
        mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);

        // 第二步
        final Intent intent = getIntent();
        if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
            final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
            final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
            if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
                Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
                finish();
                return;
            }

            mSessionId = sessionId;
            mPackageURI = Uri.fromFile(new File(info.resolvedBaseCodePath));
            mOriginatingURI = null;
            mReferrerURI = null;
        } else {
            mSessionId = -1;
            mPackageURI = intent.getData();
            mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
            mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
        }

       // 第三步
        final boolean unknownSourcesAllowedByAdmin = isUnknownSourcesAllowedByAdmin();
        final boolean unknownSourcesAllowedByUser = isUnknownSourcesEnabled();

        boolean requestFromUnknownSource = isInstallRequestFromUnknownSource(intent);

        //第四步
        mInstallFlowAnalytics = new InstallFlowAnalytics();
        mInstallFlowAnalytics.setContext(this);
        mInstallFlowAnalytics.setStartTimestampMillis(SystemClock.elapsedRealtime());
        mInstallFlowAnalytics.setInstallsFromUnknownSourcesPermitted(unknownSourcesAllowedByAdmin
                && unknownSourcesAllowedByUser);
        mInstallFlowAnalytics.setInstallRequestFromUnknownSource(requestFromUnknownSource);
        mInstallFlowAnalytics.setVerifyAppsEnabled(isVerifyAppsEnabled());
        mInstallFlowAnalytics.setAppVerifierInstalled(isAppVerifierInstalled());
        mInstallFlowAnalytics.setPackageUri(mPackageURI.toString());

        //第五步
        final String scheme = mPackageURI.getScheme();
        if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) {
            Log.w(TAG, "Unsupported scheme " + scheme);
            setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
            mInstallFlowAnalytics.setFlowFinished(
                    InstallFlowAnalytics.RESULT_FAILED_UNSUPPORTED_SCHEME);
            finish();
            return;
        }

        final PackageUtil.AppSnippet as;
        //处理scheme为package的情况
        if ("package".equals(mPackageURI.getScheme())) {
            mInstallFlowAnalytics.setFileUri(false);
            try {
                //获取package对应的Android应用信息PackageInfo如果应用名称,权限列表等...
                mPkgInfo = mPm.getPackageInfo(mPackageURI.getSchemeSpecificPart(),
                        PackageManager.GET_PERMISSIONS | PackageManager.GET_UNINSTALLED_PACKAGES);
            } catch (NameNotFoundException e) {
            }
            //如果无法获取PackageInfo,弹出一个错误的对话框,然后直接退出安装
            if (mPkgInfo == null) {
                Log.w(TAG, "Requested package " + mPackageURI.getScheme()
                        + " not available. Discontinuing installation");
                showDialogInner(DLG_PACKAGE_ERROR);
                setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
                mInstallFlowAnalytics.setPackageInfoObtained();
                mInstallFlowAnalytics.setFlowFinished(
                        InstallFlowAnalytics.RESULT_FAILED_PACKAGE_MISSING);
                return;
            }
            //创建AppSnipet对象,该对象封装了待安装Android应用的标题和图标
            as = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
                    mPm.getApplicationIcon(mPkgInfo.applicationInfo));
        } else {
           // 处理scheme为file的情况
            mInstallFlowAnalytics.setFileUri(true);
            // 获取APK文件的实际路径
            final File sourceFile = new File(mPackageURI.getPath());
            // 创建APK文件的分析器 parsed。同时分析安装包,后面会单独讲解
            PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);

            // Check for parse errors
            if (parsed == null) {
               //如果parsed == null,则说明解析出错,则弹出对话框,并退出安装
                Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
                showDialogInner(DLG_PACKAGE_ERROR);
                setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
                mInstallFlowAnalytics.setPackageInfoObtained();
                mInstallFlowAnalytics.setFlowFinished(
                        InstallFlowAnalytics.RESULT_FAILED_TO_GET_PACKAGE_INFO);
                return;
            }
            //解析没出错,生成PackageInfo,这里面包含APK文件的相关信息
            mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
                    PackageManager.GET_PERMISSIONS, 0, 0, null,
                    new PackageUserState());
            //manifest校验
            mPkgDigest = parsed.manifestDigest;
            // 设置apk的程序名称和图标,这是另一种创建AppSnippet的方式
            as = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
        }
        mInstallFlowAnalytics.setPackageInfoObtained();

        //set view
        // 第六步
        setContentView(R.layout.install_start);
        mInstallConfirm = findViewById(R.id.install_confirm_panel);
        mInstallConfirm.setVisibility(View.INVISIBLE);
        PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);

        mOriginatingUid = getOriginatingUid(intent);

         // 第七步
        // Block the install attempt on the Unknown Sources setting if necessary.
        // 如果必须要禁止来自未知来源的安装
        if (!requestFromUnknownSource) {
             //初始化操作
            initiateInstall();
            return;
        }

       // 第八步
        // If the admin prohibits it, or we're running in a managed profile, just show error
        // and exit. Otherwise show an option to take the user to Settings to change the setting.
        // 未知来源检查,如果admin禁止则直接提示错误退出。否则显示选项提示用户去设置修改上修改设置
        final boolean isManagedProfile = mUserManager.isManagedProfile();
        if (!unknownSourcesAllowedByAdmin
                || (!unknownSourcesAllowedByUser && isManagedProfile)) {
            showDialogInner(DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES);
            mInstallFlowAnalytics.setFlowFinished(
                    InstallFlowAnalytics.RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING);
        } else if (!unknownSourcesAllowedByUser) {
            // Ask user to enable setting first
            showDialogInner(DLG_UNKNOWN_SOURCES);
            mInstallFlowAnalytics.setFlowFinished(
                    InstallFlowAnalytics.RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING);
        } else {
            initiateInstall();
        }
    }

代码很多,那我们来看重点

  • 第一步: 给mPm、mInstaller、mUserManager进行初始化。
  • 第二步: 获取mSessionId、mPackageURI、mOriginatingURI、mReferrerURI 这四个是重要参数
  • 第三步: 判断是否是来自未知来源的包,看是否是非官网下载的app,这里面有三个判断依次为:
    • isUnknownSourcesAllowedByAdmin():设备管理员是否限制来自未知来源的安装
    • isUnknownSourcesEnabled():在“设置”中用户是否启用未知来源
    • isInstallRequestFromUnknownSource(Intent):安装请求是否来自一个未知的源
  • 第四步: 创建mInstallFlowAnalytics对象,并进行一些字段的赋值InstallFlowAnalytics.java是用来安装软件包的分析工具。
  • 第五步: 检查scheme是否支持,如果不支持则直接结束,如果支持scheme,这里面又分为两种情况
    • 处理scheme为package的情况
    • 处理scheme为file的情况 无论是上面的哪种情况,都是要首先获取PackageInfo对象,如果scheme是package的情况下是直接调用PackageManager. getPackageInfo()方法获取的;如果scheme是file则是通过APK的实际路径即mPackageURI.getPath()来构造一个File,然后通过 PackageParser.generatePackageInfo()方法来获取的。然后创建AppSnippet对象,AppSnippet是PackageUtil的静态内部类,内部封装了icon和label。注意不同sheme
  • 第六步: 设置activity的主界面
  • 第七步: 判断是限制未知来源的安装包
  • 第八步: 进行未知来源的检查 大家如果仔细阅读源码的话,就会知道,无论是否限制未知来源的安装包,如果没有问题都会调用initiateInstall();来进行初始化操作

OK,PackageInstallerActivity类的onCreate()方法分析完毕,为了让大家更好的理解,下面讲解下这里面涉及到几个核心方法:

  • PackageUtil.getPackageInfo(sourceFile)
  • initiateInstall()
1、PackageUtil.getPackageInfo(sourceFile)方法解析

代码在PackageUtil.java

    /**
     * Utility method to get package information for a given {@link File}
     */
    public static PackageParser.Package getPackageInfo(File sourceFile) {
        final PackageParser parser = new PackageParser();
        try {
            PackageParser.Package pkg = parser.parseMonolithicPackage(sourceFile, 0);
            parser.collectManifestDigest(pkg);
            return pkg;
        } catch (PackageParserException e) {
            return null;
        }
    }

我们看到这个方法里面主要就是new了一个PackageParser对象 然后调用parser.parseMonolithicPackage(sourceFile, 0);方法和 parser.collectManifestDigest(pkg);方法,如果不抛异常就直接返回 PackageParser.Package对象,如果抛异常则直接返回null。看来上面这两个方法很重,关于parser.parseMonolithicPackage(sourceFile, 0);方法请参考APK安装流程详解6——PackageParser解析APK(上)中四、PackageParse#parseMonolithicPackage(File, int)方法解析。

2、initiateInstall()方法解析

代码在PackageInstallerActivity.java 400行

    private void initiateInstall() {
        String pkgName = mPkgInfo.packageName;
        // Check if there is already a package on the device with this name
        // but it has been renamed to something else.
        //第一步
        String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName });
        if (oldName != null && oldName.length > 0 && oldName[0] != null) {
            pkgName = oldName[0];
            mPkgInfo.packageName = pkgName;
            mPkgInfo.applicationInfo.packageName = pkgName;
        }
        // Check if package is already installed. display confirmation dialog if replacing pkg
         // 第二步
        try {
            // This is a little convoluted because we want to get all uninstalled
            // apps, but this may include apps with just data, and if it is just
            // data we still want to count it as "installed".
            // 获取设备上的残存数据,并且标记为“installed”,实际上已经被卸载的应用
            mAppInfo = mPm.getApplicationInfo(pkgName,
                    PackageManager.GET_UNINSTALLED_PACKAGES);
            if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
如果应用是被卸载的,但是又是被标识成安装过的,则认为是新安装
                 // 如果应用是被卸载的,但是又是被标识成安装过的,则认为是新安装
                mAppInfo = null;
            }
        } catch (NameNotFoundException e) {
            mAppInfo = null;
        }

        mInstallFlowAnalytics.setReplace(mAppInfo != null);
        mInstallFlowAnalytics.setSystemApp(
                (mAppInfo != null) && ((mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0));
        // 第三步
        startInstallConfirm();
    }

initiateInstall()方法里面主要做了三件事

  • 第一步:检查设备是否有一个现在不同名,但是曾经是相同的包名,即是否是同名安装,如果是则后续是替换安装。
  • 第二步:检查设备上是否已经安装了这个安装包,如果是,后面是替换安装
  • 第三步:调用startInstallConfirm()这个方法是安装的核心代码。

下面我们就来看下startInstallConfirm()方法里面的具体实现

①、startInstallConfirm()方法解析

代码在PackageInstallerActivity.java 114行

    private void startInstallConfirm() {
        TabHost tabHost = (TabHost)findViewById(android.R.id.tabhost);
        tabHost.setup();
        ViewPager viewPager = (ViewPager)findViewById(R.id.pager);
        TabsAdapter adapter = new TabsAdapter(this, tabHost, viewPager);
        adapter.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
            @Override
            public void onTabChanged(String tabId) {
                if (TAB_ID_ALL.equals(tabId)) {
                    mInstallFlowAnalytics.setAllPermissionsDisplayed(true);
                } else if (TAB_ID_NEW.equals(tabId)) {
                    mInstallFlowAnalytics.setNewPermissionsDisplayed(true);
                }
            }
        });
        // If the app supports runtime permissions the new permissions will
        // be requested at runtime, hence we do not show them at install.

         // 根据sdk版本来判断app是否支持运行时权限,这里会显示运行时权限
        boolean supportsRuntimePermissions = mPkgInfo.applicationInfo.targetSdkVersion
                >= Build.VERSION_CODES.M;

       //显示权限列表的变量,true显示权限列表,false 未显示权限列表
        boolean permVisible = false;
        mScrollView = null;
        mOkCanInstall = false;
        int msg = 0;
 //perms这个对象包括了该应用的用户的uid以及相应的一些权限,以及权限组信息
         // perms这个对象包括了该应用的用户的uid以及相应的一些权限,以及权限组的信息
        AppSecurityPermissions perms = new AppSecurityPermissions(this, mPkgInfo);
        // 获取隐私相关权限的数量
        final int N = perms.getPermissionCount(AppSecurityPermissions.WHICH_ALL);
        //判断是否为已经安装过的应用
        if (mAppInfo != null) {
           // 如果已经安装过则继续判断是否为系统应用
            msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
                    ? R.string.install_confirm_question_update_system
                    : R.string.install_confirm_question_update;
            
            // 用来显示权限列表的scrollview
            mScrollView = new CaffeinatedScrollView(this);
            // 如果显示的内容超过了mScrollView,则就会折叠可以滚动 
            mScrollView.setFillViewport(true);
            boolean newPermissionsFound = false;
            if (!supportsRuntimePermissions) {
                //针对更新应用程序相对于旧版本而判断是否加入新的权限
                newPermissionsFound =
                        (perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0);
                mInstallFlowAnalytics.setNewPermissionsFound(newPermissionsFound);
                if (newPermissionsFound) {
                     //将新的权限列表视频添加到滚动视图中
                    permVisible = true;
                    mScrollView.addView(perms.getPermissionsView(
                            AppSecurityPermissions.WHICH_NEW));
                }
            }
            if (!supportsRuntimePermissions && !newPermissionsFound) {
                // 没有设置任何权限,只显示应用程序名称和图标
                LayoutInflater inflater = (LayoutInflater)getSystemService(
                        Context.LAYOUT_INFLATER_SERVICE);
                TextView label = (TextView)inflater.inflate(R.layout.label, null);
                label.setText(R.string.no_new_perms);
                mScrollView.addView(label);
            }
            adapter.addTab(tabHost.newTabSpec(TAB_ID_NEW).setIndicator(
                    getText(R.string.newPerms)), mScrollView);
        } else  {
            // 应用没有被安装过,则将相应的控件隐藏
            findViewById(R.id.tabscontainer).setVisibility(View.GONE);
            findViewById(R.id.divider).setVisibility(View.VISIBLE);
        }
        // 如果至少设置了一个权限
        if (!supportsRuntimePermissions && N > 0) {
            permVisible = true;
            LayoutInflater inflater = (LayoutInflater)getSystemService(
                    Context.LAYOUT_INFLATER_SERVICE);
            //解析权限列表的视图
            View root = inflater.inflate(R.layout.permissions_list, null);
            if (mScrollView == null) {
                mScrollView = (CaffeinatedScrollView)root.findViewById(R.id.scrollview);
            }
           // 添加到权限列表的视图
            ((ViewGroup)root.findViewById(R.id.permission_list)).addView(
                       perms.getPermissionsView(AppSecurityPermissions.WHICH_ALL));
            adapter.addTab(tabHost.newTabSpec(TAB_ID_ALL).setIndicator(
                    getText(R.string.allPerms)), root);
        }
        mInstallFlowAnalytics.setPermissionsDisplayed(permVisible);
        
        if (!permVisible) {
            // 如果不显示权限列表
            if (mAppInfo != null) {
                // 如果是更新安装包,并且没有任何权限要求
                // This is an update to an application, but there are no
                // permissions at all.
                //判断是否是系统应用来设置布局文件 
                msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
                        ? R.string.install_confirm_question_update_system_no_perms
                        : R.string.install_confirm_question_update_no_perms;
            } else {
                // 是新安装的app并且没有权限列表
                // This is a new application with no permissions.
                msg = R.string.install_confirm_question_no_perms;
            }
            //设置相应的UI
            tabHost.setVisibility(View.GONE);
            mInstallFlowAnalytics.setAllPermissionsDisplayed(false);
            mInstallFlowAnalytics.setNewPermissionsDisplayed(false);
            findViewById(R.id.filler).setVisibility(View.VISIBLE);
            findViewById(R.id.divider).setVisibility(View.GONE);
            mScrollView = null;
        }
        if (msg != 0) {
            ((TextView)findViewById(R.id.install_confirm_question)).setText(msg);
        }
        mInstallConfirm.setVisibility(View.VISIBLE);
        //这个是关键的控件,即点击安装button
        mOk = (Button)findViewById(R.id.ok_button);
       //这个是关键的控件,即点击取消button
        mCancel = (Button)findViewById(R.id.cancel_button);
        mOk.setOnClickListener(this);
        mCancel.setOnClickListener(this);
        if (mScrollView == null) {
            // There is nothing to scroll view, so the ok button is immediately
            // set to install.
            mOk.setText(R.string.install);
            mOkCanInstall = true;
        } else {
            mScrollView.setFullScrollAction(new Runnable() {
                @Override
                public void run() {
                    mOk.setText(R.string.install);
                    mOkCanInstall = true;
                }
            });
        }
    }

这个方法其实主要是根据不同的情况来设置相应的UI,主要是将安装包分为新安装和更新安装,在更新安装里面又分为系统应用和非系统应用,然后根据不同的情况来显示不同的UI,UI这块主要是通过getPermissionsView方法来获取不同的权限View。

PS:AppSecurityPermissions.WHICH_NEW:新加入的权限

这个重点说下mOk这个Button,因为后面咱们点击"安装"按钮的流程就是从这个按钮开始的。

(三)、PackageInstallerActivity类中点击"安装"的流程详解

由于PackageInstallerActivity实现了OnClickListener接口,所以点击事件我们直接找onClick(View)方法即可

1、onClick(View v)方法解析

代码在PackageInstallerActivity.java 114行

    public void onClick(View v) {
        if (v == mOk) {
            if (mOkCanInstall || mScrollView == null) {
                mInstallFlowAnalytics.setInstallButtonClicked();
                if (mSessionId != -1) {
                   //如果原来是确认权限请求则赋予安装权限则退出
                    mInstaller.setPermissionsResult(mSessionId, true);

                    // We're only confirming permissions, so we don't really know how the
                    // story ends; assume success.
                    mInstallFlowAnalytics.setFlowFinishedWithPackageManagerResult(
                            PackageManager.INSTALL_SUCCEEDED);
                    finish();
                } else {
                    startInstall();
                }
            } else {
                mScrollView.pageScroll(View.FOCUS_DOWN);
            }
        } else if(v == mCancel) {
            // Cancel and finish
            setResult(RESULT_CANCELED);
            if (mSessionId != -1) {
                mInstaller.setPermissionsResult(mSessionId, false);
            }
            mInstallFlowAnalytics.setFlowFinished(
                    InstallFlowAnalytics.RESULT_CANCELLED_BY_USER);
            finish();
        }
    }

我们重点看v == mOk的情况,里面其实就是做了两件事

  • 1、判断是否可以安装,mScrollVieww是否为空
  • 2、如果可以安装,那么调用startInstall()方法

那下面我们来看下startInstall()的具体实现

2、startInstall()方法解析

代码在PackageInstallerActivity.java 683行

    private void startInstall() {
        // Start subactivity to actually install the application
        Intent newIntent = new Intent();

         //带上安装包的applicationInfo
        newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
                mPkgInfo.applicationInfo);
    
        // 带上安装包的URI
        newIntent.setData(mPackageURI);

        //设置目标类
        newIntent.setClass(this, InstallAppProgress.class);
        
        //带上安装包的mPkgDigest
newIntent.putExtra(InstallAppProgress.EXTRA_MANIFEST_DIGEST, mPkgDigest);

         // 带上mInstallFlowAnalytics 
        newIntent.putExtra(
                InstallAppProgress.EXTRA_INSTALL_FLOW_ANALYTICS, mInstallFlowAnalytics);
        String installerPackageName = getIntent().getStringExtra(
                Intent.EXTRA_INSTALLER_PACKAGE_NAME);
        if (mOriginatingURI != null) {

            //带上安装包的mOriginatingURI
            newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
        }
        if (mReferrerURI != null) {
            //带上安装包的mReferrerURI
            newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
        }
        if (mOriginatingUid != VerificationParams.NO_UID) {
             //带上安装包的mOriginatingUid 这个uid不是安装应用的uid
            newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
        }
        if (installerPackageName != null) {
            newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
                    installerPackageName);
        }
        if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
            newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
            newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
        }
        if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
        startActivity(newIntent);
        finish();
    }

上面代码很简单,主要是就是构造在一个Intent,并且传递必须要的数据,

可以看到在startInstall方法中,主要构造一个intent,并且将安装包信息封装到intent中,然后跳转到InstallAppProgress类

下面我们就来看下InstallAppProgress这个类的安装流程

八、InstallAppProgress类的安装流程

(一)、InstallAppProgress类简介

/**
 * This activity corresponds to a download progress screen that is displayed
 * when the user tries
 * to install an application bundled as an apk file. The result of the application install
 * is indicated in the result code that gets set to the corresponding installation status
 * codes defined in PackageManager. If the package being installed already exists,
 * the existing package is replaced with the new one.
 */
public class InstallAppProgress extends Activity implements View.OnClickListener, OnCancelListener {
        ...
        ...
}

通过上面代码我们知道InstallAppProgress其实是一个Activity并且实现了OnClickListener和OnCancelListener接口

为了让大家更好的理解这个类,我们还是看一下这个类的注释,我的翻译如下(不喜勿喷):

这个Activity是负责当用户尝试安装APK应用程序时的进度显示的Activity。关于这个应用程序的安装结果的状态码是和PackageManager里面定义的安装状态码一一映射的。如果正在安装的应用已经在设备上存在了,则新的应用程序将会替换掉老的应用程序。

既然它是一个Activity,那么我们先看下他的onCreate()方法

(二)、InstallAppProgress的onCreate(Bundle)方法

代码在InstallAppProgress.java 167行

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        //第一步
        Intent intent = getIntent();
        mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
        mInstallFlowAnalytics = intent.getParcelableExtra(EXTRA_INSTALL_FLOW_ANALYTICS);
        mInstallFlowAnalytics.setContext(this);
        mPackageURI = intent.getData();
      
        //第二步
        final String scheme = mPackageURI.getScheme();
        if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) {
            mInstallFlowAnalytics.setFlowFinished(
                    InstallFlowAnalytics.RESULT_FAILED_UNSUPPORTED_SCHEME);
            throw new IllegalArgumentException("unexpected scheme " + scheme);
        }
        
        // 第三步 
        initView();
    }

通过上面代码我们知道onCreate方法里面主要做了3三件事

  • 首先:初始化数据,把Intent里面的数据取出
  • 其次:做scheme数据过滤,只支持scheme为file或者package格式的
  • 最后:最后调用initView()方法
1、initView()方法详解

代码在InstallAppProgress.java 226行

    public void initView() {
         // 第一步
        setContentView(R.layout.op_progress);

         // 第二步
        // 安装模式 分为安装和更新
        int installFlags = 0;
        PackageManager pm = getPackageManager();

        // 第三步
        try {
            PackageInfo pi = pm.getPackageInfo(mAppInfo.packageName,
                    PackageManager.GET_UNINSTALLED_PACKAGES);
            if(pi != null) {
                installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
            }
        } catch (NameNotFoundException e) {
        }

         // 第四步
        if((installFlags & PackageManager.INSTALL_REPLACE_EXISTING )!= 0) {
            Log.w(TAG, "Replacing package:" + mAppInfo.packageName);
        }

        // 第五步
        final PackageUtil.AppSnippet as;
        if ("package".equals(mPackageURI.getScheme())) {
            as = new PackageUtil.AppSnippet(pm.getApplicationLabel(mAppInfo),
                    pm.getApplicationIcon(mAppInfo));
        } else {
            final File sourceFile = new File(mPackageURI.getPath());
            as = PackageUtil.getAppSnippet(this, mAppInfo, sourceFile);
        }

        // 第六步
        mLabel = as.label;
        PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);
        mStatusTextView = (TextView)findViewById(R.id.center_text);
        mStatusTextView.setText(R.string.installing);
        mExplanationTextView = (TextView) findViewById(R.id.center_explanation);
        mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
        mProgressBar.setIndeterminate(true);
        // Hide button till progress is being displayed
        mOkPanel = (View)findViewById(R.id.buttons_panel);
        mDoneButton = (Button)findViewById(R.id.done_button);
        mLaunchButton = (Button)findViewById(R.id.launch_button);
        mOkPanel.setVisibility(View.INVISIBLE);

        // 第七步
        String installerPackageName = getIntent().getStringExtra(
                Intent.EXTRA_INSTALLER_PACKAGE_NAME);
        Uri originatingURI = getIntent().getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
        Uri referrer = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);
        int originatingUid = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
                VerificationParams.NO_UID);
        ManifestDigest manifestDigest = getIntent().getParcelableExtra(EXTRA_MANIFEST_DIGEST);
        VerificationParams verificationParams = new VerificationParams(null, originatingURI,
                referrer, originatingUid, manifestDigest);

        // 第八步
        PackageInstallObserver observer = new PackageInstallObserver();

        // 第九步
        if ("package".equals(mPackageURI.getScheme())) {
            try {
                pm.installExistingPackage(mAppInfo.packageName);
                observer.packageInstalled(mAppInfo.packageName,
                        PackageManager.INSTALL_SUCCEEDED);
            } catch (PackageManager.NameNotFoundException e) {
                observer.packageInstalled(mAppInfo.packageName,
                        PackageManager.INSTALL_FAILED_INVALID_APK);
            }
        } else {
            pm.installPackageWithVerificationAndEncryption(mPackageURI, observer, installFlags,
                    installerPackageName, verificationParams, null);
        }
    }

上面代码还是比较简洁的,大体上将initView()分为九个步骤,如下:

  • 第一步:设置当前Activity的布局文件
  • 第二步:获取PackageManager对象
  • 第三步:获取PackgeInfo对象,这里使用pm.getPackageInfo()方法来获取,主要是判断待安装的应用程序是否已经安装,因为如果已经安装了,则返回PackgeInfo对象,则安装模式设为更新模式,如果没有安装,则返回null
  • 第四步:如果是替换安装则打印日志
  • 第五步:根据不同的scheme来给AppSnippet进行赋值,如果scheme为package则意味着更新应用程序;scheme为file则意味着是新安装应用程序。
  • 第六步:获取布局文件中的控件
  • 第七步:从Intent中获取相应的数据信息,为下一步做准备
  • 第八步:创建安装的监听器对象
  • 第九步:根据不用的scheme来进行不同安装模式下的安装操作

这个方法里面涉及到三个重要内容如下

  • 1、PackageInstallObserver
  • 2、PackageManager的installExistingPackage(String)方法
  • 3、PackageManager的installPackageWithVerificationAndEncryption(PackageInstallObserver , int , String ,VerificationParams , ContainerEncryptionParams )方法
(1)、PackageInstallObserver类详解
(1.1)、PackageInstallObserver类源码

代码在InstallAppProgress.java 218行

    class PackageInstallObserver extends IPackageInstallObserver.Stub {
        public void packageInstalled(String packageName, int returnCode) {
            Message msg = mHandler.obtainMessage(INSTALL_COMPLETE);
            msg.arg1 = returnCode;
            mHandler.sendMessage(msg);
        }
    }

通过上面代码我们知道 PackageInstallObserver其实是继承IPackageInstallObserver.Stub 类的,在packageInstalled(String, int)方法里面其实向mHandler发送了一个Message

PS:注意这个Message的what值是INSTALL_COMPLETE,Message的arg1是安装结果的code。

那我们看下这个mHandler的里面是怎么操作的

(1.2)、InstallAppProgress类中的Handler对象mHandler解析

代码在InstallAppProgress.java 76行

    private Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case INSTALL_COMPLETE:
                     //记录安装结果
                    mInstallFlowAnalytics.setFlowFinishedWithPackageManagerResult(msg.arg1);
                    
                    // 第一步
                    // 判断是否需要安装结束后立即结束当前界面
                    if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
                        
                        Intent result = new Intent();
                        result.putExtra(Intent.EXTRA_INSTALL_RESULT, msg.arg1);
                        setResult(msg.arg1 == PackageManager.INSTALL_SUCCEEDED
                                ? Activity.RESULT_OK : Activity.RESULT_FIRST_USER,
                                        result);
                        finish();
                        return;
                    }
                     
                    // 第二步
                    // Update the status text
                    mProgressBar.setVisibility(View.INVISIBLE);
                    // Show the ok button
                    int centerTextLabel;
                    int centerExplanationLabel = -1;
                    LevelListDrawable centerTextDrawable =
                            (LevelListDrawable) getDrawable(R.drawable.ic_result_status);

                     // 第三步 
                    if (msg.arg1 == PackageManager.INSTALL_SUCCEEDED) {
                        // 安装成功
                        mLaunchButton.setVisibility(View.VISIBLE);
                        centerTextDrawable.setLevel(0);
                        centerTextLabel = R.string.install_done;
                        // Enable or disable launch button
                        // 获取应用程序启动的Intent
                        mLaunchIntent = getPackageManager().getLaunchIntentForPackage(
                                mAppInfo.packageName);
                        boolean enabled = false;
                         // 判断应用程序启动Intent是否可用
                        if(mLaunchIntent != null) {
                            List<ResolveInfo> list = getPackageManager().
                                    queryIntentActivities(mLaunchIntent, 0);
                            if (list != null && list.size() > 0) {
                                enabled = true;
                            }
                        }
                        if (enabled) {
                            mLaunchButton.setOnClickListener(InstallAppProgress.this);
                        } else {
                            mLaunchButton.setEnabled(false);
                        }
                    } else if (msg.arg1 == PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE){
                        // 由于剩余空间不足导致安装失败
                        showDialogInner(DLG_OUT_OF_SPACE);
                        return;
                    } else {
                        // 安装失败
                        // Generic error handling for all other error codes.
                        centerTextDrawable.setLevel(1);
                        centerExplanationLabel = getExplanationFromErrorCode(msg.arg1);
                        centerTextLabel = R.string.install_failed;
                        mLaunchButton.setVisibility(View.INVISIBLE);
                    }

                    // 第四步
                    if (centerTextDrawable != null) {
                    centerTextDrawable.setBounds(0, 0,
                            centerTextDrawable.getIntrinsicWidth(),
                            centerTextDrawable.getIntrinsicHeight());
                        mStatusTextView.setCompoundDrawablesRelative(centerTextDrawable, null,
                                null, null);
                    }
                    mStatusTextView.setText(centerTextLabel);
                    if (centerExplanationLabel != -1) {
                        mExplanationTextView.setText(centerExplanationLabel);
                        mExplanationTextView.setVisibility(View.VISIBLE);
                    } else {
                        mExplanationTextView.setVisibility(View.GONE);
                    }
                    mDoneButton.setOnClickListener(InstallAppProgress.this);
                    mOkPanel.setVisibility(View.VISIBLE);
                    break;
                default:
                    break;
            }
        }
    };

由于Message的what值为INSTALL_COMPLETE,我们只需要关心case值为INSTALL_COMPLETE的情况(不过貌似没有别的case),case的值为INSTALL_COMPLETE的内容主要分为四个步骤,如下:

  • 第一步:判断是否有安装结束后(不论成功或者失败),立即离开当前的需求,如果有这个要求,则完成结束后,立即返回,如果是安装成功则resultCode为PackageManager.INSTALL_SUCCEEDED,如果失败resultCode为Activity.RESULT_FIRST_USER。
  • 第二步:更变UI,并且给centerTextDrawable赋值
  • 第三步:根据安装结果的code(是msg.arg1)来更新UI及后续的操作,这里面分为三种情况:
    • 安装结果code为PackageManager.INSTALL_SUCCEEDED: 表示安装成功,首先进行UI更新,然后通过调用getPackageManager().getLaunchIntentForPackage(mAppInfo.packageName);来获取这个应用程序的启动Intent,接着判断这个Intent是否可能用,如果可用最后设置mLaunchButton的监听事件。
    • 安装结果code为PackageManager. INSTALL_FAILED_INSUFFICIENT_STORAGE: 表示由于设备没有足够的存储空间来安装该应用程序
    • 安装结果code为其他值: 如果不是上面两个code值则表示安装失败,通过调用getExplanationFromErrorCode(int) 方法来获取失败的原因文案提示并更新UI
  • 第四步:根据上面的安装结果,更新UI。

这里面涉及到两个重要方法:

  • 1、getExplanationFromErrorCode(int)方法
  • 2、getPackageManager().getLaunchIntentForPackage(mAppInfo.packageName)方法

下面我们就简单介绍这两个方法:

(1.3)、 getExplanationFromErrorCode(int)方法

代码在InstallAppProgress.java 76行

    private int getExplanationFromErrorCode(int errCode) {
        Log.d(TAG, "Installation error code: " + errCode);
        switch (errCode) {
            case PackageManager.INSTALL_FAILED_INVALID_APK:
                return R.string.install_failed_invalid_apk;
            case PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES:
                return R.string.install_failed_inconsistent_certificates;
            case PackageManager.INSTALL_FAILED_OLDER_SDK:
                return R.string.install_failed_older_sdk;
            case PackageManager.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE:
                return R.string.install_failed_cpu_abi_incompatible;
            default:
                return -1;
        }
    }

通过上面代码我知道getExplanationFromErrorCode(int) 主要是根据不同的code返回不同的String用以提醒用户。那我们就把上面四个case依次说下

  • PackageManager.INSTALL_FAILED_INVALID_APK:他表示无效的APK文件,一般是APK文件有问题
  • PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES:表示签名问题,一般是签名冲突
  • PackageManager.INSTALL_FAILED_OLDER_SDK:表示SDK版本和APK的要求的版本冲突
  • PackageManager.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE:由于native的代码与设备上的CPU_ABI不兼容

这里先不讲解PackageManager的getLaunchIntentForPackage,大家现在这里打个断点,一会回来看这里

(2)、PackageManager的installExistingPackage(String)方法简介

代码在PackageManager.java 3755行

    /**
     * If there is already an application with the given package name installed
     * on the system for other users, also install it for the calling user.
     * @hide
     */
    // @SystemApi
    public abstract int installExistingPackage(String packageName)
            throws NameNotFoundException;

咦,它也是一个抽象方法啊?先看下注释:

如果系统上已经有其他用户安装了相同包名的应用程序,则让用户继续安装。

同上,这里先不讲解installExistingPackage(String)方法,我们一会详细讲解。

(3)、PackageManager的installPackageWithVerificationAndEncryption(PackageInstallObserver , int , String ,VerificationParams , ContainerEncryptionParams )方法类简介

代码在PackageManager.java 3660行

    /**
     * Similar to
     * {@link #installPackage(Uri, IPackageInstallObserver, int, String)} but
     * with an extra verification file provided.
     *
     * @param packageURI The location of the package file to install. This can
     *            be a 'file:' or a 'content:' URI.
     * @param observer An observer callback to get notified when the package installation is
     * complete. {@link PackageInstallObserver#packageInstalled(String, Bundle, int)} will be
     * called when that happens. This parameter must not be null.
     * @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
     *            {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
     * @param installerPackageName Optional package name of the application that
     *            is performing the installation. This identifies which market
     *            the package came from.
     * @param verificationURI The location of the supplementary verification
     *            file. This can be a 'file:' or a 'content:' URI. May be
     *            {@code null}.
     * @param manifestDigest an object that holds the digest of the package
     *            which can be used to verify ownership. May be {@code null}.
     * @param encryptionParams if the package to be installed is encrypted,
     *            these parameters describing the encryption and authentication
     *            used. May be {@code null}.
     * @hide
     */
    public abstract void installPackageWithVerification(Uri packageURI,
            PackageInstallObserver observer, int flags, String installerPackageName,
            Uri verificationURI, ManifestDigest manifestDigest,
            ContainerEncryptionParams encryptionParams);

既然它也是一个抽象方法,那我们先来看下注释:

和installPackage(Uri, IPackageInstallObserver, int, String)有点类似,但是多了一个额外的验证文件。

这里先简单的说下我们上面涉及到监听器PackageInstallObserver,因为PackageInstallObserver还是蛮重要的,

(三)、PackageInstallObserver简介

1、PackageInstallObserver

PackageInstallObserver.java源码地址 代码如下:

public class PackageInstallObserver {
    private final IPackageInstallObserver2.Stub mBinder = new IPackageInstallObserver2.Stub() {
        @Override
        public void onUserActionRequired(Intent intent) {
            PackageInstallObserver.this.onUserActionRequired(intent);
        }

        @Override
        public void onPackageInstalled(String basePackageName, int returnCode,
                String msg, Bundle extras) {
            PackageInstallObserver.this.onPackageInstalled(basePackageName, returnCode, msg,
                    extras);
        }
    };

    /** {@hide} */
    public IPackageInstallObserver2 getBinder() {
        return mBinder;
    }

    public void onUserActionRequired(Intent intent) {
    }

    /**
     * This method will be called to report the result of the package
     * installation attempt.
     *
     * @param basePackageName Name of the package whose installation was
     *            attempted
     * @param extras If non-null, this Bundle contains extras providing
     *            additional information about an install failure. See
     *            {@link android.content.pm.PackageManager} for documentation
     *            about which extras apply to various failures; in particular
     *            the strings named EXTRA_FAILURE_*.
     * @param returnCode The numeric success or failure code indicating the
     *            basic outcome
     * @hide
     */
    public void onPackageInstalled(String basePackageName, int returnCode, String msg,
            Bundle extras) {
    }
}

我们看到PackageInstallObserver的getBinder()方法返回的是本地成员变量mBinder,而mBinder又是IPackageInstallObserver2.Stub。所以我们可以说mBinder也是一个AIDL的通信的服务端。

我们看来下onPackageInstalled(String,int,String,Bundle)方法的注释:

调用这个方法来获取程序安装的结果

  • 入参 basePackageName:表示安装包的包名
  • 入参 extras,可能为空,如果非空,则在里面包含导致安装失败的附加信息。可以参考android.content.pm.PackageManage类中以EXTRA_FAILURE_开头的字段
  • 入参 returnCode:表示安装成功或者失败的状态码

这时候就要看一下IPackageInstallObserver2.aidl

2、IPackageInstallObserver2.aidl简介

IPackageInstallObserver2.aidl源码地址

/**
 * API for installation callbacks from the Package Manager.  In certain result cases
 * additional information will be provided.
 * @hide
 */
oneway interface IPackageInstallObserver2 {
    void onUserActionRequired(in Intent intent);

    /**
     * The install operation has completed.  {@code returnCode} holds a numeric code
     * indicating success or failure.  In certain cases the {@code extras} Bundle will
     * contain additional details:
     *
     * <p><table>
     * <tr>
     *   <td>INSTALL_FAILED_DUPLICATE_PERMISSION</td>
     *   <td>Two strings are provided in the extras bundle: EXTRA_EXISTING_PERMISSION
     *       is the name of the permission that the app is attempting to define, and
     *       EXTRA_EXISTING_PACKAGE is the package name of the app which has already
     *       defined the permission.</td>
     * </tr>
     * </table>
     */
    void onPackageInstalled(String basePackageName, int returnCode, String msg, in Bundle extras);
}
(1)、"oneway"关键字

首先说下AIDL的关键字oneway"",AIDL可以用关键字"oneway"来表明远程调用的行为属性,使用了该关键字,那么远程调用将紧紧是调用所有的数据传输过来并立即返回,而不会等待结果的返回,也是说不会阻塞远程线程的运行。AIDL接口将最终获得一个从Binder线程池中产生的调用(和普通的远程调用类似)。如果关键字oneway在本地调用中被使用,将不会对函数调用有任何影响

(2)、理解"注释"

为了更好的理解设计者最初的设想,我们来看下"类"的注释

包管理其用于安装的的回调API。在某些情况下,它可以提供一些必要的信息。

那我们来看下onPackageInstalled的注释

安装操作完成后,会包含一个code,由这个code标识成功或者失败。在特定的情形下它也会包含一些附加信息: 比如INSTALL_FAILED_DUPLICATE_PERMISSION这个安装失败: 它的extras 的Bundle 里面可能会存在EXTRA_EXISTING_PERMISSION这个字符串,表示定义app向定义权限的内容,和EXTRA_EXISTING_PACKAGE这个字符串,表示应定义了权限的应用程序包名称。

(3)、为什么要设计成AIDL

因为你在InstallAppProgress是一个单独的进程,而PackageManagerService也是一个单独的进程,假设InstallAppProgress所在的进程名称为"InstallAppProgress",你想在"InstallAppProgress"监听"PackageManagerService"进程的结果,这里面涉及到两个进程的调用,是属于跨进程调用,所以需要使用Binder来进行进程间通信。

(三)、总结

PackageInstallObserver类内部含有一个AIDL的Binder跨进程通信,当在PackageManagerService中安装完成,会调用IPackageInstallObserver2.Stub的onPackageInstalled方法,然后在IPackageInstallObserver2.Stub 的onPackageInstalled方法里面调用PackageInstallObserver的onPackageInstalled。这样就通知到了InstallAppProgress的observer对象了

九、InstallAppProgress中涉及到PackageManager的三个方法

在InstallAppProgress.java 中的

大体流程如下,如果如果已经存在安装包了,则更新(调用PackageManager的installExistingPackage(String)方法),如果没有新安装,则进行安装(PackageManager的installPackageWithVerificationAndEncryption方法),最后无论是更新还是新安装成功都会调用PackageManager的getLaunchIntentForPackage方法

因为咱们主要是会讲解新安装,所有后面主要是跟踪PackageManager的installPackageWithVerificationAndEncryption方法,大家可以自己去跟踪PackageManager的installExistingPackage(String)方法,其实原理大差不差,很多方法都是公用的。

转发:https://cloud.tencent.com/developer/article/1199458

 
原文地址:https://www.cnblogs.com/linyinmobayu/p/14262344.html