從Tinker加載dex補(bǔ)丁看動(dòng)態(tài)加載插件過(guò)程

本篇文章基于Android Q 和 Tinker 1.9.14.7初肉。

經(jīng)過(guò)前面的分析,已經(jīng)初步了解了Tinker的整個(gè)執(zhí)行流程,對(duì)整個(gè)脈絡(luò)有了清晰的認(rèn)識(shí)。那么本篇文章就來(lái)深挖一個(gè)點(diǎn)漠秋,從加載dex補(bǔ)丁看Tinker動(dòng)態(tài)加載插件過(guò)程。

首先來(lái)回顧Tinker加載dex補(bǔ)丁過(guò)程

整個(gè)dex補(bǔ)丁加載過(guò)程分兩部分:

  • 如果系統(tǒng)ota升級(jí)后抵屿,因?yàn)閛dex失效了庆锦,所以Tinker自己會(huì)觸發(fā)一次dex加載,而這個(gè)加載過(guò)程在Q之前是會(huì)走dex2oat編譯的轧葛,Q開(kāi)始如果odex失效搂抒,當(dāng)前l(fā)oadDex過(guò)程不進(jìn)行dex2oat編譯,轉(zhuǎn)而走解釋模式執(zhí)行尿扯。另外求晶,這里區(qū)分了版本,對(duì)25及以上的版本使用PathClassLoader來(lái)加載衷笋,而低版本直接使用DexFile.loadDex芳杏。

  • dex補(bǔ)丁包加載實(shí)際上是通過(guò)hook的方式將修復(fù)后的dex插入到的DexPathList的Elements[]中最前面去,從而替代之前的基準(zhǔn)包辟宗,替代原理是雙親委派和類(lèi)緩存保證了相同類(lèi)不能被重復(fù)加載爵赵,前面加載過(guò)了后面的dex就失效了,從而達(dá)到替換的目的泊脐。

那么本篇文章主要分析如下三點(diǎn)問(wèn)題:

  • PathClassLoader與DexFile.loadDex加載類(lèi)的區(qū)別
  • dex加載過(guò)程
  • Apk加載過(guò)程與Tinker動(dòng)態(tài)加載dex補(bǔ)丁包整體順序流程

一空幻、PathClassLoader與DexFile.loadDex加載類(lèi)的區(qū)別

PathClassLoader初始化流程

流程主要包含兩個(gè)部分:

  • PathClassLoader經(jīng)過(guò)一系列初始化,最終由DexPathList觸發(fā)DexFile.loadDex來(lái)加載dex容客。

  • 通過(guò)report將插件路徑上報(bào)到DexManager統(tǒng)一管理氛悬。

乍一看,這兩種方式本質(zhì)上只有有個(gè)上報(bào)的差別耘柱,那么這個(gè)上報(bào)到底有什么意義如捅?另外,在之前老版本Tinker中调煎,這里統(tǒng)一是由DexFile.loadDex來(lái)加載的镜遣,之后為什么新增PathClassLoader加載方式?所以就很有必要研究下上報(bào)是干了什么。

在ActivityThread handleBindApplication會(huì)通過(guò)LoadedApk進(jìn)行Apk加載操作悲关,在這里會(huì)設(shè)置BaseDexClassLoader的reporter:

if (SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false)) {
    BaseDexClassLoader.setReporter(DexLoadReporter.getInstance());
}

看看這個(gè)系統(tǒng)屬性:

cepheus:/ $ getprop | grep "dalvik.vm.usejitprofiles"

[dalvik.vm.usejitprofiles]: [true]   開(kāi)關(guān)默認(rèn)是打開(kāi)的

下面來(lái)研究下整個(gè)上報(bào)過(guò)程:

上報(bào)簡(jiǎn)單流程

最終dexPath歸DexManager統(tǒng)一管理谎僻。系統(tǒng)有個(gè)jobService:BackgroundDexService它在SystemServer執(zhí)行startOtherServices時(shí)觸發(fā)schedule,在idle場(chǎng)景下觸發(fā)插件和主apk包的dex2oat編譯,簡(jiǎn)單流程如下圖所示:


