Android应用程序的安装位置

从API Level8以后,android允许将应用程序安装在外部空间,之前则只能安装到内部空间。

一、安装到外部空间的特点

二、安装路径选择的策略

它由多方面的因素影响,包括应用本身的设置、安装应用的方式以及手机系统的默认选项等。下面来一一讨论;

1.应用自身可以指定安装路径:

android:installLocation="preferExternal"
可选的还有auto和 internalOnly,当然也可以不声明此参数,则默认为PackageInfo.INSTALL_LOCATION_UNSPECIFIED

在PackageInfo.java中定义如下:

public static final int INSTALL_LOCATION_UNSPECIFIED = -1;
public static final int INSTALL_LOCATION_AUTO = 0;
public static final int INSTALL_LOCATION_INTERNAL_ONLY = 1;
public static final int INSTALL_LOCATION_PREFER_EXTERNAL = 2;

/frameworks/base/core/res/res/values/attrs_manifest.xml文件有如下定义:

<!-- The default install location defined by an application. -->
<attr name="installLocation">
    <!-- Let the system decide ideal install location -->
    <enum name="auto" value="0" />
    <!-- Explicitly request to be installed on internal phone storage only. -->
    <enum name="internalOnly" value="1" />
    <!-- Prefer to be installed on SD card. There is no guarantee that the system will honor this request. The application might end
         up being installed on internal storage if external media is unavailable or too full. -->
    <enum name="preferExternal" value="2" />
</attr>

这个信息是在哪里解析并保存到哪里的呢?

我们知道,解析apk文件的工作是由PackageParser.java来实现的,所以这里解析AndroidManifest.xml文件中的installLocation信息是由它的一个方法实现的:

public static PackageLite parsePackageLite(String packageFilePath, int flags)

private static PackageLite parsePackageLite(Resources res, XmlPullParser parser, AttributeSet attrs, int flags, String[] outError)

这里共有两个方法,第一个执行过程中又调用第二个,而在代码中搜索,只有一个文件调用了这个public方法:DefaultContainerService.java。请特别留意,这里传入的flags参数的值为0:

PackageParser.PackageLite pkg = PackageParser.parsePackageLite(packagePath, 0);

既然说到这里,那就不得不提提PackageParser.PackageLite这个类,不过它实在太简单了,直接看吧:

public static class PackageLite {
    public final String packageName;
    public final int versionCode;
    public final int installLocation;
    public final VerifierInfo[] verifiers;

    public PackageLite(String packageName, int versionCode,
            int installLocation, List<VerifierInfo> verifiers) {
        this.packageName = packageName;
        this.versionCode = versionCode;
        this.installLocation = installLocation;
        this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]);
    }
}
所以parsePackageLite这个方法就没什么可讲的了,只是把xml文件的几个信息解析出来,保存到PackageLite的一个实例中而已。

我们需要知道的只是它解析了Manifest.xml文件中的installLocation信息,若未指定,则默认值为-1,否则为0、1或2(分别代表的含义签名已经很明确了)。

它将作为系统最终判断该把应用程序安装到内部空间还是外部空间的一条重要依据。后面我们会看到更详细的。



2.关于推荐安装位置的策略:

首先认识一个简单的类PackageInfoLite,它几乎只是由几个成员变量组成,仅次而已。

public class PackageInfoLite implements Parcelable {
    public String packageName;
    public int versionCode;

    /**
     * Specifies the recommended install location. Can be one of
     * {@link #PackageHelper.RECOMMEND_INSTALL_INTERNAL} to install on internal storage
     * {@link #PackageHelper.RECOMMEND_INSTALL_EXTERNAL} to install on external media
     * {@link PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE} for storage errors
     * {@link PackageHelper.RECOMMEND_FAILED_INVALID_APK} for parse errors.
     */
    public int recommendedInstallLocation;
    public int installLocation;

    public VerifierInfo[] verifiers;

    public PackageInfoLite() {
    }
	
    ... ...
}
相比PackageParser.PackageLite,它只增加了一个成员变量:public int recommendedInstallLocation;

DefaultContainerService.java的getMinimalPackageInfo方法:

PackageParser.PackageLite pkg = PackageParser.parsePackageLite(packagePath, 0);

ret.packageName = pkg.packageName;
ret.versionCode = pkg.versionCode;
ret.installLocation = pkg.installLocation;
ret.verifiers = pkg.verifiers;

ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation, packagePath, flags, threshold);
下面重点来分析recommendedInstallLocation的赋值,请留意这里传入的recommendAppInstallLocation的两个参数:

pkg.installLocation--它是解析Manifest.xml文件而得的一个参数;

