Android12分析(更新內(nèi)容拾积、Android apk安裝、適配)

Android 12 新功能分析
https://developer.android.com/about/versions/12/features
https://blog.csdn.net/weixin_40611659/article/details/119645712
關(guān)于存儲(chǔ):
/**
* Allow apps to create the requests to manage the media files without user confirmation.
*
* @see android.Manifest.permission#MANAGE_MEDIA
* @see android.provider.MediaStore#createDeleteRequest(ContentResolver, Collection)
* @see android.provider.MediaStore#createTrashRequest(ContentResolver, Collection, boolean)
* @see android.provider.MediaStore#createWriteRequest(ContentResolver, Collection)
*
* @hide
*/

https://juejin.cn/post/6932363591689437192

關(guān)于項(xiàng)目中的適配
android 12 適配 (kdocs.cn)

apk的大體流程如下:

· 第一步:拷貝文件到指定的目錄: 在Android系統(tǒng)中,apk安裝文件是會(huì)被保存起來(lái)的拓巧,默認(rèn)情況下斯碌,用戶安裝的apk首先會(huì)被拷貝到/data/app目錄下,/data/app目錄是用戶有權(quán)限訪問(wèn)的目錄肛度,在安裝apk的時(shí)候會(huì)自動(dòng)選擇該目錄存放用戶安裝的文件傻唾,而系統(tǒng)出場(chǎng)的apk文件則被放到了/system分區(qū)下,包括/system/app承耿,/system/vendor/app冠骄,以及/system/priv-app等等,該分區(qū)只有ROOT權(quán)限的用戶才能訪問(wèn)瘩绒,這也就是為什么在沒(méi)有Root手機(jī)之前猴抹,我們沒(méi)法刪除系統(tǒng)出場(chǎng)的app的原因了。

· 第二步:解壓縮apk锁荔,寶貝文件蟀给,創(chuàng)建應(yīng)用的數(shù)據(jù)目錄 為了加快app的啟動(dòng)速度,apk在安裝的時(shí)候阳堕,會(huì)首先將app的可執(zhí)行文件dex拷貝到/data/dalvik-cache目錄跋理,緩存起來(lái)。然后恬总,在/data/data/目錄下創(chuàng)建應(yīng)用程序的數(shù)據(jù)目錄(以應(yīng)用的包名命名)前普,存放在應(yīng)用的相關(guān)數(shù)據(jù),如數(shù)據(jù)庫(kù)壹堰、xml文件拭卿、cache、二進(jìn)制的so動(dòng)態(tài)庫(kù)等贱纠。

· 第三步:解析apk的AndroidManifest.xml文件

Android系統(tǒng)中峻厚,也有一個(gè)類似注冊(cè)表的東西,用來(lái)記錄當(dāng)前所有安裝的應(yīng)用的基本信息谆焊,每次系統(tǒng)安裝或者卸載了任何apk文件惠桃,都會(huì)更新這個(gè)文件。這個(gè)文件位于如下目錄:/data/system/packages.xml辖试。系統(tǒng)在安裝這個(gè)apk的過(guò)程中辜王,會(huì)解析apk的AndroidManifest.xml文件,提取出這個(gè)apk的重要信息寫入到packages.xml文件中罐孝,這些信息包括:權(quán)限呐馆、應(yīng)用包名、APK的安裝位置莲兢、版本摹恰、userID等等辫继。由此,我們就知道了為什么一些應(yīng)用市場(chǎng)和軟件管理類的app能夠很清楚地知道當(dāng)前手機(jī)所安裝的所有app俗慈,以及這些app的詳細(xì)信息了。另外一件事就是Linux的用戶Id和用戶組Id遣耍,以便他們可以獲得合適的運(yùn)行權(quán)限闺阱。以上都是由PackageServcieManager完成的,后面我們會(huì)重點(diǎn)介紹PackageServiceManager舵变。

第四步:顯示快捷方式 如果這些應(yīng)用程序在PackageManagerService服務(wù)注冊(cè)好了酣溃,如果我們想要在Android桌米上看到這些應(yīng)用程序,還需要有一個(gè)Home應(yīng)用程序纪隙,負(fù)責(zé)從PackageManagerService服務(wù)中把這些安裝好的應(yīng)用程序取出來(lái)赊豌,并以友好的方式在桌面上展現(xiàn)出來(lái),例如以快捷圖標(biāo)的形式绵咱。在Android系統(tǒng)中碘饼,負(fù)責(zé)把系統(tǒng)中已經(jīng)安裝的應(yīng)用程序在桌面中展現(xiàn)出來(lái)的Home應(yīng)用就是Launcher了。
安裝 APK 主要分為以下三種場(chǎng)景

  • 安裝系統(tǒng)應(yīng)用:系統(tǒng)啟動(dòng)后調(diào)用 PackageManagerService.main() 初始化注冊(cè)解析安裝工作
public static PackageManagerService main(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
    // Self-check for initial settings.
    PackageManagerServiceCompilerMapping.checkProperties();

    PackageManagerService m = new PackageManagerService(context, installer,
            factoryTest, onlyCore);
    m.enableSystemUserPackages();
    ServiceManager.addService("package", m);
    final PackageManagerNative pmn = m.new PackageManagerNative();
    ServiceManager.addService("package_native", pmn);
    return m;
}

  • 通過(guò) adb 安裝:通過(guò) pm 參數(shù)悲伶,調(diào)用 PM 的 runInstall 方法艾恼,進(jìn)入 PackageManagerService 安裝安裝工作
  • 通過(guò)系統(tǒng)安裝器 PackageInstaller 進(jìn)行安裝:先調(diào)用 InstallStart 進(jìn)行權(quán)限檢查之后啟動(dòng) PackageInstallActivity,調(diào)用 PackageInstallActivity 的 startInstall 方法麸锉,點(diǎn)擊 OK 按鈕后進(jìn)入 PackageManagerService 完成拷貝解析安裝工作

