在Android,通过发送Intent可以启动应用的安装过程,如下所示:
Uri uri = Uri.fromFile(new File(filename)); Intent inent = new Intent(Intent.ACTION_VIEW); intent.SetDataAndType(uri, application/vnd.android.package-archive); startActivity(intent);
在Android的系统应用PackageInstaller中有一个PackageInstallActivity会响应这个Intent。在这个Activity中有两个重要的成员变量mPm和mInstaller,分别是ApplicationPackageManger和PackageInstaller的实例对象,这两个对象也是PackageManagerService和PackageInstallerService在应用中的代理对象。
PackageInstallerAcivity中创建这两个对象的代码如下:
protected void onCreate(Bundler icicle) { super.onCreate(icicle); mPm = getPackageManager(); mInstaller = mPm.getPackageInstaller(); ...... }
PackageManagerInstallerService是Android5.0新加入的服务,主要用于管理安装会话(Installer session)。在Android5.0中,可以通过PackageManagerInstallerService来分配一个SessionId,这个系统唯一的ID代表一次安装过程,如果一个应用的安装分成几个阶段来完成,即使设备重启了,也可以通过这个ID来继续安装过程。
PackageManagerInstallerService中提供的接口createSession来创建一个Session:
@Override public int createSession(SessionParams params, String installerPackageName, int userId) { try { return createSessionInternal(params, installerPackageName, userId); } catch (IOException e) { throw ExceptionUtils.wrap(e); } }
这个方法返回一个系统唯一值作为SessionID,如果希望再次使用这个Session,可以通过接口openSession方法来打开它,代码如下:
@Override public IPackageInstallerSession openSession(int sessionId) { try { return openSessionInternal(sessionId); } catch (IOException e) { throw ExceptionUtils.wrap(e); } }
openSession返回一个IPackageInstallerSession对象,它是Binder服务PackageInstallerSession的IBinder对象。每个Install Session都会在SystemServer中有一个对应的PackageInstallerSession对象。在PackageInstallerService中mSessions数组保存了所有PackageInstallerSession对象,定义如下:
private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
当系统启动时,PackageManageService初始化时会创建PackageManagerInstallerService服务,在这个服务的初始化函数中,会读取/data/system目录下的install_sessions.xml文件,这个文件保存系统未完成的Install Session。然后PackagemanagerInstallerService会根据文件的内容创建PackageInstallerSession对象并插入mSessions中。
PackageInstallerSession中保存了应用安装相关的数据。例如,安装包路径,安装进度、中间数据保存的目录等。
应用可以调用PackageManager的installPackage方法来开始安装过程,最终会调用到PackageManagerService的installPackage或者installPackageAsUser来执行安装过程,整个安装过程比较复杂。
整个安装过程可以分成两个阶段:
1.第一阶段把需要安装的应用复制到/data/app目录下
2.第二阶段是对apk文件扫描优化,装载到内存中。
PackageManagerService的installPackage方法只是用当前用户安装应用,最后也会调用installPackageAsUser
@Override public void installPackage(String originPath, IPackageInstallObserver2 observer, int installFlags, String installerPackageName, VerificationParams verificationParams, String packageAbiOverride) { installPackageAsUser(originPath, observer, installFlags, installerPackageName, verificationParams, packageAbiOverride, UserHandle.getCallingUserId()); }
下面我们就来看看installPackageAsUser方法:
@Override public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer, int installFlags, String installerPackageName, VerificationParams verificationParams, String packageAbiOverride, int userId) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null);//检查调用进程的权限 //检查调用进程的永华是否有权限安装应用 final int callingUid = Binder.getCallingUid(); enforceCrossUserPermission(callingUid, userId, true, true, "installPackageAsUser"); //检查指定的用户是否被限制安装应用 if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) { try { if (observer != null) { observer.onPackageInstalled("", INSTALL_FAILED_USER_RESTRICTED, null, null); } } catch (RemoteException re) { } return; } if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) { installFlags |= PackageManager.INSTALL_FROM_ADB; } else { // Caller holds INSTALL_PACKAGES permission, so we're less strict // about installerPackageName. installFlags &= ~PackageManager.INSTALL_FROM_ADB; installFlags &= ~PackageManager.INSTALL_ALL_USERS; } UserHandle user; if ((installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {//给所有用户安装 user = UserHandle.ALL; } else { user = new UserHandle(userId); } verificationParams.setInstallerUid(callingUid); final File originFile = new File(originPath); final OriginInfo origin = OriginInfo.fromUntrustedFile(originFile); final Message msg = mHandler.obtainMessage(INIT_COPY); msg.obj = new InstallParams(origin, observer, installFlags,//保存参数到InstallParamsm,发送消息 installerPackageName, verificationParams, user, packageAbiOverride); mHandler.sendMessage(msg); }
installPackageAsUser先检查调用进程是否有安装应用的权限,再检查调用进程所属的用户是否有权限安装应用,最后检查指定的用户是否被限制安装应用。如果参数installFlags带有INSTALL_ALL_USERS,则该应用将给系统中所有用户安装,否则只给指定用户安装。
安装应用实践比较长,因此不可能在一个函数中完成。上面函数把数据保存在installParams然后发送了INIT_COPY消息。
下面我们再来看看消息处理:
void doHandleMessage(Message msg) { switch (msg.what) { case INIT_COPY: { HandlerParams params = (HandlerParams) msg.obj; int idx = mPendingInstalls.size(); if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params); // If a bind was already initiated we dont really // need to do anything. The pending install // will be processed later on. if (!mBound) { // If this is the only one pending we might // have to bind to the service again. if (!connectToService()) {//绑定DefaultContainerService Slog.e(TAG, "Failed to bind to media container service"); params.serviceError(); return; } else {//连接成功把安装信息保存到mPendingInstalls // Once we bind to the service, the first // pending request will be processed. mPendingInstalls.add(idx, params); } } else {//如果已经绑定好了 mPendingInstalls.add(idx, params); // Already bound to the service. Just make // sure we trigger off processing the first request. if (idx == 0) { mHandler.sendEmptyMessage(MCS_BOUND); } } break; }
INIT_COPY消息的处理将绑定DefaultContainerService,因为这是一个异步的过程,要等待绑定的结果通过onServiceConnected返回,所以这里的安装参数放到了mPendingInstalls列表中。如果这个Service以前就绑定好了,现在就不需要再绑定,安装信息也会先放到mPendingInstalls。如果有多个安装请求同时到达,这里通过mPendingInstalls列表对他们进行排队。如果列表中只有一项,说明没有更多的安装请求,因此这种情况下回立即发出MCS_BOUND消息。而onServiceConnected方法同样是发出MCS_BOUND消息:
class DefaultContainerConnection implements ServiceConnection { public void onServiceConnected(ComponentName name, IBinder service) { if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceConnected"); IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service); mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs)); } public void onServiceDisconnected(ComponentName name) { if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceDisconnected"); } };
看下MCS_BOUND的消息处理
case MCS_BOUND: { if (DEBUG_INSTALL) Slog.i(TAG, "mcs_bound"); if (msg.obj != null) { mContainerService = (IMediaContainerService) msg.obj; } if (mContainerService == null) {//没有连接成功 // Something seriously wrong. Bail out Slog.e(TAG, "Cannot bind to media container service"); for (HandlerParams params : mPendingInstalls) { // Indicate service bind error params.serviceError();//通知出错了 } mPendingInstalls.clear(); } else if (mPendingInstalls.size() > 0) { HandlerParams params = mPendingInstalls.get(0); if (params != null) { if (params.startCopy()) {//执行安装 // We are done... look for more work or to // go idle. if (DEBUG_SD_INSTALL) Log.i(TAG, "Checking for more work or unbind..."); // Delete pending install if (mPendingInstalls.size() > 0) { mPendingInstalls.remove(0);//工作完成,删除第一项 } if (mPendingInstalls.size() == 0) {//如果没有安装消息了,延时发送10秒MCS_UNBIND消息 if (mBound) { if (DEBUG_SD_INSTALL) Log.i(TAG, "Posting delayed MCS_UNBIND"); removeMessages(MCS_UNBIND); Message ubmsg = obtainMessage(MCS_UNBIND); // Unbind after a little delay, to avoid // continual thrashing. sendMessageDelayed(ubmsg, 10000); } } else { // There are more pending requests in queue. // Just post MCS_BOUND message to trigger processing // of next pending install. if (DEBUG_SD_INSTALL) Log.i(TAG, "Posting MCS_BOUND for next work"); mHandler.sendEmptyMessage(MCS_BOUND);//还有消息继续发送MCS_BOUND消息 } } } } else { // Should never happen ideally. Slog.w(TAG, "Empty queue"); } break; }
如果结束了我们看看MCS_UNBIND消息的处理
case MCS_UNBIND: { // If there is no actual work left, then time to unbind. if (DEBUG_INSTALL) Slog.i(TAG, "mcs_unbind"); if (mPendingInstalls.size() == 0 && mPendingVerification.size() == 0) { if (mBound) { if (DEBUG_INSTALL) Slog.i(TAG, "calling disconnectService()"); disconnectService();//断开连接 } } else if (mPendingInstalls.size() > 0) { // There are more pending requests in queue. // Just post MCS_BOUND message to trigger processing // of next pending install. mHandler.sendEmptyMessage(MCS_BOUND); } break; }
MCS_UNBIND消息的处理,如果处理的时候发现mPendingInstalls又有数据了,还是发送MCS_BOUND消息继续安装,否则断开和DefaultContainerService的连接,安装结束。
下面我们看执行安装的函数startCopy:
final boolean startCopy() { boolean res; try { if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this); if (++mRetries > MAX_RETRIES) {//重试超过4次退出 Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up"); mHandler.sendEmptyMessage(MCS_GIVE_UP); handleServiceError(); return false; } else { handleStartCopy(); res = true; } } catch (RemoteException e) { if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT"); mHandler.sendEmptyMessage(MCS_RECONNECT);//安装出错,发送重新连接 res = false; } handleReturnCode(); return res; }
handleStartCopy和copyApk代码就不分析了。
handleStartCopy函数先通过DefaultContainerService调用了getMinimallPackageInfo来确定安装位置是否有足够的空间,并在PackageInfoLite对象的recommendedIntallLocation记录错误原因。发现空间不够,会调用installer的freecache方法来释放一部分空间。
再接下来handleStartCopy有很长一段都在处理apk的校验,这个校验过程是通过发送Intent ACTION_PACKAGE_NEEDS_VERIFICATION给系统中所有接受该Intent的应用来完成。如果无需校验,直接调用InstallArgs对象的copyApk方法。
而copyApk方法同样是调用DefaultContainerService的copyPackage将应用的文件复制到/data/app下,如果还有native动态库,也会把包在apk文件中的动态库提取出来。
执行完copyApk后,应用安装到了data/app目录下了。
接下来是第二阶段的工作,把应用的格式装换成oat格式,为应用创建数据目录。最后把应用信息装载进PackageManagerService的数据结构中。
接着上面startCopy方法最后会调用handleReturnCode方法,代码如下:
@Override void handleReturnCode() { // If mArgs is null, then MCS couldn't be reached. When it // reconnects, it will try again to install. At that point, this // will succeed. if (mArgs != null) { processPendingInstall(mArgs, mRet); } }
我们继续看下processPendingInstall函数。
private void processPendingInstall(final InstallArgs args, final int currentStatus) { // Queue up an async operation since the package installation may take a little while. mHandler.post(new Runnable() { public void run() { mHandler.removeCallbacks(this);//防止重复调用 // Result object to be returned PackageInstalledInfo res = new PackageInstalledInfo(); res.returnCode = currentStatus; res.uid = -1; res.pkg = null; res.removedInfo = new PackageRemovedInfo(); if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {//如果安装成功了 args.doPreInstall(res.returnCode); synchronized (mInstallLock) { installPackageLI(args, res);//装载安装的应用 } args.doPostInstall(res.returnCode, res.uid); } .......... if (!doRestore) { // 发送POST_INSTALL消息 Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0); mHandler.sendMessage(msg); } } }); }
processPendingInstall方法post了一个消息,这样安装过程以异步的方式继续执行。在post消息的处理中,首先调用installPackageLI来装载应用,然后很大的代码在执行备份,备份是通过BackupManagerService来完成的。备份完成后,通过发送POST_INSTALL消息来继续处理。而这个消息的处理主要就是在发送广播,应用安装完成后要通知系统中其他的应用开始处理,比如Launcher中需要增加应用的图标等。
我们来分析下installPackageLI方法:
private void installPackageLI(InstallArgs args, PackageInstalledInfo res) { final int installFlags = args.installFlags; String installerPackageName = args.installerPackageName; File tmpPackageFile = new File(args.getCodePath()); boolean forwardLocked = ((installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0); boolean onSd = ((installFlags & PackageManager.INSTALL_EXTERNAL) != 0); boolean replace = false; final int scanFlags = SCAN_NEW_INSTALL | SCAN_FORCE_DEX | SCAN_UPDATE_SIGNATURE; // Result object to be returned res.returnCode = PackageManager.INSTALL_SUCCEEDED; if (DEBUG_INSTALL) Slog.d(TAG, "installPackageLI: path=" + tmpPackageFile); // Retrieve PackageSettings and parse package final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY | (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0) | (onSd ? PackageParser.PARSE_ON_SDCARD : 0); PackageParser pp = new PackageParser(); pp.setSeparateProcesses(mSeparateProcesses); pp.setDisplayMetrics(mMetrics); final PackageParser.Package pkg; try { pkg = pp.parsePackage(tmpPackageFile, parseFlags);//解析apk文件 } catch (PackageParserException e) { res.setError("Failed parse during installPackageLI", e); return; }
这里先调用parsePackage解析apk文件,这个之前分析过,我们就不再分析了。
继续分析processPendingInstall函数
if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {//如果安装的升级应用,继续使用以前的老的包名 String oldName = mSettings.mRenamedPackages.get(pkgName); if (pkg.mOriginalPackages != null && pkg.mOriginalPackages.contains(oldName) && mPackages.containsKey(oldName)) { // This package is derived from an original package, // and this device has been updating from that original // name. We must continue using the original name, so // rename the new package here. pkg.setPackageName(oldName);//设置老的包名 pkgName = pkg.packageName; replace = true; if (DEBUG_INSTALL) Slog.d(TAG, "Replacing existing renamed package: oldName=" + oldName + " pkgName=" + pkgName); } else if (mPackages.containsKey(pkgName)) { // This package, under its official name, already exists // on the device; we should replace it. replace = true; if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing pacakge: " + pkgName); } }
继续分析
if (systemApp && onSd) {//不能将系统应用装载sd卡 // Disable updates to system apps on sdcard res.setError(INSTALL_FAILED_INVALID_INSTALL_LOCATION, "Cannot install updates to system apps on sdcard"); return; } if (!args.doRename(res.returnCode, pkg, oldCodePath)) {//重命名出错 res.setError(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename"); return; }
继续分析
if (replace) {//如果安装的是升级包,调用replacePackageLI replacePackageLI(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user, installerPackageName, res); } else {//如果是新应用,调用installNewPackageLI继续处理 installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES, args.user, installerPackageName, res); } synchronized (mPackages) { final PackageSetting ps = mSettings.mPackages.get(pkgName); if (ps != null) { res.newUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true); } }
下面我们分析下installNewPackageLI函数
private void installNewPackageLI(PackageParser.Package pkg, int parseFlags, int scanFlags, UserHandle user, String installerPackageName, PackageInstalledInfo res) { // Remember this for later, in case we need to rollback this install String pkgName = pkg.packageName; if (DEBUG_INSTALL) Slog.d(TAG, "installNewPackageLI: " + pkg); boolean dataDirExists = getDataPathForPackage(pkg.packageName, 0).exists(); synchronized(mPackages) { if (mSettings.mRenamedPackages.containsKey(pkgName)) { // A package with the same name is already installed, though // it has been renamed to an older name. The package we // are trying to install should be installed as an update to // the existing one, but that has not been requested, so bail. res.setError(INSTALL_FAILED_ALREADY_EXISTS, "Attempt to re-install " + pkgName + " without first uninstalling package running as " + mSettings.mRenamedPackages.get(pkgName)); return; } if (mPackages.containsKey(pkgName)) { // Don't allow installation over an existing package with the same name. res.setError(INSTALL_FAILED_ALREADY_EXISTS, "Attempt to re-install " + pkgName + " without first uninstalling."); return; } } try { PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanFlags,//调用scanPackageLI System.currentTimeMillis(), user); updateSettingsLI(newPackage, installerPackageName, null, null, res); // delete the partially installed application. the data directory will have to be // restored if it was already existing if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) { // remove package from internal structures. Note that we want deletePackageX to // delete the package data and cache directories that it created in // scanPackageLocked, unless those directories existed before we even tried to // install. deletePackageLI(pkgName, UserHandle.ALL, false, null, null, dataDirExists ? PackageManager.DELETE_KEEP_DATA : 0, res.removedInfo, true); } } catch (PackageManagerException e) { res.setError("Package couldn't be installed in " + pkg.codePath, e); } }
这里和上篇博客分析扫描apk文件类似,我们来看下这个函数scanPackageLI
private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags, int scanFlags, long currentTime, UserHandle user) throws PackageManagerException { boolean success = false; try { final PackageParser.Package res = scanPackageDirtyLI(pkg, parseFlags, scanFlags, currentTime, user); success = true; return res; } finally { if (!success && (scanFlags & SCAN_DELETE_DATA_ON_FAILURES) != 0) { removeDataDirsLI(pkg.packageName); } } }scanPackageLI函数主要调用了scanPackageDirtyLI函数,这个函数前面分析过了就不分析了。