Apk的安装过程探究

Quibbler 2021-5-27 1679

Apk的安装过程探究


        有了APK的构建过程APK的组成结构两篇铺垫,以及PackageManagerService基础。终于到了最后阶段:Apk的安装过程。这块内容比较多,抽空断断续续的看了一个星期摸清楚。通常Apk的安装方式有三种:

        手机上点击Apk文件,打开安装界面进行安装

        通过代码的方式静默安装,比如手机自带的应用商店安装应用。

        开发者使用adb命令安装apk文件

        挨个探究以上三个方式,每种方式Apk的安装流程实现。本文所有流程图使用迅捷流程图完成,在线查看地址:Apk安装流程图



1、PackageInstallerActivity界面安装Apk

        先从第一种Apk安装方式讲起,这是用户经常使用的Apk安装方式。点击下载的Apk文件,会启动系统安装应用界面,执行Apk安装过程。

        启动安装界面的常规代码如下:

    File apkFile = new File(apkPath);
    
    //构造Apk安装Intent, Intent.ACTION_INSTALL_PACKAGE已经废弃
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.addCategory(Intent.CATEGORY_DEFAULT);
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    
    /**Android 7.0以上使用FileProvider,
     * Android禁止将file://Uri暴露给其它应用,
     * 否则会抛出FileUriExposedException异常
     */
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        // 7.0+以上版本:content://Uri
        Uri apkUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", apkFile);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
    } else {
        // file://Uri
        intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
    }
    
    //启动系统安装界面
    context.startActivity(intent);


1.1、启动安装界面

        打开的安装界面是哪个应用的呢?很简单,用下面的命令就可以查看当前手机的应用包名和启动的Activity

    adb shell dumpsys window | findStr mCurrentFocus

        当前手机屏幕显示的应用是com.android.packageinstallerActivity界面是com.android.packageinstaller.PackageInstallerActivity