所有安裝方式大致相同钠绍,最終就是回到 PackageManagerService 中,安裝一個(gè) APK 的大致流程如下:

  • 拷貝到 APK 文件到指定目錄
  • 解壓縮 APK花沉,拷貝文件柳爽,創(chuàng)建應(yīng)用的數(shù)據(jù)目錄
  • 解析 APK 的 AndroidManifest.xml 文件
  • 向 Launcher 應(yīng)用申請(qǐng)?zhí)砑觿?chuàng)建快捷方式

本文主要來(lái)分析通過(guò)安裝器 PackageInstaller 安裝 APK,這是用戶最常用的一種方式

7.0以前安裝的入口是PackageInstallerActivity 7.0以后是InstallStart

3.1 InstallStart

主要工作:

判斷是否勾選“未知來(lái)源”選項(xiàng)碱屁,若未勾選跳轉(zhuǎn)到設(shè)置安裝未知來(lái)源界面
對(duì)于大于等于 Android 8.0 版本磷脯,會(huì)先檢查是否申請(qǐng)安裝權(quán)限,若沒(méi)有則中斷安裝
判斷 Uri 的 Scheme 協(xié)議忽媒,若是 content 則調(diào)用 InstallStaging, 若是 package 則調(diào)用 PackageInstallerActivity,但是實(shí)際上 installStaging中的 StagingAsyncTask 會(huì)將content協(xié)議的Uri轉(zhuǎn)換為File協(xié)議争拐,然后跳轉(zhuǎn)到PackageInstallerActivity,所以最終安裝的開始還是PackageInstallerActivity

3.2 PackageInstallerActivity

主要工作:

  1. 顯示安裝界面
  2. 初始化安裝需要用的各種對(duì)象晦雨,比如 PackageManager架曹、IPackageManager、AppOpsManager闹瞧、UserManager绑雄、PackageInstaller 等等
  3. 根據(jù)傳遞過(guò)來(lái)的 Scheme 協(xié)議做不同的處理
  4. 檢查是否允許、初始化安裝
  5. 在準(zhǔn)備安裝的之前奥邮,檢查應(yīng)用列表判斷該應(yīng)用是否已安裝万牺,若已安裝則提示該應(yīng)用已安裝罗珍,由用戶決定是否替換
  6. 在安裝界面,提取出 APK 中權(quán)限信息并展示出來(lái)
  7. 點(diǎn)擊 OK 按鈕確認(rèn)安裝后脚粟,會(huì)調(diào)用 startInstall 開始安裝工作
protected void onCreate(Bundle icicle) {
    getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
    super.onCreate(null);
    // 初始化安裝需要用到的對(duì)象
    mPm = getPackageManager();
    mIpm = AppGlobals.getPackageManager();
    mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
    mInstaller = mPm.getPackageInstaller();
    mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);

    // 根據(jù)Uri的Scheme做不同的處理
    boolean wasSetUp = processPackageUri(packageUri);
    if (!wasSetUp) {
        return;
    }
    // 顯示安裝界面
    bindUi();
    // 檢查是否允許安裝包覆旱,如果允許則啟動(dòng)安裝。如果不允許顯示適當(dāng)?shù)膶?duì)話框
    checkIfAllowedAndInitiateInstall();
}

主要做了對(duì)象的初始化核无,解析 Uri 的 Scheme扣唱,初始化界面,安裝包檢查等等工作团南,接著查看一下



processPackageUri 方法

private boolean processPackageUri(final Uri packageUri) {
    mPackageURI = packageUri;
    final String scheme = packageUri.getScheme();
    // 根據(jù)這個(gè)Scheme協(xié)議分別對(duì)package協(xié)議和file協(xié)議進(jìn)行處理
    switch (scheme) {
        case SCHEME_PACKAGE: {
            try {
                // 通過(guò)PackageManager對(duì)象獲取指定包名的包信息
                mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
                        PackageManager.GET_PERMISSIONS
                                | PackageManager.MATCH_UNINSTALLED_PACKAGES);
            } catch (NameNotFoundException e) {
            }
            if (mPkgInfo == null) {
                Log.w(TAG, "Requested package " + packageUri.getScheme()
                        + " not available. Discontinuing installation");
                showDialogInner(DLG_PACKAGE_ERROR);
                setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
                return false;
            }
            mAppSnippet = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
                    mPm.getApplicationIcon(mPkgInfo.applicationInfo));
        } break;

        case ContentResolver.SCHEME_FILE: {
            // 根據(jù)packageUri創(chuàng)建一個(gè)新的File
            File sourceFile = new File(packageUri.getPath());
            // 解析APK得到APK的信息噪沙,PackageParser.Package存儲(chǔ)了APK的所有信息
            PackageParser.Package parsed = PackageUtil.getPackageInfo(this, sourceFile);

            if (parsed == null) {
                Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
                showDialogInner(DLG_PACKAGE_ERROR);
                setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
                return false;
            }
            // 根據(jù)PackageParser.Package得到的APK信息,生成PackageInfo
            mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
                    PackageManager.GET_PERMISSIONS, 0, 0, null,
                    new PackageUserState());
            mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
        } break;

        default: {
            throw new IllegalArgumentException("Unexpected URI scheme " + packageUri);
        }
    }

    return true;
}

主要對(duì) Scheme 協(xié)議分別對(duì) package 協(xié)議和 file 協(xié)議進(jìn)行處理
SCHEME_PACKAGE:

