1. 程式人生 > >androiod 學習--PMS應用安裝過程

androiod 學習--PMS應用安裝過程

         前面,我們已經分析了,PMS我們已經分析了,PackageManagerService整個的啟動過程,對android應用管理大概有一個大概的瞭解,實際開發中可以有一個更深入的理解,前面講的只是一個管理大概的流程,以及資料如何儲存,儲存大部分都是用hashmap來儲存的,這樣儲存也是為了查詢方便,不過唯一不好的就是浪費空間。所以android的速度提升了,不過rom執行大小也增大了。

       費話少說了,我們還是繼續分析應用的安裝過程。一般應用安裝,我們都是獲取PackageManager 然後呼叫installPackage方法,不過PackageManager真正的實現類是ApplicationPackageManager,我們看一下具體的實現:

 public void installPackage(Uri packageURI, IPackageInstallObserver observer, int flags,
                               String installerPackageName) {
        try {
            mPM.installPackage(packageURI, observer, flags, installerPackageName);
        } catch (RemoteException e) {
            // Should never happen!
        }
    }
  這裡也只是呼叫PMS去實現安裝,所以要分析安裝的過程,我們也還是要分析PMS裡面的installPackage方法。不過經過各種跳轉,最終還是會走到installPackageWithVerificationAndEncryption方法裡面去,主要是生成一個InstallParams物件傳送一個copy的訊息。
final Message msg = mHandler.obtainMessage(INIT_COPY);
        msg.obj = new InstallParams(packageURI, observer, filteredFlags, installerPackageName,
                verificationParams, encryptionParams, user);
        mHandler.sendMessage(msg);
 InstallParams是繼承HandlerParams物件,主要是實現apk復現等其他功能。看Handler關於INIT_COPY的處理:
HandlerParams params = (HandlerParams) msg.obj;
                    int idx = mPendingInstalls.size();
                    if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params);
                    // If a bind was already initiated we dont really
                    // need to do anything. The pending install
                    // will be processed later on.
                    if (!mBound) {
                        // If this is the only one pending we might
                        // have to bind to the service again.
                        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);
                        }
                    } else {
                        mPendingInstalls.add(idx, params);
                        // Already bound to the service. Just make
                        // sure we trigger off processing the first request.
                        if (idx == 0) {
                            mHandler.sendEmptyMessage(MCS_BOUND);
                        }
                    }
                    break;
                }
 1.如果沒有已經繫結過DefaultContainerService 直接新增到mPendingInstalls佇列裡面去 再發一個 MCS_BOUND 訊息。

 2.沒有繫結service,則要進行繫結再新增到mPendingInstalls 佇列裡面。

 再看一下:

  HandlerParams params = mPendingInstalls.get(0);
                        if (params != null) {
                            if (params.startCopy()) {
                                // We are done...  look for more work or to
                                // go idle.
                                if (DEBUG_SD_INSTALL) Log.i(TAG,
                                        "Checking for more work or unbind...");
                                // Delete pending install
                                if (mPendingInstalls.size() > 0) {
                                    mPendingInstalls.remove(0);
                                }
                                if (mPendingInstalls.size() == 0) {
                                    if (mBound) {
                                        if (DEBUG_SD_INSTALL) Log.i(TAG,
                                                "Posting delayed MCS_UNBIND");
                                        removeMessages(MCS_UNBIND);
                                        Message ubmsg = obtainMessage(MCS_UNBIND);
                                        // Unbind after a little delay, to avoid
                                        // continual thrashing.
                                        sendMessageDelayed(ubmsg, 10000);
                                    }
                                } else {
                                    // There are more pending requests in queue.
                                    // Just post MCS_BOUND message to trigger processing
                                    // of next pending install.
                                    if (DEBUG_SD_INSTALL) Log.i(TAG,
                                            "Posting MCS_BOUND for next woek");
                                    mHandler.sendEmptyMessage(MCS_BOUND);
                                }
取出mPendingInstalls裡面的HandlerParams 同時呼叫params.startCopy()進行復制,主要是有一個重試的機制在裡面。總共有4次失敗的機會。真正執行復現的方法是handleStartCopy 先執行一個許可權等相關的檢測,最後呼叫DeviceStorageMonitorService 方法進行暫時檔案複製:
  if (mTempPackage != null) {
                            ParcelFileDescriptor out;
                            try {
                                out = ParcelFileDescriptor.open(mTempPackage,
                                        ParcelFileDescriptor.MODE_READ_WRITE);
                            } catch (FileNotFoundException e) {
                                out = null;
                                Slog.e(TAG, "Failed to create temporary file for : " + mPackageURI);
                            }

                            // Make a temporary file for decryption.
                            ret = mContainerService
                                    .copyResource(mPackageURI, encryptionParams, out);
                            IoUtils.closeQuietly(out);

                            packageFile = mTempPackage;

                            FileUtils.setPermissions(packageFile.getAbsolutePath(),
                                    FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP
                                            | FileUtils.S_IROTH,
                                    -1, -1);
                        } else {
                            packageFile = null;
                        }
                    } else {
                        packageFile = new File(mPackageURI.getPath());
                    }
 複製完成之後還是設定許可權。不要忘記後面還是有一個handleReturnCode方法。再看往下看:
  if (packageFile != null) {
                        // Remote call to find out default install location
                        final String packageFilePath = packageFile.getAbsolutePath();
                        pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath, flags,
                                lowThreshold);

                        /*
                         * If we have too little free space, try to free cache
                         * before giving up.
                         */
                        if (pkgLite.recommendedInstallLocation
                                == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
                            final long size = mContainerService.calculateInstalledSize(
                                    packageFilePath, isForwardLocked());
                            if (mInstaller.freeCache(size + lowThreshold) >= 0) {
                                pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath,
                                        flags, lowThreshold);
                            }
                            /*
                             * The cache free must have deleted the file we
                             * downloaded to install.
                             *
                             * TODO: fix the "freeCache" call to not delete
                             *       the file we care about.
                             */
                            if (pkgLite.recommendedInstallLocation
                                    == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
                                pkgLite.recommendedInstallLocation
                                    = PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
                            }
                        }
                    }
                } finally {
                    mContext.revokeUriPermission(mPackageURI,
                            Intent.FLAG_GRANT_READ_URI_PERMISSION);
                }
            }
暫時檔案複製成功之後會呼叫getMinimalPackageInfo 掃描獲取包的資訊:
 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();

            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;
        }
parsePackageLite解析包的資訊,這個方法很重要,不過前面已經講過了,這裡就不再分析,解析包之後就可以拿到包的主要資訊,還有另外一個方法比較重要:

recommendAppInstallLocation 讀取設定裡面的資訊和apk 本身設定的安裝路徑,根據剩下空間等因素,確定最終路徑.

接下來建立InstallArgs ,createInstallArgs方法會根據不同的安裝路徑選擇生成物件.再呼叫copyApk方法複製 apk包,lib庫等。

最後,再看一下handleReturnCode方法:主要是做掃尾工作,刪除臨時檔案,不過最重要的還是processPendingInstall方法:

 第一.掃描apk,解析然後把資料充分(把應用的相關資訊新增到 Settings裡面);

 第二.傳送POST_INSTALL刪除安裝過程中生成的臨時檔案。