mCurrentFocus=null
mCurrentFocus=Window{331c409 u0 com.android.packageinstaller/com.android.packageinstaller.PackageInstallerActivity}

        紧接着用后面两个命令将系统应用的程序包导出来,反编译Apk也是用这种方式将三方Apk文件导出,详见APK反编译

    adb shell pm path com.android.packageinstaller
    adb pull system/priv-app/PackageInstaller/PackageInstaller.apk

        查看APK中的AndroidManifest文件,可以找到能够处理"application/vnd.android.package-archive"的intent-filter对应的ActivityInstallStart,没错这就是安装界面的入口。

    <activity android:name=".InstallStart">
        <intent-filter android:priority="1">
            <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="content" />
            <data android:mimeType="application/vnd.android.package-archive" />
        </intent-filter>
        ...
    </activity>

        已经从AOSP中拿出packageinstaller相关的源码,单独上传到GitHub/packageinstaller方便查看。不同的手机厂商会基于AOSP开源的代码,定制各自ROM的应用安装界面。大体流程如下:


         从InstallStartonCreate()方法开始,启动PackageInstallerActivity后关闭自身。

    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        Intent nextActivity = new Intent(intent);
        nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
        // 作为nextActivity的安装源认为此活动是源,因此显式设置原始UID和sourceInfo
        nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage);
        nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo);
        nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid);
        if (isSessionInstall) {
            nextActivity.setClass(this, PackageInstallerActivity.class);
        } else {
            Uri packageUri = intent.getData();
            if (packageUri != null && packageUri.getScheme().equals(
                    ContentResolver.SCHEME_CONTENT)) {
                // [重要说明]此条路径已经废弃,但仍然可以使用。 仅可以添加必要的功能。
                // 复制文件以防止在此过程中更改文件
                nextActivity.setClass(this, InstallStaging.class);
            } else if (packageUri != null && packageUri.getScheme().equals(
                    PackageInstallerActivity.SCHEME_PACKAGE)) {
                nextActivity.setClass(this, PackageInstallerActivity.class);
            } else {
                ...
                nextActivity = null;
            }
        }
        if (nextActivity != null) {
            startActivity(nextActivity);
        }
        finish();
    }

         PackageInstallerActivity就是安装Apk确认界面。举个例子,长这样:

        onCreate()时通过bindUi()方法初始化界面。

    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);
    ...
    mOk = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);

         点击安装按钮mOk开始安装,执行startInstall()方法,启动InstallInstalling正在安装界面并关闭自身。

    private void startInstall() {
        // Start subactivity to actually install the application
        Intent newIntent = new Intent();
        newIntent.setClass(this, InstallInstalling.class);
        ...
        startActivity(newIntent);
        finish();
    }


         进入InstallInstalling,在onCreate()时先创建安装会话Session

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                        PackageInstaller.SessionParams.MODE_FULL_INSTALL);
        mSessionId = getPackageManager().getPackageInstaller().createSession(params);
        ...
        }
    }

        随后在onResume()方法中,开启安装任务。

    @Override
    protected void onResume() {
        super.onResume();
        ...
        mInstallingTask = new InstallingAsyncTask();
        mInstallingTask.execute();
        ...
    }

        Apk安装任务被封装在内部类InstallingAsyncTask中,继承自AsyncTask

    /**
     * Send the package to the package installer and then register a event result observer that
     * will call {@link #launchFinishBasedOnResult(int, int, String)}
     */
    private final class InstallingAsyncTask extends AsyncTask<Void, Void,
            PackageInstaller.Session> {
        volatile boolean isDone;
        
        protected PackageInstaller.Session doInBackground(Void... params) {
            PackageInstaller.Session session;
            ...
            session = getPackageManager().getPackageInstaller().openSession(mSessionId);
            session.setStagingProgress(0);
            ...
                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) {
                            ...
                            session.fsync(out);
                        }
                    }
                }
            return session;
        }
        
        protected void onPostExecute(PackageInstaller.Session session) {
            ...
            session.commit(pendingIntent.getIntentSender());
            ...
        }
    }

        会后面的安装结果显示不同的界面,安装成功就显示com.android.packageinstaller.InstallSuccess,否则就实现com.android.packageinstaller.InstallFailed


1.2、安装会话(Session)

         最终通过Session将APK提交给PMS处理。


        从PackageInstaller.Sessioncommit(IntentSender statusReceiver)方法继续往下看:

    public void commit(@NonNull IntentSender statusReceiver) {
        mSession.commit(statusReceiver, false);
    }


        成员mSessionIPackageInstallerSession远程会话接口,PackageInstallerSession类实现了IPackageInstallerSession接口。先不管mSession是如何创建的,2.1节会讲到。

    @Override
    public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
        ...
        mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
    }

        向Handler发送消息处理MSG_COMMIT类型的消息,内部Handler构造时使用了自定义的Handler.Callback,只会执行CallbackhandleMessage(Message msg)方法,关于Handler.Callback详见Handler中Callback的作用

    private final Handler.Callback mHandlerCallback = new Handler.Callback() {
        
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_COMMIT:
                    handleCommit();
                    break;
                ...
            }
            return true;
        }
    };

        通过Handler中执行handleCommit()方法:

    private void handleCommit() {
        ...
        try {
            synchronized (mLock) {
                commitNonStagedLocked(childSessions);
            }
        }
        ...
    }

        内部再次调用commitNonStagedLocked(List<PackageInstallerSession> )方法,最终将任务交给mPm执行,也就是PMS

    @GuardedBy("mLock")
    private void commitNonStagedLocked(List<PackageInstallerSession> childSessions){
        ...
        if (isMultiPackage()) {
            ...
            mPm.installStage(activeChildSessions);
        } else {
            mPm.installStage(committingSession);
        }
    }

        

