Android系統-包管理機制(二)PackageInstaller安裝APK

APK的安裝有不少方式,應用商城下載、文件瀏覽器安裝、adb命令安裝,咱們以文件瀏覽器爲例。java

將一個APK文件放到SD卡目錄下,文件瀏覽器顯示apk文件。android

當咱們點擊APK文件時,文件瀏覽器根據文件的後綴(.apk)解析,執行startActivity,調起packageinstaller.PackageInstallerActivity。瀏覽器

ActivityManager: START u0 {act=android.intent.action.VIEW dat=file:///storage/emulated/0/com.dewmobile.kuaiya.apk typ=application/vnd.android.package-archive flg=0x88000 cmp=com.android.packageinstaller/.PackageInstallerActivity} from uid 10032 on display 0
複製代碼

PackageInstallerActivity

PackageInstallerActivity這個類的主要做用是顯示安裝彈窗,對APK進行解析,判斷是否容許未知來源安裝,判斷應用權限,等待用戶安裝。markdown

packages/apps/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.javasession

public void onClick(View v) {
        if (v == mOk) {
            if (mOkCanInstall || mScrollView == null) {
                if (mSessionId != -1) {
                    mInstaller.setPermissionsResult(mSessionId, true);
                    clearCachedApkIfNeededAndFinish();
                } else {
                    startInstall(); //開始安裝
                }
            } else {
                mScrollView.pageScroll(View.FOCUS_DOWN);
            }
        } else if (v == mCancel) {
            // Cancel and finish
            setResult(RESULT_CANCELED);
            if (mSessionId != -1) {
                mInstaller.setPermissionsResult(mSessionId, false);
            }
            clearCachedApkIfNeededAndFinish();
        }
    }
複製代碼

而後用戶點擊安裝按鈕,就會調用startInstall()函數app

private void startInstall() {
        // Start subactivity to actually install the application
        Intent newIntent = new Intent();
        newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
                mPkgInfo.applicationInfo);
        newIntent.setData(mPackageURI);
        newIntent.setClass(this, InstallAppProgress.class);//
        String installerPackageName = getIntent().getStringExtra(
                Intent.EXTRA_INSTALLER_PACKAGE_NAME);
     
        ......
          
        if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
        startActivity(newIntent);
        finish();
    }

複製代碼

startInstall函數用於跳轉到InstallAppProgress這個Activity,關閉PackageInstallerActivity。框架

InstallAppProgress

InstallAppProgress主要用於展現安裝界面、向PMS發送包信息, 處理回調。ide

packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallAppProgress.java函數

@Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        Intent intent = getIntent();
        mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
        mPackageURI = intent.getData();

        final String scheme = mPackageURI.getScheme();
        if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) {
            throw new IllegalArgumentException("unexpected scheme " + scheme);
        }

        // 開啓安裝線程
        mInstallThread = new HandlerThread("InstallThread");
        mInstallThread.start();
        mInstallHandler = new Handler(mInstallThread.getLooper());

        //註冊安裝監聽
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(BROADCAST_ACTION);
        registerReceiver(
                mBroadcastReceiver, intentFilter, BROADCAST_SENDER_PERMISSION, null /*scheduler*/);

        initView();
    }

複製代碼

InstallAppProgress中啓動了一個HandlerThread,開啓安裝線程,註冊了一個廣播,這個廣播用來監聽系統返回的安裝結果。oop

主要代碼在在initView()函數中

final PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                    PackageInstaller.SessionParams.MODE_FULL_INSTALL);
            params.referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);
            params.originatingUri = getIntent().getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
            params.originatingUid = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
                    UID_UNKNOWN);

            File file = new File(mPackageURI.getPath());
            try {
                //解析安裝包,設置安裝位置,從AndroidManifest中獲取
                PackageLite pkg = PackageParser.parsePackageLite(file, 0);
                params.setAppPackageName(pkg.packageName);
                params.setInstallLocation(pkg.installLocation);
                params.setSize(
                    PackageHelper.calculateInstalledSize(pkg, false, params.abiOverride));
            } catch (PackageParser.PackageParserException e) {
                Log.e(TAG, "Cannot parse package " + file + ". Assuming defaults.");
                Log.e(TAG, "Cannot calculate installed size " + file + ". Try only apk size.");
                params.setSize(file.length());
            } catch (IOException e) {
                Log.e(TAG, "Cannot calculate installed size " + file + ". Try only apk size.");
                params.setSize(file.length());
            }

            mInstallHandler.post(new Runnable() {
                @Override
                public void run() {
                    //執行安裝
                    doPackageStage(pm, params);
                }
            });
