應(yīng)用安裝策略之可用存儲空間閾值

應(yīng)用安裝策略之可用存儲空間閾值

對于從來沒有深入看過源碼的我來說,峰哥真的給我安排了一個棘手且完全不熟悉的任務(wù)聘惦。但是我也只好硬著頭皮往下做遮婶,期間參考了網(wǎng)上的一些文章,啟碩哥也指點了我不少猛蔽,最終還是找到了規(guī)定的閾值剥悟。

期間代碼不需要全看灵寺,只看畫出來的重點部分即可。貼出源碼是為了方便查漏補(bǔ)缺区岗。

  • 參考打包安裝程序

    • 發(fā)現(xiàn)基本都是調(diào)用android.content.pm.PackageManager
  • 網(wǎng)上查詢各種安裝方式

    • 通過shell命令的pm install <packgename>

    • 通過調(diào)用android.content.pm.PackageManager

  • 以上兩種方式發(fā)現(xiàn)無論怎么樣最終都會調(diào)用 installer.java

    • installer.java 最終回到PackageManagerService

    • 在滿負(fù)荷調(diào)試中出現(xiàn) INSTALL_FAILED_INSUFFICIENT_STORAGE

      • 通過AndroidXRef 查詢結(jié)果定位到PackageManagerService.java
  • 根據(jù)上述兩步跟蹤問題略板,找到 PackageManagerService.java

frameworks\base\services\java\com\android\server\pm\PackageManagerService.java

  • 找到 InstallParams

    • 這一整段基本就是安裝所需的各個流程,但是我們只看其中和存儲空間相關(guān)的策略

class InstallParams extends HandlerParams {

        //......

        private InstallArgs mArgs;

        private int mRet;

        private File mTempPackage;

        //.......

        private int installLocationPolicy(PackageInfoLite pkgLite, int flags) {

            String packageName = pkgLite.packageName;

            int installLocation = pkgLite.installLocation;

            boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0;

            // reader

            synchronized (mPackages) {

                PackageParser.Package pkg = mPackages.get(packageName);

                if (pkg != null) {

                    if ((flags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {

                        // Check for downgrading.

                        if ((flags & PackageManager.INSTALL_ALLOW_DOWNGRADE) == 0) {

                            if (pkgLite.versionCode < pkg.mVersionCode) {

                                Slog.w(TAG, "Can't install update of " + packageName

                                        + " update version " + pkgLite.versionCode

                                        + " is older than installed version "

                                        + pkg.mVersionCode);

                                return PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE;

                            }

                        }

                        // Check for updated system application.

                        if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {

                            if (onSd) {

                                Slog.w(TAG, "Cannot install update to system app on sdcard");

                                return PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION;

                            }

                            return PackageHelper.RECOMMEND_INSTALL_INTERNAL;

                        } else {

                            if (onSd) {

                                // Install flag overrides everything.

                                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;

                            }

                            // If current upgrade specifies particular preference

                            if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {

                                // Application explicitly specified internal.

                                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;

                            } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {

                                // App explictly prefers external. Let policy decide

                            } else {

                                // Prefer previous location

                                if (isExternal(pkg)) {

                                    return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;

                                }

                                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;

                            }

                        }

                    } else {

                        // Invalid install. Return error code

                        return PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS;

                    }

                }

            }

            // All the special cases have been taken care of.

            // Return result based on recommended install location.

            if (onSd) {

                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;

            }

            return pkgLite.recommendedInstallLocation;

        }

        /*

        * Invoke remote method to get package information and install

        * location values. Override install location based on default

        * policy if needed and then create install arguments based

        * on the install location.

        */

        public void handleStartCopy() throws RemoteException {

            int ret = PackageManager.INSTALL_SUCCEEDED;

            final boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0;

            final boolean onInt = (flags & PackageManager.INSTALL_INTERNAL) != 0;

            PackageInfoLite pkgLite = null;

            if (onInt && onSd) {

                // Check if both bits are set.

                Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");

                ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;

            } else {

                final long lowThreshold;

                final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager

                        .getService(DeviceStorageMonitorService.SERVICE);

                if (dsm == null) {

                    Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed");

                    lowThreshold = 0L;

                } else {

                    lowThreshold = dsm.getMemoryLowThreshold();

                }

                try {

                    //......

                    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);

                }

            }

            if (ret == PackageManager.INSTALL_SUCCEEDED) {

                int loc = pkgLite.recommendedInstallLocation;

                if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {

                    //......

                } else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {

                    ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;

                } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {

                    //......

                }

            }

            final InstallArgs args = createInstallArgs(this);

            mArgs = args;

            //......

        }

        //......

    }

