LeakCanary原理詳解

screenshot.png

Android內(nèi)存泄漏一直是困擾我們Android開發(fā)者的一個(gè)心病治力,由于開發(fā)人員對(duì)于知識(shí)掌握的不夠深入或者代碼的不夠規(guī)范牡整,稍不注意就會(huì)導(dǎo)致代碼出現(xiàn)內(nèi)存泄漏短条。那么怎么解決內(nèi)存泄漏呢?

1流礁、自查渠欺,在AndroidStudio中打開Monitors妹蔽,Dump Java Heap,然后通過MAT分析工具分析hprof文件挠将,查找出內(nèi)存泄漏的地方胳岂。
2、集成Square的LeakCanary舔稀,讓LeakCanary這個(gè)第三方庫幫我們查找內(nèi)存泄漏的地方乳丰。

本文集成LeakCanary并且分析LeakCanary的源碼,看完之后就能明白LeakCanary檢測(cè)內(nèi)存泄漏的原理内贮,做到知其然并知其所以然产园。

使用:

LeakCanary使用起來很簡(jiǎn)單,首先在gradle中添加依賴:

debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'

leakcanary-android-no-op這個(gè)module是用于release版本的夜郁,因?yàn)?LeakCanary 會(huì)不斷 GC 導(dǎo)致stop the world什燕, 影響到 app 的運(yùn)行,這在release版本是不允許的竞端,因此我們只能用在debug階段秋冰。該module只有一個(gè)LeakCanary,RefWatcher婶熬,BuildConfig這三個(gè)基礎(chǔ)類剑勾,當(dāng)然也是什么都不做的埃撵。添加完依賴后在我們的application里添加如下代碼即可。

public class ExampleApplication extends Application {

 @Override 
public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {
      // This process is dedicated to LeakCanary for heap analysis.
      // You should not init your app in this process.
      return;
    }
     LeakCanary.install(this);
    // Normal app init code...
 }
}

源碼分析:

先看入口LeakCanary.java中的install(this)

/**
* Creates a {@link RefWatcher} that works out of the box, and starts watching activity
* references (on ICS+).
*/
public static RefWatcher install(Application application) {
 return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
     .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
     .buildAndInstall();
 }

這個(gè)代碼的引用鏈比較長(zhǎng)虽另,一層一層看暂刘,先看結(jié)果,該方法返回一個(gè)RefWatcher捂刺,RefWatcher用來監(jiān)視一個(gè)引用的可達(dá)情況谣拣。

/** Builder to create a customized {@link RefWatcher} with appropriate Android defaults. */
  public static AndroidRefWatcherBuilder refWatcher(Context context) {
    return new AndroidRefWatcherBuilder(context);
  }

refWatcher()方法返回一個(gè)AndroidRefWatcherBuilder,該對(duì)象繼承自RefWatcherBuilder族展,用來通過建造者模式構(gòu)建一個(gè)RefWatcher森缠。

AndroidRefWatcherBuilder.java

/**
* Sets a custom {@link AbstractAnalysisResultService} to listen to analysis results. This
* overrides any call to {@link #heapDumpListener(HeapDump.Listener)}.
*/
public AndroidRefWatcherBuilder listenerServiceClass(
  Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}

注意listenerServiceClass()方法的參數(shù)AbstractAnalysisResultService,這個(gè)類是一個(gè)繼承自IntentService的抽象類仪缸,用來處理HeapDump結(jié)果贵涵,在上面的代碼中傳入了一個(gè)DisplayLeakService,DisplayLeakService繼承自AbstractAnalysisResultService恰画。記住這個(gè)類宾茂,后面會(huì)說到。

有時(shí)候我們需要忽略一些特殊情況下的內(nèi)存泄漏比如SDK或者制造商導(dǎo)致的內(nèi)存泄漏拴还,這些內(nèi)存泄漏我們并不需要顯示出來跨晴,那么剛好AndroidExcludedRefs這個(gè)枚舉就派上用場(chǎng)了。

public static ExcludedRefs.Builder createAppDefaults() {
 return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class));
}

public static ExcludedRefs.Builder createBuilder(EnumSet<AndroidExcludedRefs> refs) {
ExcludedRefs.Builder excluded = ExcludedRefs.builder();
for (AndroidExcludedRefs ref : refs) {
  if (ref.applies) {
    ref.add(excluded);
    ((ExcludedRefs.BuilderWithParams) excluded).named(ref.name());
  }
}
return excluded;
}

