我所理解的 Android Architecture Components

我所理解的 Android Architecture Components

寫(xiě)在前面
Android Architecture Components 是 Google 2017 年 I/O 大會(huì)提出的一種架構(gòu)解決方案。在此之前痕檬,Android 應(yīng)用大多數(shù)以 MVC MVP MVVM 等比較常見(jiàn)的架構(gòu)方式被構(gòu)建霎槐。看到這樣各自為戰(zhàn)的情況梦谜,再加上開(kāi)發(fā)者們強(qiáng)烈的意愿丘跌,Google 自然也坐不住了,推出了 AAC 這種架構(gòu)方式唁桩,看它的命名闭树,Android Architecture Components,是不是大有一統(tǒng)江湖的意思荒澡。

架構(gòu)變遷

在介紹 AAC 架構(gòu)之前报辱,想和大家聊聊之前常見(jiàn)架構(gòu)模式的變遷。
在軟件行業(yè)单山,無(wú)論是什么形式的架構(gòu)碍现,大家都有一個(gè)共識(shí),如果實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn)米奸,那可測(cè)試性就是檢驗(yàn)架構(gòu)好壞的唯一標(biāo)準(zhǔn)昼接。好的架構(gòu)會(huì)盡量讓邏輯開(kāi)發(fā)者關(guān)注邏輯本身,并且減少模板代碼悴晰。

無(wú)架構(gòu)模式 -- MVC

大家可以想象一下慢睡,我們從把所有代碼寫(xiě)在一團(tuán)到有了 MVC 架構(gòu)模式逐工,有哪些提升?

  • 可測(cè)試性顯著提升(雖然我知道很多公司和開(kāi)發(fā)者都沒(méi)有寫(xiě)單元測(cè)試的習(xí)慣)
  • 數(shù)據(jù)隔離漂辐、關(guān)注點(diǎn)隔離

我們從沒(méi)有架構(gòu)泪喊,到 MVC,終于能做到「分層」了髓涯。

MVC - MVP

之后我們寫(xiě)著寫(xiě)著又發(fā)現(xiàn)袒啼,MVP 對(duì)比 MVC 更有優(yōu)勢(shì):

  • 面向接口編程
  • 將邏輯從 UI 組件(Activity、Fragment)轉(zhuǎn)移到 P 層复凳,進(jìn)一步提升可測(cè)試性

為什么邏輯放在 P 層要更合理呢瘤泪?因?yàn)槭紫?UI 組件是連接你和 OS 的那一層,也就是說(shuō)它并不完完全全屬于開(kāi)發(fā)者本身育八,會(huì)受到系統(tǒng)的影響,比如配置的更改赦邻、內(nèi)存不足重啟等等髓棋,所以盡量保證 UI 組件整潔,絕對(duì)會(huì)提升代碼的可測(cè)試性和穩(wěn)健性惶洲。
其次也符合 Passive View Pattern 的這種架構(gòu)建議模式按声,其實(shí)我們也可以把 Passive View Pattern 這種模式直接類(lèi)比到后臺(tái)和前端的關(guān)系,這也是為什么大多數(shù)公司后臺(tái)開(kāi)發(fā)者的數(shù)量大于前端開(kāi)發(fā)者的數(shù)量恬吕。

現(xiàn)存問(wèn)題

無(wú)論是 MVC MVP MVVM 它們存在什么問(wèn)題呢签则?
他們的模塊通信方式,始終是持有對(duì)象(或接口)铐料。對(duì)于 Android 系統(tǒng)來(lái)說(shuō)渐裂,每一個(gè)你在 Manifest 聲明的四大組件都有可能會(huì)突然死掉,這也造成了:

  • 持有對(duì)象可能會(huì)有 NPE
  • 會(huì)有可能內(nèi)存泄漏
  • 會(huì)寫(xiě)很多生命周期相關(guān)的模板代碼

