android之应用程序安装位置application install location



在android4.2.2中,app的安装位置是怎么样的逻辑呢?首先总结下有个大的认识,随后再进行代码的跟踪来具体的看下;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
在应用程序AndroidManifest.xml中,有android:installLocation=""这一项属性设置,可以设置项为“auto”(自动),“internalOnly”(内存),“preferExternal”
(内置sdcard)三项,也可不添加此项属性。
在Settings中,android4.2隐藏了“Preferred install location”的设置,它的可选设置有“Internal device storage”(内存),“Removable SD card”(内置sdcard),
“Let the system decide”(由系统决定)三项可选项,默认应用程序是安装在内存中的。
根据跟踪代码,利用手动安装apk文件,应用程序AndroidManifest.xml设置的安装位置的优先级高于Settings设置。
代码中首先解析AndroidManifest.xml文件。
如果在文件中有installLocation的属性设置,则会跳过读取Settings中的设置。即setting设置不生效。如果AndroidManifest属性设置为“internalOnly”,
则会安装在内存中,如果为“preferExternal”,则会安装在内置sdcard中,如果为“auto”,会安装在内存中。
如果没有installLocation的属性设置,会判断setting中用户的设置。其中,如果setting为“Internal device storage”,则会安装在内存中,如果为“Removable SD card”,
则会安装在内置sdcard中,如果为“Let the system decide”,会安装在内存中。
安装过程中,如果应用程序安装在内存,但内存已满,则会安装失败,内置sdcard也是一样。
另外,当Setting中设置为“Removable SD card”时,除了指定了android:installLocation=" internalOnly "的应用程序外,其它的都可以“Move to SDcard/phone”。
当Setting中设置为“Internal device storage”时,android:installLocation=" auto"或者” preferExternal”可以“Move to SDcard/phone”。当Setting中设置为
“Let the system decide”时,android:installLocation=" auto"或者” preferExternal”可以“Move to SDcard/phone”。

同时你还可以通过命令来查看你手机setting的默认存储位置和你安装的应用程序apk在手机中的位置;

1、安装在内部存储和外置存储应用主体的位置分别是在:/data/app/*.apk和/mnt/asec/*/pkg.apk下的;通过**pm path PACKAGE_NAME**命令可以看到;package name可以通过**pm list packages**来查看你手机中的app package;

2、当有些手机在setting中的install location被隐藏以后,我们还可以通过**adb shell pm get-install-location**和**adb shell pm set-install-location 0/1/2**来设置优先安装位置,这样就可以移动那些在manifest中没有定义installlocation的应用程序的位置了,ROM小的同学可以试下;

接下来跟踪下代码逻辑,Google的源码将app安装位置在setting中隐藏了;

首先需要让“Perferred install location”的setting项显示,代码就是在

packages/apps/Settings/res/values/settings_headers.xml中添加一项;

1
2
3
4
5
<header
        android:fragment="com.android.settings.ApplicationInstallLocationSettings"
        android:icon="@drawable/ic_tab_unselected_sdcard"
        android:title="@string/app_install_location"
        android:id="@+id/app_install_location_settings" />

title的string代码:

1
<string name="app_install_location">Preferred install location</string>

点击setting中此项时,进入ApplicationInstallLocationSettings.java,其对应的xml文件为application_install_location_settings.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
        android:title="@string/applications_settings_header"
        android:summary="@string/applications_settings_summary"
        android:key="applications_settings">
    <ListPreference
        android:key="app_install_location"
        android:title="@string/app_install_location_title"
        android:summary="@string/app_install_location_summary"
        android:persistent="false"
        android:entries="@array/app_install_location_entries"
        android:entryValues="@array/app_install_location_values"/>
</PreferenceScreen>

注意看android:entries="@array/app_install_location_entries",这个array即指定了install location有三个选择项:

1
2
3
4
<string-array name="app_install_location_titles">
        <item>Internal storage</item>
        <item>Internal SD card</item>
        <item>Let the system decide</item>

在ApplicationInstallLocationSettings类中,就是记住用户的setting设置:

1
2
android.provider.Settings.Global.putInt(this.getContentResolver(),
                    android.provider.Settings.Global.DEFAULT_INSTALL_LOCATION, APP_INSTALL_SDCARD);

用户设置了默认的安装位置了,那接下来就是安装应用程序了,存置在数据库的信息在安装app时肯定是被调用的;搜索一下android.provider.Settings.Global.DEFAULT_INSTALL_LOCATION在哪些地方被调用;发现在PackageManagerService中提供了getInstallLocation()方法供调用:

1
2
3
4
5
public int getInstallLocation() {
        return android.provider.Settings.Global.getInt(mContext.getContentResolver(),
                android.provider.Settings.Global.DEFAULT_INSTALL_LOCATION,
                PackageHelper.APP_INSTALL_AUTO);
    }

现在就以此为点,接下来看看应用程序的安装过程;

packages/apps/PackageInstaller就是管理安装的app,当打开一个apk文件时,会有这样的一个log:

1
2
10-14 11:39:49.299: I/ActivityManager(765): START u0 {act=android.intent.action.VIEW dat=file:///storage/sdcard0/Download/
baidulvyou_24.apk typ=application/vnd.android.package-archive cmp=com.android.packageinstaller/.PackageInstallerActivity} from pid 18322

如果对程序包一切检查解析完成后,则点击OK进入安装:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Start subactivity to actually install the application
                Intent newIntent = new Intent();
                newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
                        mPkgInfo.applicationInfo);
                newIntent.setData(mPackageURI);
                newIntent.setClass(this, InstallAppProgress.class);
                String installerPackageName = getIntent().getStringExtra(
                        Intent.EXTRA_INSTALLER_PACKAGE_NAME);
                if (mOriginatingURI != null) {
                    newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
                }
                if (mReferrerURI != null) {
                    newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
                }
                if (mOriginatingUid != VerificationParams.NO_UID) {
                    newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
                }
                if (installerPackageName != null) {
                    newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
                            installerPackageName);
                }
                if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
                    newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
                    newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
                }
                if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
                startActivity(newIntent);
                finish();

进入InstallAppProgress,安装进行时...,实际上会调用到PackageManagerService

1
2
pm.installPackageWithVerificationAndEncryption(mPackageURI, observer, installFlags,
                    installerPackageName, verificationParams, null);

这里参数有个installFlags,他代表的是App install是何种方式,如是否是INSTALL_REPLACE_EXISTING,INSTALL_FROM_ADB中间不细说,随后会调用到nstallParams extends HandlerParams的handleStartCopy()方法;

其中handleStartCopy()方法中一个重要的方法调用就是确定install location的;

1
2
pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath, flags,
                                lowThreshold);

他会调用

1
2
ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation,
                    packagePath, flags, threshold);

详细来看下这个方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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 : {
            /*
             * 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;
        }
        final boolean emulated = Environment.isExternalStorageEmulated();
        final File apkFile = new File(archiveFilePath);
        boolean fitsOnInternal = false;
        if (checkBoth || prefer == PREFER_INTERNAL) {
            try {
                fitsOnInternal = isUnderInternalThreshold(apkFile, isForwardLocked, threshold);
            } catch (IOException e) {
                return PackageHelper.RECOMMEND_FAILED_INVALID_URI;
            }
        }
        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;
        }
    }

仔细欣赏这段代码我们可以知道安装位置的设定主要是根据

1
2
3
4
int installPreference = Settings.Global.getInt(getApplicationContext()
                    .getContentResolver(),
                    Settings.Global.DEFAULT_INSTALL_LOCATION,
                    PackageHelper.APP_INSTALL_AUTO);

也就是setting中的设定和应用程序manifest中的设定

1
pkg.installLocation

之间的逻辑,从而确定安装位置;总结的话就可以参考文章开始的一大段描述了。

就这样,install location的流程就走完了,接下来就是安装的过程了,进行copyApk的动作;

1
ret = args.copyApk(mContainerService, true);


原文作者: cnhua5

原文地址: http://my.eoe.cn/cnhua5/archive/19730.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章