當(dāng)我們需要忽略一些我們自己已知的內(nèi)存泄漏時(shí)片林,可以創(chuàng)建一個(gè)EnumSet并且調(diào)用createBuilder方法端盆,這樣LeakCanary就不會(huì)處理這些內(nèi)存泄漏。

最后到了關(guān)鍵一個(gè)方法:

/**
* Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).
*/
public RefWatcher buildAndInstall() {
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
    LeakCanary.enableDisplayLeakActivity(context);
  ActivityRefWatcher.install((Application) context, refWatcher);
}
return refWatcher;
}

首先通過build模式構(gòu)造一個(gè)RefWatcher费封,然后調(diào)用LeakCanary.enableDisplayLeakActivity(context)這句啟用DisplayLeakActivity焕妙,代碼如下:

public static void enableDisplayLeakActivity(Context context) {
    setEnabled(context, DisplayLeakActivity.class, true);
}

public static void setEnabled(Context context, final Class<?> componentClass,
  final boolean enabled) {
final Context appContext = context.getApplicationContext();
executeOnFileIoThread(new Runnable() {
  @Override public void run() {
    setEnabledBlocking(appContext, componentClass, enabled);
    }
  });
}

public static void setEnabledBlocking(Context appContext, Class<?> componentClass,
  boolean enabled) {
ComponentName component = new ComponentName(appContext, componentClass);
PackageManager packageManager = appContext.getPackageManager();
int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
// Blocks on IPC.
packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
}

注意DisplayLeakActivity是LeakCanary這個(gè)庫自己的Activity,用來創(chuàng)建PendingIntent以便發(fā)送通知并且在這個(gè)Activity里展示內(nèi)存泄漏信息孝偎。

接著上面繼續(xù)分析访敌,之后調(diào)用ActivityRefWatcher.install((Application) context, refWatcher)。

public final class ActivityRefWatcher {

/** @deprecated Use {@link #install(Application, RefWatcher)}. */
@Deprecated
public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
 install(application, refWatcher);
}

public static void install(Application application, RefWatcher refWatcher) {
    new ActivityRefWatcher(application, refWatcher).watchActivities();
}

private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new Application.ActivityLifecycleCallbacks() {
       @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
       }

    @Override public void onActivityStarted(Activity activity) {
    }

    @Override public void onActivityResumed(Activity activity) {
    }

    @Override public void onActivityPaused(Activity activity) {
    }

    @Override public void onActivityStopped(Activity activity) {
    }

    @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
    }

    @Override public void onActivityDestroyed(Activity activity) {
      ActivityRefWatcher.this.onActivityDestroyed(activity);
    }
  };

private final Application application;
private final RefWatcher refWatcher;

/**
* Constructs an {@link ActivityRefWatcher} that will make sure the activities are not leaking
* after they have been destroyed.
*/
public ActivityRefWatcher(Application application, RefWatcher refWatcher) {
    this.application = checkNotNull(application, "application");
    this.refWatcher = checkNotNull(refWatcher, "refWatcher");
 }

void onActivityDestroyed(Activity activity) {
 refWatcher.watch(activity);
}

public void watchActivities() {
    // Make sure you don't get installed twice.
    stopWatchingActivities();
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
 }

public void stopWatchingActivities() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
}
}

代碼其實(shí)很簡(jiǎn)單衣盾,通過application注冊(cè)ActivityLifecycleCallbacks寺旺,監(jiān)聽每個(gè)Activity的生命周期方法并在onDestroy里開始檢測(cè)activity的引用。

接著我們看看他是怎么watch的势决。

public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
  return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
    new KeyedWeakReference(watchedReference, key, referenceName, queue);

ensureGoneAsync(watchStartNanoTime, reference);
}

在分析之前先說一下WeakReference和ReferenceQueue阻塑。WeakReference表示弱引用,當(dāng)一個(gè)對(duì)象只被弱引用所指向而沒有被強(qiáng)引用或者軟引用所引用果复,那么當(dāng)GC運(yùn)行的時(shí)候陈莽,該對(duì)象就會(huì)被GC回收。并且同時(shí)系統(tǒng)會(huì)把該引用加入到與該WeakReference關(guān)聯(lián)的一個(gè)引用隊(duì)列中去,也就是這個(gè)ReferenceQueue中走搁。當(dāng)然前提是你創(chuàng)建該WeakReference的時(shí)候給其關(guān)聯(lián)了ReferenceQueue独柑。

