談?wù)勎依斫獾腁ndroid應(yīng)用架構(gòu)

本篇文章已授權(quán)微信公眾號(hào) guolin_blog (郭霖)獨(dú)家發(fā)布

前言

android架構(gòu)可能是論壇討論最多的話題了螟够,mvc mvp和mvvm不絕于耳,后面又有模塊化和插件化嗽冒。對(duì)此,關(guān)于哪種架構(gòu)更好的爭論從未停止。
我的觀點(diǎn):脫離實(shí)際項(xiàng)目比較這些模式優(yōu)劣毫無意義哗伯,各種模式都有優(yōu)點(diǎn)和缺點(diǎn)讥蔽,沒有好壞之分涣易。越高級(jí)的架構(gòu)實(shí)現(xiàn)起來越復(fù)雜,需要更多的學(xué)習(xí)成本更多的人力冶伞,所以說技術(shù)選型關(guān)鍵是在你自己項(xiàng)目的特點(diǎn)新症,團(tuán)隊(duì)的水平,資源的配備响禽,開發(fā)時(shí)間的限制徒爹,這些才是重點(diǎn)!但是不少團(tuán)隊(duì)本末倒置芋类,把mvvm往自己的項(xiàng)目硬套隆嗅。

下面我從兩大塊講下我理解的Android架構(gòu):代碼層面,主要是MVC和MVP的理解侯繁。項(xiàng)目層面胖喳,主要是怎么搭建整個(gè)項(xiàng)目,怎么劃分模塊贮竟。

通俗理解MVC和MVP

先上結(jié)論:

  • MVC:Model-View-Controller丽焊,經(jīng)典模式,很容易理解咕别,主要缺點(diǎn)有兩個(gè):
  1. View對(duì)Model的依賴技健,會(huì)導(dǎo)致View也包含了業(yè)務(wù)邏輯;
  2. Controller會(huì)變得很厚很復(fù)雜惰拱。
  • MVP:Model-View-Presenter雌贱,MVC的一個(gè)演變模式,將Controller換成了Presenter,主要為了解決上述第一個(gè)缺點(diǎn)帽芽,將View和Model解耦删掀,不過第二個(gè)缺點(diǎn)依然沒有解決。
  • MVVM:Model-View-ViewModel导街,是對(duì)MVP的一個(gè)優(yōu)化模式披泪,采用了雙向綁定:View的變動(dòng),自動(dòng)反映在ViewModel搬瑰,反之亦然款票。
MVC

簡單的說:我們平時(shí)寫的Demo都是MVC,controller就是我們的activity泽论,model(數(shù)據(jù)提供者)就是讀取數(shù)據(jù)庫艾少,網(wǎng)絡(luò)請(qǐng)求這些我們一般有專門的類處理,View一般用自定義控件翼悴。

但這一切缚够,只是看起來很美。

想象實(shí)際開發(fā)中鹦赎,我們的activity代碼其實(shí)是越來越多谍椅,model和controller根本沒有分離,控件也需要關(guān)系數(shù)據(jù)和業(yè)務(wù)古话。


image.png

所以說雏吭,MVC的真實(shí)存在是MC(V),Model和Controller根本沒辦法分開陪踩,并且數(shù)據(jù)和View嚴(yán)重耦合杖们。這就是它的問題。舉個(gè)簡單例子 :獲取天氣數(shù)據(jù)展示在界面上


image.png
  • Model層
public interface WeatherModel {
    void getWeather(String cityNumber, OnWeatherListener listener);
}
................
public class WeatherModelImpl implements WeatherModel {
        @Override
    public void getWeather(String cityNumber, final OnWeatherListener listener) {
        /*數(shù)據(jù)層操作*/
        VolleyRequest.newInstance().newGsonRequest(http://www.weather.com.cn/data/sk/ + cityNumber + .html,
                Weather.class, new Response.Listener<weather>() {
                    @Override
                    public void onResponse(Weather weather) {
                        if (weather != null) {
                            listener.onSuccess(weather);
                        } else {
                            listener.onError();
                        }
                    }
                }, new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        listener.onError();
                    }
                });
    }
}

Controllor(View)層

