安装应用时,手机内部存储空间不足的弹框流程:
1.手机内部存储空间阈值为500M:
StorageManager.java中:
/**
* Return the number of available bytes at which the given path is
* considered running low on storage.
*
* @hide
*/
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);
}
2.在PackageInstaller中,在InstallInstalling.java的OnCreate中会进行一次getAllocatableBytes与file.length()的判断,以免在session.openWrite的时候抛出异常:
11-02 05:00:43.684 3993 4022 E InstallInstalling: Could not write package
11-02 05:00:43.684 3993 4022 E InstallInstalling: java.io.IOException: Failed to allocate 62778401 because only 0 allocatable
11-02 05:00:43.684 3993 4022 E InstallInstalling: at java.lang.reflect.Constructor.newInstance0(Native Method)
11-02 05:00:43.684 3993 4022 E InstallInstalling: at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
11-02 05:00:43.684 3993 4022 E InstallInstalling: at android.os.ParcelableException.readFromParcel(ParcelableException.java:56)
11-02 05:00:43.684 3993 4022 E InstallInstalling: at android.os.ParcelableException$1.createFromParcel(ParcelableException.java:82)
11-02 05:00:43.684 3993 4022 E InstallInstalling: at android.os.ParcelableException$1.createFromParcel(ParcelableException.java:79)
11-02 05:00:43.684 3993 4022 E InstallInstalling: at android.os.Parcel.readParcelable(Parcel.java:2774)
11-02 05:00:43.684 3993 4022 E InstallInstalling: at android.os.Parcel.createException(Parcel.java:1945)
11-02 05:00:43.684 3993 4022 E InstallInstalling: at android.os.Parcel.readException(Parcel.java:1918)
11-02 05:00:43.684 3993 4022 E InstallInstalling: at android.os.Parcel.readException(Parcel.java:1868)
11-02 05:00:43.684 3993 4022 E InstallInstalling: at android.content.pm.IPackageInstallerSession$Stub$Proxy.openWrite(IPackageInstallerSession.java:256)
11-02 05:00:43.684 3993 4022 E InstallInstalling: at android.content.pm.PackageInstaller$Session.openWrite(PackageInstaller.java:839)
11-02 05:00:43.684 3993 4022 E InstallInstalling: at com.android.packageinstaller.InstallInstalling$InstallingAsyncTask.doInBackground(InstallInstalling.java:347)
11-02 05:00:43.684 3993 4022 E InstallInstalling: at com.android.packageinstaller.InstallInstalling$InstallingAsyncTask.doInBackground(InstallInstalling.java:326)
11-02 05:00:43.684 3993 4022 E InstallInstalling: at android.os.AsyncTask$2.call(AsyncTask.java:333)
11-02 05:00:43.684 3993 4022 E InstallInstalling: at java.util.concurrent.FutureTask.run(FutureTask.java:266)
11-02 05:00:43.684 3993 4022 E InstallInstalling: at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
11-02 05:00:43.684 3993 4022 E InstallInstalling: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
11-02 05:00:43.684 3993 4022 E InstallInstalling: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
11-02 05:00:43.684 3993 4022 E InstallInstalling: at java.lang.Thread.run(Thread.java:764)
11-02 05:00:43.684 3993 4022 E InstallInstalling: Caused by: android.os.RemoteException: Remote stack trace:
11-02 05:00:43.684 3993 4022 E InstallInstalling: at android.util.ExceptionUtils.wrap(ExceptionUtils.java:34)
11-02 05:00:43.684 3993 4022 E InstallInstalling: at com.android.server.pm.PackageInstallerSession.openWrite(PackageInstallerSession.java:630)
11-02 05:00:43.684 3993 4022 E InstallInstalling: at android.content.pm.IPackageInstallerSession$Stub.onTransact(IPackageInstallerSession.java:82)
11-02 05:00:43.684 3993 4022 E InstallInstalling: at android.os.Binder.execTransact(Binder.java:731)
因为openWrite是调用的PackageInstallerSession中openWrite,openWrite调用doWriteInternal,doWriteInternal会调用:
mContext.getSystemService(StorageManager.class).allocateBytes(targetFd, lengthBytes,
PackageHelper.translateAllocateFlags(params.installFlags));
在StorageManager中allocateBytes的时候,实际调用的是StorageManagerService的allocateBytes:
@Override
public void allocateBytes(String volumeUuid, long bytes, int flags, String callingPackage) {
flags = adjustAllocateFlags(flags, Binder.getCallingUid(), callingPackage);
final long allocatableBytes = getAllocatableBytes(volumeUuid, flags, callingPackage);
if (bytes > allocatableBytes) {
throw new ParcelableException(new IOException("Failed to allocate " + bytes
+ " because only " + allocatableBytes + " allocatable"));
}
final StorageManager storage = mContext.getSystemService(StorageManager.class);
final long token = Binder.clearCallingIdentity();
try {
// Free up enough disk space to satisfy both the requested allocation
// and our low disk warning space.
final File path = storage.findPathForUuid(volumeUuid);
if ((flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0) {
bytes += storage.getStorageFullBytes(path);
} else {
bytes += storage.getStorageLowBytes(path);
}
mPms.freeStorage(volumeUuid, bytes, flags);
} catch (IOException e) {
throw new ParcelableException(e);
} finally {
Binder.restoreCallingIdentity(token);
}
}
当申请的bytes大于getAllocatableBytes时,则抛出异常:
if (bytes > allocatableBytes) {
throw new ParcelableException(new IOException("Failed to allocate " + bytes
+ " because only " + allocatableBytes + " allocatable"));
}
所以在进行openWrite前,判断当前申请的bytes是否大于getAllocatableBytes,如果大于,则安装失败,发送:
//mq.zhang add begin
//Add by mq.zhang,20190826,bug 5129,for show out of space dialog if needed
long sizeBytes = file.length();
//Calculate freeing disk space on the target device
long allocatableBytes = 0;
StorageManager storage = mContext.getSystemService(StorageManager.class);
//long lowThreshold = storage.getStorageLowBytes(Environment.getDataDirectory()); //得到手机内部存储空间阈值
List<VolumeInfo> volumes = storage.getVolumes();
for (VolumeInfo vol : volumes) {
if (vol.getType() == VolumeInfo.TYPE_PRIVATE) {
//StorageStatsManager stats = mContext.getSystemService(StorageStatsManager.class);
//freeBytes = stats.getFreeBytes(vol.getFsUuid()); //得到手机内部存储剩余空间
allocatableBytes = storage.getAllocatableBytes(StorageManager.convert(vol.getFsUuid()));
}
}
if (sizeBytes >= allocatableBytes) {
launchFailure(PackageInstaller.STATUS_FAILURE_STORAGE, null);
}
//mq.zhang add end
3.openWrite进行申请bytes成功后,在安装过程中还会由于copyApk的时候没有空间导致安装失败,发送:PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE
/**
* Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS}
* if the package manager service found that the device didn't have enough storage space to
* install the app.
*
* @hide
*/
@SystemApi
public static final int INSTALL_FAILED_INSUFFICIENT_STORAGE = -4;
4.在InstallFailed.java中,当收到legacyStatus为以下两种时,弹出空间不足弹框:
PackageInstaller.STATUS_FAILURE_STORAGE
PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE
//mq.zhang add begin
//Add by mq.zhang,20190826,bug 5129,for show out of space dialog if needed
int legacyStatus = getIntent().getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS,PackageManager.INSTALL_FAILED_INTERNAL_ERROR);
if (legacyStatus == PackageInstaller.STATUS_FAILURE_STORAGE
|| legacyStatus == PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE) {
(new OutOfSpaceDialog()).show(getFragmentManager(), "outofspace");
}
//mq.zhang add end
/**
* Dialog shown when we ran out of space during installation. This contains a link to the
* "manage applications" settings page.
*/
public static class OutOfSpaceDialog extends DialogFragment {
private InstallFailed mActivity;
@Override
public void onAttach(Context context) {
super.onAttach(context);
mActivity = (InstallFailed) context;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(mActivity)
.setTitle(R.string.out_of_space_dlg_title)
.setMessage(getString(R.string.out_of_space_dlg_text, mActivity.mLabel))
.setPositiveButton(R.string.manage_applications, (dialog, which) -> {
// launch manage applications
Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE");
startActivity(intent);
mActivity.finish();
})
.setNegativeButton(R.string.cancel, (dialog, which) -> mActivity.finish())
.create();
}
@Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
mActivity.finish();
}
}