apk文件安装流程

概述:
android和你通常理解的程序路径是不一样的,android应用安装完毕后,会存储在/data/app或者/system/app目录中,当程序运行时,所需要的layout文件,drawable文件等等需要从该目录中的原文件中进行读取。首先加载resource.asc,然后根据id值寻找相应的资源,而lib目录等特殊文件会存放 /data/data/你的应用包名此路径下。

  • 安装分类
  • 几个相关目录
  • APK安装流程
  • APK解析流程
  • 调用PMS

安装分类

根据触发条件可分为三类:

分类说明
系统应用安装没有安装界面,在开机时自动完成
网络下载应用安装没有安装界面,在应用市场完成
ADB命令安装没有安装界面,通过命令直接安装
外部设备安装有安装界面,通过SD卡等外部设备安装,由packageInstaller处理安装逻辑

几个相关目录

以下是比较关键的四个目录:
(1)system/app : 系统自带的应用程序,获得root权限才能删除
(2)data/app : 用户程序安装目录,安装时会把apk文件复制到此目录下
(3)data/data : 存放应用程序的数据
(4)data/dalvik-cache : 将apk中的dex文件安装到该目录下(dex文件是dalvik虚拟机的可执行文件,大小约为原始apk的四分之一)

APK安装流程

如下是比较关键的四个目录:
(1)拷贝apk到指定的目录
默认情况下,用户安装的apk首先会拷贝到/data/app下,用户有访问/data/app目录的权限,但系统出厂的apk文件会被放到/system分区下,包括/system/app,/system/vendor/app,以及/system/priv-app等。该分区需要root权限的用户才能访问。

(2)解压apk、拷贝文件、创建应用的数据目录
为了加快APP的启动速度,apk在安装的时候,会首先将APP的可执行文件(dex)拷贝到/data/dalvik-cache目录下,缓存起来。再在/data/data/目录下创建应用程序的数据目录(以应用包名命令),用来存放应用的数据库、cache、二进制的so动态库等。资源文件还在原来的APK文件里,并没有拷贝过来

(3)解析apk的AndroidManifest.xml文件
在安装apk的过程中,会解析apk的AndroidManifest.xml文件,将apk的权限、应用包名、apk的安装位置、版本、userID等重要信息保存在/data/system/packages.xml文件中。这些操作都是在PackageManagerService中完成的,并将解析出的四大组件信息注册到PackageManagerService中。

(4)显示icon图标
应用程序经过PMS中的逻辑处理后,相当于已经注册好了,如果想要在Android桌面上看到icon图标,则需要Launcher将系统中已经安装的程序展现在桌面上。

APK解析流程

解析AndroidManifest.xml文件
APK文件里包含了一个配置文件AndroidManifest.xml,Android应用程序的解析过程就是解析这个xml文件的过程。APK解析是从PackageManagerService的scanPackageLI开始的,而该方法内部又调用的是scanPackageDirtyLI()方法,我们来看一下这个方法的实现。

public class PackageManagerService extends IPackageManager.Stub {
    
       private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,
               int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
           //...
           // writer
           synchronized (mPackages) {
               // 验证已注册的ContentProvider是否有其他同名,做冲突检测。
               if ((scanFlags & SCAN_NEW_INSTALL) != 0) {
                   final int N = pkg.providers.size();
                   int i;
                   for (i=0; i<N; i++) {
                       PackageParser.Provider p = pkg.providers.get(i);
                       if (p.info.authority != null) {
                           String names[] = p.info.authority.split(";");
                           for (int j = 0; j < names.length; j++) {
                               if (mProvidersByAuthority.containsKey(names[j])) {
                                   PackageParser.Provider other = mProvidersByAuthority.get(names[j]);
                                   final String otherPackageName =
                                           ((other != null && other.getComponentName() != null) ?
                                                   other.getComponentName().getPackageName() : "?");
                                   throw new PackageManagerException(
                                           INSTALL_FAILED_CONFLICTING_PROVIDER,
                                                   "Can't install because provider name " + names[j]
                                                   + " (in package " + pkg.applicationInfo.packageName
                                                   + ") is already used by " + otherPackageName);
                               }
                           }
                       }
                   }
               }
           }
         
