前言

在PM机制系列前三篇,我们着重分析了安装的整个流程,没有具体到很多细节问题。
这一篇文章我们就会具体到很多细节问题。本篇主要就是围绕一个问题展开:
安装APK到底需要多少空间不会报错INSTALL_FAILED_INSUFFICIENT_STORAGE?可以提高我们的安装成功率。

1. 分析结果时序图

DCS: DefaultContainerService
SMS: StorageManagerService
SM: StorageManager
调用关系图如下所示,后面会进行具体分析。

#mermaid-svg-V0brcSWysuVv4w6u .label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);fill:#333;color:#333}#mermaid-svg-V0brcSWysuVv4w6u .label text{fill:#333}#mermaid-svg-V0brcSWysuVv4w6u .node rect,#mermaid-svg-V0brcSWysuVv4w6u .node circle,#mermaid-svg-V0brcSWysuVv4w6u .node ellipse,#mermaid-svg-V0brcSWysuVv4w6u .node polygon,#mermaid-svg-V0brcSWysuVv4w6u .node path{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-V0brcSWysuVv4w6u .node .label{text-align:center;fill:#333}#mermaid-svg-V0brcSWysuVv4w6u .node.clickable{cursor:pointer}#mermaid-svg-V0brcSWysuVv4w6u .arrowheadPath{fill:#333}#mermaid-svg-V0brcSWysuVv4w6u .edgePath .path{stroke:#333;stroke-width:1.5px}#mermaid-svg-V0brcSWysuVv4w6u .flowchart-link{stroke:#333;fill:none}#mermaid-svg-V0brcSWysuVv4w6u .edgeLabel{background-color:#e8e8e8;text-align:center}#mermaid-svg-V0brcSWysuVv4w6u .edgeLabel rect{opacity:0.9}#mermaid-svg-V0brcSWysuVv4w6u .edgeLabel span{color:#333}#mermaid-svg-V0brcSWysuVv4w6u .cluster rect{fill:#ffffde;stroke:#aa3;stroke-width:1px}#mermaid-svg-V0brcSWysuVv4w6u .cluster text{fill:#333}#mermaid-svg-V0brcSWysuVv4w6u div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#ffffde;border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}#mermaid-svg-V0brcSWysuVv4w6u .actor{stroke:#ccf;fill:#ECECFF}#mermaid-svg-V0brcSWysuVv4w6u text.actor>tspan{fill:#000;stroke:none}#mermaid-svg-V0brcSWysuVv4w6u .actor-line{stroke:grey}#mermaid-svg-V0brcSWysuVv4w6u .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333}#mermaid-svg-V0brcSWysuVv4w6u .messageLine1{stroke-width:1.5;stroke-dasharray:2, 2;stroke:#333}#mermaid-svg-V0brcSWysuVv4w6u #arrowhead path{fill:#333;stroke:#333}#mermaid-svg-V0brcSWysuVv4w6u .sequenceNumber{fill:#fff}#mermaid-svg-V0brcSWysuVv4w6u #sequencenumber{fill:#333}#mermaid-svg-V0brcSWysuVv4w6u #crosshead path{fill:#333;stroke:#333}#mermaid-svg-V0brcSWysuVv4w6u .messageText{fill:#333;stroke:#333}#mermaid-svg-V0brcSWysuVv4w6u .labelBox{stroke:#ccf;fill:#ECECFF}#mermaid-svg-V0brcSWysuVv4w6u .labelText,#mermaid-svg-V0brcSWysuVv4w6u .labelText>tspan{fill:#000;stroke:none}#mermaid-svg-V0brcSWysuVv4w6u .loopText,#mermaid-svg-V0brcSWysuVv4w6u .loopText>tspan{fill:#000;stroke:none}#mermaid-svg-V0brcSWysuVv4w6u .loopLine{stroke-width:2px;stroke-dasharray:2, 2;stroke:#ccf;fill:#ccf}#mermaid-svg-V0brcSWysuVv4w6u .note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-V0brcSWysuVv4w6u .noteText,#mermaid-svg-V0brcSWysuVv4w6u .noteText>tspan{fill:#000;stroke:none}#mermaid-svg-V0brcSWysuVv4w6u .activation0{fill:#f4f4f4;stroke:#666}#mermaid-svg-V0brcSWysuVv4w6u .activation1{fill:#f4f4f4;stroke:#666}#mermaid-svg-V0brcSWysuVv4w6u .activation2{fill:#f4f4f4;stroke:#666}#mermaid-svg-V0brcSWysuVv4w6u .mermaid-main-font{font-family:"trebuchet ms", verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-V0brcSWysuVv4w6u .section{stroke:none;opacity:0.2}#mermaid-svg-V0brcSWysuVv4w6u .section0{fill:rgba(102,102,255,0.49)}#mermaid-svg-V0brcSWysuVv4w6u .section2{fill:#fff400}#mermaid-svg-V0brcSWysuVv4w6u .section1,#mermaid-svg-V0brcSWysuVv4w6u .section3{fill:#fff;opacity:0.2}#mermaid-svg-V0brcSWysuVv4w6u .sectionTitle0{fill:#333}#mermaid-svg-V0brcSWysuVv4w6u .sectionTitle1{fill:#333}#mermaid-svg-V0brcSWysuVv4w6u .sectionTitle2{fill:#333}#mermaid-svg-V0brcSWysuVv4w6u .sectionTitle3{fill:#333}#mermaid-svg-V0brcSWysuVv4w6u .sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-V0brcSWysuVv4w6u .grid .tick{stroke:#d3d3d3;opacity:0.8;shape-rendering:crispEdges}#mermaid-svg-V0brcSWysuVv4w6u .grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-V0brcSWysuVv4w6u .grid path{stroke-width:0}#mermaid-svg-V0brcSWysuVv4w6u .today{fill:none;stroke:red;stroke-width:2px}#mermaid-svg-V0brcSWysuVv4w6u .task{stroke-width:2}#mermaid-svg-V0brcSWysuVv4w6u .taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-V0brcSWysuVv4w6u .taskText:not([font-size]){font-size:11px}#mermaid-svg-V0brcSWysuVv4w6u .taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-V0brcSWysuVv4w6u .taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}#mermaid-svg-V0brcSWysuVv4w6u .task.clickable{cursor:pointer}#mermaid-svg-V0brcSWysuVv4w6u .taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-V0brcSWysuVv4w6u .taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-V0brcSWysuVv4w6u .taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-V0brcSWysuVv4w6u .taskText0,#mermaid-svg-V0brcSWysuVv4w6u .taskText1,#mermaid-svg-V0brcSWysuVv4w6u .taskText2,#mermaid-svg-V0brcSWysuVv4w6u .taskText3{fill:#fff}#mermaid-svg-V0brcSWysuVv4w6u .task0,#mermaid-svg-V0brcSWysuVv4w6u .task1,#mermaid-svg-V0brcSWysuVv4w6u .task2,#mermaid-svg-V0brcSWysuVv4w6u .task3{fill:#8a90dd;stroke:#534fbc}#mermaid-svg-V0brcSWysuVv4w6u .taskTextOutside0,#mermaid-svg-V0brcSWysuVv4w6u .taskTextOutside2{fill:#000}#mermaid-svg-V0brcSWysuVv4w6u .taskTextOutside1,#mermaid-svg-V0brcSWysuVv4w6u .taskTextOutside3{fill:#000}#mermaid-svg-V0brcSWysuVv4w6u .active0,#mermaid-svg-V0brcSWysuVv4w6u .active1,#mermaid-svg-V0brcSWysuVv4w6u .active2,#mermaid-svg-V0brcSWysuVv4w6u .active3{fill:#bfc7ff;stroke:#534fbc}#mermaid-svg-V0brcSWysuVv4w6u .activeText0,#mermaid-svg-V0brcSWysuVv4w6u .activeText1,#mermaid-svg-V0brcSWysuVv4w6u .activeText2,#mermaid-svg-V0brcSWysuVv4w6u .activeText3{fill:#000 !important}#mermaid-svg-V0brcSWysuVv4w6u .done0,#mermaid-svg-V0brcSWysuVv4w6u .done1,#mermaid-svg-V0brcSWysuVv4w6u .done2,#mermaid-svg-V0brcSWysuVv4w6u .done3{stroke:grey;fill:#d3d3d3;stroke-width:2}#mermaid-svg-V0brcSWysuVv4w6u .doneText0,#mermaid-svg-V0brcSWysuVv4w6u .doneText1,#mermaid-svg-V0brcSWysuVv4w6u .doneText2,#mermaid-svg-V0brcSWysuVv4w6u .doneText3{fill:#000 !important}#mermaid-svg-V0brcSWysuVv4w6u .crit0,#mermaid-svg-V0brcSWysuVv4w6u .crit1,#mermaid-svg-V0brcSWysuVv4w6u .crit2,#mermaid-svg-V0brcSWysuVv4w6u .crit3{stroke:#f88;fill:red;stroke-width:2}#mermaid-svg-V0brcSWysuVv4w6u .activeCrit0,#mermaid-svg-V0brcSWysuVv4w6u .activeCrit1,#mermaid-svg-V0brcSWysuVv4w6u .activeCrit2,#mermaid-svg-V0brcSWysuVv4w6u .activeCrit3{stroke:#f88;fill:#bfc7ff;stroke-width:2}#mermaid-svg-V0brcSWysuVv4w6u .doneCrit0,#mermaid-svg-V0brcSWysuVv4w6u .doneCrit1,#mermaid-svg-V0brcSWysuVv4w6u .doneCrit2,#mermaid-svg-V0brcSWysuVv4w6u .doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}#mermaid-svg-V0brcSWysuVv4w6u .milestone{transform:rotate(45deg) scale(0.8, 0.8)}#mermaid-svg-V0brcSWysuVv4w6u .milestoneText{font-style:italic}#mermaid-svg-V0brcSWysuVv4w6u .doneCritText0,#mermaid-svg-V0brcSWysuVv4w6u .doneCritText1,#mermaid-svg-V0brcSWysuVv4w6u .doneCritText2,#mermaid-svg-V0brcSWysuVv4w6u .doneCritText3{fill:#000 !important}#mermaid-svg-V0brcSWysuVv4w6u .activeCritText0,#mermaid-svg-V0brcSWysuVv4w6u .activeCritText1,#mermaid-svg-V0brcSWysuVv4w6u .activeCritText2,#mermaid-svg-V0brcSWysuVv4w6u .activeCritText3{fill:#000 !important}#mermaid-svg-V0brcSWysuVv4w6u .titleText{text-anchor:middle;font-size:18px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-V0brcSWysuVv4w6u g.classGroup text{fill:#9370db;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}#mermaid-svg-V0brcSWysuVv4w6u g.classGroup text .title{font-weight:bolder}#mermaid-svg-V0brcSWysuVv4w6u g.clickable{cursor:pointer}#mermaid-svg-V0brcSWysuVv4w6u g.classGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-V0brcSWysuVv4w6u g.classGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-V0brcSWysuVv4w6u .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5}#mermaid-svg-V0brcSWysuVv4w6u .classLabel .label{fill:#9370db;font-size:10px}#mermaid-svg-V0brcSWysuVv4w6u .relation{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-V0brcSWysuVv4w6u .dashed-line{stroke-dasharray:3}#mermaid-svg-V0brcSWysuVv4w6u #compositionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-V0brcSWysuVv4w6u #compositionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-V0brcSWysuVv4w6u #aggregationStart{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-V0brcSWysuVv4w6u #aggregationEnd{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-V0brcSWysuVv4w6u #dependencyStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-V0brcSWysuVv4w6u #dependencyEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-V0brcSWysuVv4w6u #extensionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-V0brcSWysuVv4w6u #extensionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-V0brcSWysuVv4w6u .commit-id,#mermaid-svg-V0brcSWysuVv4w6u .commit-msg,#mermaid-svg-V0brcSWysuVv4w6u .branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-V0brcSWysuVv4w6u .pieTitleText{text-anchor:middle;font-size:25px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-V0brcSWysuVv4w6u .slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-V0brcSWysuVv4w6u g.stateGroup text{fill:#9370db;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-V0brcSWysuVv4w6u g.stateGroup text{fill:#9370db;fill:#333;stroke:none;font-size:10px}#mermaid-svg-V0brcSWysuVv4w6u g.statediagram-cluster .cluster-label text{fill:#333}#mermaid-svg-V0brcSWysuVv4w6u g.stateGroup .state-title{font-weight:bolder;fill:#000}#mermaid-svg-V0brcSWysuVv4w6u g.stateGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-V0brcSWysuVv4w6u g.stateGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-V0brcSWysuVv4w6u .transition{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-V0brcSWysuVv4w6u .stateGroup .composit{fill:white;border-bottom:1px}#mermaid-svg-V0brcSWysuVv4w6u .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px}#mermaid-svg-V0brcSWysuVv4w6u .state-note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-V0brcSWysuVv4w6u .state-note text{fill:black;stroke:none;font-size:10px}#mermaid-svg-V0brcSWysuVv4w6u .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.7}#mermaid-svg-V0brcSWysuVv4w6u .edgeLabel text{fill:#333}#mermaid-svg-V0brcSWysuVv4w6u .stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-V0brcSWysuVv4w6u .node circle.state-start{fill:black;stroke:black}#mermaid-svg-V0brcSWysuVv4w6u .node circle.state-end{fill:black;stroke:white;stroke-width:1.5}#mermaid-svg-V0brcSWysuVv4w6u #statediagram-barbEnd{fill:#9370db}#mermaid-svg-V0brcSWysuVv4w6u .statediagram-cluster rect{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-V0brcSWysuVv4w6u .statediagram-cluster rect.outer{rx:5px;ry:5px}#mermaid-svg-V0brcSWysuVv4w6u .statediagram-state .divider{stroke:#9370db}#mermaid-svg-V0brcSWysuVv4w6u .statediagram-state .title-state{rx:5px;ry:5px}#mermaid-svg-V0brcSWysuVv4w6u .statediagram-cluster.statediagram-cluster .inner{fill:white}#mermaid-svg-V0brcSWysuVv4w6u .statediagram-cluster.statediagram-cluster-alt .inner{fill:#e0e0e0}#mermaid-svg-V0brcSWysuVv4w6u .statediagram-cluster .inner{rx:0;ry:0}#mermaid-svg-V0brcSWysuVv4w6u .statediagram-state rect.basic{rx:5px;ry:5px}#mermaid-svg-V0brcSWysuVv4w6u .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#efefef}#mermaid-svg-V0brcSWysuVv4w6u .note-edge{stroke-dasharray:5}#mermaid-svg-V0brcSWysuVv4w6u .statediagram-note rect{fill:#fff5ad;stroke:#aa3;stroke-width:1px;rx:0;ry:0}:root{--mermaid-font-family: '"trebuchet ms", verdana, arial';--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive}#mermaid-svg-V0brcSWysuVv4w6u .error-icon{fill:#522}#mermaid-svg-V0brcSWysuVv4w6u .error-text{fill:#522;stroke:#522}#mermaid-svg-V0brcSWysuVv4w6u .edge-thickness-normal{stroke-width:2px}#mermaid-svg-V0brcSWysuVv4w6u .edge-thickness-thick{stroke-width:3.5px}#mermaid-svg-V0brcSWysuVv4w6u .edge-pattern-solid{stroke-dasharray:0}#mermaid-svg-V0brcSWysuVv4w6u .edge-pattern-dashed{stroke-dasharray:3}#mermaid-svg-V0brcSWysuVv4w6u .edge-pattern-dotted{stroke-dasharray:2}#mermaid-svg-V0brcSWysuVv4w6u .marker{fill:#333}#mermaid-svg-V0brcSWysuVv4w6u .marker.cross{stroke:#333}:root { --mermaid-font-family: "trebuchet ms", verdana, arial;}#mermaid-svg-V0brcSWysuVv4w6u {color: rgba(0, 0, 0, 0.75);font: ;}PMSDCSPackageHelperStorageManagerSMShandleStartCopygetMinimalPackageInforesolveInstallLocationfitsOnInternaltranslateAllocateFlagsgetAllocatableBytesgetAllocatableBytesgetStorageLowBytes获取最低存储空间判断是否可以安装PMSDCSPackageHelperStorageManagerSMS