1.3、PMS执行安装

         以下面执行的installStage(ActiveInstallSession )方法为例,了解PMS处理Apk安装的流程。


         进入PMSinstallStage(ActiveInstallSession )方法开始执行:

    void installStage(ActiveInstallSession activeInstallSession) {
        final Message msg = mHandler.obtainMessage(INIT_COPY);
        final InstallParams params = new InstallParams(activeInstallSession);
        msg.obj = params;
        mHandler.sendMessage(msg);
    }

         PackageManagerService类内部定义了PackageHandler用来处理各种Apk的处理请求。

    class PackageHandler extends Handler {
    
        PackageHandler(Looper looper) {
            super(looper);
        }
        
        public void handleMessage(Message msg) {
            doHandleMessage(msg);
        }
        
        void doHandleMessage(Message msg) {
            switch (msg.what) {
                case INIT_COPY: {
                    HandlerParams params = (HandlerParams) msg.obj;
                    if (params != null) {
                        params.startCopy();
                    }
                    break;
                }
                ...
            }
        }
    }

         执行关键代码params.startCopy(),这里的params是从Message携带过来的,刚刚在installStage(ActiveInstallSession )方法中创建的InstallParams实例。

    final void startCopy() {
        handleStartCopy();
        handleReturnCode();
    }
    
    abstract void handleStartCopy();
    abstract void handleReturnCode();

        InstallParams继承自内部抽象类HandlerParams,实现了handleStartCopy()handleReturnCode()两个抽象方法,完成Apk的拷贝,解析等操作。代码比较复杂,流程图进行了简化。



2、PackageInstaller安装Apk流程

        第二种方式就是代码的方式安装,这种方式现在仅限于系统应用,普通应用不能再进行任何后台静默安装操作。比如手机应用商店,下载应用后台静默安装,大都使用这种方式。

    //Apk文件
    File apkFile = new File(apkPath);
    
    //获取PackageManager和PackageInstaller
    PackageManager pm = getPackageManager();
    PackageInstaller pi = pm.getPackageInstaller();
    
    //创建SessionParams
    PackageInstaller.SessionParams sp = new PackageInstaller
                            .SessionParams(MODE_FULL_INSTALL);
    try {
        //开启安装会话
        int sessionID = pi.createSession(sp);
        PackageInstaller.Session se = pi.openSession(sessionID);
        //文件输入流
        InputStream in;
        in = new FileInputStream("apk_path");
        //输出流
        OutputStream out;
        out = se.openWrite("apk_install_session", 0, apkFile.length());
        
        int total = 0;
        byte[] buffer = new byte[65536];
        int len;
        
        while ((len = in.read(buffer)) != -1) {
            total += len;
            out.write(buffer, 0, len);
        }
        
        se.fsync(out);
        in.close();
        out.close();
        
        //创建PendingIntent
        PendingIntent broadCastTest = PendingIntent.getBroadcast(
                this,
                sessionID,
                new Intent("ACTION_INSTALL_COMPLETE"),
                PendingIntent.FLAG_UPDATE_CURRENT);
                
        //提交之前必须关闭所有流
        se.commit(broadCastTest.getIntentSender());
        se.close();
    } catch (Exception ignore) {
    }

        整体流程图简化如下:



