PackageManagerService源码分析之安装应用(四)

1、概述

在前面的几篇博文中我们已经详细的分析了PKMS的源码,并对其构造器做了深入分析,我们今天主要讲解一下通过PKMS进行apk的安装过程。

首先我们要知道apk的安装有几种方式,整体可分为2类,一类是有界面安装,一类是无界面安装。无界面安装又可分为内置apk开机安装和命令安装,而命令安装又可分为2类,一类是通过电脑的adb安装,另一类是通过手机安装的pm命令。

今天我们主要介绍两种,一种是有界面安装,另一种就是通过电脑端的adb无界面安装。

2、源码分析

1、有界面安装

有界面的安装方式相信大家平时接触的都比较多了吧,比如从网络上下载一个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命令的无界面安装方式。

2、无界面安装

大家都知道,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及其子类,首先我们先看一下这两个基类的子类关系图。

PackageManagerService源码分析之安装应用(四)_第1张图片

其中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件事:

  • 安装之前调用FileInstallArgs对象的doPreInstall方法来装备安装。
  • 调用installPackageLI方法进行安装。
  • 安装之后调用FileInstallArgs对象的doPostInstall方法来进行安装扫尾工作。
  • 安装结果后(不论成功还是失败),向Handler发送POST_INSTALL消息。

在这里我们就不详细分析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方法来通知安装结果。

3、APK安装流程总结

没想到apk的安装尽然如此复杂,不过还好的是其思路比较清晰,接下来我们简单的用一张UML流程图来表示其安装过程。

PackageManagerService源码分析之安装应用(四)_第2张图片

以上的流程图只是粗劣的表示一下其安装过程,安装的具体细节请对照源码查看文章内容。

好啦,到此整个PKMS的源码分析就全部结束啦,我们总共花了4篇的文章量才简单的分析了PKMS的部分功能,由此可想而知PKMS的工作量是多么的繁重。

你可能感兴趣的:(Android底层分析)