跟一跟Android应用的安装流程-apk拷贝

前言

上一篇文章跟着源码的脚步了解了Android系统在开机的过程中,对系统中安装的应用程序会进行apk文件解析,并且对在manifest中注册的四大组件进行解析,并且将相应组件的信息缓存在PKMS中,然后通过文件目录的分析,发现我们安装的应用程序都放在了/data/app文件目录下,并且每一个apk的命名统一标准化为base.apk,那么本文就再一次进入痛苦的源码追踪过程,去看看Android系统安装应用到底做了什么

应用安装部分分为了应用的拷贝到/data/app文件夹和后续的解析步骤,这一篇文章主要追踪apk文件是怎么拷贝到目标文件夹的过程

对于Android开发应该都了解过应用内安装更新应用的需求,通过下载apk文件,然后通知系统去执行安装步骤,会弹出一个系统的安装界面,显示部分应用的信息,当用户点击同意安装后,进入后续的安装流程,这个让用户选择的弹窗页面,就是系统提供的PackageInstallerActivity页面,位于com.android.packageinstaller包下的系统页面

public class PackageInstallerActivity extends AlertActivity {
    @Override
    protected void onCreate(Bundle icicle) {
        getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
        super.onCreate(null);
        //拿到PKMS
        mPm = getPackageManager();
        //PKMS中的处理应用安装的对象
        mInstaller = mPm.getPackageInstaller();
      
        final Intent intent = getIntent();

        mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE);
        mSourceInfo = intent.getParcelableExtra(EXTRA_ORIGINAL_SOURCE_INFO);
        mOriginatingUid = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
                PackageInstaller.SessionParams.UID_UNKNOWN);
        mOriginatingPackage = (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN)
                ? getPackageNameForUid(mOriginatingUid) : null;

        final Uri packageUri;
        if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction())) {
            final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
            final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
            mSessionId = sessionId;
            //这不拿到安装apk的路径,也就是去读取apk信息
            packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath));
            mOriginatingURI = null;
            mReferrerURI = null;
        } else {
            mSessionId = -1;
            packageUri = intent.getData();
            mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
            mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
        }
        bindUi();
    }
}

对应用开发者来说,这部分写在Actiivity中onCreate方法还是很熟悉的,给跟安装应用相关的成员变量进行了赋值操作,比如跟安装相关的PKMS服务,然后调用了bindUi去显示安装的选择页面,确认是否进行应用安装

private void bindUi() {
   //进行安装页面的视图的数据绑定
    mAlert.setIcon(mAppSnippet.icon);
    mAlert.setTitle(mAppSnippet.label);
    //这个布局可以去源码查看
    //frameworks/base/packages/PackageInstaller/res/layout/install_content_view.xml
    mAlert.setView(R.layout.install_content_view);
    mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.install),
            (ignored, ignored2) -> {
                if (mOk.isEnabled()) {
                    if (mSessionId != -1) {
                        mInstaller.setPermissionsResult(mSessionId, true);
                        finish();
                    } else {
                      //点击确认安装后,指向安装的逻辑
                        startInstall();
                    }
                }
            }, null);
    mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
            (ignored, ignored2) -> {
                // Cancel and finish
                setResult(RESULT_CANCELED);
                if (mSessionId != -1) {
                  //如果mSessionId存在,取消安装
                    mInstaller.setPermissionsResult(mSessionId, false);
                }
                finish();
            }, null);
    setupAlert();

    mOk = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
    mOk.setEnabled(false);
}
安装预览页面.jpeg

以小米手机的这个页面为例子,除了显示应用相关的信息,厂商还额外去显示了厂商应用市场的数据,底部的主要事件就是确认安装和取消安装,这部分界面的显示和控件事件的绑定都还是很简单的,确认安装后就进入了startInstall()方法

//这个方法实例化了一个Intent对象,并通过传参跳转到下一个Activity
private void startInstall() {
    Intent newIntent = new Intent();
    newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
            mPkgInfo.applicationInfo);
    newIntent.setData(mPackageURI);
    //从这里可以看出,是跳转到了InstallInstalling这个Activity,
    //这个Activity的命名不是太Activity
    newIntent.setClass(this, InstallInstalling.class);
    String installerPackageName = getIntent().getStringExtra(
            Intent.EXTRA_INSTALLER_PACKAGE_NAME);
    //...省略部分参数的解析部分
    newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
    startActivity(newIntent);
    finish();
}

虽然跳转到新的InstallInstalling页面命名上不是太Activity,但是从后面的执行方法,startActivity可以看出下一步又是一个新的页面,并且finish将当前页面关闭

//这确实是一个Activity,直接进入onCreate方法
public class InstallInstalling extends AlertActivity {
      @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ApplicationInfo appInfo = getIntent()
                .getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
        mPackageURI = getIntent().getData();

