美文网首页面试题
APK文件安装流程

APK文件安装流程

作者: 劲火星空 | 来源:发表于2019-07-30 21:07 被阅读8次

    概述:
    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

    相关文章

      网友评论

        本文标题:APK文件安装流程

        本文链接:https://www.haomeiwen.com/subject/kgmorctx.html