  • 找到重點 handleStartCopy 函數(shù)

    • 出現(xiàn)了前面提到的 INSTALL_FAILED_INSUFFICIENT_STORAGE

    public void handleStartCopy() throws RemoteException {

        int ret = PackageManager.INSTALL_SUCCEEDED;

        final boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0;

        final boolean onInt = (flags & PackageManager.INSTALL_INTERNAL) != 0;

        PackageInfoLite pkgLite = null;

        if (onInt && onSd) {

            // Check if both bits are set.

            Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");

            ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;

        } else {

            final long lowThreshold;

            final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager

                    .getService(DeviceStorageMonitorService.SERVICE);

            if (dsm == null) {

                Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed");

                lowThreshold = 0L;

            } else {

                lowThreshold = dsm.getMemoryLowThreshold();

            }

            try {

                //......

                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);

            }

        }

        if (ret == PackageManager.INSTALL_SUCCEEDED) {

            int loc = pkgLite.recommendedInstallLocation;

            if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {

                //......

            } else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {

                ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;

            } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {

                //......

            }

        }

        final InstallArgs args = createInstallArgs(this);

        mArgs = args;

        //......

    }

  • 其中有獲取最低存儲空間的信息 lowThreshold

    • dsm是 DeviceStorageMonitorService 的實體

    final long lowThreshold;

    final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager.getService(DeviceStorageMonitorService.SERVICE);

    if (dsm == null) {

        Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed");

        lowThreshold = 0L;

    } else {

        lowThreshold = dsm.getMemoryLowThreshold();

    }

  • 找到 DeviceStorageMonitorService