為了掩蓋或解決以上種種問(wèn)題钠惩,AAC 架構(gòu)就應(yīng)運(yùn)而生了柒凉,AAC 架構(gòu)包括了一系列組件,比如 LifeCycle Room LiveData ViewModel Paging 等等篓跛。其實(shí)無(wú)論是 MVC MVP MVVM 或者是其他的架構(gòu)方式膝捞,數(shù)據(jù)層的爭(zhēng)議是最小的,AAC 也提出了一種數(shù)據(jù)層的構(gòu)建方式 -- Repository愧沟,我們可以先來(lái)看看 Repository蔬咬。

Repository

一個(gè)可擴(kuò)展的、高內(nèi)聚的數(shù)據(jù)層應(yīng)該有哪些特性沐寺?

  • 涵蓋多個(gè)數(shù)據(jù)來(lái)源(Network林艘、Database、File芽丹、Cache)
  • 對(duì)數(shù)據(jù)進(jìn)行必要的邏輯處理(緩存北启、格式轉(zhuǎn)換等)
  • 對(duì)外提供單一的數(shù)據(jù)出口(對(duì)外的方法應(yīng)該是 getData,而不是 getNetData/getCacheData)

舉個(gè)例子?

public class TasksRepository {

    private final TasksDataSource mTasksRemoteDataSource;
    private final TasksDataSource mTasksLocalDataSource;

    // Prevent direct instantiation.
    private TasksRepository(@NonNull TasksDataSource tasksRemoteDataSource,
                            @NonNull TasksDataSource tasksLocalDataSource) {
        mTasksRemoteDataSource = checkNotNull(tasksRemoteDataSource);
        mTasksLocalDataSource = checkNotNull(tasksLocalDataSource);
    }
}

TasksRepository 是對(duì)外提供 Task 的數(shù)據(jù)層咕村,可以看到他持有了 tasksRemoteDataSourcetasksLocalDataSource 兩個(gè)對(duì)象场钉,這就屬于我們剛才提到的,多個(gè)數(shù)據(jù)來(lái)源懈涛。并且他們都實(shí)現(xiàn)了 TasksDataSource 符合面向接口編程逛万。

再來(lái)看它的一個(gè)對(duì)數(shù)據(jù)操作的方法:

  /**
     * Gets tasks from local data source (sqlite) unless the table is new or empty. In that case it
     * uses the network data source. This is done to simplify the sample.
     * <p>
     * Note: {@link GetTaskCallback#onDataNotAvailable()} is fired if both data sources fail to
     * get the data.
     */
    @Override
    public void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) {
        checkNotNull(taskId);
        checkNotNull(callback);

        Task cachedTask = getTaskWithId(taskId);

        // Respond immediately with cache if available
        if (cachedTask != null) {
            callback.onTaskLoaded(cachedTask);
            return;
        }

        // Load from server/persisted if needed.

        // Is the task in the local data source? If not, query the network.
        mTasksLocalDataSource.getTask(taskId, new GetTaskCallback() {
            @Override
            public void onTaskLoaded(Task task) {
                // Do in memory cache update to keep the app UI up to date
                if (mCachedTasks == null) {
                    mCachedTasks = new LinkedHashMap<>();
                }
                mCachedTasks.put(task.getId(), task);
                callback.onTaskLoaded(task);
            }

            @Override
            public void onDataNotAvailable() {
                mTasksRemoteDataSource.getTask(taskId, new GetTaskCallback() {
                    @Override
                    public void onTaskLoaded(Task task) {
                        // Do in memory cache update to keep the app UI up to date
                        if (mCachedTasks == null) {
                            mCachedTasks = new LinkedHashMap<>();
                        }
                        mCachedTasks.put(task.getId(), task);
                        callback.onTaskLoaded(task);
                    }

                    @Override
                    public void onDataNotAvailable() {
                        callback.onDataNotAvailable();
                    }
                });
            }
        });
    }

可以看到這個(gè)方法符合剛才所說(shuō)的,只對(duì)調(diào)用者提供單一的數(shù)據(jù)出口批钠,調(diào)用方根本不需要知道數(shù)據(jù)來(lái)自于緩存還是網(wǎng)絡(luò)還是數(shù)據(jù)庫(kù)宇植,并且也隱藏了緩存數(shù)據(jù)的邏輯,這就是一個(gè)典型的 Repository 構(gòu)建方式埋心。這個(gè)例子來(lái)自于 google sample todo-mvp

