在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中添加一项;
|
<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代码:
|
<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有三个选择项:
|
<string-array name="app_install_location_titles">
<item>Internal storage</item>
<item>Internal SD card</item>
<item>Let the system decide</item>
|
在ApplicationInstallLocationSettings类中,就是记住用户的setting设置:
|
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()方法供调用:
|
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:
|
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
|
pm.installPackageWithVerificationAndEncryption(mPackageURI, observer, installFlags,
installerPackageName, verificationParams, null);
|
这里参数有个installFlags,他代表的是App install是何种方式,如是否是INSTALL_REPLACE_EXISTING,INSTALL_FROM_ADB中间不细说,随后会调用到nstallParams extends HandlerParams的handleStartCopy()方法;
其中handleStartCopy()方法中一个重要的方法调用就是确定install location的;
|
pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath, flags,
lowThreshold);
|
他会调用
|
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;
}
}
|
仔细欣赏这段代码我们可以知道安装位置的设定主要是根据
|
int installPreference = Settings.Global.getInt(getApplicationContext()
.getContentResolver(),
Settings.Global.DEFAULT_INSTALL_LOCATION,
PackageHelper.APP_INSTALL_AUTO);
|
也就是setting中的设定和应用程序manifest中的设定
之间的逻辑,从而确定安装位置;总结的话就可以参考文章开始的一大段描述了。
就这样,install location的流程就走完了,接下来就是安装的过程了,进行copyApk的动作;
|
ret = args.copyApk(mContainerService, true);
|