在 package 協(xié)議中調(diào)用了 PackageManager.getPackageInfo 方法生成 PackageInfo吐根,PackageInfo 是跨進(jìn)程傳遞的包數(shù)據(jù)(activities正歼、receivers、services拷橘、providers局义、permissions等等)包含 APK 的所有信息

SCHEME_FILE:

在 file 協(xié)議的處理中調(diào)用了 PackageUtil.getPackageInfo 方法,方法內(nèi)部調(diào)用了 PackageParser.parsePackage() 把 APK 文件的 manifest 和簽名信息都解析完成并保存在了 Package膜楷,Package 包含了該 APK 的所有信息
調(diào)用 PackageParser.generatePackageInfo 生成 PackageInfo

接著往下走旭咽,都解析完成之后,回到 onCreate 方法赌厅,繼續(xù)調(diào)用 checkIfAllowedAndInitiateInstall 方法

private void checkIfAllowedAndInitiateInstall() {
    ## 首先檢查安裝應(yīng)用程序的用戶限制穷绵,如果有限制并彈出彈出提示Dialog或者跳轉(zhuǎn)到設(shè)置界面
    final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource(
            UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle());
    if ((installAppsRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
        showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER);
        return;
    } else if (installAppsRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
        startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
        finish();
        return;
    }

    // 判斷如果允許安裝未知來(lái)源或者根據(jù)Intent判斷得出該APK不是未知來(lái)源
    if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
        initiateInstall();
    } else {
        // 檢查未知安裝源限制,如果有限制彈出Dialog,顯示相應(yīng)的信息
        final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource(
                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle());
        final int unknownSourcesGlobalRestrictionSource = mUserManager.getUserRestrictionSource(
                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, Process.myUserHandle());
        final int systemRestriction = UserManager.RESTRICTION_SOURCE_SYSTEM
                & (unknownSourcesRestrictionSource | unknownSourcesGlobalRestrictionSource);
        if (systemRestriction != 0) {
            showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
        } else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
            startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
        } else if (unknownSourcesGlobalRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
            startAdminSupportDetailsActivity(
                    UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
        } else {
            // 處理未知來(lái)源的APK
            handleUnknownSources();
        }
    }
}

主要檢查安裝應(yīng)用程序的用戶限制,當(dāng) APK 文件不對(duì)或者安裝有限制則調(diào)用 showDialogInner 方法特愿,彈出 dialog 提示用戶仲墨,顯示相應(yīng)的錯(cuò)誤信息,來(lái)看一下都有那些錯(cuò)誤信息

// Dialog identifiers used in showDialog
private static final int DLG_BASE = 0;
// package信息錯(cuò)誤
private static final int DLG_PACKAGE_ERROR = DLG_BASE + 2;
// 存儲(chǔ)空間不夠
private static final int DLG_OUT_OF_SPACE = DLG_BASE + 3;
// 安裝錯(cuò)誤
private static final int DLG_INSTALL_ERROR = DLG_BASE + 4;
// 用戶限制的未知來(lái)源
private static final int DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER = DLG_BASE + 5;
private static final int DLG_ANONYMOUS_SOURCE = DLG_BASE + 6;
// 在wear上不支持
private static final int DLG_NOT_SUPPORTED_ON_WEAR = DLG_BASE + 7;
private static final int DLG_EXTERNAL_SOURCE_BLOCKED = DLG_BASE + 8;
// 安裝限制用戶使用的應(yīng)用程序
private static final int DLG_INSTALL_APPS_RESTRICTED_FOR_USER = DLG_BASE + 9;

如果用戶允許安裝未知來(lái)源揍障,會(huì)調(diào)用 initiateInstall 方法

private void initiateInstall() {
    String pkgName = mPkgInfo.packageName;
    // 檢查設(shè)備上是否存在相同包名的APK
    String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName });
    if (oldName != null && oldName.length > 0 && oldName[0] != null) {
        pkgName = oldName[0];
        mPkgInfo.packageName = pkgName;
        mPkgInfo.applicationInfo.packageName = pkgName;
    }
    // 檢查package是否已安裝, 如果已經(jīng)安裝則顯示對(duì)話框提示用戶是否替換目养。
    try {
        mAppInfo = mPm.getApplicationInfo(pkgName,
                PackageManager.MATCH_UNINSTALLED_PACKAGES);
        if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
            mAppInfo = null;
        }
    } catch (NameNotFoundException e) {
        mAppInfo = null;
    }
    // 初始化確認(rèn)安裝界面
    startInstallConfirm();
}

根據(jù)包名獲取應(yīng)用程序的信息,調(diào)用 startInstallConfirm 方法初始化安裝確認(rèn)界面后毒嫡,當(dāng)用戶點(diǎn)擊確認(rèn)按鈕之后發(fā)生了什么岩齿,接著查看確認(rèn)按鈕點(diǎn)擊事件

private void bindUi() {
   ...
    // 點(diǎn)擊確認(rèn)按鈕舞吭,安裝APK
    mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.install),
            (ignored, ignored2) -> {
                if (mOk.isEnabled()) {
                    if (mSessionId != -1) {
                        mInstaller.setPermissionsResult(mSessionId, true);
                        finish();
                    } else {
                        // 啟動(dòng)Activity來(lái)完成應(yīng)用的安裝
                        startInstall();
                    }
                }
            }, null);
   // 點(diǎn)擊取消按鈕刨仑,取消此次安裝
    mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
            (ignored, ignored2) -> {
                // Cancel and finish
                setResult(RESULT_CANCELED);
                if (mSessionId != -1) {
                    mInstaller.setPermissionsResult(mSessionId, false);
                }
                finish();
            }, null);
    setupAlert();
    mOk = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
    mOk.setEnabled(false);
}

