源碼分析 - LeakCanary

1、使用

dependencies {
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'
  releaseImplementation   'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'
  // 可選颅围,如果你使用支持庫的fragments的話
  debugImplementation   'com.squareup.leakcanary:leakcanary-support-fragment:1.6.2'
}

public class MyApplication extends Application {

    private RefWatcher refWatcher;
    @Override
    public void onCreate() {
        super.onCreate();
        refWatcher = setRefWatcher();

    }

    private RefWatcher setRefWatcher(){
        if (LeakCanary.isInAnalyzerProcess(this)) {
            return RefWatcher.DISABLED;
        }
        return LeakCanary.install(this);
    }

    public static RefWatcher getRefWathcher(Context context){
        MyApplication myApplication = (MyApplication) context.getApplicationContext();
        return myApplication.refWatcher;
    }
}

這樣使用只會檢測Activity和標(biāo)準(zhǔn)Fragment是否發(fā)生內(nèi)存泄漏,如果要檢測V4包的Fragment在執(zhí)行完onDestroy()之后是否發(fā)生內(nèi)存泄露的話砚嘴,則需要在Fragment的onDestroy()方法中加上如下兩行代碼去監(jiān)視當(dāng)前的Fragment:

RefWatcher refWatcher = WanAndroidApp.getRefWatcher(_mActivity);
refWatcher.watch(this);

源碼分析

流程:

  1. 監(jiān)聽哪些類的堆內(nèi)存泄露情況
  2. 如何找到泄露的對象或類
  3. 找到泄露后如何找到導(dǎo)致泄露的引用鏈
  4. 找到引用鏈后將引用鏈拋出來寫入到泄露日志中

首先我們從LeakCanary.install()開始

LeakCanary.install():

  public static @NonNull RefWatcher install(@NonNull Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }
  • refWatcher(application) : 創(chuàng)建一個AndroidRefWatcherBuilder

  • listenerServiceClass(DisplayLeakService.class) : 傳入了一個HeapDump.Listener桩盲,HeapDump.Listener中的一個參數(shù)是DisplayLeakService,DisplayLeakService的作用是展示泄露分析的結(jié)果日志湃番,然后會展示一個用于跳轉(zhuǎn)到顯示泄露界面DisplayLeakActivity的通知夭织。

  • excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) :
    過濾那些系統(tǒng)或者制造商導(dǎo)致的泄露吭露,這些泄露是我們開發(fā)者無法處理的泄露

  • buildAndInstall() : 創(chuàng)建一個RefWatcher吠撮,并且開始觀察Activity和Fragment。
    注意,這里如果在一個進(jìn)程調(diào)用多次會拋出UnsupportedOperationException異常

AndroidExcludedRefs 類
它是一個enum類泥兰,它聲明了Android SDK和廠商定制的SDK中存在的內(nèi)存泄露的case弄屡,根據(jù)AndroidExcludedRefs這個類的類名就可看出這些case都會被Leakcanary的監(jiān)測過濾掉。目前這個版本是有46種這樣的case被包含在內(nèi)鞋诗,后續(xù)可能會一直增加膀捷。

這里我們來說一下如何觀察Activity和Fragment的:

  public @NonNull RefWatcher buildAndInstall() {
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
      if (watchActivities) {
        ActivityRefWatcher.install(context, refWatcher);
      }
      if (watchFragments) {
        FragmentRefWatcher.Helper.install(context, refWatcher);
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }

我們看到,分別調(diào)用了ActivityRefWatcher.install(context, refWatcher);和FragmentRefWatcher.Helper.install(context, refWatcher);

我們先來看Activity:

public final class ActivityRefWatcher {
    //...
    
    public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
        Application application = (Application) context.getApplicationContext();
        ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);

        application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
  }

  private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new ActivityLifecycleCallbacksAdapter() {
        @Override public void onActivityDestroyed(Activity activity) {
          refWatcher.watch(activity);
        }
      };
}

這里可以看到削彬,ActivityRefWatcher類中的install方法中創(chuàng)建了一個ActivityRefWatcher全庸,并且注冊了它的Lifecycle,在onActivityDestroyed時調(diào)用了refWatcher.watch(activity);來進(jìn)行觀察

Fragment 的觀察基本一致:

public interface FragmentRefWatcher {
  final class Helper {
  
    public static void install(Context context, RefWatcher refWatcher) {
      List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();

      if (SDK_INT >= O) {
        fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
      }

      try {
        Class<?> fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);
        Constructor<?> constructor =
            fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);
        FragmentRefWatcher supportFragmentRefWatcher =
            (FragmentRefWatcher) constructor.newInstance(refWatcher);
        fragmentRefWatchers.add(supportFragmentRefWatcher);
      } catch (Exception ignored) {
      }