上報(bào)插件被編譯流程

也就是說(shuō)寓辱,如果上報(bào)了艘绍,當(dāng)前插件會(huì)被DexManager統(tǒng)一管理,在idle狀態(tài)下系統(tǒng)會(huì)通過(guò)一個(gè)jobService觸發(fā)對(duì)所有已安裝app的主apk以及插件的dex2oat的編譯秫筏,通常方式為speed-profile诱鞠。在Q之前,因?yàn)镈exFile.loadDex本身會(huì)走編譯流程这敬,所以也不需要上報(bào)之后統(tǒng)一編譯航夺,而Q之后DexFile.loadDex不走編譯流程了,一旦判斷沒(méi)有有效odex崔涂,則走解釋執(zhí)行阳掐。所以依賴(lài)上報(bào)讓系統(tǒng)幫忙來(lái)對(duì)插件做編譯。

注:使用dex2oat進(jìn)行AOT編譯的compile filter:

  • verify:只運(yùn)行 DEX 代碼驗(yàn)證冷蚂。
  • quicken:運(yùn)行 DEX 代碼驗(yàn)證缭保,并優(yōu)化一些 DEX 指令,以獲得更好的解釋器性能蝙茶。
  • speed-profile:運(yùn)行 DEX 代碼驗(yàn)證涮俄,并對(duì)配置文件中列出的方法進(jìn)行 AOT 編譯。
  • speed:運(yùn)行 DEX 代碼驗(yàn)證尸闸,并對(duì)所有方法進(jìn)行 AOT 編譯。

另外 附上系統(tǒng)觸發(fā)dex2oat編譯時(shí)機(jī):

路徑 描述 編譯方式 編譯內(nèi)容
Install 應(yīng)用安裝觸發(fā)的編譯 speed-profile 主apk
ota 系統(tǒng)升級(jí)觸發(fā)的編譯 verify 主apk
loadDex 動(dòng)態(tài)加載插件觸發(fā)的編譯 quicken 插件
postboot 開(kāi)機(jī)1分鐘后孕锄,jobService觸發(fā)的編譯 verify 主apk
idle&charge 同時(shí)滿足充電吮廉、idle狀態(tài)且24小時(shí)內(nèi)只觸發(fā)一次 speed-profile 主apk 和 插件

從這表中也可以明顯看出,Q上取消了load dexFile的插件編譯畸肆,那么現(xiàn)在剩下的唯一的原生的編譯場(chǎng)景為idle&charge了宦芦。

二、dex加載過(guò)程

那么轴脐,我們接下來(lái)詳細(xì)分析下DexFile.loadDex的整體邏輯调卑。

之前先來(lái)了解下編譯相關(guān)文件:

  • .dex 編譯的源文件,符合android虛擬機(jī)規(guī)范的字節(jié)碼文件大咱,APK中包含一個(gè)或多個(gè)classes.dex恬涧。

  • .vdex 存儲(chǔ)預(yù)先驗(yàn)證的dex文件,在有效期內(nèi)能減少不必要的dex文件驗(yàn)證碴巾。

  • .odex 是dex2oat對(duì)dex編譯后的優(yōu)化產(chǎn)物溯捆,節(jié)省從apk中加載dex過(guò)程和編譯優(yōu)化過(guò)程,執(zhí)行速度比解釋執(zhí)行快厦瓢,但是該文件有有效期提揍,失效需要重新編譯

  • .art 對(duì)熱點(diǎn)函數(shù)進(jìn)行對(duì)應(yīng)類(lèi)的預(yù)加載啤月,編譯過(guò)程會(huì)觸發(fā)預(yù)加載,預(yù)加載后的類(lèi)可以直接map到內(nèi)存中使用劳跃,不需要從dex中加載谎仲。

簡(jiǎn)單說(shuō)就是:

  • .vdex 優(yōu)化dex文件的合法性驗(yàn)證。
  • .odex 優(yōu)化dex文件加載刨仑。
  • .art 優(yōu)化類(lèi)加載郑诺。
loadDex流程

