Android Architecture Components原理淺析

前言:

前文已經(jīng)講述了Android架構(gòu)化組件的一些基本用法和整體使用場景海渊。但是因為筆者在學(xué)習(xí)和研究這些組件的時候本身也是帶有一些疑惑和問題的角撞,考慮到可能也會有人有相同的疑惑,所以將此過程中發(fā)現(xiàn)疑惑和解決問題的思路和過程記錄下來褥民,供有需要的同學(xué)查閱季春。

1、ViewModel:

不知道大家在學(xué)習(xí)和使用ViewModel的時候消返,是否有這個疑問载弄。一般情況下的Activity銷毀和橫豎屏切換時的Activity銷毀,Activity都是已經(jīng)被銷毀掉了(橫豎屏切換時Activity的實例對象不是同一個對象)撵颊,那為什么前者的ViewModel會被銷毀掉宇攻,而后者卻沒有呢?關(guān)于這個問題倡勇,筆者一直帶有很大的疑問逞刷,但是官方文檔只是說在橫豎屏切換時ViewModel不銷毀,而且一直拿Activity生命周期Scope說事兒妻熊,導(dǎo)致筆者對此產(chǎn)生了更多的疑惑夸浅,好奇心驅(qū)使我查看了相關(guān)源碼,終于明白了這部分的內(nèi)部實現(xiàn)扔役。

ChronometerViewModel chronometerViewModel
        = ViewModelProviders.of(this).get(ChronometerViewModel.class);
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
    initializeFactoryIfNeeded(checkApplication(activity));
    return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);
}
public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    mFactory = factory;
    this.mViewModelStore = store;
}
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    String canonicalName = modelClass.getCanonicalName();
    if (canonicalName == null) {
        throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
    }
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.get(key);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
        mMap.put(key, viewModel);
    }

    final ViewModel get(String key) {
        return mMap.get(key);
    }

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }
}

通過上述ViewModel的生成和調(diào)用流程帆喇,我們看出ViewModel其實是儲存在ViewModelStore 中的,而ViewModelStore 里主要是放了一個map亿胸,key就是這個Activity的名字+“android.arch.lifecycle.ViewModelProvider.DefaultKey”坯钦,而Value就是ViewModel法严。
那么此時我們就清楚ViewModel是如何存放的了,按照官方文檔的說法葫笼,Activity銷毀時深啤,會將對應(yīng)的ViewModel的也銷毀,那么銷毀的方式也就是調(diào)用ViewModelStore .clear()方法路星∷萁郑可是這些依然沒辦法解決我們的疑惑,為啥橫豎屏切換時洋丐,Activty也銷毀了呈昔,ViewModelStore .clear()方法卻沒有調(diào)用呢?

好的友绝,知道ViewModel的銷毀是通過ViewModelStore .clear()來實現(xiàn)的堤尾,我們就看下這個方法是在哪里調(diào)用的。然后我們就看到了如下:
有個叫HolderFragment的類調(diào)用了這個方法:

public class HolderFragment extends Fragment {
    private static final String LOG_TAG = "ViewModelStores";
    private static final HolderFragmentManager sHolderFragmentManager = new HolderFragmentManager();
    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static final String HOLDER_TAG =
            "android.arch.lifecycle.state.StateProviderHolderFragment";
    private ViewModelStore mViewModelStore = new ViewModelStore();
    public HolderFragment() {
        setRetainInstance(true);
    }
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        sHolderFragmentManager.holderFragmentCreated(this);
    }
    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        mViewModelStore.clear();
    }
    public ViewModelStore getViewModelStore() {
        return mViewModelStore;
    }
  ...
}

我們看到HolderFragment中持有了mViewModelStore 這個對象迁客,而且在它的onDestroy方法中調(diào)用了mViewModelStore.clear();方法郭宝。看到這里掷漱,就有些疑問粘室,這個HolderFragment是哪里來的?

