9.2 使用adb命令安装应用程序
通过adb提供的push和install命令可以安装和卸载应用程序,处理流程如下:
1)adb push将APK文件上传到设备中被AppDirObserver监控的目录,AppDirObserver在onEvent方法中对添加事件做出响应,进而调用scanPackageLI(File scanFile,……)实现自动安装。
2)adb install<file.apk>首先将file.apk文件上传到设备的/data/local/tmp/或者/sdcrad/tmp/目录下,然后启动pm脚本,最后在pm脚本中根据传入的install命令,通过Binder机制调用PackageManagerService.installPackageWithVerification方法安装file.apk。
adb和pm脚本的实现代码分别位于/system/core/adb和frameworks/base/cmds/pm目录下,本书不分析它们的实现机制。
可见,adb push安装APK与开机扫描安装的实现机制是一样的,都是调用scanPackageLI(File scanFile,……)方法,该方法已经详细分析过,不赘述。
adb install连接pm脚本后,调用PackageManagerService提供的installPackageWithVerification方法安装APK。接下来分析installPackageWithVerification方法。
注意 PackageManager提供了installPackage方法用于第三方APK的安装,Package Manager是个抽象类,其具体的实现由其子类ApplicationPackageManager提供。Application PackageManager通过Binder通信,间接调用了PackageManagerService的installPackage方法,该方法与installPackageWithVerification的机制类似。
9.2.1 通过消息机制安装指定的APK
installPackageWithVerification通过消息机制安装指定的APK。installPackageWith Verification定义于PackageManagerService中,其代码如下:
final PackageHandler mHandler;//PackageManagerService启动解决初始化该值
……
//参数packageURI包含要安装的APK文件的路径,后续会用到
public void installPackageWithVerification(Uri packageURI,
IPackageInstallObserver observer, int flags,
String installerPackageName, Uri verificationURI,
ManifestDigest manifestDigest){
//检查客户端是否有安装APK的权限,调用的是ContextImpl中的方法
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INSTALL_PACKAGES, null);
final int uid=Binder.getCallingUid();
final int filteredFlags;
//根据UID判断客户端类型,如果通过adb安装,则需要添加相应标记
if(uid==Process.SHELL_UID||uid==0){
filteredFlags=flags|PackageManager.INSTALL_FROM_ADB;
}else{//非adb安装
filteredFlags=flags&~PackageManager.INSTALL_FROM_ADB;
}
//发送消息码为INIT_COPY的消息,消息中填充一个InstallParams的对象
final Message msg=mHandler.obtainMessage(INIT_COPY);
msg.obj=new InstallParams(packageURI, observer, filteredFlags,
installerPackageName, verificationURI, manifestDigest);
mHandler.sendMessage(msg);//发送消息
}
安装APK的消息码为INIT_COPY,消息通过mHandler发送。
mHandler在PackageManager Service的启动过程中初始化。mHandler是一个Handler类型的成员变量,在其handleMessage方法中处理消息,该方法进而调用了doHandleMessage方法。
这里仅分析INIT_COPY消息处理,代码如下:
public void handleMessage(Message msg){
try{
doHandleMessage(msg);//调用doHandleMessage
}finally{//设置线程优先级
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
}
}
void doHandleMessage(Message msg){
switch(msg.what){//消息码
case INIT_COPY:{
//读取消息中保存的InstallParams
HandlerParams params=(HandlerParams)msg.obj;
//mPendingInstalls中记录等待安装的APK个数
int idx=mPendingInstalls.size();
if(!mBound){//判断有没有绑定服务
//连接到其他应用程序提供的服务
if(!connectToService()){
params.serviceError();
return;
}else{
//添加到等待安装列表
mPendingInstalls.add(idx, params);
}
}else{如果服务已绑定,则直接添加到等待安装列表
mPendingInstalls.add(idx, params);
if(idx==0){//如果初始等待安装列表为null
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
break;
}
……
INIT_COPY消息的处理流程如下:首先判断mBound变量的值,mBound是一个bool型的变量,初始值为false。当mBound为false时,调用connectToService方法绑定一个服务,并将当前安装信息存入mPendingInstalls中;当mBound为true时,直接将安装信息存入mPendingInstalls,并且如果mPendingInstalls的初始状态为空时,发送消息码为MCS_BOUND的空消息。
这里涉及两个问题:1)connectToService绑定的服务是什么?2)发送MCS_BOUND消息的目的是什么?
首先分析第一个问题,该方法位于PackageHandler类中,代码如下:
private boolean connectToService(){
//绑定的服务组件由DEFAULT_CONTAINER_COMPONENT指定
Intent service=new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
if(mContext.bindService(service, mDefContainerConn,//绑定该服务
Context.BIND_AUTO_CREATE)){
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mBound=true;//修改绑定状态为true
return true;
}
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
return false;
}
connectToService方法通过DEFAULT_CONTAINER_COMPONENT指定了需要绑定的服务,该服务所在的包名为com.android.defcontainer,其服务名为com.android.defcontainer.DefaultContainerService。
mDefContainerConn指定了绑定服务后的回调接口,当服务绑定后,会执行其回调方法onServiceConnected,并在该方法中获得DefaultContainerService的代理对象imcs,之后发送MCS_BOUND消息。代码如下:
final private DefaultContainerConnection mDefContainerConn=
new DefaultContainerConnection();
class DefaultContainerConnection implements ServiceConnection{
public void onServiceConnected(ComponentName name, IBinder service){
IMediaContainerService imcs=//获得服务代理对象
IMediaContainerService.Stub.asInterface(service);
mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs));
}
……
};
可见绑定服务后也需要mHandler发送消息码MCS_BOUND,不过这次发送的消息不是空消息,而是包含了imcs服务代理对象。
接下来回到doHandleMessage方法分析MCS_BOUND消息的处理分支。代码如下:
void doHandleMessage(Message msg){
switch(msg.what){
case INIT_COPY:{
……
}
case MCS_BOUND:{
if(msg.obj!=null){
//保存服务代理对象
mContainerService=(IMediaContainerService)msg.obj;
}
if(mContainerService==null){
……//服务没有绑定,不能处理安装请求
mPendingInstalls.clear();
}else if(mPendingInstalls.size()>0){
//处理最早的安装请求
HandlerParams params=mPendingInstalls.get(0);
if(params!=null){
//调用startCopy进行复制安装
if(params.startCopy()){
if(mPendingInstalls.size()>0){
//从mPendingInstalls中删除已安装信息
mPendingInstalls.remove(0);
}
if(mPendingInstalls.size()==0){
//没有安装信息,并且已经绑定MCS服务,则解除绑定
if(mBound){
removeMessages(MCS_UNBIND);
Message ubmsg=obtainMessage(MCS_UNBIND);
sendMessageDelayed(ubmsg,10000);
}
}else{
//处理下一个安装请求
mHandler.sendEmptyMessage(MCS_BOUND);
}
……
可见发送MCS_BOUND消息会依次处理mPendingInstalls中存储的安装请求,处理安装请求的操作由HandlerParams的startCopy方法完成。接下来分析startCopy方法,代码如下:
final boolean startCopy(){
boolean res;
try{
//最多尝试安装4次,如果安装失败,发送MCS_GIVE_UP
if(++mRetries>MAX_RETRIES){
mHandler.sendEmptyMessage(MCS_GIVE_UP);
handleServiceError();
return false;
}else{
handleStartCopy();//执行复制安装
res=true;
}
}catch(RemoteException e){
mHandler.sendEmptyMessage(MCS_RECONNECT);
res=false;
}
handleReturnCode();
return res;
}
startCopy会调用handleStartCopy()方法处理安装操作,该方法最多会尝试执行4次安装操作,如果超过4次,会发送MCS_GIVE_UP消息。如果安装过程中发生RemoteException异常,说明与安装服务的远程通信连接有问题,此时会发送MCS_RECONNECT消息。最后会调用handleReturnCode处理返回结果。
handleStartCopy和handleReturnCode都是HandlerParams中定义的抽象方法,需要子类提供具体的实现。HandlerParams的子类有三个,分别是InstallParams、MoveParams和MeasureParams,均位于PackageManagerService中。安装APK使用的是InstallParams,接下来分析该类的handleStartCopy和handleReturnCode方法。