APK安裝流程源碼追蹤

在手機中點擊一個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()方法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章