public static ViewModelProvider of(@NonNull FragmentActivity activity) {
    initializeFactoryIfNeeded(checkApplication(activity));
    return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);
}

根據(jù)這段代碼卜范,可以看出衔统,ViewModelStore是從ViewModelStores.of(activity)這里拿到的。接下來我們看一下
ViewModelStores的代碼:

public class ViewModelStores {

    private ViewModelStores() {
    }

    /**
     * Returns the {@link ViewModelStore} of the given activity.
     *
     * @param activity an activity whose {@code ViewModelStore} is requested
     * @return a {@code ViewModelStore}
     */
    @MainThread
    public static ViewModelStore of(@NonNull FragmentActivity activity) {
        return holderFragmentFor(activity).getViewModelStore();
    }

    /**
     * Returns the {@link ViewModelStore} of the given fragment.
     *
     * @param fragment a fragment whose {@code ViewModelStore} is requested
     * @return a {@code ViewModelStore}
     */
    @MainThread
    public static ViewModelStore of(@NonNull Fragment fragment) {
        return holderFragmentFor(fragment).getViewModelStore();
    }
}

我們看到ViewModelStore實際上是從holderFragmentFor(activity).getViewModelStore();中拿到的海雪,而holderFragmentFor(activity)其實就是HodlerFragment锦爵!

此時再思考之前的問題,mViewModelStore.clear();只在HodlerFragment的onDestroy方法中有調(diào)用奥裸,那么能解釋上述問題的唯一可能就是险掀,在橫豎屏切換時,HolderFragment的onDestroy方法并未調(diào)用刺彩!而普通的Activity銷毀時迷郑,HolderFragment的onDestroy是會調(diào)用的枝恋。

帶著這個問題创倔,繼續(xù)看HolderFragment,然后就看到了它的構(gòu)造方法:

public HolderFragment() {
    setRetainInstance(true);
}

點進(jìn)去這個方法焚碌,我們看到 setRetainInstance(true);是Fragment中的一個方法:

Frargment:

/**
     * Control whether a fragment instance is retained across Activity
     * re-creation (such as from a configuration change).  This can only
     * be used with fragments not in the back stack.  If set, the fragment
     * lifecycle will be slightly different when an activity is recreated:
     * <ul>
     * <li> {@link #onDestroy()} will not be called (but {@link #onDetach()} still
     * will be, because the fragment is being detached from its current activity).
     * <li> {@link #onCreate(Bundle)} will not be called since the fragment
     * is not being re-created.
     * <li> {@link #onAttach(Activity)} and {@link #onActivityCreated(Bundle)} <b>will</b>
     * still be called.
     * </ul>
     */
    public void setRetainInstance(boolean retain) {
        mRetainInstance = retain;
    }

    final public boolean getRetainInstance() {
        return mRetainInstance;
    }

從這個方法的注釋中畦攘,我們看到,mRetainInstance 被設(shè)置為true時十电,在Activity被重新創(chuàng)建時(比如橫豎屏切換)知押,HolderFragment的OnDestroy方法并不會被調(diào)用叹螟,而是只調(diào)用onDetach方法。而且在Activity 重建時台盯,
HolderFragment的onCreate方法也不會被調(diào)用罢绽,而是會調(diào)用onAttach和onActivityCreated方法。(也比較好理解静盅,沒有destroy良价,只是onDetch而已,自然不需要再重新調(diào)用onCreate方法)蒿叠。

至此明垢,我們的疑惑也就迎刃而解了,橫豎屏切換時市咽,HolderFragment的onDestroy方法不會被調(diào)用痊银,ViewModelStore不會clear,那么ViewModel自然存在施绎,并可供下個Activity實例使用溯革。

好奇的我們再看下Fragment中mRetainInstance是怎么用的。
然后我們看到FragmentManagerImpl的一個方法中的調(diào)用:

void saveNonConfig() {
        ArrayList<Fragment> fragments = null;
        ArrayList<FragmentManagerNonConfig> childFragments = null;
        if (mActive != null) {
            for (int i=0; i<mActive.size(); i++) {
                Fragment f = mActive.valueAt(i);
                if (f != null) {
                    if (f.mRetainInstance) {
                        if (fragments == null) {
                            fragments = new ArrayList<Fragment>();
                        }
                        fragments.add(f);
                        f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1;
                        if (DEBUG) Log.v(TAG, "retainNonConfig: keeping retained " + f);
                    }
                    FragmentManagerNonConfig child;
                    if (f.mChildFragmentManager != null) {
                        f.mChildFragmentManager.saveNonConfig();
                        child = f.mChildFragmentManager.mSavedNonConfig;
                    } else {
                        // f.mChildNonConfig may be not null, when the parent fragment is
                        // in the backstack.
                        child = f.mChildNonConfig;
                    }

                    if (childFragments == null && child != null) {
                        childFragments = new ArrayList<>(mActive.size());
                        for (int j = 0; j < i; j++) {
                            childFragments.add(null);
                        }
                    }

                    if (childFragments != null) {
                        childFragments.add(child);
                    }
                }
            }
        }
        if (fragments == null && childFragments == null) {
            mSavedNonConfig = null;
        } else {
            mSavedNonConfig = new FragmentManagerNonConfig(fragments, childFragments);
        }
    }
if (f.mRetainInstance) {
    if (fragments == null) {
        fragments = new ArrayList<Fragment>();
    }
    fragments.add(f);
    f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1;
    if (DEBUG) Log.v(TAG, "retainNonConfig: keeping retained " + f);
}

通過上述代碼可以看出谷醉,保存狀態(tài)時鬓照,如果mRetainInstance為true,會將fragment加入到集合中孤紧,而不是銷毀掉豺裆,在需要的時候,再從List中拿出來attach Activity号显。

而其實筆者關(guān)于HolderFragment還是有些好奇臭猜,HolderFragment到底是干啥用的呢?但是因為本文的主題押蚤,不想做過多的擴展蔑歌,根據(jù)HolderFragment源碼:

HolderFragment holderFragmentFor(FragmentActivity activity) {
    FragmentManager fm = activity.getSupportFragmentManager();
    HolderFragment holder = findHolderFragment(fm);
    if (holder != null) {
        return holder;
    }
    holder = mNotCommittedActivityHolders.get(activity);
    if (holder != null) {
        return holder;
    }
    if (!mActivityCallbacksIsAdded) {
        mActivityCallbacksIsAdded = true;
        activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
    }
    holder = createHolderFragment(fm);
    mNotCommittedActivityHolders.put(activity, holder);
    return holder;
}

HolderFragment是會被默認(rèn)加入到FragementActivity中的,而普通的Activity是不支持ViewModel的揽碘,F(xiàn)ragementActivity支持ViewModel次屠。那么我們大概可以了解HolderFragment的設(shè)計目的中有一項就是解決VIewModel在橫豎屏切換下不銷毀的。更多的就不做擴展了雳刺,感興趣的小伙伴可以去看源碼劫灶。

2、LiveData:

通知更新有兩種情況掖桦,一種是數(shù)據(jù)變化本昏,另一種是Observer的LifecycleOwner的狀態(tài)變化(比如從不可更新狀態(tài)變?yōu)榭筛聽顟B(tài))


LiveData1.png
LiveData2.png

LiveData3.png
LiveData4.png
LiveData5.png
LiveData6.png

根據(jù)上述源碼可知,LiveData只會在Observer的LifecycleOwner的生命周期處于>STARTED的狀態(tài)的時候才會通知更新枪汪。

LiveData7.png
LiveData8.png
LiveData9.png