當(dāng)用戶點(diǎn)擊確認(rèn)按鈕調(diào)用了 startInstall 方法拷泽,啟動(dòng)子 Activity 完成 APK 的安裝

private void startInstall() {
    // 啟動(dòng)子Activity來(lái)完成應(yīng)用的安
    Intent newIntent = new Intent();
    newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
            mPkgInfo.applicationInfo);
    newIntent.setData(mPackageURI);
    newIntent.setClass(this, InstallInstalling.class);
    ...
    if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
    startActivity(newIntent);
    finish();
}

startInstall 方法用來(lái)跳轉(zhuǎn)到 InstallInstalling,并關(guān)閉掉當(dāng)前的 PackageInstallerActivity

3.3 InstallInstalling

主要工作:

向包管理器發(fā)送包的信息咬摇,然后等待包管理器處理結(jié)果
注冊(cè)一個(gè)觀察者 InstallEventReceiver伐蒂,并接受安裝成功和失敗的回調(diào)
在方法 onResume 中創(chuàng)建同步棧,打開安裝 session肛鹏,設(shè)置安裝進(jìn)度條

InstallInstalling 首先向包管理器發(fā)送包的信息逸邦,然后等待包管理器處理結(jié)果恩沛,并在方法 InstallSuccess 和方法 InstallFailed 進(jìn)行成功和失敗的處理,查看 InstallInstalling 的 onCreate 方法:

protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...

    // 判斷安裝的應(yīng)用是否已經(jīng)存在
    if ("package".equals(mPackageURI.getScheme())) {
        try {
            getPackageManager().installExistingPackage(appInfo.packageName);
            launchSuccess();
        } catch (PackageManager.NameNotFoundException e) {
            launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
        }
    } else {
        final File sourceFile = new File(mPackageURI.getPath());
        PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile);
        ...

        if (savedInstanceState != null) {
            // 如果savedInstanceState 不為空缕减,獲取已經(jīng)存在mSessionId 和mInstallId 重新注冊(cè)
            mSessionId = savedInstanceState.getInt(SESSION_ID);
            mInstallId = savedInstanceState.getInt(INSTALL_ID);
            try {
                // 根據(jù)mInstallId向InstallEventReceiver注冊(cè)一個(gè)觀察者雷客,launchFinishBasedOnResult會(huì)接收到安裝事件的回調(diào)
                InstallEventReceiver.addObserver(this, mInstallId,
                        this::launchFinishBasedOnResult);
            } catch (EventResultPersister.OutOfIdsException e) {
            }
        } else {
            // 如果為空創(chuàng)建SessionParams,代表安裝會(huì)話的參數(shù)
            // 解析APK, 并將解析的參數(shù)賦值給SessionParams
            PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                    PackageInstaller.SessionParams.MODE_FULL_INSTALL);
            ...

            try {
                // 注冊(cè)InstallEventReceiver烛卧,并在launchFinishBasedOnResult會(huì)接收到安裝事件的回調(diào)
                mInstallId = InstallEventReceiver
                        .addObserver(this, EventResultPersister.GENERATE_NEW_ID,
                                this::launchFinishBasedOnResult);
            } catch (EventResultPersister.OutOfIdsException e) {
                launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
            }

            try {
                // createSession 內(nèi)部通過(guò)IPackageInstaller與PackageInstallerService進(jìn)行進(jìn)程間通信佛纫,
                // 最終調(diào)用的是PackageInstallerService的createSession方法來(lái)創(chuàng)建并返回mSessionId
                mSessionId = getPackageManager().getPackageInstaller().createSession(params);
            } catch (IOException e) {
                launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
            }
        }
            ...
    }
}

最終都會(huì)注冊(cè)一個(gè)觀察者 InstallEventReceiver,并在 launchFinishBasedOnResult 會(huì)接收到安裝事件的回調(diào)总放,其中 InstallEventReceiver 繼承自 BroadcastReceiver,用于接收安裝事件并回調(diào)給 EventResultPersister
createSession 內(nèi)部通過(guò) IPackageInstaller 與 PackageInstallerService 進(jìn)行進(jìn)程間通信好爬,最終調(diào)用的是 PackageInstallerService的createSession 方法來(lái)創(chuàng)建并返回 mSessionId
接下來(lái)在 onResume 方法創(chuàng)建 InstallingAsyncTask 用來(lái)執(zhí)行 APK 的安裝局雄,接著查看 onResume 方法

protected void onResume() {
    super.onResume();
    if (mInstallingTask == null) {
        PackageInstaller installer = getPackageManager().getPackageInstaller();
        // 根據(jù)mSessionId 獲取SessionInfo, 代表安裝會(huì)話的詳細(xì)信息
        PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);
        if (sessionInfo != null && !sessionInfo.isActive()) {
            mInstallingTask = new InstallingAsyncTask();
            mInstallingTask.execute();
        } else {
            // 安裝完成后會(huì)收到廣播
            mCancelButton.setEnabled(false);
            setFinishOnTouchOutside(false);
        }
    }
}

得到 SessionInfo 創(chuàng)建并創(chuàng)建 InstallingAsyncTask,InstallingAsyncTask 的 doInBackground 方法設(shè)置安裝進(jìn)度條存炮,并將 APK 信息寫入 PackageInstaller.Session炬搭,寫入完成之后,在 InstallingAsyncTask 的 onPostExecute 進(jìn)行成功與失敗的處理穆桂,接著查看 onPostExecute 方法

