在前面的几篇博文中我们已经详细的分析了PKMS的源码,并对其构造器做了深入分析,我们今天主要讲解一下通过PKMS进行apk的安装过程。
首先我们要知道apk的安装有几种方式,整体可分为2类,一类是有界面安装,一类是无界面安装。无界面安装又可分为内置apk开机安装和命令安装,而命令安装又可分为2类,一类是通过电脑的adb安装,另一类是通过手机安装的pm命令。
今天我们主要介绍两种,一种是有界面安装,另一种就是通过电脑端的adb无界面安装。
有界面的安装方式相信大家平时接触的都比较多了吧,比如从网络上下载一个apk之后就会弹出安装界面,那就是有界面安装方式。
首先我们要知道有界面安装方式的那个界面是从哪里来的,还有就是如何启动那个安装界面的。那个界面其实就是PackageInstallerActivity类,这是packages/app/PackageInstaller应用中的主界面,我们可以看一下该应用的Manifest文件。
<activity
android:name=".PackageInstallerActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:theme="@android:style/Theme.DeviceDefault.Light.NoActionBar"
android:excludeFromRecents="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.INSTALL_PACKAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
//这里配置了application/vnd.android.package-archive这个字符串的mime类型
<data android:mimeType="application/vnd.android.package-archive" />
intent-filter>
<intent-filter>
<action android:name="android.intent.action.INSTALL_PACKAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:scheme="package" />
intent-filter>
<intent-filter>
<action android:name="android.content.pm.action.CONFIRM_PERMISSIONS" />
<category android:name="android.intent.category.DEFAULT" />
intent-filter>
activity>
很明显,这里我们可以通过android.intent.action.INSTALL_PACKAGE这个action来启动,也可以通过android.intent.action.VIEW这个action加上application/vnd.android.package-archive这个type来启动,当然不加这个type也可以启动,只不过会找到很多个Activity,所以可通过如下代码进行启动。
String apkFileString = Environment.getExternalStorageDirectory().getAbsolutePath()+/.../packageName.apk;
File apkFile = new File(apkFileString);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.fromFile(apkFile), application/vnd.android.package-archive);
mContext.startActivity(intent);
接下来我们直接进入PackageInstallerActivity类中。
public class PackageInstallerActivity extends Activity implements OnCancelListener, OnClickListener {
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
}
protected void onResume() {
super.onResume();
mPm = getPackageManager();
mInstaller = mPm.getPackageInstaller();
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
//加载界面布局
setContentView(R.layout.install_start);
//读取apk包相关信息
PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);
//读取权限相关信息
//......
}
}
在其OnResume方法中主要是加载布局和对apk包进行相关解析,将权限信息显示在界面上,而apk相关信息将保存下来,以便后期安装时使用,接下来点击安装按钮后将调用以下类的相关方法。
public class InstallAppProgress extends Activity implements View.OnClickListener, OnCancelListener {
public void initView() {
setContentView(R.layout.op_progress);
PackageManager pm = getPackageManager();
//......
//创建安装完成后的监听接口
PackageInstallObserver observer = new PackageInstallObserver();
if ("package".equals(mPackageURI.getScheme())) {
try {
pm.installExistingPackage(mAppInfo.packageName);
//监听安装结果
observer.packageInstalled(mAppInfo.packageName,
PackageManager.INSTALL_SUCCEEDED);
} catch (PackageManager.NameNotFoundException e) {
observer.packageInstalled(mAppInfo.packageName,
PackageManager.INSTALL_FAILED_INVALID_APK);
}
} else {
pm.installPackageWithVerificationAndEncryption(mPackageURI, observer, installFlags,
installerPackageName, verificationParams, null);
}
}
}
好啦,以上就是通过有界面安装的大致过程,这里我们只做简单分析,具体细节大家自行研究吧,接下来我们在一起看一下通过adb install命令的无界面安装方式。
大家都知道,adb install有很多的参数,但是我们今天就分析最简单的adb install xxx.apk,adb是一个命令而install是其参数,这里我们直接进入处理install的代码逻辑(system/core/adb/commandline.c)。
int adb_commandline(int argc, char **argv)
{
//......
if (!strcmp(argv[0], "install")) {
if (argc < 2) return usage();
//调用install_app函数进行处理
return install_app(ttype, serial, argc, argv);
}
}
我们进入install_app函数,看其细节。
int install_app(transport_type transport, char* serial, int argc, char** argv)
{
//手机内部存储路径
static const char *const DATA_DEST = "/data/local/tmp/%s";
//SD卡路径
static const char *const SD_DEST = "/sdcard/tmp/%s";
const char* where = DATA_DEST;
for (i = 1; i < argc; i++) {
//表示安装在SD卡上
if (!strcmp(argv[i], "-s")) {
where = SD_DEST;
}
}
char* apk_file = argv[last_apk];
//安装路径
char apk_dest[PATH_MAX];
snprintf(apk_dest, sizeof apk_dest, where, get_basename(apk_file));
//调用do_sync_push将apk文件push到手机中
int err = do_sync_push(apk_file, apk_dest, 0 /* no show progress */);
if (err) {
goto cleanup_apk;
} else {
argv[last_apk] = apk_dest; /* destination name, not source location */
}
//调用shell pm命令去安装
pm_command(transport, serial, argc, argv);
cleanup_apk:
//在手机中执行shell rm来删除刚刚推入的apk文件
delete_file(transport, serial, apk_dest);
return err;
}
这个方法主要的功能就是找到apk安装的路径,然后执行pm命令去安装,并在最终通过rm命令将apk进行删除,我们在来看一下pm_command函数的功能吧。
static int pm_command(transport_type transport, char* serial,
int argc, char** argv)
{
char buf[4096];
//通过pm命令去执行安装操作
snprintf(buf, sizeof(buf), "shell:pm");
while(argc-- > 0) {
char *quoted = escape_arg(*argv++);
strncat(buf, " ", sizeof(buf) - 1);
strncat(buf, quoted, sizeof(buf) - 1);
free(quoted);
}
send_shellcommand(transport, serial, buf);
return 0;
}
那什么是pm呢?其实pm只是一个脚本,其源码所在路径(frameworks/base/cmds/pm/pm)。
# Script to start "pm" on the device, which has a very rudimentary
# shell.
#
base=/system
export CLASSPATH=$base/framework/pm.jar
exec app_process $base/bin com.android.commands.pm.Pm "$@"
可以发现其执行的是pm.jar包的main函数,我们进入Pm.java类。
public static void main(String[] args) {
int exitCode = 1;
try {
exitCode = new Pm().run(args);
} catch (Exception e) {
Log.e(TAG, "Error", e);
System.err.println("Error: " + e);
if (e instanceof RemoteException) {
System.err.println(PM_NOT_RUNNING_ERR);
}
}
System.exit(exitCode);
}
这里直接创建了Pm对象并调用其run方法,我们进入其run方法。
public int run(String[] args) throws IOException, RemoteException {
mUm = IUserManager.Stub.asInterface(ServiceManager.getService("user"));
//这里获得PKMS的代理对象
mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
if (mPm == null) {
System.err.println(PM_NOT_RUNNING_ERR);
return 1;
}
mInstaller = mPm.getPackageInstaller();
//处理install参数,还有很多其他参数
if ("install".equals(op)) {
return runInstall();
}
//......
}
可以发现这里又将安装的操作交给了runInstall这个方法,我们再次进入该方法。
private int runInstall() {
while ((opt=nextOption()) != null) {
//处理很多的参数命令
if (opt.equals("-l")) {
installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
} else if (opt.equals("-r")) {
installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
} else if (opt.equals("-i")) {
installerPackageName = nextOptionData();
if (installerPackageName == null) {
System.err.println("Error: no value specified for -i");
return 1;
}
} else if (opt.equals("-t")) {
installFlags |= PackageManager.INSTALL_ALLOW_TEST;
} else if (opt.equals("-s")) {
// Override if -s option is specified.
installFlags |= PackageManager.INSTALL_EXTERNAL;
} else if (opt.equals("-f")) {
// Override if -s option is specified.
installFlags |= PackageManager.INSTALL_INTERNAL;
}
//......
}
//多用户手机时将所有用户都安装
if (userId == UserHandle.USER_ALL) {
userId = UserHandle.USER_OWNER;
installFlags |= PackageManager.INSTALL_ALL_USERS;
}
//监听安装结果
LocalPackageInstallObserver obs = new LocalPackageInstallObserver();
//调用PKMS的installPackageAsUser方法进行安装操作
mPm.installPackageAsUser(apkFilePath, obs.getBinder(), installFlags,installerPackageName, verificationParams, abi, userId);
synchronized (obs) {
while (!obs.finished) {
obs.wait();
}
//当安装成功后打印Success
if (obs.result == PackageManager.INSTALL_SUCCEEDED) {
System.out.println("Success");
return 0;
} else {
System.err.println("Failure ["
+ installFailureToString(obs)
+ "]");
return 1;
}
}
}
以上就是runInstall方法的主要内容,首先根据安装参数来设置installFlags属性值,然后创建LocalPackageInstallObserver对象来监听安装结果,最后调用PKMS对象的installPackageAsUser来执行安装操作,当然最后无论安装成功还是失败都需返回一个结果以供PC的命令行进行展示,接下来我们将进入PKMS的installPackageAsUser方法。
//originPath表示apk路径,observer是LocalPackageInstallObserver对象,用于监听apk安装结果,installFlags是安装参数
public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer,
int installFlags,...) {
//......
//将操作通过发送Handler来处理
final Message msg = mHandler.obtainMessage(INIT_COPY);
//注意这里创建了InstallParams对象并将其传递给msg.obj对象,后面我们会详细分析这个对象
msg.obj = new InstallParams(origin, observer, installFlags,installerPackageName, verificationParams, user, packageAbiOverride);
mHandler.sendMessage(msg);
}
可以看到installPackageAsUser方法还是蛮简单的,只是创建一个Message对象,然后通过Handler来发送INIT_COPY的消息,不过这里大家要注意参数的传递,我们进入Handler的处理消息的代码。
public void handleMessage(Message msg) {
switch (msg.what) {
case INIT_COPY: {
//(1)获得传递过来的params对象,其实际值是InstallParams
HandlerParams params = (HandlerParams) msg.obj;
//mPendingInstalls用于存储待安装应用,idx表示当前待安装个数
int idx = mPendingInstalls.size();
if (!mBound) {
//(2)通过bindService来启动另外一个服务
if (!connectToService()) {
params.serviceError();
return;
} else {
//如果另外一个服务已启动,将其添加到mPendingInstalls中
mPendingInstalls.add(idx, params);
}
} else {
mPendingInstalls.add(idx, params);
if (idx == 0) {
//(3)表示要启动安装
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
break;
}
}
}
在以上代码中我们看到在(1)处的位置传递过来的参数实际是InstallParams对象,这个我们后面分析,在这里就不详细阐述了,我们来看一下connectToService()这个方法,进入该方法的源码。
//创建DefaultContainerService类的ComponentName对象
static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(DEFAULT_CONTAINER_PACKAGE,
"com.android.defcontainer.DefaultContainerService");
private boolean connectToService() {
//设置Intent
Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
//通过bindServiceAsUser来启动DefaultContainerService服务
if (mContext.bindServiceAsUser(service, mDefContainerConn,Context.BIND_AUTO_CREATE, UserHandle.OWNER)) {
mBound = true;
return true;
}
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
return false;
}
没有想到吧我们安装一个应用的时候还需要另外一个apk来提供服务,这个apk就是DefaultContainerService.apk,通过bindServiceAsUser方法来启动该服务,我们接下来回到handleMessage中,看其第三步来处理MCS_BOUND消息。
public void handleMessage(Message msg) {
switch (msg.what) {
case MCS_BOUND: {
if (msg.obj != null) {
mContainerService = (IMediaContainerService) msg.obj;
}
//如果Service没有启动则不能安装程序
if (mContainerService == null) {
for (HandlerParams params : mPendingInstalls) {
params.serviceError();
}
mPendingInstalls.clear();
}else if (mPendingInstalls.size() > 0) {
HandlerParams params = mPendingInstalls.get(0);
if (params != null) {
//调用params对象的startCopy方法,该方法有基类HandlerParams定义
if (params.startCopy()) {
if (mPendingInstalls.size() > 0) {
//删除队列头
mPendingInstalls.remove(0);
}
if (mPendingInstalls.size() == 0) {
if (mBound) {
//如果安装请求完成了,在通过调用unbindService方法来解绑服务
removeMessages(MCS_UNBIND);
Message ubmsg = obtainMessage(MCS_UNBIND);
sendMessageDelayed(ubmsg, 10000);
}
} else {
//如果还有待安装的事件,将继续发送MCS_BOUND消息来完成安装
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
}
break;
}
}
}
MCS_BOUND的逻辑还是比较简单的,主要就是调用startCopy方法来完成安装,这里我们先分析一下HandlerParams及其相关子类,这里在介绍HandlerParams时我们也会同时介绍InstallArgs及其子类,首先我们先看一下这两个基类的子类关系图。
其中HandlerParams和InstallArgs都是抽象基类。
HandlerParams有三个子类(InstallParams,MoveParams,MeasureParams),其中InstallParams用于处理Apk的安装操作,MoveParams用于处理已安装应用的搬家操作,MeasureParams用于查询已安装应用所占的存储空间大小。
InstallArgs有二个子类(FileInstallArgs,AsecInstallArgs),FileInstallArgs针对的是安装的内部存储空间的apk,而AsecInstallArgs针对的是安装在SD卡上的apk。
我们接着回到之前的MCS_BOUND分支逻辑中,在分析params.startCopy()的方法之前,我们先看一下MCS_UNBIND这个分支逻辑。
public void handleMessage(Message msg) {
switch (msg.what) {
case MCS_UNBIND: {
if (mPendingInstalls.size() == 0 && mPendingVerification.size() == 0) {
if (mBound) {
//这里调用disconnectService方法来进行Service的解绑
disconnectService();
}
} else if (mPendingInstalls.size() > 0) {
//如果还有待安装的应用将继续执行MCS_BOUND分支逻辑
mHandler.sendEmptyMessage(MCS_BOUND);
}
break;
}
}
}
以上代码逻辑还是比较简单的,这里直接将操作转给disconnectService方法,我们进入disconnectService方法。
private void disconnectService() {
mContainerService = null;
mBound = false;
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
//这里调用Context.unbindService对之前的DefaultContainerService服务进行解绑
mContext.unbindService(mDefContainerConn);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
}
对服务解绑操作我们也了解了,这里我们就进入params.startCopy()方法进行分析,看其具体是如何进行安装的。
final boolean startCopy() {
boolean res;
try {
//MAX_RETRIES的值是4,表示默认安装4次,如果还不成功就表示安装失败
if (++mRetries > MAX_RETRIES) {
mHandler.sendEmptyMessage(MCS_GIVE_UP);
handleServiceError();
return false;
} else {
//调用HandlerParams子类的handleStartCopy方法
handleStartCopy();
res = true;
}
} catch (RemoteException e) {
mHandler.sendEmptyMessage(MCS_RECONNECT);
res = false;
}
//调用HandlerParams子类的handleReturnCode方法,将处理结果返回
handleReturnCode();
return res;
}
我们进入startCopy方法,发现其主要就执行了两步,先执行HandlerParams子类的handleStartCopy方法,然后在执行其handleReturnCode方法将结果返回,在前面的代码逻辑中我们也提到了这里的HandlerParams子类其实就是InstallParams对象,我们进入该对象的handleStartCopy方法。
public void handleStartCopy() throws RemoteException {
//根据adb install的参数来判断安装位置
final boolean onSd = (installFlags & PackageManager.INSTALL_EXTERNAL) != 0;
final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) != 0;
//内部存储和SD卡不能同时安装
if (onInt && onSd) {
ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
} else {
//通过StorageManager对象并查询内部存储空间最小余量
final StorageManager storage = StorageManager.from(mContext);
final long lowThreshold = storage.getStorageLowBytes(
Environment.getDataDirectory());
//......
//创建安装参数对象
final InstallArgs args = createInstallArgs(this);
//......
//调用InstallArgs对象的copyApk方法完成apk的拷贝
ret = args.copyApk(mContainerService, true);
}
}
}
这里我们主要来看一下createInstallArgs方法,看其内部是如何创建安装参数。
private InstallArgs createInstallArgs(InstallParams params)
{
if (installOnSd(params.installFlags) || installOnInternalSd(params.installFlags) ||
params.isForwardLocked())
{
return new AsecInstallArgs(params);
} else {
//安装在内部存储空间
return new FileInstallArgs(params);
}
}
可以看到以上分为安装在内部存储空间和外部存储空间两种情况,我们这里分析安装在内部存储空间,InstallArgs对象的copyApk方法我们就不详细分析了,主要就是复制Apk的操作,现在我们进入HandlerParams子类的handleReturnCode方法,这里的HandlerParams子类其实就是InstallParams对象。
void handleReturnCode() {
if (mArgs != null) {
processPendingInstall(mArgs, mRet);
}
}
private void processPendingInstall(final InstallArgs args, final int currentStatus) {
mHandler.post(new Runnable() {
public void run() {
mHandler.removeCallbacks(this);
//......
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
//调用FileInstallArgs对象的doPreInstall方法
args.doPreInstall(res.returnCode);
synchronized (mInstallLock) {
//调用installPackageLI方法进行安装
installPackageLI(args, res);
}
//调用FileInstallArgs对象的doPostInstall方法
args.doPostInstall(res.returnCode, res.uid);
}
}
}
//备份和恢复相关操作
//......
if (!doRestore) {
//向mHandler发送一条POST_INSTALL消息
Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
mHandler.sendMessage(msg);
}
}
handleReturnCode方法中直接将操作转给processPendingInstall方法, 我们进入该方法发现其主要做了以下4件事:
在这里我们就不详细分析doPreInstall、installPackageLI、doPostInstall三个方法了,大家有兴趣可以去研究一下,这里我们直接进入POST_INSTALL消息。
public void handleMessage(Message msg) {
switch (msg.what) {
case POST_INSTALL: {
PostInstallData data = mRunningInstalls.get(msg.arg1);
mRunningInstalls.delete(msg.arg1);
//apk安装发送ACTION_PACKAGE_ADDED
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
res.pkg.applicationInfo.packageName,
extras, null, null, updateUsers);
//apk更新将发送ACTION_PACKAGE_REPLACED和ACTION_MY_PACKAGE_REPLACED
if (update) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
res.pkg.applicationInfo.packageName,
extras, null, null, updateUsers);
sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
null, null,
res.pkg.applicationInfo.packageName,
null, updateUsers);
}
//安装和更新的区别是:PACKAGE_REPLACED多携带了一个参数
if (update) {
extras.putBoolean(Intent.EXTRA_REPLACING, true);
}
Runtime.getRuntime().gc();
if (deleteOld) {
synchronized (mInstallLock) {
//调用FileInstallArgs.doPostDeleteLI进行资源清理
res.removedInfo.args.doPostDeleteLI(true);
}
}
if (args.observer != null) {
try {
Bundle extras = extrasForInstallResult(res);
//重点:通知pm安装结果
args.observer.onPackageInstalled(res.name, res.returnCode,
res.returnMsg, extras);
}
break;
}
}
}
发送POST_INSTALL的目的就是清理一些资源,但其最主要的目的就是通知pm的安装结果,还记得在之前的installPackageAsUser方法中将监听器传给了InstallParams.observer成员变量,这个监听器就是LocalPackageInstallObserver的Binder对象,现在回调其onPackageInstalled方法来通知安装结果。
没想到apk的安装尽然如此复杂,不过还好的是其思路比较清晰,接下来我们简单的用一张UML流程图来表示其安装过程。
以上的流程图只是粗劣的表示一下其安装过程,安装的具体细节请对照源码查看文章内容。
好啦,到此整个PKMS的源码分析就全部结束啦,我们总共花了4篇的文章量才简单的分析了PKMS的部分功能,由此可想而知PKMS的工作量是多么的繁重。