postValue是通過開啟異步線程涌穆,然后通過Runnable中調(diào)用setValue來實現(xiàn)異步線程數(shù)據(jù)變化時怔昨,通知主線程更新UI。注意setValue方法中的mVersion宿稀,該字段主要用于避免重復(fù)回調(diào)趁舀,LiveData的mVersion字段會和Oberser的lastVersion字段進(jìn)行比較來確認(rèn)此次數(shù)據(jù)變化是否已經(jīng)回調(diào)過了。從代碼中可以看出祝沸,setValu方法每執(zhí)行一次赫编,mVersion都會加1。

可能有些人會比較好奇奋隶,上面的LifeCycleBoundObserver的onStateChanged方法是在哪里觸發(fā)調(diào)用的呢擂送?

LiveData10.png
LiveData11.png

可以看到在LifeCycleRegistry的addObserver方法中,會一直判斷當(dāng)前Observer的LifeCycleOwner的生命周期是否發(fā)生變化唯欣,一旦變化就通知在管理列表里的Observer嘹吨,在dispatchEvent方法中調(diào)用通知方法(mLifeCycleObserver.onStateChanged()方法)。

3境氢、LifecycleObserver:

這里我們主要是看被注解的方法是如何在達(dá)到生命周期的狀態(tài)時蟀拷,被調(diào)用的。
我們看上圖中的ObserverWithState方法萍聊,看到
mLifecycleObserver = Lifecycling.getCallback(observer);
點進(jìn)去Lifecycling類可以看到:

LivecycleObserver1.png
LifecycleObsever2.png

在生命周期狀態(tài)變化時问芬,會調(diào)用CallbackInfo.invokeCallbacks方法,而CallbackInfo 是ClassesInfoCache的內(nèi)部類寿桨。


LifecycleObserver4.png
private CallbackInfo createInfo(Class klass, @Nullable Method[] declaredMethods) {
        Class superclass = klass.getSuperclass();
        Map<MethodReference, Lifecycle.Event> handlerToEvent = new HashMap<>();
        if (superclass != null) {
            CallbackInfo superInfo = getInfo(superclass);
            if (superInfo != null) {
                handlerToEvent.putAll(superInfo.mHandlerToEvent);
            }
        }

        Class[] interfaces = klass.getInterfaces();
        for (Class intrfc : interfaces) {
            for (Map.Entry<MethodReference, Lifecycle.Event> entry : getInfo(
                    intrfc).mHandlerToEvent.entrySet()) {
                verifyAndPutHandler(handlerToEvent, entry.getKey(), entry.getValue(), klass);
            }
        }

        Method[] methods = declaredMethods != null ? declaredMethods : getDeclaredMethods(klass);
        boolean hasLifecycleMethods = false;
        for (Method method : methods) {
            OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
            if (annotation == null) {
                continue;
            }
            hasLifecycleMethods = true;
            Class<?>[] params = method.getParameterTypes();
            int callType = CALL_TYPE_NO_ARG;
            if (params.length > 0) {
                callType = CALL_TYPE_PROVIDER;
                if (!params[0].isAssignableFrom(LifecycleOwner.class)) {
                    throw new IllegalArgumentException(
                            "invalid parameter type. Must be one and instanceof LifecycleOwner");
                }
            }
            Lifecycle.Event event = annotation.value();

            if (params.length > 1) {
                callType = CALL_TYPE_PROVIDER_WITH_EVENT;
                if (!params[1].isAssignableFrom(Lifecycle.Event.class)) {
                    throw new IllegalArgumentException(
                            "invalid parameter type. second arg must be an event");
                }
                if (event != Lifecycle.Event.ON_ANY) {
                    throw new IllegalArgumentException(
                            "Second arg is supported only for ON_ANY value");
                }
            }
            if (params.length > 2) {
                throw new IllegalArgumentException("cannot have more than 2 params");
            }
            MethodReference methodReference = new MethodReference(callType, method);
            verifyAndPutHandler(handlerToEvent, methodReference, event, klass);
        }
        CallbackInfo info = new CallbackInfo(handlerToEvent);
        mCallbackMap.put(klass, info);
        mHasLifecycleMethods.put(klass, hasLifecycleMethods);
        return info;
    }