public class MainActivity extends ActionBarActivity implements OnWeatherListener, View.OnClickListener {
    private WeatherModel weatherModel;
    private EditText cityNOInput;
    private TextView city;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        weatherModel = new WeatherModelImpl();
        initView();
    }
    //初始化View
    private void initView() {
        cityNOInput = findView(R.id.et_city_no);
        city = findView(R.id.tv_city);
        ...
        findView(R.id.btn_go).setOnClickListener(this);
    }
    //顯示結(jié)果
    public void displayResult(Weather weather) {
        WeatherInfo weatherInfo = weather.getWeatherinfo();
        city.setText(weatherInfo.getCity());
        ...
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_go:
                weatherModel.getWeather(cityNOInput.getText().toString().trim(), this);
                break;
        }
    }
    @Override
    public void onSuccess(Weather weather) {
        displayResult(weather);
    }
    @Override
    public void onError() {
        Toast.makeText(this, 獲取天氣信息失敗, Toast.LENGTH_SHORT).show();
    }
    private T findView(int id) {
        return (T) findViewById(id);
    }
}

簡單分析下這個(gè)例子:
1肩狂、activity里面的控件必須關(guān)心業(yè)務(wù)和數(shù)據(jù)摘完,才能知道自己怎么展示。換句話說婚温,我們很難讓兩個(gè)人在不互相溝通的情況下描焰,一人負(fù)責(zé)獲取數(shù)據(jù),一人負(fù)責(zé)展示UI栅螟,然后完成這個(gè)功能荆秦。
2、所以的邏輯都在activity里面力图。
完美的體現(xiàn)了MVC的兩大缺點(diǎn)步绸,下面看看MVP怎么解決第一個(gè)缺點(diǎn)的

MVP
image.png

看上圖可以看出,從MVC中View被拆成了Presenter和View吃媒,真正實(shí)現(xiàn)了邏輯處理和View的分離瓤介。下面寫一個(gè)實(shí)例:模擬一個(gè)登錄界面吕喘,輸入用戶名和密碼,可以登錄以及清除密碼

  • Model層
/**
定義業(yè)務(wù)接口
*/
public interface IUserBiz
{
    public void login(String username, String password, OnLoginListener loginListener);
}
/**
結(jié)果回調(diào)接口
*/
public interface OnLoginListener
{
    void loginSuccess(User user);
    void loginFailed();
}
/**
具體Model的實(shí)現(xiàn)
*/
public class UserBiz implements IUserBiz
{
    @Override
    public void login(final String username, final String password, final OnLoginListener loginListener)
    {
        //模擬子線程耗時(shí)操作
        new Thread()
        {
            @Override
            public void run()
            {
                try
                {
                    Thread.sleep(2000);
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                //模擬登錄成功
                if ("zhy".equals(username) && "123".equals(password))
                {
                    User user = new User();
                    user.setUsername(username);
                    user.setPassword(password);
                    loginListener.loginSuccess(user);
                } else
                {
                    loginListener.loginFailed();
                }
            }
        }.start();
    }
}
  • View
    上面說到View層是以接口的形式定義刑桑,我們不關(guān)心數(shù)據(jù)氯质,不關(guān)心邏輯處理!只關(guān)心和用戶的交互祠斧,那么這個(gè)登錄界面應(yīng)該有的操作就是(把這個(gè)界面想成一個(gè)容器闻察,有輸入和輸出):

獲取用戶名,獲取密碼琢锋,現(xiàn)實(shí)進(jìn)度條辕漂,隱藏進(jìn)度條,跳轉(zhuǎn)到其他界面吴超,展示失敗dialog钉嘹,清除用戶名,清除密碼鲸阻。接下來定義接口:

public interface IUserLoginView
{
    String getUserName();
    String getPassword();
    void clearUserName();
    void clearPassword();
    void showLoading();
    void hideLoading();
    void toMainActivity(User user);
    void showFailedError();
}

然后Activity實(shí)現(xiàn)這個(gè)這個(gè)接口:

public class UserLoginActivity extends ActionBarActivity implements IUserLoginView
{
    private EditText mEtUsername, mEtPassword;
    private Button mBtnLogin, mBtnClear;
    private ProgressBar mPbLoading;
    private UserLoginPresenter mUserLoginPresenter = new UserLoginPresenter(this);
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_login);
        initViews();
    }
    private void initViews()
    {
        mEtUsername = (EditText) findViewById(R.id.id_et_username);
        mEtPassword = (EditText) findViewById(R.id.id_et_password);
        mBtnClear = (Button) findViewById(R.id.id_btn_clear);
        mBtnLogin = (Button) findViewById(R.id.id_btn_login);
        mPbLoading = (ProgressBar) findViewById(R.id.id_pb_loading);
        mBtnLogin.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                mUserLoginPresenter.login();
            }
        });
        mBtnClear.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                mUserLoginPresenter.clear();
            }
        });
    }
    @Override
    public String getUserName()
    {
        return mEtUsername.getText().toString();
    }
    @Override
    public String getPassword()
    {
        return mEtPassword.getText().toString();
    }
    @Override
    public void clearUserName()
    {
        mEtUsername.setText("");
    }
    @Override
    public void clearPassword()
    {
        mEtPassword.setText("");
    }
    @Override
    public void showLoading()
    {
        mPbLoading.setVisibility(View.VISIBLE);
    }
    @Override
    public void hideLoading()
    {
        mPbLoading.setVisibility(View.GONE);
    }
    @Override
    public void toMainActivity(User user)
    {
        Toast.makeText(this, user.getUsername() +
                " login success , to MainActivity", Toast.LENGTH_SHORT).show();
    }
    @Override
    public void showFailedError()
    {
        Toast.makeText(this,
                "login failed", Toast.LENGTH_SHORT).show();
    }
}
  • Presenter
    Presenter的作用就是從View層獲取用戶的輸入跋涣,傳遞到Model層進(jìn)行處理,然后回調(diào)給View層鸟悴,輸出給用戶仆潮!
