在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
|