回到代碼中去,上面代碼首先通過UUID.randomUUID生成一個(gè)唯一標(biāo)識(shí)符用來表示這個(gè)KeyedWeakReference的key私植,并把其加入一個(gè)Set集合中忌栅,然后再創(chuàng)建一個(gè)KeyedWeakReference并把需要監(jiān)控的object和引用隊(duì)列關(guān)聯(lián)到這個(gè)KeyedWeakReference,KeyedWeakReference繼承自WeakReference曲稼。最后調(diào)用ensureGoneAsync()方法索绪。

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
  @Override public Retryable.Result run() {
    return ensureGone(reference, watchStartNanoTime);
  }
});
}

上面代碼執(zhí)行WatchExecutor的execute()方法,真正實(shí)現(xiàn)WatchExecutor接口的是AndroidWatchExecutor類贫悄,在這個(gè)類里面給主線程的消息隊(duì)列添加一個(gè)IdleHandler瑞驱,當(dāng)主線程空閑沒有消息的時(shí)候就執(zhí)行execute()方法,而在上面的execute()方法里面則調(diào)用ensureGone()方法窄坦。

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

   removeWeaklyReachableReferences();

    if (debuggerControl.isDebuggerAttached()) {
    // The debugger can create false leaks.
    return RETRY;
    }
    if (gone(reference)) {
    return DONE;
    }
    gcTrigger.runGc();
    removeWeaklyReachableReferences();
    if (!gone(reference)) {
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

      File heapDumpFile = heapDumper.dumpHeap();
      if (heapDumpFile == RETRY_LATER) {
        // Could not dump the heap.
        return RETRY;
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
      heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));
    }
     return DONE;
}

在這個(gè)方法中首先調(diào)用removeWeaklyReachableReferences()方法檢查引用隊(duì)列ReferenceQueue中是否已經(jīng)包含了該object引用對(duì)象唤反,因?yàn)槿绻鸕eferenceQueue中有了這個(gè)對(duì)象的引用則說明這個(gè)對(duì)象已經(jīng)被GC回收了,那么就不存在內(nèi)存溢出這一現(xiàn)象嫡丙,調(diào)用一次這個(gè)方法之后再調(diào)用gcTrigger.runGc()執(zhí)行一次GC拴袭,然后再次調(diào)用removeWeaklyReachableReferences這個(gè)方法檢查ReferenceQueue中是否有這個(gè)對(duì)象读第,這樣兩次檢查引用隊(duì)列中的對(duì)象之后如果發(fā)現(xiàn)引用隊(duì)列中還沒有這個(gè)引用對(duì)象則說明泄漏了曙博。內(nèi)存泄漏了怎么辦呢?執(zhí)行heapDumper.dumpHeap()方法讓其生成一個(gè)文件怜瞒。同樣實(shí)現(xiàn)heapDumper接口的是AndroidHeapDumper父泳,然后看看他的dumpHeap()方法。

@Override 
public File dumpHeap() {
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
if (heapDumpFile == RETRY_LATER) {
  return RETRY_LATER;
}
FutureResult<Toast> waitingForToast = new FutureResult<>();
showToast(waitingForToast);

if (!waitingForToast.wait(5, SECONDS)) {
  CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
  return RETRY_LATER;
}
Toast toast = waitingForToast.get();
try {
  Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
  cancelToast(toast);
  return heapDumpFile;
} catch (Exception e) {
  CanaryLog.d(e, "Could not dump heap");
  // Abort heap dump
  return RETRY_LATER;
  }
}

上面代碼中首先通過LeakDirectoryProvider生成一個(gè)文件吴汪,實(shí)現(xiàn)LeakDirectoryProvider接口的是DefaultLeakDirectoryProvider惠窄,來看看其的newHeapDumpFile()方法:

  @Override 