protected void onPostExecute(PackageInstaller.Session session) {
    if (session != null) {
        Intent broadcastIntent = new Intent(BROADCAST_ACTION);
        broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        broadcastIntent.setPackage(getPackageName());
        broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);

        PendingIntent pendingIntent = PendingIntent.getBroadcast(
                InstallInstalling.this,
                mInstallId,
                broadcastIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);

        session.commit(pendingIntent.getIntentSender());
        mCancelButton.setEnabled(false);
        setFinishOnTouchOutside(false);
    } else {
        getPackageManager().getPackageInstaller().abandonSession(mSessionId);

        if (!isCancelled()) {
            launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null);
        }
    }
}

創(chuàng)建了 broadcastIntent宫盔,并通過(guò) PackageInstaller.Session 的 commit 方法發(fā)送出去,通過(guò) broadcastIntent 構(gòu)造方法指定的 Intent 的 Action 為 BROADCAST_ACTION享完,而 BROADCAST_ACTION 是一個(gè)常量值

 private static final String BROADCAST_ACTION =
            "com.android.packageinstaller.ACTION_INSTALL_COMMIT";

回到 InstallInstalling.OnCreate 方法灼芭,在 OnCreate 方法注冊(cè) InstallEventReceiver,而 InstallEventReceiver 繼承自 BroadcastReceiver般又,而使用 BroadcastReceiver 需要在 AndroidManifest.xml注冊(cè)彼绷,接著查看 AndroidManifest.xml:
/frameworks/base/packages/PackageInstaller/AndroidManifest.xml

<receiver android:name=".InstallEventReceiver"
        android:permission="android.permission.INSTALL_PACKAGES"
        android:exported="true">
    <intent-filter android:priority="1">
        <action android:name="com.android.packageinstaller.ACTION_INSTALL_COMMIT" />
    </intent-filter>
</receiver>

安裝結(jié)束之后,會(huì)在觀察者 InstallEventReceiver 注冊(cè)的回調(diào)方法 launchFinishBasedOnResult 處理安裝事件的結(jié)果茴迁,接著查看 launchFinishBasedOnResult

private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) {
    if (statusCode == PackageInstaller.STATUS_SUCCESS) {
        launchSuccess();
    } else {
        launchFailure(legacyStatus, statusMessage);
    }
}

private void launchSuccess() {
    Intent successIntent = new Intent(getIntent());
    successIntent.setClass(this, InstallSuccess.class);
    successIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
    startActivity(successIntent);
    finish();
}
    
private void launchFailure(int legacyStatus, String statusMessage) {
    Intent failureIntent = new Intent(getIntent());
    failureIntent.setClass(this, InstallFailed.class);
    failureIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
    failureIntent.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, legacyStatus);
    failureIntent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, statusMessage);
    startActivity(failureIntent);
    finish();
}

安裝成功和失敗寄悯,都會(huì)啟動(dòng)一個(gè)新的 Activity(InstallSuccess、InstallFailed)將結(jié)果展示給用戶堕义,然后 finish 掉 InstallInstalling
總結(jié)一下 PackageInstaller 安裝APK的過(guò)程:
現(xiàn)在來(lái)總結(jié)下PackageInstaller初始化的過(guò)程:

根據(jù)Uri的Scheme協(xié)議不同猜旬,跳轉(zhuǎn)到不同的界面,content協(xié)議跳轉(zhuǎn)到InstallStart倦卖,其他的跳轉(zhuǎn)到PackageInstallerActivity洒擦。本文應(yīng)用場(chǎng)景中,如果是Android7.0以及更高版本會(huì)跳轉(zhuǎn)到InstallStart糖耸。

InstallStart將content協(xié)議的Uri轉(zhuǎn)換為File協(xié)議秘遏,然后跳轉(zhuǎn)到PackageInstallerActivity。

PackageInstallerActivity會(huì)分別對(duì)package協(xié)議和file協(xié)議的Uri進(jìn)行處理嘉竟,如果是file協(xié)議會(huì)解析APK文件得到包信息PackageInfo邦危。
點(diǎn)擊 OK 按鈕確認(rèn)安裝后洋侨,會(huì)調(diào)用 startInstall 開始安裝工作
如果用戶允許安裝,然后跳轉(zhuǎn)到 InstallInstalling倦蚪,進(jìn)行 APK 的安裝工作
在 InstallInstalling 中希坚,向包管理器發(fā)送包的信息,然后注冊(cè)一個(gè)觀察者 InstallEventReceiver陵且,并接受安裝成功和失敗的回調(diào)

PackageInstallSession

Android通過(guò)PackageInstallerSession來(lái)表示一次安裝過(guò)程裁僧,一個(gè)PackageInstallerSession包含一個(gè)系統(tǒng)中唯一的一個(gè)SessionId,如果一個(gè)應(yīng)用的安裝必須分幾個(gè)階段來(lái)完成慕购,即使設(shè)備重啟了聊疲,也可以通過(guò)這個(gè)ID來(lái)繼續(xù)安裝過(guò)程

PackageInstallerService