這里DexFile就是dex文件的一個(gè)封裝對(duì)象,這里很多細(xì)節(jié)不一一鋪開(kāi)贸人,直接到核心方法OpenDexFilesFromOat,Q之前這個(gè)方法會(huì)通過(guò)MakeUpToDate來(lái)做編譯间景,而Q上去掉了。

這里簡(jiǎn)單梳理下Q上OpenDexFilesFromOat到底做了些什么:

std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat(
    const char* dex_location,
    jobject class_loader,
    jobjectArray dex_elements,
    const OatFile** out_oat_file,
    std::vector<std::string>* error_msgs) {
  ScopedTrace trace(__FUNCTION__);
  CHECK(dex_location != nullptr);
  CHECK(error_msgs != nullptr);
  // Verify we aren't holding the mutator lock, which could starve GC if we
  // have to generate or relocate an oat file.
  Thread* const self = Thread::Current();
  Locks::mutator_lock_->AssertNotHeld(self);
  Runtime* const runtime = Runtime::Current();
  std::unique_ptr<ClassLoaderContext> context;
  // If the class_loader is null there's not much we can do. This happens if a dex files is loaded
  // directly with DexFile APIs instead of using class loaders.
  if (class_loader == nullptr) {
    LOG(WARNING) << "Opening an oat file without a class loader. "
                << "Are you using the deprecated DexFile APIs?";
    context = nullptr;
  } else {
    context = ClassLoaderContext::CreateContextForClassLoader(class_loader, dex_elements);
  }
  //初始化OatFileAssistant
  OatFileAssistant oat_file_assistant(dex_location,
                                      kRuntimeISA,
                                      !runtime->IsAotCompiler(),
                                      only_use_system_oat_files_);
  // Get the oat file on disk.
  //獲取oat file
  std::unique_ptr<const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release());
  VLOG(oat) << "OatFileAssistant(" << dex_location << ").GetBestOatFile()="
           << reinterpret_cast<uintptr_t>(oat_file.get())
            << " (executable=" << (oat_file != nullptr ? oat_file->IsExecutable() : false) << ")";
  const OatFile* source_oat_file = nullptr;
  CheckCollisionResult check_collision_result = CheckCollisionResult::kPerformedHasCollisions;
  std::string error_msg;
  if ((class_loader != nullptr || dex_elements != nullptr) && oat_file != nullptr) {
    // Prevent oat files from being loaded if no class_loader or dex_elements are provided.
   // This can happen when the deprecated DexFile.<init>(String) is called directly, and it
   // could load oat files without checking the classpath, which would be incorrect.
   // Take the file only if it has no collisions, or we must take it because of preopting.
   //odex沖突檢測(cè)
   //是否提供class_loader或dex_elements,同時(shí)檢測(cè)odex文件路徑等艺智。
   check_collision_result = CheckCollision(oat_file.get(), context.get(), /*out*/ &error_msg);
    //odex沖突檢測(cè)通過(guò)
   bool accept_oat_file = AcceptOatFile(check_collision_result);
    if (!accept_oat_file) {
       //如果沒(méi)通過(guò),那么原始dex文件存在也給通過(guò)
     // Failed the collision check. Print warning.
     if (runtime->IsDexFileFallbackEnabled()) {
        if (!oat_file_assistant.HasOriginalDexFiles()) {
          // We need to fallback but don't have original dex files. We have to
         // fallback to opening the existing oat file. This is potentially
         // unsafe so we warn about it.
         accept_oat_file = true;
          LOG(WARNING) << "Dex location " << dex_location << " does not seem to include dex file. "
                      << "Allow oat file use. This is potentially dangerous.";
        } else {
          // We have to fallback and found original dex files - extract them from an APK.
         // Also warn about this operation because it's potentially wasteful.
         LOG(WARNING) << "Found duplicate classes, falling back to extracting from APK : "
                      << dex_location;
          LOG(WARNING) << "NOTE: This wastes RAM and hurts startup performance.";
        }
      } else {
        // TODO: We should remove this. The fact that we're here implies -Xno-dex-file-fallback
       // was set, which means that we should never fallback. If we don't have original dex
       // files, we should just fail resolution as the flag intended.
       if (!oat_file_assistant.HasOriginalDexFiles()) {
          accept_oat_file = true;
        }
        LOG(WARNING) << "Found duplicate classes, dex-file-fallback disabled, will be failing to "
                       " load classes for " << dex_location;
      }
      LOG(WARNING) << error_msg;
    }
    if (accept_oat_file) {
      VLOG(class_linker) << "Registering " << oat_file->GetLocation();
      //source_oat_file 賦值為oat_file 倘要, 并且oat_files_中添加 oat_file
     source_oat_file = RegisterOatFile(std::move(oat_file));
      *out_oat_file = source_oat_file;
    }
  }
  std::vector<std::unique_ptr<const DexFile>> dex_files;
  // Load the dex files from the oat file.
  if (source_oat_file != nullptr) {
    bool added_image_space = false;
    if (source_oat_file->IsExecutable()) {
      ScopedTrace app_image_timing("AppImage:Loading");
      // We need to throw away the image space if we are debuggable but the oat-file source of the
     // image is not otherwise we might get classes with inlined methods or other such things.
     std::unique_ptr<gc::space::ImageSpace> image_space;
      if (ShouldLoadAppImage(check_collision_result,
                             source_oat_file,
                             context.get(),
                             &error_msg)) {
        //加載.art文件
       image_space = oat_file_assistant.OpenImageSpace(source_oat_file);
      }
      if (image_space != nullptr) {
        ScopedObjectAccess soa(self);
        StackHandleScope<1> hs(self);
        Handle<mirror::ClassLoader> h_loader(
            hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader)));
        // Can not load app image without class loader.
       if (h_loader != nullptr) {
          std::string temp_error_msg;
          // Add image space has a race condition since other threads could be reading from the
         // spaces array.
         {
            ScopedThreadSuspension sts(self, kSuspended);
            gc::ScopedGCCriticalSection gcs(self,
                                            gc::kGcCauseAddRemoveAppImageSpace,
                                            gc::kCollectorTypeAddRemoveAppImageSpace);
            ScopedSuspendAll ssa("Add image space");
            runtime->GetHeap()->AddSpace(image_space.get());
          }
          {
            ScopedTrace trace2(StringPrintf("Adding image space for location %s", dex_location));
            //去.art里找dexfile 對(duì)應(yīng)的熱點(diǎn)函數(shù)類(lèi),map到內(nèi)存,并加入到class table
           added_image_space = runtime->GetClassLinker()->AddImageSpace(image_space.get(),
                                                                         h_loader,
                                                                         dex_elements,
                                                                         dex_location,
                                                                         /*out*/&dex_files,
                                                                         /*out*/&temp_error_msg);
          }
          if (added_image_space) {
            // Successfully added image space to heap, release the map so that it does not get
           // freed.
           image_space.release();  // NOLINT b/117926937
           // Register for tracking.
           for (const auto& dex_file : dex_files) {
              dex::tracking::RegisterDexFile(dex_file.get());
            }
          } else {
            LOG(INFO) << "Failed to add image file " << temp_error_msg;
            dex_files.clear();
            {
              ScopedThreadSuspension sts(self, kSuspended);
              gc::ScopedGCCriticalSection gcs(self,
                                              gc::kGcCauseAddRemoveAppImageSpace,
                                              gc::kCollectorTypeAddRemoveAppImageSpace);
              ScopedSuspendAll ssa("Remove image space");
              runtime->GetHeap()->RemoveSpace(image_space.get());
            }
            // Non-fatal, don't update error_msg.
         }
        }
      }
    }
    if (!added_image_space) {
      DCHECK(dex_files.empty());
      //嘗試從odex中加載dex
     dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location);
      // Register for tracking.
     for (const auto& dex_file : dex_files) {
        dex::tracking::RegisterDexFile(dex_file.get());
      }
    }
    if (dex_files.empty()) {
      error_msgs->push_back("Failed to open dex files from " + source_oat_file->GetLocation());
    } else {
      // Opened dex files from an oat file, madvise them to their loaded state.
      for (const std::unique_ptr<const DexFile>& dex_file : dex_files) {
         OatDexFile::MadviseDexFile(*dex_file, MadviseState::kMadviseStateAtLoad);
       }
    }
  }
  // Fall back to running out of the original dex file if we couldn't load any
  // dex_files from the oat file.
  if (dex_files.empty()) {
    //前面都沒(méi)加載dexfile,那么直接嘗試加載原始dex文件十拣。
   if (oat_file_assistant.HasOriginalDexFiles()) {
      if (Runtime::Current()->IsDexFileFallbackEnabled()) {
        static constexpr bool kVerifyChecksum = true;
        const ArtDexFileLoader dex_file_loader;
        if (!dex_file_loader.Open(dex_location,
                                  dex_location,
                                  Runtime::Current()->IsVerificationEnabled(),
                                  kVerifyChecksum,
                                  /*out*/ &error_msg,
                                  &dex_files)) {
          LOG(WARNING) << error_msg;
          error_msgs->push_back("Failed to open dex files from " + std::string(dex_location)
                                + " because: " + error_msg);
        }
      } else {
        error_msgs->push_back("Fallback mode disabled, skipping dex files.");
      }
    } else {
      error_msgs->push_back("No original dex files found for dex location "
         + std::string(dex_location));
    }
  }
  if (Runtime::Current()->GetJit() != nullptr) {
    ScopedObjectAccess soa(self);
    Runtime::Current()->GetJit()->RegisterDexFiles(
        dex_files, soa.Decode<mirror::ClassLoader>(class_loader));
  }
  return dex_files;
}

