android PKMS-2 InstallInstalling文件詳解

1 概述

InstallInstalling是普通安裝流程中括堤,系統(tǒng)啟動的第二個Activity
InstallStart封裝的數(shù)據(jù)有
包信息,應用信息 用戶id抱既,包括啟動進程傳來的原始數(shù)據(jù)信息

2 InstallInstalling都做了什么

作為系統(tǒng)應用中第二個Activity剪返,他同樣要遵循生命周期來做一些事情

2.1 onCreate
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //通過INTENT_ATTR_APPLICATION_INFO,獲取應用進程(發(fā)起安裝進程)相關信息
        ApplicationInfo appInfo = getIntent()
                .getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
        //獲取應用進程URI數(shù)據(jù)
        mPackageURI = getIntent().getData();

        if ("package".equals(mPackageURI.getScheme())) {
            /**
             * case 當前URI的Scheme為package
             * 
             * 過程 跨進程根據(jù)應用進程提供的包信息來安裝倚搬,跳轉安裝成功activity
             */
            try {
                getPackageManager().installExistingPackage(appInfo.packageName);
                launchSuccess();
            } catch (PackageManager.NameNotFoundException e) {
                launchFailure(PackageInstaller.STATUS_FAILURE,
                        PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
            }
        } else {
            /**
             * case 當前URI的Scheme不為package
             * 過程
             * 1 創(chuàng)建file對象指定安裝包路徑
             * 2 封裝應用程序相關信息
             * 3 設置提示框相關UI和按鈕行為 (用戶可以點擊取消按鈕取消當次安裝)
             * 4 檢查當前Activity是否保存上次安裝操作中的session_id install_id
             *   存在的話添加監(jiān)聽器
             * 5 首先創(chuàng)建會話屬性PackageInstaller.SessionParams
             *   檢查intent是否攜帶referrerUri
             *   根據(jù)refererUrl是否攜帶可以判斷當次安裝資源是本地還是網(wǎng)絡下載
             *   當次應用是否為及時應用
             *   設置referrerUri
             *   設置安裝包來源URI
             *   設置安裝包所屬user
             *   解析安裝包部分數(shù)據(jù)
             *   設置安裝包名稱
             *   設置安裝原因
             *   解析安裝包獲取包名冶共,安裝位置,安裝大小
             * 
             * 上述提到的這些信息都會保存到我們新創(chuàng)建的會話屬性中 PackageInstaller.SessionParams params
             * 
             * 6 根據(jù)會話屬性創(chuàng)建會話拿到會話id
             */
            final File sourceFile = new File(mPackageURI.getPath());
            PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile);

            mAlert.setIcon(as.icon);
            mAlert.setTitle(as.label);
            mAlert.setView(R.layout.install_content_view);
            mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
                    (ignored, ignored2) -> {
                        if (mInstallingTask != null) {
                            mInstallingTask.cancel(true);
                        }

                        if (mSessionId > 0) {
                            getPackageManager().getPackageInstaller().abandonSession(mSessionId);
                            mSessionId = 0;
                        }

                        setResult(RESULT_CANCELED);
                        finish();
                    }, null);
            setupAlert();
            requireViewById(R.id.installing).setVisibility(View.VISIBLE);

            if (savedInstanceState != null) {
                mSessionId = savedInstanceState.getInt(SESSION_ID);
                mInstallId = savedInstanceState.getInt(INSTALL_ID);

                // Reregister for result; might instantly call back if result was delivered while
                // activity was destroyed
                try {
                    InstallEventReceiver.addObserver(this, mInstallId,
                            this::launchFinishBasedOnResult);
                } catch (EventResultPersister.OutOfIdsException e) {
                    // Does not happen
                }
            } else {
                PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                        PackageInstaller.SessionParams.MODE_FULL_INSTALL);
                final Uri referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);
                params.setPackageSource(
                        referrerUri != null ? PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
                                : PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE);      
                params.setInstallAsInstantApp(false);
                params.setReferrerUri(referrerUri);
                params.setOriginatingUri(getIntent()
                        .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI));
                params.setOriginatingUid(getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
                        UID_UNKNOWN));
                params.setInstallerPackageName(getIntent().getStringExtra(
                        Intent.EXTRA_INSTALLER_PACKAGE_NAME));
                params.setInstallReason(PackageManager.INSTALL_REASON_USER);
                File file = new File(mPackageURI.getPath());
                try {
                    final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
                    final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
                            input.reset(), file, /* flags */ 0);
                    if (result.isError()) {
                        Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.");
                        Log.e(LOG_TAG,
                                "Cannot calculate installed size " + file + ". Try only apk size.");
                        params.setSize(file.length());
                    } else {
                        final PackageLite pkg = result.getResult();
                        params.setAppPackageName(pkg.getPackageName());
                        params.setInstallLocation(pkg.getInstallLocation());
                        params.setSize(InstallLocationUtils.calculateInstalledSize(pkg,
                                params.abiOverride));
                    }
                } catch (IOException e) {
                    Log.e(LOG_TAG,
                            "Cannot calculate installed size " + file + ". Try only apk size.");
                    params.setSize(file.length());
                }

                try {
                    mInstallId = InstallEventReceiver
                            .addObserver(this, EventResultPersister.GENERATE_NEW_ID,
                                    this::launchFinishBasedOnResult);
                } catch (EventResultPersister.OutOfIdsException e) {
                    launchFailure(PackageInstaller.STATUS_FAILURE,
                            PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
                }

                try {
                    mSessionId = getPackageManager().getPackageInstaller().createSession(params);
                } catch (IOException e) {
                    launchFailure(PackageInstaller.STATUS_FAILURE,
                            PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
                }
            }

            mCancelButton = mAlert.getButton(DialogInterface.BUTTON_NEGATIVE);
        }
    }