      if (fragmentRefWatchers.size() == 0) {
        return;
      }

      Helper helper = new Helper(fragmentRefWatchers);

      Application application = (Application) context.getApplicationContext();
      application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
    }

    private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
        new ActivityLifecycleCallbacksAdapter() {
          @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            for (FragmentRefWatcher watcher : fragmentRefWatchers) {
              watcher.watchFragments(activity);
            }
          }
        };
  
    //...
  }
}

這里面的邏輯很簡單融痛,首先在將Android標(biāo)準(zhǔn)的Fragment的RefWatcher類壶笼,即AndroidOfFragmentRefWatcher添加到新創(chuàng)建的fragmentRefWatchers中。在使用反射將leakcanary-support-fragment包下面的SupportFragmentRefWatcher添加進(jìn)來雁刷,如果你在app的build.gradle下沒有添加下面這行引用的話覆劈,則會拿不到此類,即LeakCanary只會檢測Activity和標(biāo)準(zhǔn)Fragment這兩種情況沛励。

watcher.watchFragments(activity);最終調(diào)用到了:

@Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
    View view = fragment.getView();
    if (view != null) {
        refWatcher.watch(view);
    }
}

@Override
public void onFragmentDestroyed(FragmentManagerfm, Fragment fragment) {
    refWatcher.watch(fragment);
}

可以看到责语,不管是activity還是fragment,最后都調(diào)用到了watch中目派,我們來看下watch(activity)方法中做了什么:

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);
}
  • 為每個reference創(chuàng)建一個唯一的UUID坤候,把這個UUID放入到set集合retainedKeys中。
  • 為watchedReference對象創(chuàng)建一個弱引用KeyedWeakReference
  • 調(diào)用ensureGoneAsync(watchStartNanoTime, reference);

我們先看看KeyedWeakReference中做了什么:

final class KeyedWeakReference extends WeakReference<Object> {
  public final String key;
  public final String name;

  KeyedWeakReference(Object referent, String key, String name,
      ReferenceQueue<Object> referenceQueue) {
    //1址貌、
    super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
    this.key = checkNotNull(key, "key");
    this.name = checkNotNull(name, "name");
  }
}

在super中铐拐,將referent注冊到queue中,這里說一下练对,在我們創(chuàng)建軟引用遍蟋、弱引用時,可以聲明一個ReferenceQueue:

public WeakReference(T referent, ReferenceQueue<? super T> q) {
    super(referent, q);
}

在KeyedWeakReference內(nèi)部螟凭,使用了key和name標(biāo)識了一個被檢測的WeakReference對象虚青。在注釋1處,將弱引用和引用隊(duì)列 ReferenceQueue 關(guān)聯(lián)起來螺男,如果弱引用reference持有的對象被GC回收棒厘,JVM就會把這個弱引用加入到與之關(guān)聯(lián)的引用隊(duì)列referenceQueue中。即 KeyedWeakReference 持有的 Activity 對象如果被GC回收下隧,該對象就會加入到引用隊(duì)列 referenceQueue 中奢人。

如果我們在創(chuàng)建一個引用對象時,指定了ReferenceQueue淆院,那么當(dāng)引用對象指向的對象達(dá)到合適的狀態(tài)(根據(jù)引用類型不同而不同)時何乎,GC 會把引用對象本身添加到這個隊(duì)列中,方便我們處理它,因?yàn)椤耙脤ο笾赶虻膶ο?GC 會自動清理支救,但是引用對象本身也是對象(是對象就占用一定資源)抢野,所以需要我們自己清理「髂”

參考:Reference 指孤、ReferenceQueue 詳解

再來看看ensureGoneAsync(watchStartNanoTime, reference):

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

通過異步調(diào)用ensureGone(reference, watchStartNanoTime);,watchExecutor我們往回翻找可以發(fā)現(xiàn)其實(shí)是AndroidWatchExecutor贬堵,來看下AndroidWatchExecutor:

public final class AndroidWatchExecutor implements WatchExecutor {

  static final String LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump";
  private final Handler mainHandler;
  private final Handler backgroundHandler;
  private final long initialDelayMillis;
  private final long maxBackoffFactor;

  //1恃轩、
  public AndroidWatchExecutor(long initialDelayMillis) {
    mainHandler = new Handler(Looper.getMainLooper());
    HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
    handlerThread.start();
    backgroundHandler = new Handler(handlerThread.getLooper());
    this.initialDelayMillis = initialDelayMillis;
    maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
  }

