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

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