public class UserLoginPresenter
{
    private IUserBiz userBiz;
    private IUserLoginView userLoginView;
    private Handler mHandler = new Handler();
//Presenter必須要能拿到View和Model的實(shí)現(xiàn)類
    public UserLoginPresenter(IUserLoginView userLoginView)
    {
        this.userLoginView = userLoginView;
        this.userBiz = new UserBiz();
    }
    public void login()
    {
        userLoginView.showLoading();
        userBiz.login(userLoginView.getUserName(), userLoginView.getPassword(), new OnLoginListener()
        {
            @Override
            public void loginSuccess(final User user)
            {
                //需要在UI線程執(zhí)行
                mHandler.post(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        userLoginView.toMainActivity(user);
                        userLoginView.hideLoading();
                    }
                });
            }
            @Override
            public void loginFailed()
            {
                //需要在UI線程執(zhí)行
                mHandler.post(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        userLoginView.showFailedError();
                        userLoginView.hideLoading();
                    }
                });
            }
        });
    }
    public void clear()
    {
        userLoginView.clearUserName();
        userLoginView.clearPassword();
    }
}

分析下這個(gè)例子:
1、我們有了IUserLoginView 這個(gè)接口(協(xié)議)遣臼,activity里面的控件根本不需要關(guān)心數(shù)據(jù),只要實(shí)現(xiàn)這個(gè)接口在每個(gè)方法中“按部就班”的展示UI就行了拾并。換句話說揍堰,我們讓兩個(gè)人一起開發(fā)這個(gè)功能,一人要處理數(shù)據(jù)并且制定接口(協(xié)議)嗅义,另一人直接用activity實(shí)現(xiàn)這個(gè)接口屏歹,閉著眼睛就可以在每個(gè)回調(diào)里展示UI,合作很愉快之碗。
2蝙眶、MVP成功解決了MVC的第一個(gè)缺點(diǎn),但是邏輯處理還是雜糅在Activity褪那。

MVC到MVP簡單說幽纷,就是增加了一個(gè)接口降低一層耦合。那么博敬,用樣的MVP到MVVM就是再加一個(gè)接口唄友浸。實(shí)際項(xiàng)目我建議用MVP模式,MVVM還是復(fù)雜了對(duì)于中小型項(xiàng)目有點(diǎn)過度設(shè)計(jì)偏窝,這里就不展開講收恢。

模塊化

image.png