2.1、开启会话

        通过PackageManager获取PackageInstaller实例,实际实现方法在ApplicationPackageManager类中。

    @Override
    public PackageInstaller getPackageInstaller() {
        if (mInstaller == null) {
                mInstaller = new PackageInstaller(mPM.getPackageInstaller(),
                        mContext.getPackageName(), getUserId());
        return mInstaller;
    }

        其次,构造“会话”参数PackageInstaller中的SessionParams实例,只需要一个mode参数,有两种,一般使用MODE_FULL_INSTALL

    /**
     * Construct parameters for a new package install session.
     *
     * @param mode one of {@link #MODE_FULL_INSTALL} or
     *            {@link #MODE_INHERIT_EXISTING} describing how the session
     *            should interact with an existing app.
     */
    public SessionParams(int mode) {
        this.mode = mode;
    }

        通过PackageInstaller开启会话,并获取会话id。

    public int createSession(@NonNull SessionParams params) throws IOException {
        ...
        return mInstaller.createSession(params, installerPackage, mUserId);
    }

        用sessionID打开“会话”

    public @NonNull Session openSession(int sessionId) throws IOException {
        ...
        return new Session(mInstaller.openSession(sessionId));
    }

        

2.2、写入Apk并提交

        文件流的写入细节就不看了,实现非常复杂。跳到写完文件流提交的地方commit()方法:

        public void commit(@NonNull IntentSender statusReceiver) {
            ...
            mSession.commit(statusReceiver, false);
        }

        执行IPackageInstallerSessioncommit()方法,IPackageInstallerSession接口的实现类是PackageInstallerSession

    public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
        ...
        mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
    }

        通过Handler执行handleCommit()方法,又调用commitNonStagedLocked(List<PackageInstallerSession> )方法

    @GuardedBy("mLock")
    private void commitNonStagedLocked(List<PackageInstallerSession> childSessions)
            throws PackageManagerException {
        ...
        if (isMultiPackage()) {
            mPm.installStage(activeChildSessions);
        } else {
            mPm.installStage(committingSession);
        }
    }

        最后又到了PMSinstallStage(...)中执行,详见1.3节



3、adb命令安装Apk

        最后要探索的这种安装方式,开发者接触的较多。使用adb命令安装Apk文件,关于adb命令的用法详见adb常用命令一文

    adb install file.apk

        这种方式安装Apk,分为两个阶段:(1)adb命令传递处理阶段,(2)PMS执行命令阶段。


3.1、adb命令传递阶段

       这部分adb相关的源码在/system/core/adb目录下,主体流程如下:


       从adb的主函数入口开始探索:main.cpp

int main(int argc, char* argv[], char* envp[]) {
    ...
    return adb_commandline(argc - 1, const_cast<const char**>(argv + 1));
}

       执行commandline.cpp中的adb_commandline(...)方法,在该方法中,解析输入的install参数,并执行adb_install.cpp中的install_app(argc, argv)方法:

int install_app(int argc, const char** argv) {
    ...
    switch (installMode) {
        case INSTALL_PUSH:
            return install_app_legacy(passthrough_argv.size(), passthrough_argv.data(),
                                      use_fastdeploy, use_localagent);
        case INSTALL_STREAM:
            return install_app_streamed(passthrough_argv.size(), passthrough_argv.data(),
                                        use_fastdeploy, use_localagent);
        case INSTALL_DEFAULT:
        default:
            return 1;
    }
}

       以其中执行的install_app_legacy()方法为例,先将apk push到/data/local/tmp目录。

static int install_app_legacy(int argc, const char** argv, bool use_fastdeploy,
                              bool use_localagent) {
    ...
    std::vector<const char*> apk_file = {argv[last_apk]};
    std::string apk_dest =
            "/data/local/tmp/" + android::base::Basename(argv[last_apk]);
    ...
    if (!do_sync_push(apk_file, apk_dest.c_str(), false)) goto cleanup_apk;
    result = pm_command(argc, argv);
    ...
}

       再执行pm_command(argc, argv)方法,向命令中拼接一个pm命令参数:

static int pm_command(int argc, const char** argv) {
    std::string cmd = "pm";
    while (argc-- > 0) {
        cmd += " " + escape_arg(*argv++);
    }
    return send_shell_command(cmd);
}

        最后调用commandline.cpp中的send_shell_command(cmd)方法发送命令给adbd,后面的逻辑太复杂,简单概括步骤如下:

        命令通过socket发送给adbd

        adbd调用pm处理目录

        pm转交给cmd处理

        cmd解析package命令cmdMain()方法中

        通过Binder调用package:调用IBinder::shellCommand()

        执行PMS.onShellCommand()