這里代碼挺長(zhǎng)封拧,但是總結(jié)起來(lái)就加載dex文件到內(nèi)存,并且初始化好DexFile返回夭问。而加載dex文件的邏輯是:

.art > odex > 從原始dex文件

.art在編譯期做了熱點(diǎn)函數(shù)對(duì)應(yīng)類(lèi)的預(yù)加載泽西,保存了內(nèi)存image,如果它有則通過(guò)classlinker.AddImageSpace直接將image 加載到內(nèi)存缰趋,省略類(lèi)加載過(guò)程捧杉,直接加入class table。找不到退而求其次秘血,執(zhí)行oat_file_assistant.LoadDexFiles味抖,從odex加載dex,因?yàn)閛dex中保存了一個(gè)優(yōu)化后的dex,都不行最后嘗試從原始dex文件中加載灰粮,如果此時(shí)vdex還有效仔涩,則能繞過(guò)dex驗(yàn)證過(guò)程,否則需要重新驗(yàn)證。

三粘舟、Apk加載過(guò)程與Tinker動(dòng)態(tài)加載dex補(bǔ)丁包整體順序流程

dex補(bǔ)丁包加載是在TinkerApplication的onBaseContextAttached中通過(guò)TinkerLoader.tryLoad觸發(fā)的熔脂,而TinkerApplication是繼承Application的。

