APK安装流程源码追踪

本文详细探讨了Android系统中APK安装的过程,从点击apk文件开始,包括系统如何识别apk并启动安装程序,以及安装过程涉及的关键步骤,如Intent的设置、PackageInstallerActivity的启动、安装选项选择、InstallAppProgress的运行,直至最终通过MediaContainerService完成apk的复制到相应位置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在手机中点击一个apk文件,系统就会自动安装,这其中涉及到两个步骤:

1.系统识别apk文件,并启动负责安装的应用

2.安装应用

1.

首先看下负责安装apk 的activity的manifest中的声明:

        <activity android:name=".PackageInstallerActivity"
                android:configChanges="orientation|keyboardHidden|screenSize"
                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" />
                <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>
        </activity>

再到fileexplorer中找到响应点击事件的地方:

listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
            		rundalog = true;
                if (mSelectionManager.inSelectionMode()) {
                    mSelectionManager.toggle(mFileAdapter.getItem(arg2).toString());
                } else if (startMode == CATEGORY_SEL && category == CategoryFragment.FAVORITE) {
                    String path = mPathItems.get(arg2);
                    File f = new File(path);
                    listview.setItemChecked(arg2, false);
                    if (f.exists()) {
                        if (f.isDirectory())
                            mCallback.onDirectorySelectedOk(path, -1, 0, -1);
                        else
                            openFile(f);
                    } else
                        Toast.makeText(context, R.string.toast_file_not_exists,
                                Toast.LENGTH_LONG).show();
                } else if (getActivity().getIntent().getAction() != null &&
                        getActivity().getIntent().getAction().equals(Intent.ACTION_GET_CONTENT)
                        && (startMode == DEFAULT || startMode == CATEGORY_SEL)) {
                    File f = new File(mPathItems.get(arg2));
                    listview.setItemChecked(arg2, false);
                    if (f.isDirectory()) {
                        loadDir(mPathItems.get(arg2));
                    } else {
                        mCallback.onFileSelectedOk(MODE_INVALID, mPathItems.get(arg2));
                    }
                } else {
                    listview.setItemChecked(arg2, false);
                    File f = new File(mPathItems.get(arg2));
                    if (f.exists()) {
                        if (f.isDirectory()) {
				  issearch = false;
                            loadDir(mPathItems.get(arg2));
                        } else {
                            openFile(f);
                        }
                    } else {
                                       Toast.makeText(context, R.string.toast_file_not_exists,
                                Toast.LENGTH_LONG).show();
                        loadDir(mCurrentPath);
                    }
                }		
             }
        });

这里会先判断是文件还是文件夹,如果是文件就openFile(f),文件夹就  loadDir(mPathItems.get(arg2)),

这里我们看打开文件,主要代码:

    private void openFile(File f) {
        // We will first guess the MIME type of this file
        final Uri fileUri = Uri.fromFile(f);
        final Intent intent = new Intent();
        intent.setAction(android.content.Intent.ACTION_VIEW);
        // Send file name to system application for sometimes it will be used.
        intent.putExtra(Intent.EXTRA_TITLE, f.getName());
        Uri contentUri = null;

        String type = getMIMEType(f);
             if (!"application/octet-stream".equals(type)) {
            if (contentUri != null) {
                intent.setDataAndType(contentUri, type);
            } else {
                intent.setDataAndType(fileUri, type);
            }
            // If no activity can handle the intent then
            // give user a chooser dialog.
            try {
                startActivitySafely(intent);

            } catch (ActivityNotFoundException e) {
                showChooserDialog(fileUri, intent);
            }
        } else {
            showChooserDialog(fileUri, intent);
        }
    }

这里一个非常重要的方法出现了,即文件的类型的鉴别,即String type = getMIMEType(f);

看源码:

  private String getMIMEType(File f) {
        String type = "";
        String fileName = f.getName();
        String ext = fileName.substring(fileName.lastIndexOf(".")
                + 1, fileName.length()).toLowerCase();

        if (ext.equals("jpg") || ext.equals("jpeg") || ext.equals("gif")
                || ext.equals("png") || ext.equals("bmp") || ext.equals("wbmp")) {
            type = "image/*";
        } else if (ext.equals("mp3") || ext.equals("amr")
                || ext.equals("aac") || ext.equals("m4a") || ext.equals("mid")
                || ext.equals("xmf") || ext.equals("ogg") || ext.equals("wav")
                || ext.equals("qcp") || ext.equals("awb") || ext.equals("flac") 
                ||ext.equals("3gpp")) {//wangcunxi add 20140102
            type = "audio/*";
        } else if (ext.equals("3gp") || ext.equals("avi") || ext.equals("mp4")
                || ext.equals("3g2") || ext.equals("divx")
                || ext.equals("mkv") || ext.equals("webm") || ext.equals("ts")
                || ext.equals("mov")) {
            type = "video/*";
        } else if (ext.equals("apk")) {
            type = "application/vnd.android.package-archive";
        } else if (ext.equals("vcf")) {
         ........        } else {
            type = "application/octet-stream";
        }

        return type;
    }


当文件类型为apk时, type="application/vnd.android.package-archive";

至此流程就基本清晰了,

点击文件后会new一个intent,set intent的action为:android.content.Intent.ACTION_VIEW