在 AAC 架構(gòu)中指郁,Repository 中數(shù)據(jù)庫(kù)為 Room,不過(guò)如果你有已經(jīng)在使用的拷呆,穩(wěn)定的數(shù)據(jù)庫(kù)解決方案闲坎,其實(shí)是沒(méi)有必要替換的,所以 Room 的使用不是本文的重點(diǎn)茬斧。數(shù)據(jù)庫(kù)中讀出來(lái)的數(shù)據(jù)腰懂,自然可以配合 LiveData,關(guān)于 LiveData 的特性项秉,一會(huì)再談绣溜。

所以以上所說(shuō)的 Repository 應(yīng)該是如圖所示:


Repository

ViewModel

可以試想一下,假如我們把 Repository 直接寫(xiě)在 UI 組件(Activity娄蔼、Fragment)里面怖喻,會(huì)造成哪些問(wèn)題呢?

  • 違背了 UI 層盡量簡(jiǎn)單的原則贷屎,并不能很好的進(jìn)行測(cè)試
  • 因?yàn)?Activity罢防、Fragment 可能因?yàn)榕渲酶摹?nèi)存不足唉侄,導(dǎo)致了視圖銷(xiāo)毀或者重建咒吐,此時(shí)就會(huì)產(chǎn)生大量的冗余邏輯代碼

為了解決以上問(wèn)題,需要一個(gè)數(shù)據(jù)和 UI 組件的中間層 -- ViewModel

官方文檔對(duì) ViewModel 的概括:

The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations.

也就是說(shuō) ViewModel 是基于生命周期感知的属划,來(lái)持有并管理 UI 層相關(guān)數(shù)據(jù)的類(lèi)恬叹,并且它可以在 app 的 config 發(fā)生改變重建時(shí),始終存活下來(lái)同眯。

ViewModel 生命周期

看了 ViewModel 的描述和它的生命周期圖绽昼,可以總結(jié)下 ViewModel 的特性:

  • 生命周期感知。一般在 UI 組件的 onCreate 方法中由開(kāi)發(fā)者手動(dòng)的創(chuàng)建须蜗,隨著 UI 組件真正銷(xiāo)毀調(diào)用 onDestory 時(shí)硅确,調(diào)用 onCleared 方法銷(xiāo)毀目溉。
  • 與 UI 組件解耦。無(wú)論由于配置更改 UI 組件反復(fù)創(chuàng)建了多少個(gè)實(shí)例菱农,對(duì) ViewModel 來(lái)說(shuō)都是未知的缭付,ViewModel 也不需要關(guān)心,UI 組件最終拿到的都是第一次創(chuàng)建的那個(gè)保持原有數(shù)據(jù)的 ViewModel循未。并且 ViewModel 中也不應(yīng)該有任何 android * 包下的引用(除了 android * arch)陷猫,如果需要一個(gè) Application 的 Context,啟動(dòng)一些 Service 的話(huà)的妖,使用 AndroidViewModel 這個(gè)類(lèi)就可以了绣檬。

來(lái)看一個(gè)簡(jiǎn)單的例子:

public class UserProfileViewModel extends ViewModel {
    private String userId;
    private User user;

    public void init(String userId) {
        this.userId = userId;
    }
    public User getUser() {
        return user;
    }
}


public class UserProfileFragment extends Fragment {
    private static final String UID_KEY = "uid";
    private UserProfileViewModel viewModel;

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        String userId = getArguments().getString(UID_KEY);
        viewModel = ViewModelProviders.of(this).get(UserProfileViewModel.class);
        viewModel.init(userId);
    }

    @Override
    public View onCreateView(LayoutInflater inflater,
                @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.user_profile, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        User user = viewModel.getUser();
    }
}

