Android APK安装流程(4)--APK加载

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android APK安装流程(4)--APK加载相关的知识,希望对你有一定的参考价值。

参考技术A 上面 主要分析到APK的copy过程,这里我们开始分析APK的加载过程。直接看之前流程进行到下一步的 processPendingInstall() 方法:

installPackagesLI() 可以支持单包和多包加载,加载主要分为4个阶段:

执行完2-2的 scanPackageTrackLI() 之后Pms的两大核心数据结构都已经准备好了,一个是代表扫描结果的final ArrayMap<String, PackageParser.Package> mPackages = new ArrayMap<>();中的PackageParser.Package,另外一个是mSettings.mPackages的PackageSetting 数据结构,这两个结构PackageParser.Package代表扫描结果,为静态数据,扫描完成后就不会发生变化。PackageSetting用于存储安装应用的动态数据,如权限授予情况等。PackageParser.Package由于是静态数据,扫描apk就可以获取。PackageSetting生成之后会被记录到文件中,以后每次系统启动都会重新加载。

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

以上是关于Android APK安装流程(4)--APK加载的主要内容,如果未能解决你的问题,请参考以下文章

Android 安装包优化APK 打包流程 ( 文件结构 | 打包流程 | 安装流程 | 安卓虚拟机 )

apk文件安装流程

apk文件安装流程

apk文件安装流程

Android源代码解析之(十三)--&gt;apk安装流程

Android 安装包优化资源混淆 ( 资源混淆效果 | APK 构建流程简介 | 资源 ID 组成 )