那么在應(yīng)用啟動(dòng)流程中柑肴,application初始化流程是怎樣的:

應(yīng)用冷啟動(dòng)ActivityThread初始化以及attach Application過(guò)程

這里借用我之前文章一張圖霞揉,雖然是android 4.4的,但是基本流程沒(méi)有什么變化晰骑。

handleBindApplication : 通過(guò)getPackageInfo初始化LoadApk零聚,初始化Instrumentation,通過(guò)makeApplication加載主apk, 然后初始化Application類(lèi),最后通過(guò)Instrumentation執(zhí)行Application的onCreate回調(diào)隶症。

也就是說(shuō)政模,當(dāng)tinker熱修復(fù)合成patch包,應(yīng)用程序進(jìn)程被kill重啟之后蚂会,走冷啟動(dòng)流程(冷啟動(dòng)詳細(xì)流程可以參考之前文章:深入剖析應(yīng)用啟動(dòng)流程)淋样,先通過(guò)LoadedApk加載之前出現(xiàn)bug的基準(zhǔn)apk,然后加載application并初始化胁住,初始化過(guò)程走application的onBaseContextAttached生命周期趁猴,Tinker通過(guò)TinkerLoader.tryLoad來(lái)走補(bǔ)丁包加載,加載原理便是hook DexPathList 將修復(fù)的合成dex添加到Elements[]頭部彪见。