也許你覺(jué)得這個(gè) demo 看起來(lái)平平無(wú)奇,但是 ViewModel 自身的框架已經(jīng)為你做了以下工作:

  • 隨著 Fragment 的 onDestory嫂粟,ViewModel 自動(dòng) onCleared
  • 配置更改 Fragment 重啟時(shí)娇未,ViewModel 未感知,始終攜帶數(shù)據(jù)存活
  • ViewModel 不持有任何 View 相關(guān)的引用星虹。(在任何位置持有 Activity忘蟹、Fragment 都是有風(fēng)險(xiǎn)的,有可能造成 NPE搁凸、內(nèi)存泄漏)

肯定有同學(xué)會(huì)問(wèn),你 ViewModel 都不持有 View 的對(duì)象狠毯,那數(shù)據(jù)更改了护糖,怎么通知 View 呢?對(duì)了就是我們接下來(lái)要說(shuō)的觀察者模式嚼松,觀察者模式對(duì)于 MVC MVP 這種持有對(duì)象和接口的模式來(lái)說(shuō)嫡良,簡(jiǎn)直就是降維打擊,可以試想一下献酗,如果我的 View 層去觀察 ViewModel 數(shù)據(jù)的變化寝受,當(dāng) View 層被殺死,那最多也就是不再去觀察 ViewModel 了罕偎,ViewModel 對(duì)此也不在意很澄,所以就會(huì)減少特別多的 NPE 和內(nèi)存泄漏。

在 AAC 架構(gòu)中颜及,觀察者模式是通過(guò) LiveData 這個(gè)類(lèi)配合完成的甩苛。

LiveData

LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state.

從官方文檔的描述中,可以總結(jié)出被 LiveData 包裝的數(shù)據(jù)俏站,有這樣的特性:

  • 生命周期感知讯蒲,LiveData 僅僅會(huì)在你 UI 組件處于 active 狀態(tài)時(shí),notify 數(shù)據(jù)肄扎,這也為我們避免了很多 NPE 和內(nèi)存泄漏
  • 數(shù)據(jù)自動(dòng)更新墨林,當(dāng) UI 組件從 inactive 到 active 狀態(tài)時(shí)赁酝,假設(shè)在此期間有數(shù)據(jù)更新,LiveData 會(huì)將最新的數(shù)據(jù) notify 給 UI 組件旭等,比如 App 回到 Home 頁(yè)再切換回來(lái)酌呆。
  • 自動(dòng)清除引用,當(dāng) UI 組件調(diào)用 onDestory 時(shí)辆雾,LiveData 會(huì)清除 LifeCycle 相關(guān)的引用和注冊(cè)

可以來(lái)看一下 ViewModel 配合 LiveData 應(yīng)該是怎樣的一個(gè)效果:

public class NameViewModel extends ViewModel {

// Create a LiveData with a String
private MutableLiveData<String> mCurrentName;

    public MutableLiveData<String> getCurrentName() {
        if (mCurrentName == null) {
            mCurrentName = new MutableLiveData<String>();
        }
        return mCurrentName;
    }

// Rest of the ViewModel...
}
public class NameActivity extends AppCompatActivity {

    private NameViewModel mModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Other code to setup the activity...

        // Get the ViewModel.
        mModel = ViewModelProviders.of(this).get(NameViewModel.class);


        // Create the observer which updates the UI.
        final Observer<String> nameObserver = new Observer<String>() {
            @Override
            public void onChanged(@Nullable final String newName) {
                // Update the UI, in this case, a TextView.
                mNameTextView.setText(newName);
            }
        };

        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
        mModel.getCurrentName().observe(this, nameObserver);
    }
}

mCurrentName 被 LiveData 包裹肪笋,這個(gè)時(shí)候就賦予了 mCurrentName 被觀察的能力。當(dāng)數(shù)據(jù)發(fā)生更改時(shí)度迂,如果 NameActivity 是 active 狀態(tài)藤乙,則會(huì)回調(diào) onChanged 方法。
自然惭墓,我也可以主動(dòng)地去更改 LiveData 中的數(shù)據(jù)的值:

mButton.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        String anotherName = "John Doe";
        mModel.getCurrentName().setValue(anotherName);
    }
});

除此以外坛梁,還有 postValue 方法,將值直接拋到 UI 線程腊凶。setSource 方法划咐,綁定觀察數(shù)據(jù)源