        if ("package".equals(mPackageURI.getScheme())) {
            try {
                getPackageManager().installExistingPackage(appInfo.packageName);
                launchSuccess();
            } catch (PackageManager.NameNotFoundException e) {
                launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
            }
        } else {
            //根据传入的mPackageURI,去创建一个对应的文件
            final File sourceFile = new File(mPackageURI.getPath());
            PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile);
                      //显示安装软件相关的信息
            mAlert.setIcon(as.icon);
            mAlert.setTitle(as.label);
            mAlert.setView(R.layout.install_content_view);
            mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
                    (ignored, ignored2) -> {}, null);
            setupAlert();
            requireViewById(R.id.installing).setVisibility(View.VISIBLE);
                        //如果savedInstanceState不为null,获取保存的mSessionId和mInstallId
            if (savedInstanceState != null) {
                //安装包的会话id
                mSessionId = savedInstanceState.getInt(SESSION_ID);
                //等待安装的事件id
                mInstallId = savedInstanceState.getInt(INSTALL_ID);

                try {
                  //根据mInstallId向InstallEventReceiver注册一个观察者
                  //launchFinishBasedOnResult会接收到安装事件的回调
                    InstallEventReceiver.addObserver(this, mInstallId,
                            this::launchFinishBasedOnResult);
                }
            } else {
               //savedInstanceState为null,创建一个SessionParams对象,包装安装会话的参数
                PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                        PackageInstaller.SessionParams.MODE_FULL_INSTALL);
                params.setInstallAsInstantApp(false);
                params.setInstallerPackageName(getIntent().getStringExtra(
                        Intent.EXTRA_INSTALLER_PACKAGE_NAME));
                params.setInstallReason(PackageManager.INSTALL_REASON_USER);
                                //对对需要安装的apk对象路径创建文件对象
                File file = new File(mPackageURI.getPath());
                try {
                    //对apk文件进行解析,将解析的apk相关信息保存在params中
                    PackageParser.PackageLite pkg = PackageParser.parsePackageLite(file, 0);
                    params.setAppPackageName(pkg.packageName);
                    params.setInstallLocation(pkg.installLocation);
                    params.setSize(
                            PackageHelper.calculateInstalledSize(pkg, false, params.abiOverride));
                }

                try {
                  //类似的,需要向InstallEventReceiver注册观察者,并且会返回一个安装的会话id
                    mInstallId = InstallEventReceiver
                            .addObserver(this, EventResultPersister.GENERATE_NEW_ID,
                                    this::launchFinishBasedOnResult);
                }

                try {
                  //与PKMS内部的installer对象通信,拿到一个安装的事件id
                    mSessionId = getPackageManager().getPackageInstaller().createSession(params);
                } 
            }

            mCancelButton = mAlert.getButton(DialogInterface.BUTTON_NEGATIVE);
            mSessionCallback = new InstallSessionCallback();
        }
    }
}
安装页面.jpeg

这个页面也是对需要安装的apk文件解析出基本信息,让用户可视化预览需要安装的应用内容,并且会跟PKMS进行通信,调用到createSession()方法,创建一个mSessionId的事件id

而具体的安装事件还得继续在oncreate生命周期之后的onResume()中

//InstallInstalling.java
@Override
protected void onResume() {
    super.onResume();
    if (mInstallingTask == null) {
        //应用安装的对象
        PackageInstaller installer = getPackageManager().getPackageInstaller();
        //这里通过上面创建的mSessionId去获取sessionInfo
        PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);

        if (sessionInfo != null && !sessionInfo.isActive()) {
            //创建一个安装任务并且调用execute()方法
            mInstallingTask = new InstallingAsyncTask();
            mInstallingTask.execute();
        } else {
            mCancelButton.setEnabled(false);
            setFinishOnTouchOutside(false);
        }
    }
}

//AsyncTask这个常用的做异步任务的对象
private final class InstallingAsyncTask extends AsyncTask {
    volatile boolean isDone;

    //doInBackground的具体业务就是拿到包的Uri,通过文件IO流的方式将apk的内容写到Session中
    @Override
    protected PackageInstaller.Session doInBackground(Void... params) {
        PackageInstaller.Session session;
        //前面定义了sessionId,全程追踪标记
        session = getPackageManager().getPackageInstaller().openSession(mSessionId);
        session.setStagingProgress(0);

        try {
            //构建File对象
            File file = new File(mPackageURI.getPath());

            try (InputStream in = new FileInputStream(file)) {
                long sizeBytes = file.length();
                try (OutputStream out = session
                        .openWrite("PackageInstaller", 0, sizeBytes)) {
                    byte[] buffer = new byte[1024 * 1024];
                    while (true) {
                        int numRead = in.read(buffer);

                        if (numRead == -1) {
                            session.fsync(out);
                            break;
                        }

                        if (isCancelled()) {
                            session.close();
                            break;
                        }
                                                //这里可以看到写入操作,将APK的信息通过IO读写写到了session中
                        out.write(buffer, 0, numRead);
                        if (sizeBytes > 0) {
                            float fraction = ((float) numRead / (float) sizeBytes);
                            session.addProgress(fraction);
                        }
                    }
                }
            }

            return session;
        } catch (IOException | SecurityException e) {} finally {
            synchronized (this) {
                isDone = true;
                notifyAll();
            }
        }
    }

