Android |《看完不忘系列》之Retrofit

嗨丸相,我是哈利迪~《看完不忘系列》將以從樹干到細(xì)枝的思路分析一些技術(shù)框架拌蜘,本文將對開源項目Retrofit進(jìn)行介紹。

本文約2800字陪竿,閱讀大約8分鐘禽翼。

Retrofit源碼基于最新版本2.9.0

預(yù)備

Retrofit使得網(wǎng)絡(luò)調(diào)用可以像RESTful設(shè)計風(fēng)格一樣簡潔,如:

interface WanApi {
    //用注解標(biāo)記網(wǎng)絡(luò)請求方式get族跛、post捐康,參數(shù)path、query等
    @GET("article/list/{page}/json")
    Call<WanArticleBean> articleList(@Path("page") int page);
}

又如庸蔼,后端的Spring Boot框架通過約定大于配置思想省去了很多配置,其在網(wǎng)絡(luò)接口RestController上也運(yùn)用了這種風(fēng)格贮匕,

@RestController
public class ActivityController {
    @Autowired
    private ActivityService activityService;

    //用注解標(biāo)記網(wǎng)絡(luò)請求方式和入?yún)?    @GetMapping("/goods")
    public ResultEntity queryGoods(@RequestParam("page") int page) {
        return activityService.queryGoods(page);
    }
}

Retrofit的底層網(wǎng)絡(luò)實現(xiàn)基于okhttp姐仅,自身的類不是很多,最核心的點(diǎn)就是動態(tài)代理了。代理模式簡單來說掏膏,就是為對象提供一個增強(qiáng)控制其訪問的代理劳翰。下面我們先來了解下靜態(tài)代理動態(tài)代理~

靜態(tài)代理

編譯期就完成代理

  • 源碼級:手動編寫代理類、APT生成代理類
  • 字節(jié)碼級:編譯期生成字節(jié)碼馒疹、

舉個栗子佳簸,

image
interface 賺錢 {
    void makeMoney(int income);
}

class 小鮮肉 implements 賺錢 { //委托類
    @Override
    public void makeMoney(int income) {
        System.out.println("開拍,賺個" + income);
    }
}

class 經(jīng)紀(jì)人 implements 賺錢 { //代理類
    賺錢 xxr;

    public 經(jīng)紀(jì)人(賺錢 xxr) {
        this.xxr = xxr;
    }

    @Override
    public void makeMoney(int income) {
        if (income < 1000_0000) { //控制訪問
            System.out.println("才" + income + "颖变,先回去等通知吧");
        } else {
            xxr.makeMoney(income);
        }
    }
}

public static void main(String[] args) {
    賺錢 xxr = new 小鮮肉();
    賺錢 jjr = new 經(jīng)紀(jì)人(xxr);
    jjr.makeMoney(100_0000); //輸出:才1000000生均,先回去等通知吧
    jjr.makeMoney(1000_0000); //輸出:開拍,賺個10000000
}

為什么代理類委托類要實現(xiàn)相同接口腥刹?是為了盡可能保證代理類的內(nèi)部結(jié)構(gòu)和委托類一致马胧,這樣對代理類的操作都可以轉(zhuǎn)移到委托類上,代理類只關(guān)注增強(qiáng)控制衔峰。

動態(tài)代理

運(yùn)行期生成字節(jié)碼佩脊,如Proxy.newProxyInstance、CGLIB

Proxy.newProxyInstance是java自帶垫卤,只能對接口代理(因為生成的類已經(jīng)繼承了Proxy威彰,java沒法多繼承)

CGLIB則更強(qiáng)大,還能對普通類代理穴肘,底層基于ASM(ASM使用類似SAX解析器逐行掃描來提高性能)

舉個栗子歇盼,

image
class 合作標(biāo)準(zhǔn) implements InvocationHandler {
    賺錢 xxr;

    public 合作標(biāo)準(zhǔn)(賺錢 xxr) {
        this.xxr = xxr;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        int income = (int) args[0];
        if (income < 1000_0000) { //控制訪問
            System.out.println("才" + income + ",先回去等通知吧");
            return null;
        } else {
            return method.invoke(xxr, args);
        }
    }
}