總結:onCreate 生命周期每界,主要就是為新的安裝包創(chuàng)建安裝會話

2.2 onResume
    protected void onResume() {
        super.onResume();

        // This is the first onResume in a single life of the activity
        if (mInstallingTask == null) {
            /**
             * case 安裝任務為空
             * 
             * 過程 創(chuàng)建安裝異步任務捅僵,并且執(zhí)行異步任務
             */
            PackageInstaller installer = getPackageManager().getPackageInstaller();
            PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);

            if (sessionInfo != null && !sessionInfo.isActive()) {
                mInstallingTask = new InstallingAsyncTask();
                mInstallingTask.execute();
            } else {
                // we will receive a broadcast when the install is finished
                mCancelButton.setEnabled(false);
                setFinishOnTouchOutside(false);
            }
        }
    }

總結onResume主要就是創(chuàng)建異步任務,執(zhí)行安裝相關操作

3 附加

既然這里提到了異步任務眨层,我們也剛好可以了解一下AyncTask

3.1 概述

AsyncTask 是 Android 提供的一個用于在后臺執(zhí)行異步任務的類庙楚。它提供了一種簡單的方法來執(zhí)行后臺操作,并在主線程中更新 UI趴樱。AsyncTask 是在 Android 中比較常用的工具之一馒闷,特別適合于需要在后臺執(zhí)行任務并更新 UI 的場景。

AsyncTask 類包含四個步驟方法:

onPreExecute():在后臺任務執(zhí)行前被調用叁征,通常用于在執(zhí)行后臺任務前做一些初始化操作纳账,例如顯示進度條等。
doInBackground(Params...):在后臺執(zhí)行耗時操作航揉,該方法運行在后臺線程中塞祈,用于執(zhí)行耗時任務,但不能操作 UI帅涂。
onProgressUpdate(Progress...):在調用 publishProgress(Progress...) 后被調用议薪,用于更新任務的執(zhí)行進度,可以在這個方法中更新 UI媳友。
onPostExecute(Result):在后臺任務執(zhí)行完畢并通過 return 語句返回結果時被調用斯议,用于處理執(zhí)行結果并更新 UI。