static class CallbackInfo {
        final Map<Lifecycle.Event, List<MethodReference>> mEventToHandlers;
        final Map<MethodReference, Lifecycle.Event> mHandlerToEvent;

        CallbackInfo(Map<MethodReference, Lifecycle.Event> handlerToEvent) {
            mHandlerToEvent = handlerToEvent;
            mEventToHandlers = new HashMap<>();
            for (Map.Entry<MethodReference, Lifecycle.Event> entry : handlerToEvent.entrySet()) {
                Lifecycle.Event event = entry.getValue();
                List<MethodReference> methodReferences = mEventToHandlers.get(event);
                if (methodReferences == null) {
                    methodReferences = new ArrayList<>();
                    mEventToHandlers.put(event, methodReferences);
                }
                methodReferences.add(entry.getKey());
            }
        }

        @SuppressWarnings("ConstantConditions")
        void invokeCallbacks(LifecycleOwner source, Lifecycle.Event event, Object target) {
            invokeMethodsForEvent(mEventToHandlers.get(event), source, event, target);
            invokeMethodsForEvent(mEventToHandlers.get(Lifecycle.Event.ON_ANY), source, event,
                    target);
        }

        private static void invokeMethodsForEvent(List<MethodReference> handlers,
                LifecycleOwner source, Lifecycle.Event event, Object mWrapped) {
            if (handlers != null) {
                for (int i = handlers.size() - 1; i >= 0; i--) {
                    handlers.get(i).invokeCallback(source, event, mWrapped);
                }
            }
        }
    }
void invokeCallback(LifecycleOwner source, Lifecycle.Event event, Object target) {
            //noinspection TryWithIdenticalCatches
            try {
                switch (mCallType) {
                    case CALL_TYPE_NO_ARG:
                        mMethod.invoke(target);
                        break;
                    case CALL_TYPE_PROVIDER:
                        mMethod.invoke(target, source);
                        break;
                    case CALL_TYPE_PROVIDER_WITH_EVENT:
                        mMethod.invoke(target, source, event);
                        break;
                }
            } catch (InvocationTargetException e) {
                throw new RuntimeException("Failed to call observer method", e.getCause());
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }

3此衅、Room:

BookDao_impl:

  @Override
  public LiveData<List<Book>> findBooksBorrowedByNameAfter(String userName, Date after) {
    final String _sql = "SELECT * FROM Book INNER JOIN Loan ON Loan.book_id = Book.id INNER JOIN User on User.id = Loan.user_id WHERE User.name LIKE ? AND Loan.endTime > ? ";
    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 2);
    int _argIndex = 1;
    if (userName == null) {
      _statement.bindNull(_argIndex);
    } else {
      _statement.bindString(_argIndex, userName);
    }
    _argIndex = 2;
    final Long _tmp;
    _tmp = DateConverter.toTimestamp(after);
    if (_tmp == null) {
      _statement.bindNull(_argIndex);
    } else {
      _statement.bindLong(_argIndex, _tmp);
    }
    return new ComputableLiveData<List<Book>>() {
      private Observer _observer;

      @Override
      protected List<Book> compute() {
        if (_observer == null) {
          _observer = new Observer("Book","Loan","User") {
            @Override
            public void onInvalidated(@NonNull Set<String> tables) {
              invalidate();
            }
          };
          __db.getInvalidationTracker().addWeakObserver(_observer);
        }
        final Cursor _cursor = __db.query(_statement);
        try {
          final int _cursorIndexOfId = _cursor.getColumnIndexOrThrow("id");
          final int _cursorIndexOfTitle = _cursor.getColumnIndexOrThrow("title");
          final int _cursorIndexOfId_1 = _cursor.getColumnIndexOrThrow("id");
          final int _cursorIndexOfId_2 = _cursor.getColumnIndexOrThrow("id");
          final List<Book> _result = new ArrayList<Book>(_cursor.getCount());
          while(_cursor.moveToNext()) {
            final Book _item;
            _item = new Book();
            _item.id = _cursor.getString(_cursorIndexOfId);
            _item.title = _cursor.getString(_cursorIndexOfTitle);
            _item.id = _cursor.getString(_cursorIndexOfId_1);
            _item.id = _cursor.getString(_cursorIndexOfId_2);
            _result.add(_item);
          }
          return _result;
        } finally {
          _cursor.close();
        }
      }

      @Override
      protected void finalize() {
        _statement.release();
      }
    }.getLiveData();
  }