public static void main(String[] args) {
    賺錢 xxr = new 小鮮肉();
    合作標(biāo)準(zhǔn) standard = new 合作標(biāo)準(zhǔn)(xxr);
    //生成類(字節(jié)碼):class $Proxy0 extends Proxy implements 賺錢
    //然后反射創(chuàng)建其實例bd梢褐,即來一場臨時的商務(wù)拓展
    賺錢 bd = (賺錢) Proxy.newProxyInstance(賺錢.class.getClassLoader(),
                                        new Class[]{賺錢.class}, 
                                        standard);
    //調(diào)用makeMoney旺遮,內(nèi)部轉(zhuǎn)發(fā)給了合作標(biāo)準(zhǔn)的invoke
    bd.makeMoney(100_0000);
    bd.makeMoney(1000_0000);
}

通過栗子可以看出,動態(tài)代理不需要提前創(chuàng)建具體的代理類(如經(jīng)紀(jì)人經(jīng)紀(jì)公司)去實現(xiàn)賺錢接口盈咳,而是先擬一份合作標(biāo)準(zhǔn)(InvocationHandler)耿眉,等到運(yùn)行期才創(chuàng)建代理類$Proxy0(字節(jié)碼),然后反射創(chuàng)建其實例商務(wù)拓展鱼响,這樣顯得更為靈活鸣剪。

了解完動態(tài)代理,就可以開始Retrofit之旅了~

樹干

簡單使用

引入依賴丈积,

implementation 'com.squareup.okhttp3:okhttp:3.14.9'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.google.code.gson:gson:2.8.6'

定義接口WanApi筐骇,

interface WanApi {
    //用注解標(biāo)記網(wǎng)絡(luò)請求類型get,參數(shù)path
    @GET("article/list/{page}/json")
    Call<WanArticleBean> articleList(@Path("page") int page);
}

發(fā)起請求江滨,

class RetrofitActivity extends AppCompatActivity {
    final String SERVER = "https://www.xxx.com/";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(SERVER) //指定服務(wù)器地址
            .addConverterFactory(GsonConverterFactory.create()) //用gson將數(shù)據(jù)反序列化成實體
            .build();
        //運(yùn)行期生成一個實現(xiàn)WanApi接口的類(字節(jié)碼)铛纬,并反射創(chuàng)建其實例
        WanApi wanApi = retrofit.create(WanApi.class);
        //得到Retrofit的call,他封裝了okhttp的call
        Call<WanArticleBean> call = wanApi.articleList(0);
        //請求入隊
        call.enqueue(new Callback<WanArticleBean>() {
            @Override
            public void onResponse(Call<WanArticleBean> call, Response<WanArticleBean> response) {
                //得到數(shù)據(jù)實體
                WanArticleBean bean = response.body();
                //不同于okhttp唬滑,Retrofit已經(jīng)用Handler幫我們切回主線程了
                mBinding.tvResult.setText("" + bean.getData().getDatas().size());
            }

            @Override
            public void onFailure(Call<WanArticleBean> call, Throwable t) {}
        });
    }
}

實現(xiàn)原理

image

由于Retrofit底層基于okhttp告唆,哈迪在《看完不忘系列》之okhttp已經(jīng)對網(wǎng)絡(luò)流程做了分析棺弊,所以本文忽略網(wǎng)絡(luò)實現(xiàn)只關(guān)注Retrofit自身的一些處理,Retrofit對象的構(gòu)建就是簡單的builder模式擒悬,我們直接看create模她,

//Retrofit.java
public <T> T create(final Class<T> service) {
    //驗證
    validateServiceInterface(service);
    return (T)
        //動態(tài)代理
        Proxy.newProxyInstance(
        service.getClassLoader(), //類加載器
        new Class<?>[] {service}, //一組接口
        new InvocationHandler() {
            //判斷android和jvm平臺及其版本
            private final Platform platform = Platform.get();

            @Override
            public Object invoke(Object proxy, Method method, Object[] args){
                //如果該方法是Object的方法,直接執(zhí)行不用管
                if (method.getDeclaringClass() == Object.class) {
                    return method.invoke(this, args);
                }
                //isDefaultMethod:檢查是否是java8開始支持的接口默認(rèn)方法
                return platform.isDefaultMethod(method)
                    ? platform.invokeDefaultMethod(method, service, proxy, args)
                    : loadServiceMethod(method).invoke(args); //我們關(guān)注這里
            }
        });
}