    //doInBackground完成后,在onPostExecute方法中,会调用commit方法进行安装
    @Override
    protected void onPostExecute(PackageInstaller.Session session) {
        if (session != null) {
            Intent broadcastIntent = new Intent(BROADCAST_ACTION);
            broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
            broadcastIntent.setPackage(getPackageName());
            broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);

            PendingIntent pendingIntent = PendingIntent.getBroadcast(
                    InstallInstalling.this,
                    mInstallId,
                    broadcastIntent,
                    PendingIntent.FLAG_UPDATE_CURRENT);
                        //session里面已经保存有apk的信息,调用commit进行后续安装流程
            session.commit(pendingIntent.getIntentSender());
            mCancelButton.setEnabled(false);
            setFinishOnTouchOutside(false);
        } else {}
    }
}

可以看到应用安装的就是将整个apk文件通过IO流的方式进行读取,并且保存在到安装器PackageInstaller对象的一次安装会话中,上面的流程除了解析apk部分概览信息,没有对apk有额外的操作

//PackageInstaller.java
public static class Session implements Closeable {
    //IPackageInstallerSession是一个aidl文件,说要要通过这个对象进行跨进程通信
    //要找实现功能的地方,就要找到这个类的Stub对象的实现,最终调用到了PackageInstallerSession对象中
    protected final IPackageInstallerSession mSession;

    public Session(IPackageInstallerSession session) {
        mSession = session;
    }
    //继续调用
    public void commit(@NonNull IntentSender statusReceiver) {
        try {
            mSession.commit(statusReceiver, false);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}

//PackageInstallerSession.java
//可以看出这个是AIDL生成的服务端Stub的实现类
public class PackageInstallerSession extends IPackageInstallerSession.Stub {
    public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
        //将传入参数进一步markAsSealed方法
        if (!markAsSealed(statusReceiver, forTransfer)) {
            return;
        }
        if (isMultiPackage()) {
          //...
        }
                //可以看到就是通过handler发送一个MSG_STREAM_VALIDATE_AND_COMMIT的消息
        dispatchStreamValidateAndCommit();
    }
}

//看到handler就需要直接定位到handlemessage的位置
private void dispatchStreamValidateAndCommit() {
    mHandler.obtainMessage(MSG_STREAM_VALIDATE_AND_COMMIT).sendToTarget();
}

private final Handler.Callback mHandlerCallback = new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_STREAM_VALIDATE_AND_COMMIT:
                //首次处理的message消息
                handleStreamValidateAndCommit();
                break;
            case MSG_INSTALL:
                //准备好后回调到这个消息进行安装操作
                handleInstall();
                break;
        }
        return true;
    }
};

private void handleStreamValidateAndCommit() {
    boolean allSessionsReady = false;
    try {
        //会对需要执行的安装会话session进行可用性检查
        allSessionsReady = streamValidateAndCommit();
    } catch (PackageManagerException e) {
        unrecoverableFailure = e;
    }

    if (isMultiPackage()) {
      //...Multi也就是拆分糊调用同样的方法
    }
    //处理完后继续发生Message消息
    mHandler.obtainMessage(MSG_INSTALL).sendToTarget();
}

将apk文件信息保存在session后,通过跨进程的方式将任务继续分发,先对session内的数据和锁状态进行了检查操作,通过handler机制分发任务,最终执行到了handleInstall()方法去执行安装操作

//PackageInstaller.java
private void handleInstall() {
    if (isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked()) {}
    if (params.isStaged) {}
    if (isApexInstallation()) {}
        
    //处理multiPackage的情况
    List childSessions = getChildSessionsNotLocked();

    try {
        synchronized (mLock) {
            //继续往后执行
            installNonStagedLocked(childSessions);
        }
    } catch (PackageManagerException e) {}
}

//这个方法中,会将处理好的session传给PKMS进行处理
private void installNonStagedLocked(List childSessions)
        throws PackageManagerException {
    final PackageManagerService.ActiveInstallSession installingSession =
            makeSessionActiveLocked();
    if (isMultiPackage()) {
        List installingChildSessions =
                new ArrayList<>(childSessions.size());
        mPm.installStage(installingChildSessions);
    } else {
        //不管是不是multi得类型,最终都需要跨进程执行到PKMS的installStage方法
        mPm.installStage(installingSession);
    }
}