frameworks\base\services\java\com\android\server\DeviceStorageMonitorService.java

  • 找到 getMemoryLowThreshold


    public long getMemoryLowThreshold() {

        return mMemLowThreshold;

    }

  • 找到 mMemLowThreshold


    final StorageManager sm = StorageManager.from(context);

    mMemLowThreshold = sm.getStorageLowBytes(DATA_PATH);

    mMemFullThreshold = sm.getStorageFullBytes(DATA_PATH);

    mMemCacheStartTrimThreshold = ((mMemLowThreshold*3)+mMemFullThreshold)/4;

    mMemCacheTrimToThreshold = mMemLowThreshold

            + ((mMemLowThreshold-mMemCacheStartTrimThreshold)*2);

    mFreeMemAfterLastCacheClear = mTotalMemory;

    checkMemory(true);

  • 根據(jù) checkMemory 確認(rèn) mMemLowThreshold 是我們要找的閾值目標(biāo)


    private final void checkMemory(boolean checkCache) {

        //if the thread that was started to clear cache is still running do nothing till its

        //finished clearing cache. Ideally this flag could be modified by clearCache

        // and should be accessed via a lock but even if it does this test will fail now and

        //hopefully the next time this flag will be set to the correct value.

        if(mClearingCache) {

          //......

        } else {

            restatDataDir();

            if (localLOGV)  Slog.v(TAG, "freeMemory="+mFreeMem);

            //post intent to NotificationManager to display icon if necessary

            if (mFreeMem < mMemLowThreshold) {

                if (checkCache) {

                    // We are allowed to clear cache files at this point to

                    // try to get down below the limit, because this is not

                    // the initial call after a cache clear has been attempted.

                    // In this case we will try a cache clear if our free

                    // space has gone below the cache clear limit.

                    if (mFreeMem < mMemCacheStartTrimThreshold) {

                        // We only clear the cache if the free storage has changed

                        // a significant amount since the last time.

                        if ((mFreeMemAfterLastCacheClear-mFreeMem)

                                >= ((mMemLowThreshold-mMemCacheStartTrimThreshold)/4)) {

                            // See if clearing cache helps

                            // Note that clearing cache is asynchronous and so we do a

                            // memory check again once the cache has been cleared.

                            mThreadStartTime = System.currentTimeMillis();

                            mClearSucceeded = false;

                            clearCache();

                        }

                    }

                } else {

                    // This is a call from after clearing the cache.  Note

                    // the amount of free storage at this point.

                    mFreeMemAfterLastCacheClear = mFreeMem;

                    if (!mLowMemFlag) {

                        // We tried to clear the cache, but that didn't get us

                        // below the low storage limit.  Tell the user.

                        Slog.i(TAG, "Running low on memory. Sending notification");

                        sendNotification();

                        mLowMemFlag = true;

                    } else {

                        if (localLOGV) Slog.v(TAG, "Running low on memory " +

                                "notification already sent. do nothing");

                    }

                }

            } else {

                //......

        }

        if(localLOGV) Slog.i(TAG, "Posting Message again");

        //keep posting messages to itself periodically

        postCheckMemoryMsg(true, DEFAULT_CHECK_INTERVAL);

    }

  • 找到 StorageManager.java

\frameworks\base\core\java\android\os\storage\StorageManager.java

  • 找到 getStorageLowBytes 函數(shù)


    private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;

    private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES;

    private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES;

    public long getStorageLowBytes(File path) {

        final long lowPercent = Settings.Global.getInt(mResolver,

                Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE);

        final long lowBytes = (path.getTotalSpace() * lowPercent) / 100;

        final long maxLowBytes = Settings.Global.getLong(mResolver,

                Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES, DEFAULT_THRESHOLD_MAX_BYTES);

        return Math.min(lowBytes, maxLowBytes);

    }

  • 確認(rèn)目標(biāo)

    • 總存儲空間的10%500M中較小的那一個
  • 找到系統(tǒng)判定空間可用空間較低的閾值后慈缔,根據(jù)之前的錯誤提示信息 INSTALL_FAILED_INSUFFICIENT_STORAGE 叮称,定位問題


    if (ret == PackageManager.INSTALL_SUCCEEDED) {

        int loc = pkgLite.recommendedInstallLocation;

        if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {

            //......

        } else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {

            ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;

        } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {

            //......

        }

    }

  • 再找到loc的聲明

    • pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath, flags, lowThreshold)

    • int loc = pkgLite.recommendedInstallLocation

    • 可以看出下一步需要找到 getMinimalPackageInfo 的聲明

  • 經(jīng)過一番查找,在 DefaultContainerService.java 中找到其聲明

frameworks\base\packages\DefaultContainerService\src\com\android\defcontainer\DefaultContainerService.java


    public PackageInfoLite getMinimalPackageInfo(final String packagePath, int flags,

            long threshold) {

        PackageInfoLite ret = new PackageInfoLite();

        //...

        ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation,

                packagePath, flags, threshold);

        return ret;

    }

  • 找到 recommendedInstallLocation 的聲明


    private int recommendAppInstallLocation(int installLocation, String archiveFilePath, int flags,

            long threshold) {

        int prefer;

        boolean checkBoth = false;

        final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;

        //......

        final boolean emulated = Environment.isExternalStorageEmulated();

        final File apkFile = new File(archiveFilePath);

        boolean fitsOnInternal = false;

        if (checkBoth || prefer == PREFER_INTERNAL) {

            try {

                fitsOnInternal = isUnderInternalThreshold(apkFile, isForwardLocked, threshold);

            } catch (IOException e) {

                return PackageHelper.RECOMMEND_FAILED_INVALID_URI;

            }

        }

        boolean fitsOnSd = false;

        if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)) {

            try {

                fitsOnSd = isUnderExternalThreshold(apkFile, isForwardLocked);

            } catch (IOException e) {

                return PackageHelper.RECOMMEND_FAILED_INVALID_URI;

            }

        }

        if (prefer == PREFER_INTERNAL) {

            if (fitsOnInternal) {

                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;

            }

        } else if (!emulated && prefer == PREFER_EXTERNAL) {

            if (fitsOnSd) {

                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;

            }

        }

        if (checkBoth) {

            if (fitsOnInternal) {

                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;

            } else if (!emulated && fitsOnSd) {

                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;

            }

        }

        /*

            * If they requested to be on the external media by default, return that

            * the media was unavailable. Otherwise, indicate there was insufficient

            * storage space available.

            */

        if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)

                && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {

            return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE;

        } else {

            return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;

        }

    }

  • loc的關(guān)鍵返回判斷信息語句如下


    if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)

            && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {

        return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE;

    } else {

        return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;

    }

  • 聯(lián)系前文藐鹤,依據(jù)判斷條件瓤檐,找到關(guān)鍵語句