Proxy.newProxyInstance動態(tài)代理懂牧,運(yùn)行期會生成一個類(字節(jié)碼)如$ProxyN侈净,實現(xiàn)傳入的接口即WanApi,重寫接口方法然后轉(zhuǎn)發(fā)給InvocationHandler的invoke僧凤,如下(偽代碼)畜侦,

class $ProxyN extends Proxy implements WanApi{
    Call<WanArticleBean> articleList(@Path("page") int page){
        //轉(zhuǎn)發(fā)給invocationHandler
        invocationHandler.invoke(this,method,args);
    }
}

我們先看validateServiceInterface驗證邏輯,

//Retrofit.java
private void validateServiceInterface(Class<?> service) {
    //檢查:WanApi不是接口就拋異常...
    //檢查:WanApi不能有泛型參數(shù)拼弃,不能實現(xiàn)其他接口...
    if (validateEagerly) { //是否進(jìn)行嚴(yán)格檢查夏伊,默認(rèn)關(guān)閉
        Platform platform = Platform.get();
        for (Method method : service.getDeclaredMethods()) { //遍歷WanApi方法
            //不是默認(rèn)方法,并且不是靜態(tài)方法
            if (!platform.isDefaultMethod(method) && !Modifier.isStatic(method.getModifiers())) {
                //把方法提前加載進(jìn)來(檢查下有沒有問題)
                loadServiceMethod(method);
            }
        }
    }
}

如果開了validateEagerly吻氧,會一次性把接口WanApi的所有方法都檢查一遍并加載進(jìn)來溺忧,可以在debug模式下開啟,提前發(fā)現(xiàn)錯誤寫法盯孙,比如在@GET請求設(shè)置了@Body這種錯誤就會拋出異常:

java.lang.IllegalArgumentException: Non-body HTTP method cannot contain @Body.

loadServiceMethod

然后是loadServiceMethod(method).invoke(args)鲁森,看名字可知是先找方法,然后執(zhí)行,

//Retrofit.java
//緩存,用了線程安全ConcurrentHashMap
final Map<Method, ServiceMethod<?>> serviceMethodCache = new ConcurrentHashMap<>();

ServiceMethod<?> loadServiceMethod(Method method) {
    ServiceMethod<?> result = serviceMethodCache.get(method);
    //WanApi的articleList方法已緩存忱详,直接返回
    if (result != null) return result;
    synchronized (serviceMethodCache) {
        result = serviceMethodCache.get(method);
        if (result == null) {
            //解析articleList的注解,創(chuàng)建ServiceMethod并緩存起來
            result = ServiceMethod.parseAnnotations(this, method);
            serviceMethodCache.put(method, result);
        }
    }
    return result;
}

跟進(jìn)ServiceMethod.parseAnnotations痛垛,

//ServiceMethod.java
static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    //1.
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
    //檢查:articleList方法返回類型不能用通配符和void...
    //2.
    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
}

先看1. RequestFactory.parseAnnotations,

//RequestFactory.java
static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
    return new Builder(retrofit, method).build();
}

class Builder {
    RequestFactory build() {
        //解析方法注解如GET
        for (Annotation annotation : methodAnnotations) {
            parseMethodAnnotation(annotation);
        }
        //省略各種檢查...
        //解析參數(shù)注解如Path
        int parameterCount = parameterAnnotationsArray.length;
        parameterHandlers = new ParameterHandler<?>[parameterCount];
        for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
            parameterHandlers[p] =
                parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
        }
        //省略各種檢查...
        return new RequestFactory(this);
    }
}

得到RequestFactory后桶蛔,看2. HttpServiceMethod.parseAnnotations匙头,HttpServiceMethod負(fù)責(zé)適配和轉(zhuǎn)換處理,將接口方法的調(diào)用調(diào)整為HTTP調(diào)用仔雷,