安裝的遠(yuǎn)程服務(wù)類,用于返回sessionid



 @Override
    public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
        if (hasParentSessionId()) {
            throw new IllegalStateException(
                    "Session " + sessionId + " is a child of multi-package session "
                            + getParentSessionId() +  " and may not be committed directly.");
        }

        if (!markAsSealed(statusReceiver, forTransfer)) {
            return;
        }
        if (isMultiPackage()) {
            synchronized (mLock) {
                final IntentSender childIntentSender =
                        new ChildStatusIntentReceiver(mChildSessions.clone(), statusReceiver)
                                .getIntentSender();
                boolean sealFailed = false;
                for (int i = mChildSessions.size() - 1; i >= 0; --i) {
                    // seal all children, regardless if any of them fail; we'll throw/return
                    // as appropriate once all children have been processed
                    if (!mChildSessions.valueAt(i)
                            .markAsSealed(childIntentSender, forTransfer)) {
                        sealFailed = true;
                    }
                }
                if (sealFailed) {
                    return;
                }
            }
        }

        dispatchSessionSealed();
    }

   /**
     * Kicks off the install flow. The first step is to persist 'sealed' flags
     * to prevent mutations of hard links created later.
     */
    private void dispatchSessionSealed() {
        mHandler.obtainMessage(MSG_ON_SESSION_SEALED).sendToTarget();
    }
 private final Handler.Callback mHandlerCallback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_ON_SESSION_SEALED:
                    handleSessionSealed();
                    break;
                case MSG_STREAM_VALIDATE_AND_COMMIT:
                    handleStreamValidateAndCommit();
                    break;
                case MSG_INSTALL:
                    handleInstall();
                    break;
                case MSG_ON_PACKAGE_INSTALLED:
                    final SomeArgs args = (SomeArgs) msg.obj;
                    final String packageName = (String) args.arg1;
                    final String message = (String) args.arg2;
                    final Bundle extras = (Bundle) args.arg3;
                    final IntentSender statusReceiver = (IntentSender) args.arg4;
                    final int returnCode = args.argi1;
                    args.recycle();

                    sendOnPackageInstalled(mContext, statusReceiver, sessionId,
                            isInstallerDeviceOwnerOrAffiliatedProfileOwner(), userId,
                            packageName, returnCode, message, extras);

                    break;
                case MSG_SESSION_VALIDATION_FAILURE:
                    final int error = msg.arg1;
                    final String detailMessage = (String) msg.obj;
                    onSessionValidationFailure(error, detailMessage);
                    break;
            }

            return true;
        }
    };

經(jīng)過(guò)一系列處理最終是調(diào)用的還是Pms中的安裝方法

 private void installNonStaged()
            throws PackageManagerException {
        final PackageManagerService.InstallParams installingSession = makeInstallParams();
        if (installingSession == null) {
            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                    "Session should contain at least one apk session for installation");
        }
        if (isMultiPackage()) {
            final List<PackageInstallerSession> childSessions;
            synchronized (mLock) {
                childSessions = getChildSessionsLocked();
            }
            List<PackageManagerService.InstallParams> installingChildSessions =
                    new ArrayList<>(childSessions.size());
            boolean success = true;
            PackageManagerException failure = null;
            for (int i = 0; i < childSessions.size(); ++i) {
                final PackageInstallerSession session = childSessions.get(i);
                try {
                    final PackageManagerService.InstallParams installingChildSession =
                            session.makeInstallParams();
                    if (installingChildSession != null) {
                        installingChildSessions.add(installingChildSession);
                    }
                } catch (PackageManagerException e) {
                    failure = e;
                    success = false;
                }
            }
            if (!success) {
                final IntentSender statusReceiver;
                synchronized (mLock) {
                    statusReceiver = mRemoteStatusReceiver;
                }
                sendOnPackageInstalled(mContext, statusReceiver, sessionId,
                        isInstallerDeviceOwnerOrAffiliatedProfileOwner(), userId, null,
                        failure.error, failure.getLocalizedMessage(), null);
                return;
            }
            mPm.installStage(installingSession, installingChildSessions);
        } else {
            mPm.installStage(installingSession);
        }
    }