再判断文件的类型,如果是apk就把intent的type设置为:application/vnd.android.package-archive

用此intent去隐性启动 PackageInstallerActivity

2. 先看一个类图


启动PackageInstallerActivity后,用户会点选是安装到手机还是安装到SD卡,

点选后会启动InstallAppProgress:

 Intent newIntent = new Intent();
                newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
                        mPkgInfo.applicationInfo);
                newIntent.setData(mPackageURI);
                newIntent.setClass(this, InstallAppProgress.class);
                newIntent.putExtra(InstallAppProgress.EXTRA_MANIFEST_DIGEST, mPkgDigest);
                String installerPackageName = getIntent().getStringExtra(
                        Intent.EXTRA_INSTALLER_PACKAGE_NAME);


InstallAppProgress会调用ApplicationPackageManager的 installPackageWithVerificationAndEncryption方法

            pm.installPackageWithVerificationAndEncryption(mPackageURI, observer, installFlags,
                    installerPackageName, verificationParams, null);


其中pm=getPackageManager(),即

    public PackageManager getPackageManager() {
        if (mPackageManager != null) {
            return mPackageManager;
        }

        IPackageManager pm = ActivityThread.getPackageManager();
        if (pm != null) {
            // Doesn't matter if we make more than one instance.
            return (mPackageManager = new ApplicationPackageManager(this, pm));
        }

        return null;
    }


ApplicationPackageManager则直接调用PackageManagerService 中的 installPackageWithVerification

  mPM.installPackageWithVerification(packageURI, observer, flags, installerPackageName,
                    verificationURI, manifestDigest, encryptionParams);


mPM是构造函数中直接赋值为由外部传入的pm = ActivityThread.getPackageManager()即

    public static IPackageManager getPackageManager() {
        if (sPackageManager != null) {
            //Slog.v("PackageManager", "returning cur default = " + sPackageManager);
            return sPackageManager;
        }
        IBinder b = ServiceManager.getService("package");
        //Slog.v("PackageManager", "default service binder = " + b);
        sPackageManager = IPackageManager.Stub.asInterface(b);
        //Slog.v("PackageManager", "default service = " + sPackageManager);
        return sPackageManager;
    }

在PackageManagerService中首先new一个InstallParams对象保存apk包的信息,

再给一个PackageHandler 类型的Handler发送一个INIT_COPY消息,连同InstallParams对象一起发送

        final Message msg = mHandler.obtainMessage(INIT_COPY);
        msg.obj = new InstallParams(packageURI, observer, filteredFlags, installerPackageName,
                verificationParams, encryptionParams, user);
        mHandler.sendMessage(msg);


在handle收到INIT_COPY消息后,先链接MediaContainerService,MediaContainerService是在DefaultContainerService中实现其接口方法,

然后把InstallParams对象保存到一个arraylist列表中

                        if (!connectToService()) {
                            Slog.e(TAG, "Failed to bind to media container service");
                            params.serviceError();
                            return;
                        } else {
                            // Once we bind to the service, the first
                            // pending request will be processed.
                            mPendingInstalls.add(idx, params);
                        }


在连接成功后,会给handle发送一个MCS_BOUND消息

 public void onServiceConnected(ComponentName name, IBinder service) {
            if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceConnected");
            IMediaContainerService imcs =
                IMediaContainerService.Stub.asInterface(service);
            mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs));
        }


在handle收到MCS_BOUND后,会调用到InstallParams中的handleStartCopy()方法,

在handleStartCopy()中会先解析调用MediaContainerService中的getMinimalPackageInfo方法,

通过解析apk中manifest获取报名、安装地址、大小等信息:

        public PackageInfoLite getMinimalPackageInfo(final String packagePath, int flags,
                long threshold) {
            PackageInfoLite ret = new PackageInfoLite();

            if (packagePath == null) {
                Slog.i(TAG, "Invalid package file " + packagePath);
                ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
                return ret;
            }

            DisplayMetrics metrics = new DisplayMetrics();
            metrics.setToDefaults();
           //解析manifest获取基本信息
            PackageParser.PackageLite pkg = PackageParser.parsePackageLite(packagePath, 0);
            if (pkg == null) {
                Slog.w(TAG, "Failed to parse package");

                final File apkFile = new File(packagePath);
                if (!apkFile.exists()) {
                    ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
                } else {
                    ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
                }

                return ret;
            }

            ret.packageName = pkg.packageName;
            ret.versionCode = pkg.versionCode;
            ret.installLocation = pkg.installLocation;
            ret.verifiers = pkg.verifiers;

            ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation,
                    packagePath, flags, threshold);

            return ret;
        }

然后调用InstallArgs中的copyApk()方法,在创建InstallArgs时,会根据安装位置不一样,创建不同子类的对象:

    private InstallArgs createInstallArgs(InstallParams params) {
        if (installOnSd(params.flags) || params.isForwardLocked()) {
            return new AsecInstallArgs(params);//安装在SD卡中
        } else {
            return new FileInstallArgs(params);//安装在手机中
        }
    }
 

 安装到SD卡主要是调用MediaContainerService中的copyResourceToContainer()方法

 安装到手机里调用MediaContainerService的copyResource()方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值