上一篇文章中就写了PKMS的主要职责只有就是负责apk的安装,从安装界面的开始,用户点击确认安装后,一直在对本次安装的apk文件进行处理封装到Session对象中,并对Session进行各种预处理,最终还是需要执行到PKMS的安装方法

//PackageManagerService.java
void installStage(ActiveInstallSession activeInstallSession) {
        //创建了一个INIT_COPY的消息对象
    final Message msg = mHandler.obtainMessage(INIT_COPY);
    //将前面封装的session对象进行处理成InstallParams对象,安装包的数据对象
    final InstallParams params = new InstallParams(activeInstallSession);
    
    msg.obj = params;
    //贯穿整个Android系统的handler-message机制
    mHandler.sendMessage(msg);
}

//处理部分
class PackageHandler extends Handler {
  
    public void handleMessage(Message msg) {
        try {
            doHandleMessage(msg);
        } finally {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        }
    }

    void doHandleMessage(Message msg) {
        switch (msg.what) {
            case INIT_COPY: {
                HandlerParams params = (HandlerParams) msg.obj;
                if (params != null) {
                    //应用的安装实际执行的是APK的拷贝动作
                    params.startCopy();
                }
                break;
            }
        }
    }
}

//PackageManagerService#HandlerParams
//这是一个抽象类,有multi和普通的两个实现类
final void startCopy() {
    handleStartCopy();//这一步做拷贝前的准备工作
    handleReturnCode();//执行拷贝工作
}

// InstallParams extends HandlerParams
private InstallArgs mArgs;
void handleReturnCode() {
    if (mVerificationCompleted
            && mIntegrityVerificationCompleted && mEnableRollbackCompleted) {
        //前面准备工作OK了
        if (mRet == PackageManager.INSTALL_SUCCEEDED) {
            //执行apk文件的拷贝,执行到InstallArgs的方法
            mRet = mArgs.copyApk();
        }
      
        //↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
        //还有下一步
        //文件拷贝结束后,执行应用的安装工作
        processPendingInstall(mArgs, mRet);
        //↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
    }
}

//InstallArgs也是抽象类
class FileInstallArgs extends InstallArgs {
    //int copyApk() -> 调用 doCopyApk();

    private int doCopyApk() {
        //...继续
        final File tempDir =
            mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);
        //这个文件路径通过源码跟踪,最终执行到了
        //Environment.getDataAppDirectory(volumeUuid)
        //也就是/data/app
            codeFile = tempDir;
        int ret = PackageManagerServiceUtils.copyPackage(
                origin.file.getAbsolutePath(), codeFile);
        return ret;
    }
}

//调用到PackageManagerServiceUtils工具类
public static int copyPackage(String packagePath, File targetDir) {
    try {
        final File packageFile = new File(packagePath);
        final PackageParser.PackageLite pkg = PackageParser.parsePackageLite(packageFile, 0); 
        //将apk文件复制到目标路径data/app,并统一命名为base.apk
        copyFile(pkg.baseCodePath, targetDir, "base.apk");
        if (!ArrayUtils.isEmpty(pkg.splitNames)) {
            for (int i = 0; i < pkg.splitNames.length; i++) {
                copyFile(pkg.splitCodePaths[i], targetDir,
                        "split_" + pkg.splitNames[i] + ".apk");
            }
        }
        return PackageManager.INSTALL_SUCCEEDED;
    }
}

//具体的文件流操作,复制文件
private static void copyFile(String sourcePath, File targetDir, String targetName)
        throws ErrnoException, IOException {
    final File targetFile = new File(targetDir, targetName);
    final FileDescriptor targetFd = Os.open(targetFile.getAbsolutePath(),
            O_RDWR | O_CREAT, 0644);
    Os.chmod(targetFile.getAbsolutePath(), 0644);
    FileInputStream source = null;
    try {
        source = new FileInputStream(sourcePath);
        FileUtils.copy(source.getFD(), targetFd);
    } finally {
        IoUtils.closeQuietly(source);
    }
}

通过一些列的操作,就将我们需要安装的apk安装包通过文件读写的方式,拷贝到了/data/app文件目录,并且重命名为base.apk,当拷贝完apk后安装流程还没有结束,就比如上一篇分析的,对应apk的manifest解析没有进行,如果要打开这个app应用,连四大组件的信息都找不到

在上面代码中也有标记,当copyapk结束后,会去执行processPendingInstall()方法,继续处理应用安装的和PKMS的解析,在后面的文章进行跟踪记录

你可能感兴趣的:(跟一跟Android应用的安装流程-apk拷贝)