继续上一章讲解。
在Android中,通过发送Intent,就可以启动应用的安装过程,如下所示:
Uri uri = Uri.fromFile(new File(fileName));
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri, "application/vnd.android.package-archive");
startActivity(intent);
在Android的系统应用PackageInstaller中有一个PackageInstallerActivity会响应这个Intent。在这个Activity中,有两个重要的成员变量mPm,是ApplicationPackageManager的实例对象,也是PackageManagerService在应用中的代理对象。创建代码如下:
mPm = getPackageManager();
一、管理“安装会话”----PackageInstallerService
PackageInstallerService是Android5.0新加入的服务,主要用于管理“安装会话(Installer Session)”。在Android5.0中,可以通过PackageInstallerService来分配一个SessionId,这个系统唯一的ID代表一次安装过程,如果一个应用的安装必须分成几个阶段来完成,即使设备重启了,也可以通过这个ID来继续安装过程。
PackageInstallerService中提供了接口createSession()创建一个Session:
public int createSession(SessionParams params, String installerPackageName, int userId) {
try {
return createSessionInternal(params, installerPackageName, userId);
} catch (IOException e) {
throw ExceptionUtils.wrap(e);
}
}
createSession方法将返回一个系统唯一值作为SessionID。如果希望再次使用这个Session,可以通过接口openSession打开它,代码如下:
public IPackageInstallerSession openSession(int sessionId) {
try {
return openSessionInternal(sessionId);
} catch (IOException e) {
throw ExceptionUtils.wrap(e);
}
}
private IPackageInstallerSession openSessionInternal(int sessionId) throws IOException {
synchronized (mSessions) {
final PackageInstallerSession session = mSessions.get(sessionId);
if (session == null || !isCallingUidOwner(session)) {
throw new SecurityException("Caller has no access to session " + sessionId);
}
session.open();
return session;
}
}
openSession方法返回一个IPackageInstallerSession对象,它是Binder服务PackageInstallerSession的IBinder对象。在PackageInstallerService中mSessions数组保存了所有PackageInstallerSession对象,代码如下:
private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
当系统启动时,PackageManagerService初始化时会创建PackageInstallerService服务,在这个服务的初始化函数中,会读取/data/system目录下install_sessions.xml文件,这个文件中保存了系统中未完成的Install Session。PackageInstallerService会根据文件的内容创建PackageInstallerSession对象并插入到mSessions中。
PacakgeInstallerSession中保存了应用安装相关的数据,如,安装包的路径、安装的进度、中间数据保存的目录等。
二、应用安装第一阶段:复制文件
应用中可以调用PackageManager的installPackage()方法来开始安装过程,这个方法会调用PackageManagerService的installPackage()方法或installPackageAsUser()接口来执行安装过程。整个过程比较复杂,我们先看看安装序列图,如下:
应用安装的过程大概分成两个阶段,第一阶段把需要安装的应用复制到/data/app目录下,第二阶段是对apk文件进行扫描优化,然后装载到内存中。
PackageManagerService的installPackage方法用来给当前用户安装应用,而installPackageAsUser()方法给指定的用户安装应用。installPackage()方法只是使用当前用户的uid来调用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;//调用进程uid为0或2000,设置installFlags
} 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) {//如果installFlags带有标记INSTALL_ALL_USERS,则给所有用户安装
user = UserHandle.ALL;
} else {
user = new UserHandle(userId);
}
verificationParams.setInstallerUid(callingUid);
final File originFile = new File(originPath);
final OriginInfo origin = OriginInfo.fromUntrustedFile(originFile);
//保存参数到InstallParams对象并发送消息
final Message msg = mHandler.obtainMessage(INIT_COPY);
msg.obj = new InstallParams(origin, observer, installFlags,
installerPackageName, verificationParams, user, packageAbiOverride);
mHandler.sendMessage(msg);
}
installPackageAsUser()方法首先检查调用进程是否有安装应用的权限,再检查调用进程的所属用户是否有权限安装应用,
最后检查指定的用户是否被限制安装应用。如果参数installFlags带有标记INSTALL_ALL_USERS,则该应用将给系统中
所有用户安装,否则只给指定的用户安装。
installPackageAsUser()方法,把调用的参数保存在InstallParams对象中,然后发送INIT_COPY消息。
InstallParams是安装过程中主要的数据结构,从HandleParams类派生,用来记录安装应用的参数。
从HandleParams派生的还有MoveParams和MeasureParams,MoveParams用于将应用移动到SD卡,
MeasureParams用于方法getPackageSizeInfo中。InstallParams成员变量mArgs,是FileInstallArgs类型,用来执行
apk文件的复制。
我们看下INIT_COPY消息的处理,在doHandleMessage()方法中,如下:
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 {
// Once we bind to the service, the first连接成功,把安装信息保存到mPedingInstalls中
// 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) {//如果mPendingInstalls只有一项,立刻发MCS_BOUND消息
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) {//如果DefaultContainerService没有连接成功
// 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) {
if (mBound) {//如果没有安装信息了,发送延时10秒的MCS_UNBIND消息
if (DEBUG_SD_INSTALL) Log.