//HttpServiceMethod.java
//ResponseT響應(yīng)類型如WanArticleBean蹂析,ReturnT返回類型如Call
static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
    Retrofit retrofit, Method method, RequestFactory requestFactory) {
    //省略kotlin協(xié)程邏輯...
    Annotation[] annotations = method.getAnnotations();
    //遍歷找到合適的適配器
    CallAdapter<ResponseT, ReturnT> callAdapter =
        createCallAdapter(retrofit, method, adapterType, annotations);
    //得到響應(yīng)類型,如WanArticleBean
    Type responseType = callAdapter.responseType();
    //遍歷找到合適的轉(zhuǎn)換器
    Converter<ResponseBody, ResponseT> responseConverter =
        createResponseConverter(retrofit, method, responseType);
    okhttp3.Call.Factory callFactory = retrofit.callFactory;
    return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
}

可見最終返回了一個CallAdapted碟婆,看到CallAdapted电抚,

//CallAdapted extends HttpServiceMethod extends ServiceMethod
class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {
    private final CallAdapter<ResponseT, ReturnT> callAdapter;

    CallAdapted(
        RequestFactory requestFactory,
        okhttp3.Call.Factory callFactory,
        Converter<ResponseBody, ResponseT> responseConverter,
        CallAdapter<ResponseT, ReturnT> callAdapter) {
        super(requestFactory, callFactory, responseConverter);
        this.callAdapter = callAdapter;
    }

    @Override
    protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
        //適配器
        return callAdapter.adapt(call);
    }
}

那這個CallAdapter實例到底是誰呢,我們先回到Retrofit.Builder竖共,

//Retrofit.Builder.java
public Retrofit build() {
    Executor callbackExecutor = this.callbackExecutor;
    //如果沒設(shè)置線程池蝙叛,則給android平臺設(shè)置一個默認(rèn)的MainThreadExecutor(用Handler將回調(diào)切回主線程)
    if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
    }
    List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
    //添加默認(rèn)的DefaultCallAdapterFactory
    callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
}

DefaultCallAdapterFactory這個工廠創(chuàng)建具體的CallAdapter實例,

//DefaultCallAdapterFactory.java
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);
    //如果指定了SkipCallbackExecutor注解公给,就表示不需要切回主線程
    final Executor executor =
        Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
        ? null
        : callbackExecutor;
    return new CallAdapter<Object, Call<?>>() {
        @Override
        public Type responseType() {
            return responseType;
        }

        @Override
        public Call<Object> adapt(Call<Object> call) {
            //默認(rèn)情況下甥温,返回用主線程池包裝的Call锻煌,他的enqueue會使用主線程池的execute
            return executor == null ? call : new ExecutorCallbackCall<>(executor, call);
        }
    };
}

invoke

前邊loadServiceMethod得到了CallAdapted,然后執(zhí)行invoke姻蚓,實現(xiàn)在父類HttpServiceMethod里,

//HttpServiceMethod.java
final ReturnT invoke(Object[] args) {
    //終于見到okhttp了匣沼!
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    return adapt(call, args);
}

class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {
    private final CallAdapter<ResponseT, ReturnT> callAdapter;

    @Override
    protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
        //用前邊得到的適配器狰挡,把OkHttpCall包成ExecutorCallbackCall
        return callAdapter.adapt(call);
    }
}

然后是請求入隊,ExecutorCallbackCall.enqueue -> OkHttpCall.enqueue释涛,

//ExecutorCallbackCall.java
void enqueue(final Callback<T> callback) {
    delegate.enqueue(
        new Callback<T>() {
            @Override
            public void onResponse(Call<T> call, final Response<T> response) {
                //將回調(diào)切回主線程
                callbackExecutor.execute(
                    () -> {
                        callback.onResponse(ExecutorCallbackCall.this, response);
                    });
                //...
            }

            @Override
            public void onFailure(Call<T> call, final Throwable t) {}
        });
}

//OkHttpCall.java
void enqueue(final Callback<T> callback) {
    //okhttp邏輯
    okhttp3.Call call;
    call.enqueue(new okhttp3.Callback() {
        void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
            callback.onResponse(OkHttpCall.this, response);
        }
    })
}

總算把流程跑通了加叁,回到前邊再看一遍流程圖,就豁然開朗了~

細(xì)枝