  @Override public void execute(@NonNull Retryable retryable) {
    //2、
    if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
      waitForIdle(retryable, 0);
    } else {
      postWaitForIdle(retryable, 0);
    }
  }

  private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
    mainHandler.post(new Runnable() {
      @Override public void run() {
        waitForIdle(retryable, failedAttempts);
      }
    });
  }

  private void waitForIdle(final Retryable retryable, final int failedAttempts) {
    // This needs to be called from the main thread.
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
      @Override public boolean queueIdle() {
        //3黎做、
        postToBackgroundWithDelay(retryable, failedAttempts);
        return false;
      }
    });
  }

  private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
    long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
    long delayMillis = initialDelayMillis * exponentialBackoffFactor;
    backgroundHandler.postDelayed(new Runnable() {
      @Override public void run() {
        //4详恼、
        Retryable.Result result = retryable.run();
        if (result == RETRY) {
          //5、
          postWaitForIdle(retryable, failedAttempts + 1);
        }
      }
    }, delayMillis);
  }
}
  • 1引几、initialDelayMillis默認(rèn)為5s
  • 2昧互、如果當(dāng)前是主線程,調(diào)用waitForIdle(retryable, 0);伟桅,如果不是敞掘,通過handler機(jī)制切換到主線程調(diào)用waitForIdle(retryable, failedAttempts);
  • 3、queueIdle方法是在消息隊(duì)列用完消息時調(diào)用楣铁,必須在主線程調(diào)用
  • 4玖雁、在子線程中調(diào)用retryable.run(),run()方法調(diào)用到了RefWatcher類ensureGoneAsync方法中的run方法
  • 5盖腕、如果是RETRY赫冬,那么遞歸調(diào)用postWaitForIdle,但是時間會加1s

接下來我們在看下ensureGone(reference, watchStartNanoTime)方法:

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    
    //1溃列、
    removeWeaklyReachableReferences();

    //2劲厌、
    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    
    //3、
    if (gone(reference)) {
      return DONE;
    }
    
    //4听隐、
    gcTrigger.runGc();
    
    //5补鼻、
    removeWeaklyReachableReferences();
    //6、
    if (!gone(reference)) {
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

      //7雅任、
      File heapDumpFile = heapDumper.dumpHeap();
      if (heapDumpFile == RETRY_LATER) {
        // Could not dump the heap.
        return RETRY;
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);

      HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
          .referenceName(reference.name)
          .watchDurationMs(watchDurationMs)
          .gcDurationMs(gcDurationMs)
          .heapDumpDurationMs(heapDumpDurationMs)
          .build();

      //8风范、
      heapdumpListener.analyze(heapDump);
    }
    return DONE;
  }
private void removeWeaklyReachableReferences() {
    // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
    // reachable. This is before finalization or garbage collection has actually happened.
    KeyedWeakReference ref;
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      retainedKeys.remove(ref.key);
    }
}
  • 1、調(diào)用removeWeaklyReachableReferences沪么,queue.poll()是從ReferenceQueue中取出queue的第一個Reference硼婿,如果它是可達(dá)的,那么將它從queue中移除并且返回禽车,如果有不可達(dá)的Reference(該被回收的)寇漫,直接返回null拳喻。通過while循環(huán)queue.poll(),可以將queue中的所有可達(dá)對象找出來并且從queue中移除猪腕,再調(diào)用將其從retainedKeys中remove掉。如果有需要被回收的對象钦勘,則直接停止循環(huán)
  • 2陋葡、如果當(dāng)前是debug模式下,那么不執(zhí)行回收策咯彻采,因?yàn)樵赿ebug模式下可能存在假泄露
  • 3腐缤、如果retainedKeys為空,那么表示所有對象均被回收肛响,不存在泄露
  • 4岭粤、執(zhí)行g(shù)c
  • 5、再次調(diào)用removeWeaklyReachableReferences
  • 6特笋、如果retainedKeys還不為空剃浇,那么說明存在不能被回收的弱引用對象,表示已經(jīng)發(fā)生了泄露
  • 7猎物、生成一個heapDumpFile虎囚,并且創(chuàng)建一個Notification來通知用戶
  • 8、回調(diào)到ServiceHeapDumpListener的analyze方法中來分析泄露

來看下ServiceHeapDumpListener類:

public final class ServiceHeapDumpListener implements HeapDump.Listener {