public File newHeapDumpFile() {
List<File> pendingHeapDumps = listFiles(new FilenameFilter() {
  @Override public boolean accept(File dir, String filename) {
    return filename.endsWith(PENDING_HEAPDUMP_SUFFIX);
  }
});

for (File file : pendingHeapDumps) {
  if (System.currentTimeMillis() - file.lastModified() < ANALYSIS_MAX_DURATION_MS) {
    CanaryLog.d("Could not dump heap, previous analysis still is in progress.");
    return RETRY_LATER;
  }
}
cleanupOldHeapDumps();
File storageDirectory = externalStorageDirectory();
if (!directoryWritableAfterMkdirs(storageDirectory)) {
  if (!hasStoragePermission()) {
    CanaryLog.d("WRITE_EXTERNAL_STORAGE permission not granted");
    requestWritePermissionNotification();
  } else {
    String state = Environment.getExternalStorageState();
    if (!Environment.MEDIA_MOUNTED.equals(state)) {
      CanaryLog.d("External storage not mounted, state: %s", state);
    } else {
      CanaryLog.d("Could not create heap dump directory in external storage: [%s]",
          storageDirectory.getAbsolutePath());
    }
  }
  // Fallback to app storage.
  storageDirectory = appStorageDirectory();
  if (!directoryWritableAfterMkdirs(storageDirectory)) {
    CanaryLog.d("Could not create heap dump directory in app storage: [%s]",
        storageDirectory.getAbsolutePath());
    return RETRY_LATER;
  }
}
return new File(storageDirectory, UUID.randomUUID().toString() + PENDING_HEAPDUMP_SUFFIX);
}

我們可以看到都是一些對(duì)文件的操作,創(chuàng)建一個(gè)以.hprof結(jié)尾的文件并返回漾橙。

繼續(xù)接著分析上面的dumpHeap()方法杆融,創(chuàng)建完文件后調(diào)用Debug.dumpHprofData()方法把heap信息寫入文件中。

再回到上面的ensureGone()方法霜运,當(dāng)把heap信息寫入文件之后脾歇,調(diào)用heapdumpListener.analyze()方法進(jìn)行分析。而實(shí)現(xiàn)了heapdumpListener接口的是ServiceHeapDumpListener類淘捡,來看看其的analyze()方法:

@Override 
public void analyze(HeapDump heapDump) {
    checkNotNull(heapDump, "heapDump");
    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}

執(zhí)行HeapAnalyzerService.runAnalysis方法藕各,這個(gè)方法第三個(gè)參數(shù)listenerServiceClass就是我們之前在上面創(chuàng)建AndroidRefWatcherBuilder時(shí)提到的DisplayLeakService,而HeapAnalyzerService是一個(gè)運(yùn)行在單獨(dú)進(jìn)程中的IntentService:

public final class HeapAnalyzerService extends IntentService {
private static final String LISTENER_CLASS_EXTRA = "listener_class_extra";
private static final String HEAPDUMP_EXTRA = "heapdump_extra";

public static void runAnalysis(Context context, HeapDump heapDump,
  Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
Intent intent = new Intent(context, HeapAnalyzerService.class);
intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
intent.putExtra(HEAPDUMP_EXTRA, heapDump);
context.startService(intent);
}

public HeapAnalyzerService() {
    super(HeapAnalyzerService.class.getSimpleName());
}

@Override protected void onHandleIntent(Intent intent) {
    if (intent == null) {
      CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
      return;
 }
    String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
    HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
}

在runAnalysis()方法里啟動(dòng)自己焦除,然后在onHandleIntent方法里調(diào)用heapAnalyzer.checkForLeak()方法進(jìn)行分析激况。

public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
    long analysisStartNanoTime = System.nanoTime();
    if (!heapDumpFile.exists()) {
     Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
    return failure(exception, since(analysisStartNanoTime));
    }

 try {
     HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
     HprofParser parser = new HprofParser(buffer);
     Snapshot snapshot = parser.parse();
     deduplicateGcRoots(snapshot);

     Instance leakingRef = findLeakingReference(referenceKey, snapshot);
     // False alarm, weak reference was cleared in between key check and heap dump.
     if (leakingRef == null) {
      return noLeak(since(analysisStartNanoTime));
     }

    return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
    }

這個(gè)方法主要就是依靠Square的另一個(gè)庫haha來解析heap數(shù)據(jù),HprofParser是haha庫的解析器對(duì)象,解析hprof文件并生成Snapshot快照乌逐,然后找到該實(shí)例到GCroot的最短強(qiáng)引用路徑竭讳,并生成AnalysisResult對(duì)象≌闾撸回到上面的代碼代咸,生成AnalysisResult對(duì)象后回調(diào)到AbstractAnalysisResultService。我們知道AbstractAnalysisResultService是一個(gè)繼承自IntentService的抽象類成黄,在他的onHandleIntent()方法里調(diào)用抽象方法onHeapAnalyzed()并刪除了hprof文件呐芥,子類DisplayLeakService重寫了onHeapAnalyzed()方法,

@Override 
protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
    String leakInfo = leakInfo(this, heapDump, result, true);
    CanaryLog.d("%s", leakInfo);
 boolean resultSaved = false;
 boolean shouldSaveResult = result.leakFound || result.failure != null;
 if (shouldSaveResult) {
     heapDump = renameHeapdump(heapDump);
    resultSaved = saveResult(heapDump, result);
 }