3.2、PMS执行命令

       命令从adb紧接着进入PMS执行,执行主流程如下:


       PackageManagerService中的onShellCommand(...)方法执行命令:

    @Override
    public void onShellCommand(FileDescriptor in, FileDescriptor out,
            FileDescriptor err, String[] args, ShellCallback callback,
            ResultReceiver resultReceiver) {
        (new PackageManagerShellCommand(this, mPermissionManagerService)).exec(
                this, in, out, err, args, callback, resultReceiver);
    }

       构造PackageManagerShellCommand实例并执行,PackageManagerShellCommand继承自ShellCommand

    public int exec(Binder target, ...) {
            ...
        final int result = super.exec(target, in, out, err, args);
        return result;
    }

       ShellCommand又继承自BasicShellCommandHandler,执行它的exec(...)方法:

    public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
            String[] args) {
        String cmd;
        ...
            res = onCommand(mCmd);
        ...
        return res;
    }

       PackageManagerShellCommand实现了BasicShellCommandHandler中的onCommand(String cmd)抽象方法。

    @Override
    public int onCommand(String cmd) {
        if (cmd == null) {
            return handleDefaultCommands(cmd);
        }
        try {
            switch (cmd) {
                case "path":
                    return runPath();
                ...
                case "install":
                    return runInstall();
                case "install-streaming":
                    return runStreamingInstall();
                ...
                }
        } 
        return -1;
    }

       解析命令行中的install参数,执行对应的runInstall()方法

    private int runInstall() throws RemoteException {
        return doRunInstall(makeInstallParams());
    }

       调用doRunInstall(InstallParams params)执行最终的安装,分为三步:添加文件、写入文件、提交。

    private int doRunInstall(final InstallParams params) throws RemoteException {
        ...
            doAddFiles(sessionId, args, params.sessionParams.sizeBytes, isApex)
            ...
            doWriteSplits(sessionId, args, params.sessionParams.sizeBytes, isApex)
            ...
            doCommitSession(sessionId, false /*logSuccess*/)
        ...
    }

       细节和前面2.2节提到的Session会话安装一样。



流程附件:

        启动安装界面流程

        APK安装会话:PackageInstallerSession

        PMS安装APK

        PackageInstaller代码安装流程

        adb命令安装流程

        执行PMS.onShellCommand()

        apk_install_sequence

        

不忘初心的阿甘
最新回复 (1)
  • Quibbler 2021-5-27
    2


    Android的应用安装涉及到的几个目录


    /data/app:存放用户安装apk的目录,安装时,把apk拷贝到这里

    /data/app/~~-zcyVWv4fXg8poNL8-7Bsg==

    /data/app/~~-zcyVWv4fXg8poNL8-7Bsg==/package-iIktCcF4ZfEkdjrHwRoPYg==/lib

    /data/app/~~-zcyVWv4fXg8poNL8-7Bsg==/package-iIktCcF4ZfEkdjrHwRoPYg==/lib/arm

    /data/app/~~-zcyVWv4fXg8poNL8-7Bsg==/package-iIktCcF4ZfEkdjrHwRoPYg==/oat

    /data/app/~~-zcyVWv4fXg8poNL8-7Bsg==/package-iIktCcF4ZfEkdjrHwRoPYg==/oat/arm


    system/app:系统自带app,访问需要有root权限

    /system/app

    /system/app/packagename/lib

    /system/app/packagename/oat


    vender/app:设备厂商提供的app

    /vendor/app/

    /vendor/app/


    /data/data:应用安装完成后,在/data/data目录下自动生成和APK包名相同的文件夹,用户存放应用程序的数据

    /data/data/packagename

    /data/data/android.a.b/cache

    /data/data/android.a.b/code_cache

    /data/data/android.a.b/database

    /data/data/android.a.b/shared_prefs

    /data/data/android.a.b/lib


    /data/dalivk-cache:存放apk的dex、odex、vdex、art相关文件,便于应用启动时直接执行

    odex、vdex、art是优化的可执行二进制文件,加快启动速度

    /data/dalvik-cache/arm

    /data/dalvik-cache/arm64


    • 安卓笔记本
      3
        登录 注册 QQ
返回
仅供学习交流,切勿用于商业用途。如有错误欢迎指出:fluent0418@gmail.com