寫(xiě)在最后的注意點(diǎn):

Android Q之前儡司,DexFile.loadDex會(huì)先判斷是否需要走編譯邏輯,如果要走余指,則需要同步等待編譯完才能執(zhí)行后續(xù)流程捕犬,因此如果合成后的patch包tinker_classN.apk太大的話,dex補(bǔ)丁加載過(guò)程會(huì)出現(xiàn)明顯卡頓酵镜,這個(gè)問(wèn)題系統(tǒng)層不太好去規(guī)避碉碉。

舉個(gè)比較典型的場(chǎng)景,比如系統(tǒng)ota升級(jí)之后淮韭,首次啟動(dòng)app加載熱修復(fù)包垢粮,必然會(huì)走編譯,因?yàn)閛ta升級(jí)之后odex失效需要重新編譯靠粪。那么系統(tǒng)層要想優(yōu)化主要有兩個(gè)思路:1)預(yù)編譯蜡吧;2)不同步等待編譯,先走解釋模式占键;

1)預(yù)編譯:在ota升級(jí)過(guò)程中或者升級(jí)完重啟手機(jī)過(guò)程中找個(gè)時(shí)機(jī)去將插件編譯一下昔善,這有個(gè)問(wèn)題就是:tinker_classN.apk存放的目錄是在data/data/packageName 應(yīng)用私有目錄下,這個(gè)目錄在鎖屏解鎖前捞慌,內(nèi)的文件都是按FBE加密的,系統(tǒng)沒(méi)法獲取有效解密文件柬批。但是如果鎖屏解鎖之后再做預(yù)加載顯然有點(diǎn)晚了啸澡。

2)不同步等待編譯,先走解釋模式: 這種便是google 在Android Q上的優(yōu)化氮帐,但是需要配合dexpath上報(bào)嗅虏,來(lái)讓系統(tǒng)在idle時(shí)進(jìn)行插件編譯,這也是原生唯一能編譯插件的地方上沐,但是這種場(chǎng)景觸發(fā)太不及時(shí)了皮服,因此可以增加一些場(chǎng)景來(lái)及時(shí)觸發(fā)插件編譯,保證下次應(yīng)用使用更快。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載龄广,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者硫眯。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市择同,隨后出現(xiàn)的幾起案子两入,更是在濱河造成了極大的恐慌,老刑警劉巖敲才,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件裹纳,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡紧武,警方通過(guò)查閱死者的電腦和手機(jī)剃氧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)阻星,“玉大人朋鞍,你說(shuō)我怎么就攤上這事∑群幔” “怎么了番舆?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)矾踱。 經(jīng)常有香客問(wèn)我恨狈,道長(zhǎng),這世上最難降的妖魔是什么呛讲? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任禾怠,我火速辦了婚禮,結(jié)果婚禮上贝搁,老公的妹妹穿的比我還像新娘吗氏。我一直安慰自己,他們只是感情好雷逆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布弦讽。 她就那樣靜靜地躺著,像睡著了一般膀哲。 火紅的嫁衣襯著肌膚如雪往产。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,624評(píng)論 1 305
  • 那天某宪,我揣著相機(jī)與錄音仿村,去河邊找鬼。 笑死兴喂,一個(gè)胖子當(dāng)著我的面吹牛蔼囊,可吹牛的內(nèi)容都是我干的焚志。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼畏鼓,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼酱酬!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起滴肿,我...
    開(kāi)封第一講書(shū)人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤岳悟,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后泼差,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體贵少,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年堆缘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了滔灶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吼肥,死狀恐怖录平,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缀皱,我是刑警寧澤斗这,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站啤斗,受9級(jí)特大地震影響表箭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜钮莲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一免钻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧崔拥,春花似錦极舔、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至慈俯,卻和暖如春渤刃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肥卡。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工溪掀, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留事镣,地道東北人步鉴。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓揪胃,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親氛琢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子喊递,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355