  ...

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

可以看到蔫磨,這里執(zhí)行了HeapAnalyzerService的runAnalysis()方法淘讥,為了避免降低app進(jìn)程的性能或占用內(nèi)存,這里將HeapAnalyzerService設(shè)置在了一個獨(dú)立的進(jìn)程中堤如。接著繼續(xù)分析runAnalysis()方法里面的處理蒲列。

public final class HeapAnalyzerService extends ForegroundService
implements AnalyzerProgressListener {

    ...

    public static void runAnalysis(Context context, HeapDump heapDump,
    Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
        ...
        ContextCompat.startForegroundService(context, intent);
    }

    ...
    
    @Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
        ...

        // 1
        HeapAnalyzer heapAnalyzer =
            new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);

        // 2
        AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
        heapDump.computeRetainedHeapSize);
        
        // 3
        AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
    }
        ...
}

這里的HeapAnalyzerService實(shí)質(zhì)是一個類型為IntentService的ForegroundService,執(zhí)行startForegroundService()之后搀罢,會回調(diào)onHandleIntentInForeground()方法蝗岖。

  • 1、首先會新建一個HeapAnalyzer對象榔至,顧名思義剪侮,它就是根據(jù)RefWatcher生成的heap dumps信息來分析被懷疑的泄漏是否是真的。
  • 2洛退、調(diào)用它的checkForLeak()方法去使用haha庫解析 hprof文件
  • 3瓣俯、通過sendResultToListener調(diào)用到onHandleIntentInForeground中的onHeapAnalyzed,最后調(diào)用到DisplayLeakService中的DisplayLeakActivity.createPendingIntent來開啟一個泄露的Activity

到此兵怯,我們就分析完了整個過程彩匕,整理一下

  • 1、Leakcanary.install中創(chuàng)建了一個RefWatcher
  • 2媒区、通過RefWatcher中的watch()來觀察Activity和fragment驼仪,觀察的方式是為被觀察的activity或fragment創(chuàng)建一個WeakReference
  • 3掸犬、將WeakReference放入到一個set集合retainedKeys中
  • 4、調(diào)用檢測方法绪爸,在異步線程中湾碎,先將retainedKeys中可達(dá)的全部移除
  • 5、一旦發(fā)現(xiàn)不可達(dá)的WeakReference奠货,執(zhí)行g(shù)c
  • 6介褥、如果引用仍然沒有被清除(retainedKeys不為空),那么它將會把堆棧信息保存在文件系統(tǒng)中的.hprof文件里递惋。
  • 7柔滔、HeapAnalyzerService被開啟在一個獨(dú)立的進(jìn)程中,并且HeapAnalyzer使用了HAHA開源庫解析了指定時刻的堆椘妓洌快照文件heap dump睛廊。
  • 8、從heap dump中杉编,HeapAnalyzer根據(jù)一個獨(dú)特的引用key找到了KeyedWeakReference超全,并且定位了泄露的引用。
  • 9邓馒、HeapAnalyzer為了確定是否有泄露卵迂,計(jì)算了到GC Roots的最短強(qiáng)引用路徑,然后建立了導(dǎo)致泄露的鏈?zhǔn)揭谩?/li>
  • 10绒净、這個結(jié)果被傳回到app進(jìn)程中的DisplayLeakService见咒,然后一個泄露通知便展現(xiàn)出來了。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末挂疆,一起剝皮案震驚了整個濱河市改览,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌缤言,老刑警劉巖宝当,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異胆萧,居然都是意外死亡庆揩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門跌穗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來订晌,“玉大人,你說我怎么就攤上這事蚌吸⌒獠Γ” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵羹唠,是天一觀的道長奕枢。 經(jīng)常有香客問我娄昆,道長,這世上最難降的妖魔是什么缝彬? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任萌焰,我火速辦了婚禮,結(jié)果婚禮上谷浅,老公的妹妹穿的比我還像新娘扒俯。我一直安慰自己,他們只是感情好壳贪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著寝杖,像睡著了一般违施。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瑟幕,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天磕蒲,我揣著相機(jī)與錄音,去河邊找鬼只盹。 笑死辣往,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的殖卑。 我是一名探鬼主播站削,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼孵稽!你這毒婦竟也來了许起?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤菩鲜,失蹤者是張志新(化名)和其女友劉穎园细,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體接校,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡猛频,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蛛勉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鹿寻。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖诽凌,靈堂內(nèi)的尸體忽然破棺而出烈和,到底是詐尸還是另有隱情,我是刑警寧澤皿淋,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布招刹,位于F島的核電站恬试,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏疯暑。R本人自食惡果不足惜训柴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望妇拯。 院中可真熱鬧幻馁,春花似錦、人聲如沸越锈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽甘凭。三九已至稀拐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間丹弱,已是汗流浹背德撬。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留躲胳,地道東北人蜓洪。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像坯苹,于是被迫代替她去往敵國和親隆檀。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

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