fitsOnInternal = isUnderInternalThreshold(apkFile, isForwardLocked, threshold)

  • 尋找其聲明 isUnderInternalThreshold


    private boolean isUnderInternalThreshold(File apkFile, boolean isForwardLocked, long threshold)

            throws IOException {

        long size = apkFile.length();

        if (size == 0 && !apkFile.exists()) {

            throw new FileNotFoundException();

        }

        if (isForwardLocked) {

            size += PackageHelper.extractPublicFiles(apkFile.getAbsolutePath(), null);

        }

        final StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath());

        final long availInternalSize = (long) internalStats.getAvailableBlocks()

                * (long) internalStats.getBlockSize();

        return (availInternalSize - size) > threshold;

    }

  • 從上面我們可以看出返回值為 可用空間—安裝大小>最低閾值

結(jié)論

  • 我們終于做完了所有的事了,其結(jié)論就是:當(dāng)滿足 可用空間的大小 減去 安裝包需要的大小 大于 最低閾值(500M或存儲空間總大小的10%中的較小值) 的條件時,安裝才能進(jìn)行娱节。

后記

為了找出這個規(guī)定的閾值挠蛉,我查了不少文章,幾乎把安裝流程完全走了一遍肄满,可以說工作量非常巨大了谴古。并且一開始還成功地掉入了坑里,差點就把錯誤的結(jié)論拿出來講了稠歉。后來想想看好像不太對勁掰担,重新梳理了一遍邏輯才發(fā)現(xiàn)確實做錯了。不過走這一遍怒炸,雖然不知道有什么用带饱,但是相信也是積淀的一部分,希望在未來的日子里能夠讓我少踩一點坑横媚。

以下為有幫助的工具和參考文章

AndroidXRef 查源碼巨好用的網(wǎng)站

Android7.0 PackageManagerService

APK安裝流程詳解14——PMS中的新安裝流程上(拷貝)補(bǔ)充

Android PackageManagerService分析二:安裝APK

APK安裝流程源碼追蹤

androiod 學(xué)習(xí)--PMS應(yīng)用安裝過程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末纠炮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子灯蝴,更是在濱河造成了極大的恐慌恢口,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件穷躁,死亡現(xiàn)場離奇詭異耕肩,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)问潭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進(jìn)店門猿诸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人狡忙,你說我怎么就攤上這事梳虽。” “怎么了灾茁?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵窜觉,是天一觀的道長谷炸。 經(jīng)常有香客問我,道長禀挫,這世上最難降的妖魔是什么旬陡? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮语婴,結(jié)果婚禮上描孟,老公的妹妹穿的比我還像新娘。我一直安慰自己砰左,他們只是感情好匿醒,可當(dāng)我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著菜职,像睡著了一般青抛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酬核,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天蜜另,我揣著相機(jī)與錄音,去河邊找鬼嫡意。 笑死举瑰,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蔬螟。 我是一名探鬼主播此迅,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼旧巾!你這毒婦竟也來了耸序?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤鲁猩,失蹤者是張志新(化名)和其女友劉穎坎怪,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體廓握,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡搅窿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了隙券。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片男应。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖娱仔,靈堂內(nèi)的尸體忽然破棺而出沐飘,到底是詐尸還是另有隱情,我是刑警寧澤牲迫,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布薪铜,位于F島的核電站众弓,受9級特大地震影響恩溅,放射性物質(zhì)發(fā)生泄漏隔箍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一脚乡、第九天 我趴在偏房一處隱蔽的房頂上張望蜒滩。 院中可真熱鬧,春花似錦奶稠、人聲如沸俯艰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽竹握。三九已至,卻和暖如春辆飘,著一層夾襖步出監(jiān)牢的瞬間啦辐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工蜈项, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留芹关,地道東北人。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓紧卒,卻偏偏與公主長得像侥衬,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子跑芳,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,086評論 2 355