複製代碼
private void doPackageStage(PackageManager pm, PackageInstaller.SessionParams params) {
        //初始化安裝器
        final PackageInstaller packageInstaller = pm.getPackageInstaller();
        PackageInstaller.Session session = null;
        try {
            final String packageLocation = mPackageURI.getPath();
            final File file = new File(packageLocation);
            //獲取sessionId
            final int sessionId = packageInstaller.createSession(params);
            final byte[] buffer = new byte[65536];
     				//獲取session
            session = packageInstaller.openSession(sessionId);

            final InputStream in = new FileInputStream(file);
            final long sizeBytes = file.length();
            // 根據這個session回話, 獲取一個OutPutstream, 而後將文件寫入到這個OutPutStream中
            final OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes);
            try {
                int c;
                while ((c = in.read(buffer)) != -1) {
                    out.write(buffer, 0, c);
                    if (sizeBytes > 0) {
                        final float fraction = ((float) c / (float) sizeBytes);
                        //安裝中
                        session.addProgress(fraction);
                    }
                }
                session.fsync(out);
            } finally {
                IoUtils.closeQuietly(in);
                IoUtils.closeQuietly(out);
            }

            // Create a PendingIntent and use it to generate the IntentSender
            Intent broadcastIntent = new Intent(BROADCAST_ACTION);
            PendingIntent pendingIntent = PendingIntent.getBroadcast(
                    InstallAppProgress.this /*context*/,
                    sessionId,
                    broadcastIntent,
                    PendingIntent.FLAG_UPDATE_CURRENT);
            //最後調用session的commit方法進行提交
            session.commit(pendingIntent.getIntentSender());
        } catch (IOException e) {
            onPackageInstalled(PackageInstaller.STATUS_FAILURE);
        } finally {
            IoUtils.closeQuietly(session);
        }
    }
複製代碼

最終廣播接受安裝結果

private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final int statusCode = intent.getIntExtra(
                    PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
            if (statusCode == PackageInstaller.STATUS_PENDING_USER_ACTION) {
                context.startActivity((Intent)intent.getParcelableExtra(Intent.EXTRA_INTENT));
            } else {
                onPackageInstalled(statusCode);
            }
        }
    };
複製代碼

應用安裝的前期工做,用戶能夠看到的部分基本就這些,接下來的工做就都由Java框架層處理。

Java框架層

上面最終調用PackageInstallerSession的commit方法,將安裝APK的信息發給框架層。

frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java

@Override
    public void commit(IntentSender statusReceiver) {
        ......
        mActiveCount.incrementAndGet();
        final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext,
                statusReceiver, sessionId, mIsInstallerDeviceOwner, userId);
        mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
    }
複製代碼

commit函數中向Handler發送一個類型爲MSG_COMMIT的消息,adapter.getBinder()會獲得IPackageInstallObserver2觀察者,處理消息邏輯以下:

frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java

private final Handler.Callback mHandlerCallback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            // Cache package manager data without the lock held
            final PackageInfo pkgInfo = mPm.getPackageInfo(
                    params.appPackageName, PackageManager.GET_SIGNATURES /*flags*/, userId);
            final ApplicationInfo appInfo = mPm.getApplicationInfo(
                    params.appPackageName, 0, userId);

            synchronized (mLock) {
                if (msg.obj != null) {
                    mRemoteObserver = (IPackageInstallObserver2) msg.obj;
                }

                try {
                    commitLocked(pkgInfo, appInfo);
                } catch (PackageManagerException e) {
                    final String completeMsg = ExceptionUtils.getCompleteMessage(e);
                    Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
                    destroyInternal();
                    dispatchSessionFinished(e.error, completeMsg, null);
                }

                return true;
            }
        }
    };
複製代碼
private void commitLocked(PackageInfo pkgInfo, ApplicationInfo appInfo) throws PackageManagerException {
     ...
      mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params,
              installerPackageName, installerUid, user, mCertificates);
  }
複製代碼

在commitLocked方法中,調用PMS的installStage方法,這樣邏輯就進入PMS中了。

總結

PackageInstaller安裝APK的過程,簡單來講就兩步:

  1. 將APK的信息經過IO流的形式寫入到PackageInstaller.Session中。
  2. 調用PackageInstaller.Session的commit方法,將APK的信息交由PMS處理。
相關文章
相關標籤/搜索