CallAdapter

CallAdapter適配器用于適配返回類型唇撬,比如還可以支持Rxjava它匕、協(xié)程的使用,

interface WanApi {
    //Call
    @GET("article/list/{page}/json")
    Call<WanArticleBean> articleList(@Path("page") int page);

    //Rxjava窖认,需要 addCallAdapterFactory(RxJavaCallAdapterFactory.create())
    @GET("article/list/{page}/json")
    Observable<WanArticleBean> articleListRx(@Path("page") int page);
}

Converter

Converter轉(zhuǎn)換器用于轉(zhuǎn)換參數(shù)類型豫柬,比如把Long時間戳格式化成string再傳給后端,

interface WanApi {
    //Long cur 當(dāng)前時間
    @GET("article/list/{page}/json")
    Call<WanArticleBean> articleList(@Path("page") int page, @Query("cur") Long cur);
}

class TimeConverter implements Converter<Long, String> {
    private SimpleDateFormat mFormat = new SimpleDateFormat("yyyy-MM-dd-HHmmss");

    @Override
    public String convert(Long value) throws IOException {
        if (value > 1_000_000_000_000L) {//毫秒扑浸,不是很嚴(yán)謹(jǐn) - -
            return mFormat.format(new Date(value));
        }
        return String.valueOf(value);
    }
}

class TimeConverterFactory extends Converter.Factory {

    @Override
    public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        if (type == Long.class) {
            //使用自定義TimeConverter
            return new TimeConverter();
        }
        return super.stringConverter(type, annotations, retrofit);
    }

    public static Converter.Factory create() {
        return new TimeConverterFactory();
    }
}

//再設(shè)置一下就行了烧给,addConverterFactory(TimeConverterFactory.create())

動態(tài)替換url

在構(gòu)建Retrofit時傳入HttpUrl對象,之后這個實例就一直存在不會更改喝噪,所以可以反射修改他的字段比如host础嫡,來實現(xiàn)動態(tài)替換服務(wù)端地址,

String SERVER = "https://www.xxx.com/";
HttpUrl httpUrl = HttpUrl.get(SERVER);
Retrofit retrofit = new Retrofit.Builder()
    //.baseUrl(SERVER)
    .baseUrl(httpUrl) //使用HttpUrl
    .build();

尾聲

咱們下期見~??

系列文章:

參考資料


歡迎關(guān)注原創(chuàng)技術(shù)公眾號:哈利迪ei

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市晚唇,隨后出現(xiàn)的幾起案子巫财,更是在濱河造成了極大的恐慌,老刑警劉巖缺亮,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件翁涤,死亡現(xiàn)場離奇詭異,居然都是意外死亡萌踱,警方通過查閱死者的電腦和手機(jī)葵礼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來并鸵,“玉大人鸳粉,你說我怎么就攤上這事≡暗#” “怎么了届谈?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵枯夜,是天一觀的道長。 經(jīng)常有香客問我艰山,道長湖雹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任曙搬,我火速辦了婚禮摔吏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘纵装。我一直安慰自己征讲,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布橡娄。 她就那樣靜靜地躺著诗箍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挽唉。 梳的紋絲不亂的頭發(fā)上滤祖,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天,我揣著相機(jī)與錄音橱夭,去河邊找鬼氨距。 笑死,一個胖子當(dāng)著我的面吹牛棘劣,可吹牛的內(nèi)容都是我干的俏让。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼茬暇,長吁一口氣:“原來是場噩夢啊……” “哼首昔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起糙俗,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤勒奇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后巧骚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赊颠,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年劈彪,在試婚紗的時候發(fā)現(xiàn)自己被綠了竣蹦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡沧奴,死狀恐怖痘括,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤纲菌,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布挠日,位于F島的核電站,受9級特大地震影響翰舌,放射性物質(zhì)發(fā)生泄漏嚣潜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一灶芝、第九天 我趴在偏房一處隱蔽的房頂上張望郑原。 院中可真熱鬧,春花似錦夜涕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至住诸,卻和暖如春驾胆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贱呐。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工丧诺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人奄薇。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓驳阎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親馁蒂。 傳聞我的和親對象是個殘疾皇子呵晚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評論 2 354