/**
     * Creates a computable live data which is computed when there are active observers.
     * <p>
     * It can also be invalidated via {@link #invalidate()} which will result in a call to
     * {@link #compute()} if there are active observers (or when they start observing)
     */
    @SuppressWarnings("WeakerAccess")
    public ComputableLiveData() {
        mLiveData = new LiveData<T>() {
            @Override
            protected void onActive() {
                // TODO if we make this class public, we should accept an executor
                ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
            }
        };
    }
@Query("SELECT * FROM Book " +
            "INNER JOIN Loan ON Loan.book_id = Book.id " +
            "INNER JOIN User on User.id = Loan.user_id " +
            "WHERE User.name LIKE :userName " +
            "AND Loan.endTime > :after "
    )
    LiveData<List<Book>> findBooksBorrowedByNameAfter(String userName, Date after);

    @Query("SELECT * FROM Book " +
            "INNER JOIN Loan ON Loan.book_id = Book.id " +
            "INNER JOIN User on User.id = Loan.user_id " +
            "WHERE User.name LIKE :userName"
    )
    List<Book> findBooksBorrowedByNameSync(String userName);

可以看出在BookDao類中,從數(shù)據(jù)庫中讀取book數(shù)據(jù)的操作亭螟,第一個方法是異步的挡鞍,第二個方法則是同步操作,需要自己開線程進(jìn)行異步操作预烙,再回調(diào)給主線程墨微。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市扁掸,隨后出現(xiàn)的幾起案子翘县,更是在濱河造成了極大的恐慌,老刑警劉巖谴分,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锈麸,死亡現(xiàn)場離奇詭異,居然都是意外死亡狸剃,警方通過查閱死者的電腦和手機掐隐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钞馁,“玉大人虑省,你說我怎么就攤上這事∩耍” “怎么了探颈?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長训措。 經(jīng)常有香客問我伪节,道長绩鸣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任化借,我火速辦了婚禮,結(jié)果婚禮上捡多,老公的妹妹穿的比我還像新娘蓖康。我一直安慰自己垒手,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布科贬。 她就那樣靜靜地躺著泳梆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鸭丛。 梳的紋絲不亂的頭發(fā)上唐责,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天鼠哥,我揣著相機與錄音,去河邊找鬼朴恳。 笑死,一個胖子當(dāng)著我的面吹牛呆贿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播冒晰,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼竟块,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蒋情?” 一聲冷哼從身側(cè)響起耸携,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤夺衍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后的畴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體尝胆,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年煎娇,在試婚紗的時候發(fā)現(xiàn)自己被綠了缓呛。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片杭隙。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖票髓,靈堂內(nèi)的尸體忽然破棺而出铣耘,到底是詐尸還是另有隱情,我是刑警寧澤裆操,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布踪区,位于F島的核電站,受9級特大地震影響朽缴,放射性物質(zhì)發(fā)生泄漏密强。R本人自食惡果不足惜蜗元,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望薪鹦。 院中可真熱鬧惯豆,春花似錦、人聲如沸地熄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至裂明,卻和暖如春太援,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背粉寞。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工唧垦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人巧还。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓麸祷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親阶牍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

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