平台
Android 5.0
问题
使用命令pm set-install-location 2后,使用命令pm install -r example.apk,打开“设置-应用-SD卡”,应用未勾选,没有安装在SD卡中
原因
5.0的源码中没有读取set的值,若不满足一系列判断(安装命令是否指定安装位置,应用自己是否指定安装位置),最后默认是安装在Internal
解决方法
参考Android 4.4,不满足一系列判断时,再获取set的值,该值保存在Settings.global.DEFAULT_INSTALL_LOCATION,参考:
//com.android.defcontainer.DefaultContainerService.java
private int recommendAppInstallLocation(int installLocation, String archiveFilePath, int flags,
long threshold) {
...
check_inner : {
...
// Pick user preference
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;
}
...
}
将上面的读取设置的代码放到5.0对应的PackageHelper.resolveInstallLocation方法中。
详细内容
决定应用安装位置
应用安装位置的设置有三种:Auto、Internal和External,对应的值是0、1和2 。决定应用安装位置的有几种方式:
(1)安装命令指定:pm install命令可以使用参数“-s”强制指定安装位置为External
pm install: install a single legacy package
pm install-create: create an install session
-l: forward lock application
-r: replace existing application
-t: allow test packages
-i: specify the installer package name
-s: install application on sdcard
-f: install application on internal flash
-d: allow version code downgrade
-p: partial application install
-S: size in bytes of entire session
(2)应用在AndroidManifest.xml中指定,参数也是:auto,internal和external,注意是在manifest节点而不是application节点:
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:installLocation="auto">
<application>
...
</application>
</manifest>
(3)通过命令设置:通过命令“pm set-install-location”或者“settings set global default_install_location”设置的值是同一个,保存位置前文也提到了,保存在Settings.global.DEFAULT_INSTALL_LOCATION:
这三个的优先级在4.4是(1)>(2)>(3),即优先判断install命令的参数installFlags,再判断应用自身是否指定了位置,最后判断setting数据库中的值,5.0只有前面两步,如果前面两步都不能确定,是直接指定安装在Internal的。
Android 4.4和5.0差别
pm install命令可以从Pm.java文件开始跟进,4.4和5.0最终都会调用DefaultContentService的getMinimalPackageInfo方法,在这里两个版本有了差别:
//Android 4.4
public PackageInfoLite getMinimalPackageInfo(final String packagePath, int flags,
long threshold) {
...
//4.4在这里直接调用了recommendAppInstallLocation方法,获取到的值就是应用即将安装的位置
ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation,
packagePath, flags, threshold);
return ret;
}
...
private int recommendAppInstallLocation(int installLocation, String archiveFilePath, int flags,
long threshold) {
int prefer;
boolean checkBoth = false;
final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
//check_inner就是按照命令参数,应用自身指定,最后数据库的方法获取到安装位置
check_inner : {
/*
* Explicit install flags should override the manifest settings.
*/
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. */
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
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;
}
...
}
//Android 5.0
public PackageInfoLite getMinimalPackageInfo(String packagePath, int flags,
String abiOverride) {
...
//5.0调用了PackageHelper.resolveInstallLocation
ret.recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,
pkg.packageName, pkg.installLocation, sizeBytes, flags);
ret.multiArch = pkg.multiArch;
return ret;
}
//PackageHelper.java
public static int resolveInstallLocation(Context context, SessionParams params)
throws IOException {
...
//注意这里if也是先判断installFlags,然后判断PackageInfo中的installLocation,也就是应用指定的
if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
prefer = RECOMMEND_INSTALL_INTERNAL;
ephemeral = true;
checkBoth = false;
} else if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
prefer = 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 medium
if (existingInfo != null) {
// TODO: distinguish if this is external ASEC
if ((existingInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
prefer = RECOMMEND_INSTALL_EXTERNAL;
} else {
prefer = RECOMMEND_INSTALL_INTERNAL;
}
} else {
prefer = RECOMMEND_INSTALL_INTERNAL;
}
checkBoth = true;
} else {
//最后这里直接指定了,没有再去读取setting数据库
prefer = RECOMMEND_INSTALL_INTERNAL;
checkBoth = false;
}
...
}
Android4.4和5.0虽然调用的方法名不一样,但是两个方法做得事情都是一致的。针对描述问题,我的修改方式是在最后的else分支中判断Settings数据库中设置中的值
总结
应用安装位置有几种设置方式:安装命令指定,应用自身指定和命令设置位置。4.4和5.0有区别,5.0没有兑命令设置位置的情况做判断。