上圖是一個(gè)項(xiàng)目常見的架構(gòu)方式
1武学、最底層是基礎(chǔ)庫,放置與業(yè)務(wù)無關(guān)的模塊:比如基礎(chǔ)網(wǎng)絡(luò)請(qǐng)求伦意,圖片壓縮等等火窒,可以按需分為邏輯模塊,通用UI模塊和第三方庫驮肉。(建議采用獨(dú)立的svn分支)
2熏矿、中間層是通用業(yè)務(wù)層,放置公司多個(gè)android項(xiàng)目的通用業(yè)務(wù)模塊(和業(yè)務(wù)相關(guān)的)缆八,比如登錄流程曲掰,文件上傳/下載等。
3奈辰、最上層就是應(yīng)用層了栏妖,比如公司有三個(gè)android項(xiàng)目:LbBoss,BV和BVHD。我們還可以針對(duì)相似的項(xiàng)目再抽取通用層(比如這里的BV和BV PAD版奖恰,通用層為BVCommon)吊趾。

新建一個(gè)app,我們往往有兩種模塊劃分方法:

  • 按照類型劃分:


    image.png
  • 按照業(yè)務(wù)劃分:
    每一個(gè)包都是一個(gè)業(yè)務(wù)模塊瑟啃,每個(gè)模塊下再按照類型來分论泛。
  • 怎么選
    我建議中小型的新項(xiàng)目按照類型比較好,因?yàn)殚_始代碼量不多按照業(yè)務(wù)來分不切實(shí)際蛹屿,一個(gè)包只放幾個(gè)文件屁奏?? 況且前期業(yè)務(wù)不穩(wěn)定错负,等到開發(fā)中期業(yè)務(wù)定型了坟瓢,再進(jìn)行重構(gòu)難度也不大。

上面講的模塊劃分既不屬于模塊化也不屬于插件化犹撒,僅僅是一個(gè)簡單package結(jié)構(gòu)不同而已,app還是一個(gè)app并沒有產(chǎn)生什么變化折联。通常講的模塊化,是指把業(yè)務(wù)劃分為不同的moduler(類型是library)识颊,每個(gè)moduler之間都不依賴诚镰,app(類型是application)只是一個(gè)空殼依賴所有的moduler。


image.png

每個(gè)紅色箭頭都是一個(gè)業(yè)務(wù)模塊祥款,紅色框是我們的app里面只包含簡單的業(yè)務(wù):自定義Application清笨,入口Activity,build.gradle編譯打包配置镰踏『睿看下項(xiàng)目的依賴關(guān)系:


image.png

這樣架構(gòu)后,帶來最大的不同就是:不同業(yè)務(wù)模塊完全分離奠伪,好處就是不同模塊的開發(fā)絕對(duì)不會(huì)互相耦合了跌帐,因?yàn)槟阍谀KA 根本訪問不到模塊B的API首懈。此時(shí)模塊間通信急需解決,Intent隱式跳轉(zhuǎn)可以處理部分Activity的跳轉(zhuǎn)谨敛,但真正的業(yè)務(wù)場景遠(yuǎn)不止兩個(gè)界面跳一跳究履。你之前封裝的業(yè)務(wù)通用方法,工具類脸狸,數(shù)據(jù)緩存現(xiàn)在其他模塊都拿不到了最仑,本本來可以復(fù)用的控件,fragment都不能共享炊甲,而這些都是和業(yè)務(wù)耦合沒辦法拿到底層基礎(chǔ)庫泥彤。

模塊間通信

針對(duì)上面問題有兩個(gè)解決辦法,根據(jù)自己項(xiàng)目實(shí)際情況卿啡,如果項(xiàng)目的前期搭建已經(jīng)很優(yōu)秀吟吝,有完善的基礎(chǔ)庫,不同模塊間的通信不是很多颈娜,可以自己實(shí)現(xiàn)剑逃。如果項(xiàng)目比較龐大,不同業(yè)務(wù)間頻繁調(diào)用建議使用阿里巴巴的開源庫官辽。

  • 自己實(shí)現(xiàn)
    1蛹磺、首先每個(gè)moduler有個(gè)目錄叫include,里面有三個(gè)類同仆,此處以一個(gè)bbs論壇模塊為例說明萤捆,
    IBBSNotify:里面是一堆interface,作用是該模塊對(duì)外的回調(diào)俗批,只能被動(dòng)被觸發(fā)鳖轰。
    IBBService:里面是一堆interface,作用是對(duì)外暴露的方法扶镀,讓別的模塊來主動(dòng)調(diào),比如enterBbsActivity
    IBBSServiceImpl:很明顯是IBBService的實(shí)現(xiàn)焰轻,比如enterBbsActivity就是具體怎么跳轉(zhuǎn)到論壇界面臭觉,傳遞什么數(shù)據(jù)。