packageManagerService

 final void startCopy() {
            if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);
            handleStartCopy();
            handleReturnCode();
        }

 @Override
        void handleReturnCode() {
            processPendingInstall();
        }

        private void processPendingInstall() {
            InstallArgs args = createInstallArgs(this);
            if (mRet == PackageManager.INSTALL_SUCCEEDED) {
                mRet = args.copyApk();
            }
            if (mRet == PackageManager.INSTALL_SUCCEEDED) {
                F2fsUtils.releaseCompressedBlocks(
                        mContext.getContentResolver(), new File(args.getCodePath()));
            }
            if (mParentInstallParams != null) {
                mParentInstallParams.tryProcessInstallRequest(args, mRet);
            } else {
                PackageInstalledInfo res = createPackageInstalledInfo(mRet);
                processInstallRequestsAsync(
                        res.returnCode == PackageManager.INSTALL_SUCCEEDED,
                        Collections.singletonList(new InstallRequest(args, res)));
            }
        }
  // Queue up an async operation since the package installation may take a little while.
    private void processInstallRequestsAsync(boolean success,
            List<InstallRequest> installRequests) {
        mHandler.post(() -> {
            List<InstallRequest> apexInstallRequests = new ArrayList<>();
            List<InstallRequest> apkInstallRequests = new ArrayList<>();
            for (InstallRequest request : installRequests) {
                if ((request.args.installFlags & PackageManager.INSTALL_APEX) != 0) {
                    apexInstallRequests.add(request);
                } else {
                    apkInstallRequests.add(request);
                }
            }
            // Note: supporting multi package install of both APEXes and APKs might requir some
            // thinking to ensure atomicity of the install.
            if (!apexInstallRequests.isEmpty() && !apkInstallRequests.isEmpty()) {
                // This should've been caught at the validation step, but for some reason wasn't.
                throw new IllegalStateException(
                        "Attempted to do a multi package install of both APEXes and APKs");
            }
            if (!apexInstallRequests.isEmpty()) {
                if (success) {
                    // Since installApexPackages requires talking to external service (apexd), we
                    // schedule to run it async. Once it finishes, it will resume the install.
                    Thread t = new Thread(() -> installApexPackagesTraced(apexInstallRequests),
                            "installApexPackages");
                    t.start();
                } else {
                    // Non-staged APEX installation failed somewhere before
                    // processInstallRequestAsync. In that case just notify the observer about the
                    // failure.
                    InstallRequest request = apexInstallRequests.get(0);
                    notifyInstallObserver(request.installResult, request.args.observer);
                }
                return;
            }
            if (success) {
                for (InstallRequest request : apkInstallRequests) {
                    request.args.doPreInstall(request.installResult.returnCode);
                }
                synchronized (mInstallLock) {
                    installPackagesTracedLI(apkInstallRequests);
                }
                for (InstallRequest request : apkInstallRequests) {
                    request.args.doPostInstall(
                            request.installResult.returnCode, request.installResult.uid);
                }
            }
            for (InstallRequest request : apkInstallRequests) {
                restoreAndPostInstall(request.args.user.getIdentifier(), request.installResult,
                        new PostInstallData(request.args, request.installResult, null));
            }
        });
    }
  if (!doRestore) {
            // No restore possible, or the Backup Manager was mysteriously not
            // available -- just fire the post-install work request directly.
            if (DEBUG_INSTALL) Log.v(TAG, "No restore - queue post-install for " + token);

            Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "postInstall", token);

            Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
            mHandler.sendMessage(msg);
        }

  case POST_INSTALL: {
                    if (DEBUG_INSTALL) Log.v(TAG, "Handling post-install for " + msg.arg1);

                    PostInstallData data = mRunningInstalls.get(msg.arg1);
                    final boolean didRestore = (msg.arg2 != 0);
                    mRunningInstalls.delete(msg.arg1);

                    if (data != null && data.res.freezer != null) {
                        data.res.freezer.close();
                    }

                    if (data != null && data.mPostInstallRunnable != null) {
                        data.mPostInstallRunnable.run();
                    } else if (data != null && data.args != null) {
                        InstallArgs args = data.args;
                        PackageInstalledInfo parentRes = data.res;

                        final boolean killApp = (args.installFlags
                                & PackageManager.INSTALL_DONT_KILL_APP) == 0;
                        final boolean virtualPreload = ((args.installFlags
                                & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);

                        handlePackagePostInstall(parentRes, killApp, virtualPreload,
                                didRestore, args.installSource.installerPackageName, args.observer,
                                args.mDataLoaderType);

https://www.edrawmax.cn/online/share.html?code=98e50f7242b611ec802fefb61da1a18d

Android 12 針對(duì)使用 PackageInstaller API 的應(yīng)用引入了 setRequireUserAction() 方法沪悲。此方法可讓安裝程序應(yīng)用執(zhí)行應(yīng)用更新而無(wú)需用戶確認(rèn)操作获洲。

   @UserActionRequirement
    private int computeUserActionRequirement() {
        final String packageName;
        synchronized (mLock) {
            if (mPermissionsManuallyAccepted) {
                return USER_ACTION_NOT_NEEDED;
            }
            packageName = mPackageName;
        }

        final boolean forcePermissionPrompt =
                (params.installFlags & PackageManager.INSTALL_FORCE_PERMISSION_PROMPT) != 0
                        || params.requireUserAction == SessionParams.USER_ACTION_REQUIRED;
        if (forcePermissionPrompt) {
            return USER_ACTION_REQUIRED;
        }
        // It is safe to access mInstallerUid and mInstallSource without lock
        // because they are immutable after sealing.
        final boolean isInstallPermissionGranted =
                (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES,
                        mInstallerUid) == PackageManager.PERMISSION_GRANTED);
        final boolean isSelfUpdatePermissionGranted =
                (mPm.checkUidPermission(android.Manifest.permission.INSTALL_SELF_UPDATES,
                        mInstallerUid) == PackageManager.PERMISSION_GRANTED);
        final boolean isUpdatePermissionGranted =
                (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGE_UPDATES,
                        mInstallerUid) == PackageManager.PERMISSION_GRANTED);
        final boolean isUpdateWithoutUserActionPermissionGranted = (mPm.checkUidPermission(
                android.Manifest.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION, mInstallerUid)
                == PackageManager.PERMISSION_GRANTED);
        final int targetPackageUid = mPm.getPackageUid(packageName, 0, userId);
        final boolean isUpdate = targetPackageUid != -1 || isApexSession();
        final InstallSourceInfo existingInstallSourceInfo = isUpdate
                ? mPm.getInstallSourceInfo(packageName)
                : null;
        final String existingInstallerPackageName = existingInstallSourceInfo != null
                ? existingInstallSourceInfo.getInstallingPackageName()
                : null;
        final boolean isInstallerOfRecord = isUpdate
                && Objects.equals(existingInstallerPackageName, getInstallerPackageName());
        final boolean isSelfUpdate = targetPackageUid == mInstallerUid;
        final boolean isPermissionGranted = isInstallPermissionGranted
                || (isUpdatePermissionGranted && isUpdate)
                || (isSelfUpdatePermissionGranted && isSelfUpdate);
        final boolean isInstallerRoot = (mInstallerUid == Process.ROOT_UID);
        final boolean isInstallerSystem = (mInstallerUid == Process.SYSTEM_UID);

        // Device owners and affiliated profile owners  are allowed to silently install packages, so
        // the permission check is waived if the installer is the device owner.
        final boolean noUserActionNecessary = isPermissionGranted || isInstallerRoot
                || isInstallerSystem || isInstallerDeviceOwnerOrAffiliatedProfileOwner();

        if (noUserActionNecessary) {
            return USER_ACTION_NOT_NEEDED;
        }

        if (mPm.isInstallDisabledForPackage(getInstallerPackageName(), mInstallerUid, userId)) {
            // show the installer to account for device poslicy or unknown sources use cases
            return USER_ACTION_REQUIRED;
        }

        if (params.requireUserAction == SessionParams.USER_ACTION_NOT_REQUIRED
                && isUpdateWithoutUserActionPermissionGranted
                && (isInstallerOfRecord || isSelfUpdate)) {
            return USER_ACTION_PENDING_APK_PARSING;
        }

        return USER_ACTION_REQUIRED;
    }
 private PackageManagerService.VerificationParams prepareForVerification()
            throws PackageManagerException {
        assertNotLocked("makeSessionActive");

        @UserActionRequirement
        int userActionRequirement = USER_ACTION_NOT_NEEDED;
        // TODO(b/159331446): Move this to makeSessionActiveForInstall and update javadoc
        if (!params.isMultiPackage) {
            userActionRequirement = computeUserActionRequirement();
            if (userActionRequirement == USER_ACTION_REQUIRED) {
                sendPendingUserActionIntent();
                return null;
            } // else, we'll wait until we parse to determine if we need to
        }

        boolean silentUpdatePolicyEnforceable = false;
        synchronized (mLock) {
            if (mRelinquished) {
                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                        "Session relinquished");
            }
            if (mDestroyed) {
                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                        "Session destroyed");
            }
            if (!mSealed) {
                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                        "Session not sealed");
            }
            PackageLite result = parseApkLite();
            if (result != null) {
                mPackageLite = result;
                synchronized (mProgressLock) {
                    mInternalProgress = 0.5f;
                    computeProgressLocked(true);
                }

                extractNativeLibraries(
                        mPackageLite, stageDir, params.abiOverride, mayInheritNativeLibs());

                    1 安裝程序聲明了 UPDATE_PACKAGES_WITHOUT_USER_ACTION 權(quán)限。
                    2  正在安裝的應(yīng)用程序的目標(biāo)是 API 級(jí)別 29(Android 10)或更高

                if (userActionRequirement == USER_ACTION_PENDING_APK_PARSING) {
                    if (result.getTargetSdk() < Build.VERSION_CODES.Q) {
                        sendPendingUserActionIntent();
                        return null;
                    }

                    3 安裝器選擇了新的行為 不需要操作的行為

                    if (params.requireUserAction == SessionParams.USER_ACTION_NOT_REQUIRED) {
                        silentUpdatePolicyEnforceable = true;
                    }
                }
            }
        }
        if (silentUpdatePolicyEnforceable) {
            if (!mSilentUpdatePolicy.isSilentUpdateAllowed(
                    getInstallerPackageName(), getPackageName())) {
                // Fall back to the non-silent update if a repeated installation is invoked within
                // the throttle time.
                sendPendingUserActionIntent();
                return null;
            }
            記錄靜默更新程序
            mSilentUpdatePolicy.track(getInstallerPackageName(), getPackageName());
        }
        synchronized (mLock) {
            return makeVerificationParamsLocked();
        }
    }
 private PackageManagerService.VerificationParams makeVerificationParamsLocked() {
        final IPackageInstallObserver2 localObserver;
        if (!hasParentSessionId()) {
            // Avoid attaching this observer to child session since they won't use it.
            localObserver = new IPackageInstallObserver2.Stub() {
                @Override
                public void onUserActionRequired(Intent intent) {
                    throw new IllegalStateException();
                }

                @Override
                public void onPackageInstalled(String basePackageName, int returnCode, String msg,
                        Bundle extras) {
                    if (returnCode == INSTALL_SUCCEEDED) {
                        onVerificationComplete();
                    } else {
                        onSessionVerificationFailure(returnCode, msg);
                    }
                }
            };
        } else {
            localObserver = null;
        }

  private void onVerificationComplete() {
        // Staged sessions will be installed later during boot
        if (isStaged()) {
            // TODO(b/136257624): Remove this once all verification logic has been transferred out
            //  of StagingManager.
            mStagingManager.notifyPreRebootVerification_Apk_Complete(mStagedSession);
            // TODO(b/136257624): We also need to destroy internals for verified staged session,
            //  otherwise file descriptors are never closed for verified staged session until reboot
            return;
        }

        install();
    }

直接執(zhí)行安裝

https://new.qq.com/omn/20210520/20210520A0689300.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末殿如,一起剝皮案震驚了整個(gè)濱河市贡珊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌涉馁,老刑警劉巖门岔,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異烤送,居然都是意外死亡寒随,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門胯努,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)牢裳,“玉大人,你說(shuō)我怎么就攤上這事叶沛∑蜒叮” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵灰署,是天一觀的道長(zhǎng)判帮。 經(jīng)常有香客問(wèn)我,道長(zhǎng)溉箕,這世上最難降的妖魔是什么晦墙? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮肴茄,結(jié)果婚禮上晌畅,老公的妹妹穿的比我還像新娘。我一直安慰自己寡痰,他們只是感情好抗楔,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布棋凳。 她就那樣靜靜地躺著,像睡著了一般连躏。 火紅的嫁衣襯著肌膚如雪剩岳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天入热,我揣著相機(jī)與錄音拍棕,去河邊找鬼。 笑死勺良,一個(gè)胖子當(dāng)著我的面吹牛绰播,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播尚困,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼幅垮,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了尾组?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤示弓,失蹤者是張志新(化名)和其女友劉穎讳侨,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奏属,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡跨跨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了囱皿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勇婴。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖嘱腥,靈堂內(nèi)的尸體忽然破棺而出耕渴,到底是詐尸還是另有隱情,我是刑警寧澤齿兔,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布橱脸,位于F島的核電站,受9級(jí)特大地震影響分苇,放射性物質(zhì)發(fā)生泄漏添诉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一医寿、第九天 我趴在偏房一處隱蔽的房頂上張望栏赴。 院中可真熱鬧,春花似錦靖秩、人聲如沸须眷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)柒爸。三九已至准浴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捎稚,已是汗流浹背乐横。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留今野,地道東北人葡公。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像条霜,于是被迫代替她去往敵國(guó)和親催什。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容