flags--它是PackageManagerService中调用getMinimalPackageInfo方法时传入的参数,这里它应该代码的是在install某个应用时命令的附加参数,比如要求应用安装在SD中或内部空间中等。

下面来重点分析recommendAppInstallLocation这个方法:

private int recommendAppInstallLocation(int installLocation, String archiveFilePath, int flags,
        long threshold) {
    int prefer;
	//checkBoth的含义是:当确定了要安装在哪个位置后,我们应该去检查这个空间大小是否足够,如果checkBoth为true,则内部/外部我们都得查
    boolean checkBoth = false;

    final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;

    check_inner : {
        /*
         * Explicit install flags should override the manifest settings.
         */
		//如果安装应用时通过设置flags明确指出安装路径,那么flags说了算,直接退出check_inner
        if ((flags & PackageManager.INSTALL_INTERNAL) != 0) {
            prefer = PREFER_INTERNAL;
            break check_inner;
        } else if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) {
            prefer = PREFER_EXTERNAL;
            break check_inner;
        }

        /* No install flags. Check for manifest option. */
		//如果安装应用时没有明确指出安装路径,那么以apk本身的manifest中的值为准
		//注意一:如果apk中选择的是auto,那么它和internalOnly的结果是一样的
		//注意二:如果apk中选择的不是internalOnly,这里会设置checkBoth = true,它的意思是我们需要内部和外部空间的大小我们都要check,因为最终有可能还是安装在内部
        if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
            prefer = PREFER_INTERNAL;
            break check_inner;
        } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
            prefer = PREFER_EXTERNAL;
            checkBoth = true;
            break check_inner;
        } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
            // We default to preferring internal storage.
            prefer = PREFER_INTERNAL;
            checkBoth = true;
            break check_inner;
        }

        // Pick user preference
		//如果前两者都没有指定,那么系统数据库中有个默认的安装位置,以此值为准,这个值是我们作为手机开发者更改apk默认安装位置可以修改的地方
		//同样地,当这个值既不是内部、也不是外部时(正常地应该是auto),我们仍然将其置为内部
        int installPreference = Settings.Global.getInt(getApplicationContext()
                .getContentResolver(),
                Settings.Global.DEFAULT_INSTALL_LOCATION,
                PackageHelper.APP_INSTALL_AUTO);
        if (installPreference == PackageHelper.APP_INSTALL_INTERNAL) {
            prefer = PREFER_INTERNAL;
            break check_inner;
        } else if (installPreference == PackageHelper.APP_INSTALL_EXTERNAL) {
            prefer = PREFER_EXTERNAL;
            break check_inner;
        }
        /*
         * Fall back to default policy of internal-only if nothing else is specified.
         */
        prefer = PREFER_INTERNAL;
    }

	//判断外部设置是否有效
    final boolean emulated = Environment.isExternalStorageEmulated();

	//需要根据apk文件的大小来衡量手机中空间是否足够
    final File apkFile = new File(archiveFilePath);

	//fitsOnInternal代表安装在内部是否合适,所谓合适是指通过之前的判断,你想安装在内部空间中,而且内部空间大小足够
    boolean fitsOnInternal = false;
    if (checkBoth || prefer == PREFER_INTERNAL) {
        try {
            fitsOnInternal = isUnderInternalThreshold(apkFile, isForwardLocked, threshold);
        } catch (IOException e) {
            return PackageHelper.RECOMMEND_FAILED_INVALID_URI;
        }
    }

	//fitsOnSd代表安装在外部是否合适,所谓合适是指通过之前的判断,你想安装在外部空间中,而且外部空间大小足够
    boolean fitsOnSd = false;
    if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)) {
        try {
            fitsOnSd = isUnderExternalThreshold(apkFile, isForwardLocked);
        } catch (IOException e) {
            return PackageHelper.RECOMMEND_FAILED_INVALID_URI;
        }
    }

	//如果你想安装在内部/外部,而且内部/外部也是合适的,那就推荐你安装在内部/外部
    if (prefer == PREFER_INTERNAL) {
        if (fitsOnInternal) {
            return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
        }
    } else if (!emulated && prefer == PREFER_EXTERNAL) {
        if (fitsOnSd) {
            return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
        }
    }

    if (checkBoth) {
        if (fitsOnInternal) {
            return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
        } else if (!emulated && fitsOnSd) {
            return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
        }
    }

    /*
     * If they requested to be on the external media by default, return that
     * the media was unavailable. Otherwise, indicate there was insufficient
     * storage space available.
     */
    if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)
            && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
        return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE;
    } else {
        return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
    }
}



2


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值