app 安装的流程:
网络下载应用安装――通过应用市场完成,没有安装界面
ADB工具安装――没有安装界面。
第三方应用安装――通过SD卡里的APK文件安装,有安装界面,由 packageinstaller.apk应用处理安装及卸载过程的界面。
安装其实就是把apk文件copy到了对应的目录
data/app —————安装时把 apk文件复制到此目录,—- 可以将文件取出并安装,和我们本身的apk 是一样的。
data/data/包名————— 开辟存放应用程序的文件数据的文件夹
包括我们应用的 so库,缓存文件 等等。将apk中的dex文件安装到data/dalvik-cache目录下(dex文件是dealvik虚拟机的可执行文件,其大小约为原始apk文件大小的四分之一)
Android手机为啥启动那么慢呢?
其实就是会把安装过的app在一次的安装一变
PackageManagerService源码:
PMS会把整个目录扫描一遍,包括系统目录(data/system、data/app等)
首先我们看下packageManagerService的 main方法中的代码:
其中这个main是被SystemServer调用的
public static PackageManagerService main(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
PackageManagerService m = new PackageManagerService(context, installer,
factoryTest, onlyCore);
ServiceManager.addService("package", m);
return m;
}
这段代码呢,我们可以看到通过new 的方式创建了一个对象,并添加到了 ServiceManager
中,serviceManager
内部是一个 HashMap的集合,存储了很多相关的 binder
服务,缓存起来,我们在使用的时候, 会通过 getService(key)
的方式去 map
中获取。
在PackageManagerService构造函数中,是用同步的方式初始化了解析所需要的文件目录:
File dataDir = Environment.getDataDirectory();
mAppDataDir = new File(dataDir, "data");
mAppInstallDir = new File(dataDir, "app");
mAppLib32InstallDir = new File(dataDir, "app-lib");
mAsecInternalPath = new File(dataDir, "app-asec").getPath();
mUserAppDataDir = new File(dataDir, "user");
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
我们可以看到在 构造函数中调用了scanDirLI
方法, 我们继续跟进。
private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
final File[] files = dir.listFiles();
...
for (File file : files) {
... // 进行校验文件 格式
try {
scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
scanFlags, currentTime, null);
} catch (PackageManagerException e) {
... // 删除了无效的文件目录
}
}
}
这里遍历的文件目录,就是我们上面的初始化的File, 比如我们 app的目录 就是data/app
下。 进行了遍历,下面我们进scanPackageLI 看看它都做了什么?
private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
long currentTime, UserHandle user) throws PackageManagerException {
if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
parseFlags |= mDefParseFlags;
PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
pp.setOnlyCoreApps(mOnlyCore);
pp.setDisplayMetrics(mMetrics);
if ((scanFlags & SCAN_TRUSTED_OVERLAY) != 0) {
parseFlags |= PackageParser.PARSE_TRUSTED_OVERLAY;
}
final PackageParser.Package pkg;
try {
pkg = pp.parsePackage(scanFile, parseFlags);
} catch (PackageParserException e) {
throw PackageManagerException.from(e);
}
PackageSetting ps = null;
PackageSetting updatedPkg;
// reader
synchronized (mPackages) {
// Look to see if we already know about this package.
String oldName = mSettings.mRenamedPackages.get(pkg.packageName);
if (pkg.mOriginalPackages != null && pkg.mOriginalPackages.contains(oldName)) {
// This package has been renamed to its original name. Let's
// use that.
ps = mSettings.peekPackageLPr(oldName);
}
// If there was no original package, see one for the real package name.
if (ps == null) {
ps = mSettings.peekPackageLPr(pkg.packageName);
}
// Check to see if this package could be hiding/updating a system
// package. Must look for it either under the original or real
// package name depending on our state.
updatedPkg = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
if (DEBUG_INSTALL && updatedPkg != null) Slog.d(TAG, "updatedPkg = " + updatedPkg);
}
boolean updatedPkgBetter = false;
// First check if this is a system package that may involve an update
if (updatedPkg != null && (parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
// If new package is not located in "/system/priv-app" (e.g. due to an OTA),
// it needs to drop FLAG_PRIVILEGED.
if (locationIsPrivileged(scanFile)) {
updatedPkg.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
} else {
updatedPkg.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
}
if (ps != null && !ps.codePath.equals(scanFile)) {
// The path has changed from what was last scanned... check the
// version of the new path against what we have stored to determine
// what to do.
if (DEBUG_INSTALL) Slog.d(TAG, "Path changing from " + ps.codePath);
if (pkg.mVersionCode <= ps.versionCode) {
// The system package has been updated and the code path does not match
// Ignore entry. Skip it.
if (DEBUG_INSTALL) Slog.i(TAG, "Package " + ps.name + " at " + scanFile
+ " ignored: updated version " + ps.versionCode
+ " better than this " + pkg.mVersionCode);
if (!updatedPkg.codePath.equals(scanFile)) {
Slog.w(PackageManagerService.TAG, "Code path for hidden system pkg : "
+ ps.name + " changing from " + updatedPkg.codePathString
+ " to " + scanFile);
updatedPkg.codePath = scanFile;
updatedPkg.codePathString = scanFile.toString();
updatedPkg.resourcePath = scanFile;
updatedPkg.resourcePathString = scanFile.toString();
}
updatedPkg.pkg = pkg;
throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
"Package " + ps.name + " at " + scanFile
+ " ignored: updated version " + ps.versionCode
+ " better than this " + pkg.mVersionCode);
} else {
// The current app on the system partition is better than
// what we have updated to on the data partition; switch
// back to the system partition version.
// At this point, its safely assumed that package installation for
// apps in system partition will go through. If not there won't be a working
// version of the app
// writer
synchronized (mPackages) {
// Just remove the loaded entries from package lists.
mPackages.remove(ps.name);
}
logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile
+ " reverting from " + ps.codePathString
+ ": new version " + pkg.mVersionCode
+ " better than installed " + ps.versionCode);
InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),
ps.codePathString, ps.resourcePathString, getAppDexInstructionSets(ps));
synchronized (mInstallLock) {
args.cleanUpResourcesLI();
}
synchronized (mPackages) {
mSettings.enableSystemPackageLPw(ps.name);
}
updatedPkgBetter = true;
}
}
}
if (updatedPkg != null) {
// An updated system app will not have the PARSE_IS_SYSTEM flag set
// initially
parseFlags |= PackageParser.PARSE_IS_SYSTEM;
// An updated privileged app will not have the PARSE_IS_PRIVILEGED
// flag set initially
if ((updatedPkg.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
}
}
// Verify certificates against what was last scanned
collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags);
/*
* A new system app appeared, but we already had a non-system one of the
* same name installed earlier.
*/
boolean shouldHideSystemApp = false;
if (updatedPkg == null && ps != null
&& (parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0 && !isSystemApp(ps)) {
/*
* Check to make sure the signatures match first. If they don't,
* wipe the installed application and its data.
*/
if (compareSignatures(ps.signatures.mSignatures, pkg.mSignatures)
!= PackageManager.SIGNATURE_MATCH) {
logCriticalInfo(Log.WARN, "Package " + ps.name + " appeared on system, but"
+ " signatures don't match existing userdata copy; removing");
deletePackageLI(pkg.packageName, null, true, null, null, 0, null, false);
ps = null;
} else {
/*
* If the newly-added system app is an older version than the
* already installed version, hide it. It will be scanned later
* and re-added like an update.
*/
if (pkg.mVersionCode <= ps.versionCode) {
shouldHideSystemApp = true;
logCriticalInfo(Log.INFO, "Package " + ps.name + " appeared at " + scanFile
+ " but new version " + pkg.mVersionCode + " better than installed "
+ ps.versionCode + "; hiding system");
} else {
/*
* The newly found system app is a newer version that the
* one previously installed. Simply remove the
* already-installed application and replace it with our own
* while keeping the application data.
*/
logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile
+ " reverting from " + ps.codePathString + ": new version "
+ pkg.mVersionCode + " better than installed " + ps.versionCode);
InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),
ps.codePathString, ps.resourcePathString, getAppDexInstructionSets(ps));
synchronized (mInstallLock) {
args.cleanUpResourcesLI();
}
}
}
}
// The apk is forward locked (not public) if its code and resources
// are kept in different files. (except for app in either system or
// vendor path).
// TODO grab this value from PackageSettings
if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
if (ps != null && !ps.codePath.equals(ps.resourcePath)) {
parseFlags |= PackageParser.PARSE_FORWARD_LOCK;
}
}
// TODO: extend to support forward-locked splits
String resourcePath = null;
String baseResourcePath = null;
if ((parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0 && !updatedPkgBetter) {
if (ps != null && ps.resourcePathString != null) {
resourcePath = ps.resourcePathString;
baseResourcePath = ps.resourcePathString;
} else {
// Should not happen at all. Just log an error.
Slog.e(TAG, "Resource path not set for pkg : " + pkg.packageName);
}
} else {
resourcePath = pkg.codePath;
baseResourcePath = pkg.baseCodePath;
}
// Set application objects path explicitly.
pkg.applicationInfo.volumeUuid = pkg.volumeUuid;
pkg.applicationInfo.setCodePath(pkg.codePath);
pkg.applicationInfo.setBaseCodePath(pkg.baseCodePath);
pkg.applicationInfo.setSplitCodePaths(pkg.splitCodePaths);
pkg.applicationInfo.setResourcePath(resourcePath);
pkg.applicationInfo.setBaseResourcePath(baseResourcePath);
pkg.applicationInfo.setSplitResourcePaths(pkg.splitCodePaths);
// Note that we invoke the following method only if we are about to unpack an application
PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags
| SCAN_UPDATE_SIGNATURE, currentTime, user);
/*
* If the system app should be overridden by a previously installed
* data, hide the system app now and let the /data/app scan pick it up
* again.
*/
if (shouldHideSystemApp) {
synchronized (mPackages) {
/*
* We have to grant systems permissions before we hide, because
* grantPermissions will assume the package update is trying to
* expand its permissions.
*/
grantPermissionsLPw(pkg, true, pkg.packageName);
mSettings.disableSystemPackageLPw(pkg.packageName);
}
}
return scannedPkg;
}
在里面做了两件比较重要的事情:
创建了
PackageParser
对象PackageParser pp = new PackageParser();
调用了
parsePackage
方法 并返回了PackageParser.Package
对象。pkg = pp.parsePackage(scanFile, parseFlags);
我们要在这里稍微停一下, 说说这个PackageParser.Package
. 为啥要停呢? 它有啥特别之处么? 让我们看一下它的类的信息就清楚了:
public final static class Package {
public String packageName;
/** Names of any split APKs, ordered by parsed splitName */
public String[] splitNames;
// TODO: work towards making these paths invariant
/**
* Path where this package was found on disk. For monolithic packages
* this is path to single base APK file; for cluster packages this is
* path to the cluster directory.
*/
public String codePath;
/** Path of base APK */
public String baseCodePath;
/** Paths of any split APKs, ordered by parsed splitName */
public String[] splitCodePaths;
/** Flags of any split APKs; ordered by parsed splitName */
public int[] splitFlags;
public boolean baseHardwareAccelerated;
// For now we only support one application per package.
public final ApplicationInfo applicationInfo = new ApplicationInfo();
public final ArrayList<Permission> permissions = new ArrayList<Permission>(0);
public final ArrayList<PermissionGroup> permissionGroups = new ArrayList<PermissionGroup>(0);
public final ArrayList<Activity> activities = new ArrayList<Activity>(0);
public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
public final ArrayList<Provider> providers = new ArrayList<Provider>(0);
public final ArrayList<Service> services = new ArrayList<Service>(0);
public final ArrayList<Instrumentation> instrumentation = new ArrayList<Instrumentation>(0);
public final ArrayList<String> requestedPermissions = new ArrayList<String>();
public final ArrayList<Boolean> requestedPermissionsRequired = new ArrayList<Boolean>();
... // 部分代码。
}
Ok, 你也看到了, 里面定义了 我们的packageName
、apk
路径 baseCodePath
以及 各种 Arraylist
来保存我们的Activity
、Service
、权限 等等。
这里我们会发现receivers 的集合类型也是Activity,这是因为广播在AndroidManifest.xml
文件的结构跟Activity是一样的
注意:这里的Activity不是四大组件的Activity,而是PackageParser.Package
的内部类,相当于JavaBean,
好的,我们继续往下面走。 下面我们的 主角从 PackageManagerService
切换到了 我们的PackageParser
.
public Package parsePackage(File packageFile, int flags) throws PackageParserException {
if (packageFile.isDirectory()) {
return parseClusterPackage(packageFile, flags);
} else {
return parseMonolithicPackage(packageFile, flags);
}
}
我们看到parsePakcage
转调用了parseMonolithicPackage
方法,让我们继续。
@Deprecated
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
if (mOnlyCoreApps) {
final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
if (!lite.coreApp) {
throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"Not a coreApp: " + apkFile);
}
}
final AssetManager assets = new AssetManager();
try {
final Package pkg = parseBaseApk(apkFile, assets, flags);
pkg.codePath = apkFile.getAbsolutePath();
return pkg;
} finally {
IoUtils.closeQuietly(assets);
}
}
这里的AssetManager
现在还不能加载apk里的资源,如果想可以加载,可定会调用AssetManager
的addAssetPath
方法,才可以有效,我们这里猜测一下,下一步肯定是调用了addAssetPath
方法。
不多说,直接进入我们的 parseBaseApk
方法。 这里有两点用说:
private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
throws PackageParserException {
final String apkPath = apkFile.getAbsolutePath();
mParseError = PackageManager.INSTALL_SUCCEEDED;
mArchiveSourcePath = apkFile.getAbsolutePath();
if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);
Resources res = null;
XmlResourceParser parser = null;
try {
res = new Resources(assets, mMetrics, null);
assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
Build.VERSION.RESOURCES_SDK_INT);
parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
//ANDROID_MANIFEST_FILENAME="AndroidManifest.xml"
final String[] outError = new String[1];
final Package pkg = parseBaseApk(res, parser, flags, outError);
if (pkg == null) {
throw new PackageParserException(mParseError,
apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
}
pkg.baseCodePath = apkPath;
pkg.mSignatures = null;
return pkg;
} catch (PackageParserException e) {
throw e;
} catch (Exception e) {
throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to read manifest from " + apkPath, e);
} finally {
IoUtils.closeQuietly(parser);
}
}
-
parseBaseApk
方法里面调用了loadApkIntoAssetManager
方法
private static int loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags)
throws PackageParserException {
if ((flags & PARSE_MUST_BE_APK) != 0 && !isApkPath(apkPath)) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
"Invalid package file: " + apkPath);
}
// The AssetManager guarantees uniqueness for asset paths, so if this asset path
// already exists in the AssetManager, addAssetPath will only return the cookie
// assigned to it.
int cookie = assets.addAssetPath(apkPath);
if (cookie == 0) {
throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Failed adding asset path: " + apkPath);
}
return cookie;
}
这个方法里面就调用了AssetManager
的addAssetPath
方法,把apk路径传到了addAssetPath
方法中
-
parseBaseApk
方法调用了重载方法 解析AndroidManifest.xml
文件
private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags,
String[] outError) throws XmlPullParserException, IOException {}
这个方法有下面一段代码
String tagName = parser.getName();
if (tagName.equals("application")) {
... // 省略部分代码
if (!parseBaseApplication(pkg, res, parser, attrs, flags, outError)) {
return null;
}
}
这里就是就是对应AndroidManifest.xml
文件的application标签
直接来看 parseBaseApplication
中的操作:
解析了application 节点中的相关信息 如 icon、theme等。
解析了application 节点下各个子节点的信息。
String tagName = parser.getName();
if (tagName.equals("activity")) {
Activity a = parseActivity(owner, res, parser, attrs, flags, outError, false,
owner.baseHardwareAccelerated);
if (a == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.activities.add(a);
} else if (tagName.equals("receiver")) {
Activity a = parseActivity(owner, res, parser, attrs, flags, outError, true, false);
if (a == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.receivers.add(a);
} else if (tagName.equals("service")) {
Service s = parseService(owner, res, parser, attrs, flags, outError);
if (s == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.services.add(s);
} else if (tagName.equals("provider")) {
Provider p = parseProvider(owner, res, parser, attrs, flags, outError);
if (p == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.providers.add(p);
}
我们只看其中的一个节点:
if (tagName.equals("activity")) {
Activity a = parseActivity(owner, res, parser, attrs, flags, outError, false,
owner.baseHardwareAccelerated);
if (a == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.activities.add(a);
}
parseActivity 中解析了 <activity> 节点下的相关信息,比如 exported、tag、theme、intent-filter 等,封装成了一个类即Activity。 然后执行了
owner.activities.add(a);
这里的封装成了一个类即Activity是PackageParser.Package
的内部类
继续看parseActivity
方法,
- 这个方法里面有个
mParseActivityArgs.tag = receiver ? "<receiver>" : "<activity>";
就是判断是广播还是Activity
- 这个方法里面还有一段代码
if (parser.getName().equals("intent-filter")) {
ActivityIntentInfo intent = new ActivityIntentInfo(a);
if (!parseIntent(res, parser, attrs, true, intent, outError)) {
return null;
}
if (intent.countActions() == 0) {
Slog.w(TAG, "No actions in intent filter at "
+ mArchiveSourcePath + " "
+ parser.getPositionDescription());
} else {
a.intents.add(intent);
}
}
就是解析intent-filter
标签,然后调用parseIntent
方法解析intent-filter
标签的标签
因为一个”<receiver>” 或”<activity>”有可能有多个intent-filter
,所以使用Activity里面的ArrayList<II> intents
集合添加每个intent
总结
从整体的角度看,我们所做的这一系列操作,其实只是在做一件事,创建一个PackageParser.Package
对象,然后填充它,然后 return 回去。