           if (mPlatformPackage == pkg) {
              //...
           } else {
               // This is a normal package, need to make its data directory.
               dataPath = getDataPathForPackage(pkg.packageName, 0);
               if (dataPath.exists()) {
                   //...
               } else {
                   //invoke installer to do the actual installation
                   //这里创建了应用数据目录,用于存放用户数据
                   int ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid,
                                              pkg.applicationInfo.seinfo);
                   //...
               }
             
           }
         
           // We also need to dexopt any apps that are dependent on this library.  Note that
           // if these fail, we should abort the install since installing the library will
           // result in some apps being broken.
           if (clientLibPkgs != null) {
               if ((scanFlags & SCAN_NO_DEX) == 0) {
                   for (int i = 0; i < clientLibPkgs.size(); i++) {
                       PackageParser.Package clientPkg = clientLibPkgs.get(i);
                       if (performDexOptLI(clientPkg, null /* instruction sets */, forceDex,
                               (scanFlags & SCAN_DEFER_DEX) != 0, false) == DEX_OPT_FAILED) {
                           throw new PackageManagerException(INSTALL_FAILED_DEXOPT,
                                   "scanPackageLI failed to dexopt clientLibPkgs");
                       }
                   }
               }
           }
         
           // writer
           synchronized (mPackages) {
               //...
               // 以下对四大组件进行注册
               int N = pkg.providers.size();
               StringBuilder r = null;
               int i;
               for (i=0; i<N; i++) {
                   PackageParser.Provider p = pkg.providers.get(i);
                   p.info.processName = fixProcessName(pkg.applicationInfo.processName,
                           p.info.processName, pkg.applicationInfo.uid);
                   //注册Content Provider
                   mProviders.addProvider(p);
                   //...
               }
               //...
           }
       }
       //...
   }
}

scanPackageDirtyLI是一个上千行的函数,它主要完成的工作如下所示:
(1)调用PackageParser的parsePackage()方法解析AndroidMainfest.xml文件,主要包括四大组件、权限信息、用户ID,其他use-feature、shared-userId、use-library等 信息,并保存到PackageManagerService相应的成员变量中。
(2)调用签名验证方法verifySignaturesLP()进行签名验证,验证失败的无法进行安装。
(3)调用createDataDirsDirtyLI()方法创建应用目录/data/data/package,同时将APK中提取的DEX文件保存到/data/dalvik-cache中。
(4)调用performDexOptLI()方法执行dexopt操作。

解析 APK 文件

Apk的解析是PackageParser的parsePackage()函数来完成的,我们来看看它的实现。

public class PackageParser {
    
     public Package parsePackage(File packageFile, int flags) throws PackageParserException {
         if (packageFile.isDirectory()) {
             return parseClusterPackage(packageFile, flags);
         } else {
             return parseMonolithicPackage(packageFile, flags);
         }
     }
     
     private Package parseClusterPackage(File packageDir, int flags) throws PackageParserException {
             //...
             
             //初始化AssetManager
             final AssetManager assets = new AssetManager();
             try {
                 //...
                 //解析Base APk,解析AndroidManifest.xml
                 final Package pkg = parseBaseApk(baseApk, assets, flags);
                 if (pkg == null) {
                     throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                             "Failed to parse base APK: " + baseApk);
                 }
     
                 //如果splitName不为空,则循环解析Split Apk
                 if (!ArrayUtils.isEmpty(lite.splitNames)) {
                     final int num = lite.splitNames.length;
                     pkg.splitNames = lite.splitNames;
                     pkg.splitCodePaths = lite.splitCodePaths;
                     pkg.splitRevisionCodes = lite.splitRevisionCodes;
                     pkg.splitFlags = new int[num];
                     pkg.splitPrivateFlags = new int[num];
     
                     for (int i = 0; i < num; i++) {
                         //解析
                         parseSplitApk(pkg, i, assets, flags);
                     }
                 }
     
                 pkg.setCodePath(packageDir.getAbsolutePath());
                 pkg.setUse32bitAbi(lite.use32bitAbi);
                 return pkg;
             } finally {
                 IoUtils.closeQuietly(assets);
             }
         }
}

注:Split APK是Google为解决65535上限以及APK越来越大的问题而提出的一种方案,它可以将一个庞大的APK按照屏幕密度、ABI等形式拆分成多个独立的APK,这些APK共享相同的data、cache目录。 共享相同的进程,共享相同的包名。它们还可以使用各自的资源,并且继承了Base APK里的资源。

该方法调用parseBaseApk()去解析AndroidManifest.xml,AndroidManifest.xml也是xml文件,当然也使用XmlResourceParser来解析。这个解析相应标签并保存到PackageManagerService对应的成员变量中去。

调用PMS流程

在应用中,我们通常是调用Context的getPackageManager()方法返回PackageManager对象,这里返回的实际是ApplicationPackageManager对象,这个对象创建时使用IPackageManager对象作为参数,IPackageManager是PackageManagerService实现的AIDL接口,通过binder来获取PackageManagerService对象。

因此ApplicationPackageManager是PackageManagerService的代理对象,ApplicationPackageManager继承自PackageManager,PackageManager中定义了可以操作PackageManagerService的接口,在PackageManager中,实现对apk安装的方法是installExistingPackage和installPackageWithVerificationAndEncryption(前者是通过包名去升级应用,后者是安装新的应用)

尊重作者,尊重原创,参考文章:
https://blog.csdn.net/wy391920778/article/details/79881142
https://blog.csdn.net/qq_25804863/article/details/48565569

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值