平台

        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:

android 找不到AppCompatActivity android 找不到set_Android

        这三个的优先级在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没有兑命令设置位置的情况做判断。