 PendingIntent pendingIntent;
 String contentTitle;
    String contentText;

    if (!shouldSaveResult) {
    contentTitle = getString(R.string.leak_canary_no_leak_title);
    contentText = getString(R.string.leak_canary_no_leak_text);
    pendingIntent = null;
    } else if (resultSaved) {
    pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);

    if (result.failure == null) {
    String size = formatShortFileSize(this, result.retainedHeapSize);
     String className = classSimpleName(result.className);
     if (result.excludedLeak) {
         contentTitle = getString(R.string.leak_canary_leak_excluded, className, size);
        } else {
         contentTitle = getString(R.string.leak_canary_class_has_leaked, className, size);
        }
    } else {
     contentTitle = getString(R.string.leak_canary_analysis_failed);
    }
    contentText = getString(R.string.leak_canary_notification_message);
    } else {
    contentTitle = getString(R.string.leak_canary_could_not_save_title);
    contentText = getString(R.string.leak_canary_could_not_save_text);
    pendingIntent = null;
    }
    // New notification id every second.
    int notificationId = (int) (SystemClock.uptimeMillis() / 1000);
    showNotification(this, contentTitle, contentText, pendingIntent, notificationId);
    afterDefaultHandling(heapDump, result, leakInfo);
}

我們看到該方法通過DisplayLeakActivity生成了一個(gè)PendingIntent奋岁,構(gòu)建一個(gè)Notification發(fā)出來思瘟。最后調(diào)用afterDefaultHandling()方法,這是一個(gè)空方法闻伶,我們可以重寫這個(gè)方法進(jìn)行自己的一些操作滨攻,比如把泄漏信息上傳到我們自己的服務(wù)器等等。

整個(gè)LeakCanary總算是分析完了蓝翰,我們來總結(jié)一下光绕,首先設(shè)置lifecycleCallbacks監(jiān)聽Activity的onDestroy()方法,然后創(chuàng)建KeyedWeakReference并啟動(dòng)GC畜份,利用WeakReference和ReferenceQueue的機(jī)制判斷Activity是否有泄漏诞帐,如果已經(jīng)泄漏則dump heap并生成一個(gè)hprof文件并解析這個(gè)文件,得到泄漏對(duì)象到GC根的最短路徑爆雹,得到這個(gè)路徑后發(fā)一個(gè)Notification展示出來即可停蕉。

參考:

LeakCanary
LeakCanary原理分析

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市钙态,隨后出現(xiàn)的幾起案子慧起,更是在濱河造成了極大的恐慌,老刑警劉巖册倒,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蚓挤,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡驻子,警方通過查閱死者的電腦和手機(jī)灿意,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拴孤,“玉大人脾歧,你說我怎么就攤上這事⊙菔欤” “怎么了鞭执?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵司顿,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我兄纺,道長(zhǎng)大溜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任估脆,我火速辦了婚禮钦奋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘疙赠。我一直安慰自己付材,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布圃阳。 她就那樣靜靜地躺著厌衔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捍岳。 梳的紋絲不亂的頭發(fā)上富寿,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音锣夹,去河邊找鬼页徐。 笑死,一個(gè)胖子當(dāng)著我的面吹牛银萍,可吹牛的內(nèi)容都是我干的变勇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼砖顷,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼贰锁!你這毒婦竟也來了赃梧?” 一聲冷哼從身側(cè)響起滤蝠,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎授嘀,沒想到半個(gè)月后物咳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(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,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡压鉴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出锻拘,到底是詐尸還是另有隱情油吭,我是刑警寧澤击蹲,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站婉宰,受9級(jí)特大地震影響歌豺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜心包,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一类咧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蟹腾,春花似錦痕惋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至珊随,卻和暖如春述寡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背叶洞。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工鲫凶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人衩辟。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓螟炫,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親艺晴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子昼钻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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