上文中,提到了很多次生命周期的監(jiān)測(cè)钧萍,其實(shí)是與 LifeCycle 這個(gè)組件相關(guān)的褐缠,LifeCycle 這個(gè)組件就是典型的為了減少模板代碼而生的。同理如果你是在開(kāi)發(fā)邏輯中风瘦,寫(xiě)了很多模板型的代碼队魏,這個(gè)時(shí)候你就要考慮從架構(gòu)中優(yōu)化,減少這些不必要的模板代碼万搔。關(guān)于 Lifecycle胡桨,其實(shí)可說(shuō)的點(diǎn)并不是很多,有興趣可以去官方文檔查看瞬雹。

到目前為止昧谊,我的 UI 層和 ViewModel 通過(guò) LiveData 建立了可靠的觀察者模式:


View - ViewModel

當(dāng)發(fā)生了 View 層的銷(xiāo)毀,會(huì)有怎樣的結(jié)果呢酗捌?


View Destory

這就是觀察者模式的優(yōu)勢(shì)所在呢诬,View 層銷(xiāo)毀,對(duì) ViewModel 來(lái)說(shuō)只是少了一個(gè)觀察者而已意敛,我不持有你的對(duì)象馅巷,我肯定不會(huì)內(nèi)存泄漏,也不會(huì)有什么 NPE〔菀觯現(xiàn)在看起來(lái) View 層和 ViewModel 層的通信方式已經(jīng)升級(jí)為觀察者模式了钓猬,那在另一側(cè),ViewModel 層和 Repository 也應(yīng)該有觀察者模式這種高級(jí)通信方式吧撩独。

@Dao
public interface ProductDao {
    @Query("SELECT * FROM products")
    LiveData<List<ProductEntity>> loadAllProducts(); 
}

可以看到我們從 Room 數(shù)據(jù)庫(kù)取出的數(shù)據(jù)直接拿 LiveData 包裝后敞曹,作為返回值拋出账月。

public class DataRepository {

private MediatorLiveData<List<ProductEntity>> mObservableProducts;

    private DataRepository(final AppDatabase database) {
        mDatabase = database;
        mObservableProducts = new MediatorLiveData<>();

        mObservableProducts.addSource(mDatabase.productDao().loadAllProducts(),
                productEntities -> {
                    if (mDatabase.getDatabaseCreated().getValue() != null) {
                        mObservableProducts.postValue(productEntities);
                    }
                });
    }

    /**
     * Get the list of products from the database and get notified when the data changes.
     */
    public LiveData<List<ProductEntity>> getProducts() {
        return mObservableProducts;
    }

}

DataRepository 從數(shù)據(jù)庫(kù)中取出數(shù)據(jù),構(gòu)建成 MediatorLiveData<List<ProductEntity>> mObservableProducts 對(duì)象澳迫,通過(guò) getProducts 方法對(duì) ViewModel 暴露數(shù)據(jù)局齿。

public class ProductListViewModel extends AndroidViewModel {

    // MediatorLiveData can observe other LiveData objects and react on their emissions.
    private final MediatorLiveData<List<ProductEntity>> mObservableProducts;

    public ProductListViewModel(Application application) {
        super(application);

        mObservableProducts = new MediatorLiveData<>();
        // set by default null, until we get data from the database.
        mObservableProducts.setValue(null);

        LiveData<List<ProductEntity>> products = ((BasicApp) application).getRepository()
                .getProducts();

        // observe the changes of the products from the database and forward them
        mObservableProducts.addSource(products, mObservableProducts::setValue);
    }

    /**
     * Expose the LiveData Products query so the UI can observe it.
     */
    public LiveData<List<ProductEntity>> getProducts() {
        return mObservableProducts;
    }

}

通過(guò)代碼可以看到,通過(guò)一系列 LiveData 對(duì)象橄登,將 ViewModel 和 Repository 也建立了觀察者模式:


ViewModel - Repository

作為開(kāi)發(fā)者抓歼,我只需要在 ViewModel 調(diào)用 onCleared 方法時(shí),去掉 Repository 的引用就好了拢锹。

其他

