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方法。