2. 开始安装

经过前三篇的安装流程,可以清楚的知道安装核心代码开始处就在handleStartCopy方法,代码如下:
/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

public void handleStartCopy() throws RemoteException {...// If we're already staged, we've firmly committed to an install locationif (origin.staged) {//安装时候,传参进来的origin.staged是为true,file != nullif (origin.file != null) {installFlags |= PackageManager.INSTALL_INTERNAL; //这样就会走到这里给installFlags赋值,installFlags &= ~PackageManager.INSTALL_EXTERNAL;//走内部安装逻辑} else {throw new IllegalStateException("Invalid stage location");}}/*确定APK的安装位置。onSd:安装到SD卡, onInt:内部存储即Data分区,ephemeral:安装到临时存储(Instant Apps安装)这里根据上面installFlags的赋值可知onSd=false;onInt=true;ephemeral=false;*/      final boolean onSd = (installFlags & PackageManager.INSTALL_EXTERNAL) != 0;final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) != 0;final boolean ephemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;PackageInfoLite pkgLite = null;if (onInt && onSd) {// APK不能同时安装在SD卡和Data分区Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;//安装标志冲突,Instant Apps不能安装到SD卡中} else if (onSd && ephemeral) {Slog.w(TAG,  "Conflicting flags specified for installing ephemeral on external");ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;} else { //最终会走到这里来//获取APK的少量的信息pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags,packageAbiOverride);//1if (DEBUG_EPHEMERAL && ephemeral) {Slog.v(TAG, "pkgLite for install: " + pkgLite);}...if (ret == PackageManager.INSTALL_SUCCEEDED) {//判断安装的位置int loc = pkgLite.recommendedInstallLocation;if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;} else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) {ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;}  else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; //2} ...}else{loc = installLocationPolicy(pkgLite);...}}//根据InstallParams创建InstallArgs对象final InstallArgs args = createInstallArgs(this);mArgs = args;if (ret == PackageManager.INSTALL_SUCCEEDED) {...if (!origin.existing && requiredUid != -1&& isVerificationEnabled(verifierUser.getIdentifier(), installFlags, installerUid)) {...} else{ret = args.copyApk(mContainerService, true);}}mRet = ret;}

handleStartCopy方法的代码很多,这里截取关键的部分。

  1. 注释1处通过IMediaContainerService跨进程调用DefaultContainerService的getMinimalPackageInfo方法,该方法轻量解析APK并得到APK的少量信息,轻量解析的原因是这里不需要得到APK的全部信息,APK的少量信息会封装到PackageInfoLite中。在上一篇中我们一笔带过,这里信息量很多,里面就有我们这篇文章所涉及到的安装空间判断。
  2. 注释2处,如果loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE,就会返回INSTALL_FAILED_INSUFFICIENT_STORAGE。所以要从loc的返回值分析。下面看一下getMinimalPackageInfo方法。

/frameworks/base/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java

/*** Parse given package and return minimal details.** @param packagePath absolute path to the package to be copied. Can be*            a single monolithic APK file or a cluster directory*            containing one or more APKs.*/
@Override
public PackageInfoLite getMinimalPackageInfo(String packagePath, int flags,String abiOverride) {final Context context = DefaultContainerService.this;PackageInfoLite ret = new PackageInfoLite();if (packagePath == null) {Slog.i(TAG, "Invalid package file " + packagePath);ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;return ret;}final File packageFile = new File(packagePath);final PackageParser.PackageLite pkg;final long sizeBytes;try {pkg = PackageParser.parsePackageLite(packageFile, 0);sizeBytes = PackageHelper.calculateInstalledSize(pkg, abiOverride); //1} catch (PackageParserException | IOException e) {Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e);if (!packageFile.exists()) {ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;} else {ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;}return ret;}final int recommendedInstallLocation;final long token = Binder.clearCallingIdentity();try {recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,pkg.packageName, pkg.installLocation, sizeBytes, flags);//2} finally {Binder.restoreCallingIdentity(token);}ret.packageName = pkg.packageName;ret.splitNames = pkg.splitNames;ret.versionCode = pkg.versionCode;ret.versionCodeMajor = pkg.versionCodeMajor;ret.baseRevisionCode = pkg.baseRevisionCode;ret.splitRevisionCodes = pkg.splitRevisionCodes;ret.installLocation = pkg.installLocation;ret.verifiers = pkg.verifiers;ret.recommendedInstallLocation = recommendedInstallLocation;ret.multiArch = pkg.multiArch;return ret;
}
  1. 注释1处计算安装apk需要的空间大小。
  2. 注释2处是核心代码,根据apk需要的空间大小计算推荐存储位置,参数flags就是就是我们之前赋值的内部存储。

下面看一下resolveInstallLocation方法
/frameworks/base/core/java/com/android/internal/content/PackageHelper.java

@Deprecated
public static int resolveInstallLocation(Context context, String packageName,int installLocation, long sizeBytes, int installFlags) {final SessionParams params = new SessionParams(SessionParams.MODE_INVALID);params.appPackageName = packageName;params.installLocation = installLocation;params.sizeBytes = sizeBytes;params.installFlags = installFlags;try {return resolveInstallLocation(context, params); //1} catch (IOException e) {throw new IllegalStateException(e);}
}/*** Given a requested {@link PackageInfo#installLocation} and calculated* install size, pick the actual location to install the app.*/
public static int resolveInstallLocation(Context context, SessionParams params)throws IOException {ApplicationInfo existingInfo = null;try {existingInfo = context.getPackageManager().getApplicationInfo(params.appPackageName,PackageManager.MATCH_ANY_USER);} catch (NameNotFoundException ignored) {}final int prefer;final boolean checkBoth;boolean ephemeral = false;if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {prefer = RECOMMEND_INSTALL_INTERNAL;ephemeral = true;checkBoth = false;} else if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) { //2prefer = RECOMMEND_INSTALL_INTERNAL;checkBoth = false;} else if ((params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {prefer = RECOMMEND_INSTALL_EXTERNAL;checkBoth = false;} else if (params.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {prefer = RECOMMEND_INSTALL_INTERNAL;checkBoth = false;} else if (params.installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {prefer = RECOMMEND_INSTALL_EXTERNAL;checkBoth = true;} else if (params.installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {// When app is already installed, prefer same mediumif (existingInfo != null) {// TODO: distinguish if this is external ASECif ((existingInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {prefer = RECOMMEND_INSTALL_EXTERNAL;} else {prefer = RECOMMEND_INSTALL_INTERNAL;}} else {prefer = RECOMMEND_INSTALL_INTERNAL;}checkBoth = true;} else {prefer = RECOMMEND_INSTALL_INTERNAL;checkBoth = false;}boolean fitsOnInternal = false;if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) {fitsOnInternal = fitsOnInternal(context, params); //3}boolean fitsOnExternal = false;if (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) {fitsOnExternal = fitsOnExternal(context, params);}if (prefer == RECOMMEND_INSTALL_INTERNAL) {  //4// The ephemeral case will either fit and return EPHEMERAL, or will not fit// and will fall through to return INSUFFICIENT_STORAGEif (fitsOnInternal) {return (ephemeral)? PackageHelper.RECOMMEND_INSTALL_EPHEMERAL: PackageHelper.RECOMMEND_INSTALL_INTERNAL; //5}} else if (prefer == RECOMMEND_INSTALL_EXTERNAL) {if (fitsOnExternal) {return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;}}if (checkBoth) {if (fitsOnInternal) {return PackageHelper.RECOMMEND_INSTALL_INTERNAL;} else if (fitsOnExternal) {return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;}}return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;  //6
}
  1. 注释1处将我们传入的参数全部都放入SessionParams内,执行resolveInstallLocation
  2. 根据之前传入的flags值代码会运营到注释2处。此处赋值:
    prefer = RECOMMEND_INSTALL_INTERNAL;
    checkBoth = false;
  3. 根据之前的赋值,会走到注释3处,调用fitsOnInternal()方法。
    如果fitsOnInternal为true,就会走到注释5处,返回RECOMMEND_INSTALL_INTERNAL
    如果fitsOnInternal为false,就会走到注释6处,返回RECOMMEND_FAILED_INSUFFICIENT_STORAGE。

下面我们看一下fitsOnInternal()方法
/frameworks/base/core/java/com/android/internal/content/PackageHelper.java

public static boolean fitsOnInternal(Context context, SessionParams params) throws IOException {final StorageManager storage = context.getSystemService(StorageManager.class);final UUID target = storage.getUuidForPath(Environment.getDataDirectory());return (params.sizeBytes <= storage.getAllocatableBytes(target,translateAllocateFlags(params.installFlags)));  //1
}
  1. 就是判断安装需要的空间大小是否小于系统能分配的存储大小。
  2. translateAllocateFlags方法如下,由于installFlags并没有设置INSTALL_ALLOCATE_AGGRESSIVE标识符,所以返回0。
    public static int translateAllocateFlags(int installFlags) {if ((installFlags & PackageManager.INSTALL_ALLOCATE_AGGRESSIVE) != 0) {return StorageManager.FLAG_ALLOCATE_AGGRESSIVE;} else {return 0;}
    }
    

下面就会调用到StorageManager的getAllocatableBytes方法
/frameworks/base/core/java/android/os/storage/StorageManager.java

public long getAllocatableBytes(@NonNull UUID storageUuid,@RequiresPermission @AllocateFlags int flags) throws IOException {try {return mStorageManager.getAllocatableBytes(convert(storageUuid), flags,mContext.getOpPackageName()); //1} catch (ParcelableException e) {e.maybeRethrow(IOException.class);throw new RuntimeException(e);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}
}
  1. 注释1继续调用StorageManagerService的getAllocatableBytes方法
    /frameworks/base/services/core/java/com/android/server/StorageManagerService.java
 @Override
public long getAllocatableBytes(String volumeUuid, int flags, String callingPackage) {flags = adjustAllocateFlags(flags, Binder.getCallingUid(), callingPackage);final StorageManager storage = mContext.getSystemService(StorageManager.class);final StorageStatsManager stats = mContext.getSystemService(StorageStatsManager.class);final long token = Binder.clearCallingIdentity();try {// In general, apps can allocate as much space as they want, except// we never let them eat into either the minimum cache space or into// the low disk warning space. To avoid user confusion, this logic// should be kept in sync with getFreeBytes().final File path = storage.findPathForUuid(volumeUuid);final long usable = path.getUsableSpace();//1final long lowReserved = storage.getStorageLowBytes(path);//2final long fullReserved = storage.getStorageFullBytes(path);if (stats.isQuotaSupported(volumeUuid)) {   //3final long cacheTotal = stats.getCacheBytes(volumeUuid);final long cacheReserved = storage.getStorageCacheBytes(path, flags);final long cacheClearable = Math.max(0, cacheTotal - cacheReserved);if ((flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0) {return Math.max(0, (usable + cacheClearable) - fullReserved);} else {return Math.max(0, (usable + cacheClearable) - lowReserved); //4}} else {// When we don't have fast quota information, we ignore cached// data and only consider unused bytes.if ((flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0) {return Math.max(0, usable - fullReserved);} else {return Math.max(0, usable - lowReserved);//5}}} catch (IOException e) {throw new ParcelableException(e);} finally {Binder.restoreCallingIdentity(token);}
}
  1. 注释1处会先求出分区可用的空间
  2. 注释2处会求出系统运行需要的最低存储
  3. 注释3处会 判断是否支持Quota,在9.0虚拟机上和真机上测试都是支持的。
  4. 注释4如果支持,就会计算cacheTotal(总共的cache大小),cacheReserved(需要保留的cache大小),求出可以清理的cache大小。返回getTotalSpace + 可清理cache大小-需要保留的
  5. 注释5如果不支持返回getTotalSpace的5%和500M之间的最小值

下面我们看一下getStorageLowBytes方法
/frameworks/base/core/java/android/os/storage/StorageManager.java

private static final int DEFAULT_THRESHOLD_PERCENTAGE = 5;
private static final long DEFAULT_THRESHOLD_MAX_BYTES = DataUnit.MEBIBYTES.toBytes(500);public long getStorageLowBytes(File path) {final long lowPercent = Settings.Global.getInt(mResolver,Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE);final long lowBytes = (path.getTotalSpace() * lowPercent) / 100;final long maxLowBytes = Settings.Global.getLong(mResolver,Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES, DEFAULT_THRESHOLD_MAX_BYTES);return Math.min(lowBytes, maxLowBytes);
}

我们发现。系统设置的默认阈值是5%,最终返回值为getTotalSpace的5%和500M之间的最小值。

总结

至此,算是全部结束了,我们发现只要系统空间小于Math.min(getTotalSpace的5%,500M)+ PackageHelper.calculateInstalledSize(pkg, abiOverride),系统就会报空间不足。

Android 9.0 PM机制系列(四) APK安装需要空间分析相关推荐

  1. Android 6.0 PM机制系列(四) APK安装需要空间分析

    前言 在Android 9.0 PM机制系列(四) APK安装需要空间分析文章中,我们重点分析了Android9.0需要的最小APK安装存储空间大小.结论就是:只要系统空间小于Math.min(get ...

  2. Android 10.0 PackageManagerService(三)APK扫描-[Android取经之路]

    摘要:上一节讲解了PKMS的 权限扫描,扫描/system/etc/permissions中的xml,存入相应的结构体中,供之后权限管理使用. 这一节主要来讲讲APK的扫描. 阅读本文大约需要花费15 ...

  3. Android 4.0.4系统下实现apk的静默安装和启动

    转自http://www.linuxidc.com/Linux/2013-02/79403.htm 未亲测 最近在Android 4.0.4系统下实现apk的静默安装和启动的功能,这里和大家分享一下, ...

  4. 深入理解 Android 9.0 Crash 机制(二)

    极力推荐Android 开发大总结文章:欢迎收藏 Android 开发技术文章大总结 本篇文章主要介绍 Android 开发中的部分知识点,通过阅读本篇文章,您将收获以下内容: 九. AppError ...

  5. Docker系列四~docker安装mysql

    Docker系列四 docker安装mysql 搜索mysql版本 [root@localhost conf.d]# docker search mysql INDEX NAME DESCRIPTIO ...

  6. Android 8.0 VDEX机制简介

    背景 Android 8.0在odex的基础上又引入了vdex机制,目的是为了降低dex2oat时间. 因为当系统ota后,用户自己安装的应用是不会发生任何变化的,但framework代码已经发生了变 ...

  7. android应用是非正式版本,Androidstudio 打包apk安装失败 应用是非正式发布版本,当前设备不支持安装...

    Androidstudio 打包apk安装失败 应用是非正式发布版本,当前设备不支持安装 环境:Androidstudio 4.0  华为手机版本 10.0 android { compileSdkV ...

  8. Android 6.0 BluetoothAdapter.startDiscovery()扫描不到蓝牙的问题分析及解决

    一.问题描述 最近在做蓝牙相关的项目,用 BluetoothAdapter.startDiscovery() 这个方法在Android 6.0 的机子上扫描不到对设备可见的蓝牙,但在 Android ...

  9. android 6.0 logcat机制(一)java层写log,logd接受log

    第一篇博客,讲的主要是c++,java中打印log,然后通过socket传给logd,然后logd是如何处理接受log的. 一.logcat常用命令 logcat -c 清除已有log信息 logca ...

最新文章

  1. 看动画轻松理解「链表」实现「LRU缓存淘汰算法」
  2. chm文件打不开问题
  3. 【LoadRunner】OSGI性能测试实例
  4. ubuntu mysql5.6 编译安装_Ubuntu14.04编译安装mysql5.6.26
  5. IE下checkbox或radio隐藏bug
  6. lisp标注界址点号_(IP服务年终大盘点第二期)协会理事单位湖北高韬律师事务所完成韩国商标注册优先审查...
  7. C# 强制关闭当前程序进程(完全Kill掉不留痕迹)
  8. 如何优雅的移植JavaScript组件到Blazor
  9. 电脑睡眠快捷键_电脑快速进入睡眠的快捷键是什么?
  10. python创建sqlite3 unicode error_python/sqlite3:发生异常:sqlite3.operationalerror
  11. 电动汽车真的省钱吗?
  12. C++并发与多线程(三)单例设计模式与共享数据分析、call_once、condition_variable使用
  13. 数据恢复哪家强?四大数据恢复类软件评测
  14. [小o地图-数据] - 获取全国行政区划轮廓数据(上)
  15. 断层成像CT和ET重建算法
  16. amd处理器更新zen4服务器芯片,AMD将推出64 核心Zen 4处理器,整体性能提升了40%...
  17. 『C语言从入门到进阶』第 ⑥ 期 - 初识指针
  18. intellij IDE 快捷键(windows)
  19. 微信商户发放红包接口调试经验总结
  20. 最简洁的百度图片爬虫

热门文章

  1. 量化交易-回测调参-神龙摆尾
  2. 2172php,MAX2172 直接变频至低IF的调谐器,用于数字音频广播
  3. TokenGazer《一问到底》第53期:研究员 vs Cocos-BCX
  4. 解决下载github-production-release-asset-2e65be.s3.amazonaws.com上release文件慢的问题
  5. 李云大连理工计算机系2015级,大连理工大学考研研究生导师简介-李秀英
  6. 有时候,我们曾经有着同样的困扰。
  7. DOTA2无法找到有效的direct 3D
  8. mysql批量删除5000条数据_mysql批量删除大量数据
  9. SQL server 复杂查询
  10. 【已补蓝奏云链接】PyTorch中MNIST数据集(附datasets.MNIST离线包)下载慢/安装慢的解决方案