3.2 異步下載任務
    private final class InstallingAsyncTask extends AsyncTask<Void, Void,
            PackageInstaller.Session> {
        volatile boolean isDone;

        @Override
        protected PackageInstaller.Session doInBackground(Void... params) {
            PackageInstaller.Session session;
            try {
                //首先獲取當次安裝會話
                session = getPackageManager().getPackageInstaller().openSession(mSessionId);
            } catch (IOException e) {
                synchronized (this) {
                    isDone = true;
                    notifyAll();
                }
                return null;
            }
            //設置初始進度
            session.setStagingProgress(0);

            try {
                //根據(jù)mPackageURI創(chuàng)建file對象
                File file = new File(mPackageURI.getPath());
                //獲取輸入流讀取文件數(shù)據(jù)寫入到內存
                try (InputStream in = new FileInputStream(file)) {
                    long sizeBytes = file.length();
                    //通過session.openWrite方法創(chuàng)建了輸出流
                    try (OutputStream out = session
                            .openWrite("PackageInstaller", 0, sizeBytes)) {
                        //設置數(shù)據(jù)緩存數(shù)組
                        byte[] buffer = new byte[1024 * 1024];
                        while (true) {
                            //輸入流讀取數(shù)據(jù)
                            int numRead = in.read(buffer);

                            if (numRead == -1) {
                                session.fsync(out);
                                break;
                            }

                            if (isCancelled()) {
                                session.close();
                                break;
                            }
                            //輸出流將數(shù)據(jù)寫到session中
                            out.write(buffer, 0, numRead);
                            if (sizeBytes > 0) {
                                //同步每次寫入進度
                                float fraction = ((float) numRead / (float) sizeBytes);
                                session.addProgress(fraction);
                            }
                        }
                    }
                }

                return session;
            } catch (IOException | SecurityException e) {
                Log.e(LOG_TAG, "Could not write package", e);
                //出現(xiàn)異常關閉會話
                session.close();

                return null;
            } finally {
                synchronized (this) {
                    isDone = true;
                    notifyAll();
                }
            }
        }

        @Override
        protected void onPostExecute(PackageInstaller.Session session) {
            if (session != null) {
                //case 1 安裝會話不為空 創(chuàng)建intent對象
                Intent broadcastIntent = new Intent(BROADCAST_ACTION);
                //對intent進行數(shù)據(jù)封裝
                broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
                //封裝包名
                broadcastIntent.setPackage(getPackageName());
                //封裝安裝id
                broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);
                //獲取待處理的廣播intent
                PendingIntent pendingIntent = PendingIntent.getBroadcast(
                        InstallInstalling.this,
                        mInstallId,
                        broadcastIntent,
                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
                //這里執(zhí)行commit操作將IntentSender提交
                /**
                 * IntentSender 用于允許一個應用程序的組件請求另一個應用程序的組件執(zhí)行操作醇锚,
                 * 即使此時兩者可能不在運行中哼御。
                 */
                session.commit(pendingIntent.getIntentSender());
                mCancelButton.setEnabled(false);
                setFinishOnTouchOutside(false);
            } else {
                //失敗的話丟棄會話
                getPackageManager().getPackageInstaller().abandonSession(mSessionId);

                if (!isCancelled()) {
                    //執(zhí)行安裝失敗通知
                    launchFailure(PackageInstaller.STATUS_FAILURE,
                            PackageManager.INSTALL_FAILED_INVALID_APK, null);
                }
            }
        }
    }
}

總結:當次異步任務主要就是坯临,讀取文件數(shù)據(jù)寫到session中,然后構建了廣播意圖調用session.commit方法

接下來就是普通安裝的session處理流程

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末恋昼,一起剝皮案震驚了整個濱河市看靠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌液肌,老刑警劉巖挟炬,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嗦哆,居然都是意外死亡谤祖,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門老速,熙熙樓的掌柜王于貴愁眉苦臉地迎上來粥喜,“玉大人,你說我怎么就攤上這事橘券《钕妫” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵约郁,是天一觀的道長缩挑。 經常有香客問我,道長鬓梅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任谨湘,我火速辦了婚禮绽快,結果婚禮上,老公的妹妹穿的比我還像新娘紧阔。我一直安慰自己坊罢,他們只是感情好,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布擅耽。 她就那樣靜靜地躺著活孩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪乖仇。 梳的紋絲不亂的頭發(fā)上憾儒,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機與錄音乃沙,去河邊找鬼起趾。 笑死,一個胖子當著我的面吹牛警儒,可吹牛的內容都是我干的训裆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼边琉!你這毒婦竟也來了属百?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤变姨,失蹤者是張志新(化名)和其女友劉穎诸老,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钳恕,經...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡别伏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了忧额。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厘肮。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖睦番,靈堂內的尸體忽然破棺而出类茂,到底是詐尸還是另有隱情,我是刑警寧澤托嚣,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布巩检,位于F島的核電站,受9級特大地震影響示启,放射性物質發(fā)生泄漏兢哭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一夫嗓、第九天 我趴在偏房一處隱蔽的房頂上張望迟螺。 院中可真熱鬧,春花似錦舍咖、人聲如沸矩父。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽窍株。三九已至,卻和暖如春攻柠,著一層夾襖步出監(jiān)牢的瞬間球订,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工辙诞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辙售,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓飞涂,卻偏偏與公主長得像旦部,于是被迫代替她去往敵國和親祈搜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內容

  • 本期主要內容 1: 什么是AsyncTask士八。 2: AsyncTask的使用方法容燕。 3: AsyncTask的內...
    AKyS佐毅閱讀 927評論 0 0
  • 一藏古、Android中的線程 在操作系統(tǒng)中遗嗽,線程是操作系統(tǒng)調度的最小單元,同時線程又是一種受限的系統(tǒng)資源刻坊,即線程不可...
    kdong閱讀 115評論 0 0
  • 由于Android的特性蝗茁,如果要執(zhí)行耗時操作醋虏,則必須方法子線程中執(zhí)行。除了Thread可以開啟子線程外哮翘,Andro...
    Ruheng閱讀 25,769評論 6 18
  • 原創(chuàng)作品颈嚼,轉載請注明出處OService是Android四大組件之一,它主要是去執(zhí)行耗時操作(不需要與用戶交互并且...
    XiMiMax閱讀 3,941評論 4 6
  • 之前有寫過一篇博客饭寺,關于Android AsyncTask使用方法 AsyncTask 的使用方法阻课,想著不能又是知...
    Jere_Chen閱讀 222評論 0 0