image.png

2辱志、每個(gè)模塊方法和回調(diào)都有了蝠筑,in和out都具備了,別的模塊怎么使用呢揩懒?就該app該上場了什乙,app不能只是一個(gè)殼里面要定義一個(gè)ModulerManager implements 所有模塊的對(duì)外interface,作為每個(gè)模塊的中轉(zhuǎn)站已球,A模塊告訴ModulerManager我想跳轉(zhuǎn)到論壇模塊臣镣,接著ModulerManager調(diào)用IBBService.enterBbsActivity辅愿,IBBSServiceImpl是IBBService的具體實(shí)現(xiàn)(多態(tài))然后調(diào)用IBBSServiceImpl.enterBbsActivity跳轉(zhuǎn)到BBS界面。
3忆某、通信是解決了点待,其實(shí)踩坑才剛剛開始:
a. 這里的app是我們新建的,那么之前項(xiàng)目的app模塊要降為library:

apply plugin: 'com.android.library'

殼app的build.gradle配置:

apply plugin: 'com.android.application'

性質(zhì)發(fā)生巨大變化弃舒。里面的自定義application癞埠,build.gradle,代碼混淆配置等全部移到app
b.R.java在Lib類型的moduler中不是final的聋呢,所有switch case語句全部替換成if else
c.一定要再建一個(gè)common模塊苗踪,放置通用數(shù)據(jù),緩存等
d.還有很多通用功能削锰,例如分享通铲,推送,盡量剝離業(yè)務(wù)放到common
e.其他與項(xiàng)目相關(guān)的細(xì)節(jié)

  • 開源庫ARouter
    專門用于接續(xù)模塊間通信喂窟,這里不講了使用起來很簡單测暗。

其他

插件化:
插件化其實(shí)最后發(fā)布的產(chǎn)品也是一個(gè)apk,只不過大小可以控制(可以隨意去掉某些模塊)磨澡,支持用戶動(dòng)態(tài)加載子apk碗啄。因此,插件化就是動(dòng)態(tài)加載apk稳摄。有人說我用intent隱式可以直接跳轉(zhuǎn)到另一個(gè)apk啊稚字,干嘛還要插件化。

其實(shí)是兩碼事厦酬,intent只是指定一個(gè)Activity跳過去胆描,后面的交互完成不受你控制,2個(gè)apk也是運(yùn)行在獨(dú)立的進(jìn)程數(shù)據(jù)無法共享仗阅。而插件化可以讓兩個(gè)apk運(yùn)行在一個(gè)進(jìn)程昌讲,可以完全像同一個(gè)apk一樣開發(fā)。不過减噪,我覺得插件化只適合需要多部門并行開發(fā)的那種短绸,比如支付寶這種超級(jí)app,一般的app開發(fā)除非特殊需要筹裕,否則用不到醋闭。

插件化也有成熟的框架拐辽,在此不詳細(xì)說了器躏。另外艺骂,每個(gè)人的習(xí)慣不一樣留瞳,組件化舔痪,模塊化在我看來差不多肾筐,沒必要糾結(jié)兩個(gè)名詞慷吊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末馍惹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子洞拨,更是在濱河造成了極大的恐慌扯罐,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烦衣,死亡現(xiàn)場離奇詭異歹河,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)花吟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門秸歧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人衅澈,你說我怎么就攤上這事键菱。” “怎么了今布?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵经备,是天一觀的道長。 經(jīng)常有香客問我部默,道長侵蒙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任傅蹂,我火速辦了婚禮纷闺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘份蝴。我一直安慰自己犁功,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布婚夫。 她就那樣靜靜地躺著浸卦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪案糙。 梳的紋絲不亂的頭發(fā)上镐躲,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音侍筛,去河邊找鬼。 笑死撒穷,一個(gè)胖子當(dāng)著我的面吹牛匣椰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播端礼,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼禽笑,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼入录!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起佳镜,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤僚稿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蟀伸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蚀同,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年啊掏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蠢络。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡迟蜜,死狀恐怖刹孔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情娜睛,我是刑警寧澤髓霞,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站畦戒,受9級(jí)特大地震影響方库,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜兢交,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一薪捍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧配喳,春花似錦酪穿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至涧团,卻和暖如春只磷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背泌绣。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工钮追, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人阿迈。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓元媚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子刊棕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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