1.ViewModel 是 onSaveInstanceState 方法的替代方案嗎谣妻?

ViewModel 的數(shù)據(jù)是在內(nèi)存層面持有的,官方文檔也反復(fù)提到過(guò)卒稳,ViewModel 也僅僅是在配置更改 UI 層重建時(shí)蹋半,可以存活的。當(dāng) App 由于系統(tǒng)內(nèi)存不足被殺死時(shí)充坑,ViewModel 也會(huì)銷(xiāo)毀减江,這個(gè)時(shí)候就需要借助 onSaveInstanceState 來(lái)保存一些輕量的數(shù)據(jù)。所以數(shù)據(jù)持久化這塊捻爷,ViewModel 解決了配置更改重啟的問(wèn)題辈灼,但是除此之外的,依然要依靠原有的方案也榄。

2.ViewModel 是 Fragment.setRetainInstance(true) 的替代方案嗎茵休?

Fragment.setRetainInstance(true) 這個(gè)方法的目的就是在配置更改時(shí),保留 Fragment 的實(shí)例手蝎,所以 ViewModel 完全可以成為這個(gè)方法的替代方案。

3.看起來(lái) RxJava 一樣可以完成 LiveData 所做的事

是的俐芯,如果你 RxJava 玩得好棵介,一樣能做出和 LiveData 一樣的效果,官網(wǎng)也明確表明吧史,如果你執(zhí)意使用 RxJava 替代 LiveData邮辽,請(qǐng)保證你能處理 UI 的生命周期,使你的 data streams 配合 UI 組件狀態(tài)正確地 pause destory贸营。(何必為難自己呢吨述,用 LiveData 多好),Google 也出了一個(gè)類(lèi)可以將二者配合使用 LiveDataReactiveStreams

總結(jié)

所以完整的 AAC 架構(gòu)圖應(yīng)為:


AAC

要想打造一個(gè)能處理繁重業(yè)務(wù)量的架構(gòu)是很不容易的钞脂,我也準(zhǔn)備在公司項(xiàng)目中揣云,使用 AAC 重構(gòu)一個(gè)模塊,有任何心得感受或者采坑了冰啃,會(huì)更新到這個(gè)文章中邓夕。

參考資料:
https://developer.android.com/topic/libraries/architecture/
https://github.com/googlesamples/android-architecture-components

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末刘莹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子焚刚,更是在濱河造成了極大的恐慌点弯,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件矿咕,死亡現(xiàn)場(chǎng)離奇詭異抢肛,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)碳柱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)捡絮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人士聪,你說(shuō)我怎么就攤上這事锦援。” “怎么了剥悟?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵灵寺,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我区岗,道長(zhǎng)略板,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任慈缔,我火速辦了婚禮叮称,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘藐鹤。我一直安慰自己瓤檐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布娱节。 她就那樣靜靜地躺著挠蛉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肄满。 梳的紋絲不亂的頭發(fā)上谴古,一...
    開(kāi)封第一講書(shū)人閱讀 48,954評(píng)論 1 283
  • 那天,我揣著相機(jī)與錄音稠歉,去河邊找鬼掰担。 笑死,一個(gè)胖子當(dāng)著我的面吹牛怒炸,可吹牛的內(nèi)容都是我干的带饱。 我是一名探鬼主播,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼阅羹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼纠炮!你這毒婦竟也來(lái)了月趟?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤恢口,失蹤者是張志新(化名)和其女友劉穎孝宗,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體耕肩,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡因妇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了猿诸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片婚被。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖梳虽,靈堂內(nèi)的尸體忽然破棺而出址芯,到底是詐尸還是另有隱情,我是刑警寧澤窜觉,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布谷炸,位于F島的核電站,受9級(jí)特大地震影響禀挫,放射性物質(zhì)發(fā)生泄漏旬陡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一语婴、第九天 我趴在偏房一處隱蔽的房頂上張望描孟。 院中可真熱鬧,春花似錦砰左、人聲如沸匿醒。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)青抛。三九已至,卻和暖如春酬核,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背适室。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工嫡意, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人捣辆。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓蔬螟,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親汽畴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子旧巾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

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