Retrofit使用詳解(二)

OKHttp(一)之Calls

OKHttp(二)之Connections

OkHttp(三)之使用方法

OkHttp(四)之攔截器

OkHttp(五)之HTTPS

Retrofit使用詳解(一)

Retrofit使用詳解(二)

Retrofit使用詳解(三)

Retrofit使用詳解(四)

Retrofit使用詳解(五)

Retrofit使用詳解(六)

同步請求和異步請求

Retrofit支持同步請求和異步請求。

同步請求

使用的同步請求通過定義返回類型來聲明。 下面的示例期望執行getTasks方法時返回Task列表。

public interface TaskService {  
    @GET("/tasks")
    Call<List<Task>> getTasks();
}

在Retrofit 2中,每個請求都被包裝到一個Call對象中。 實際的同步或異步請求將在稍后創建的調用對象上的所需方法執行。 但是,同步和異步請求的接口類在Retrofit 2中是相同的。

同步方法在主線程上執行。 這意味阻塞UI,并且在此期間不可能進行交互。

Note: 警告:同步請求會觸發Android 4.0或更高版本上的應用崩潰。 你會遇到NetworkOnMainThreadException錯誤。

同步方法能夠直接返回值,因為操作在網絡請求期間阻止其他任何操作。

對于非阻塞UI,你必須在一個單獨的線程中的請求執行。 這意味著,你仍然可以在等待響應時與應用程序本身進行交互。

Get Results from Synchronous Requests

以下代碼片段說明了使用Retrofit執行的同步請求:

TaskService taskService = ServiceGenerator.createService(TaskService.class);  
Call<List<Task>> call = taskService.getTasks();  
List<Task>> tasks = call.execute().body();  

在Call對象上使用.execute()方法,在Retrofit2中執行同步請求。反序列化響應主體可通過響應對象上的.body()方法獲得。

Asynchronous Requests

public interface TaskService {  
    @GET("/tasks")
    Call<List<Task>> getTasks();
}

Retrofit在單獨的線程中執行。Callback類是通用的,并映射您定義的返回類型。 我們的示例返回一個任務列表,Callback在內部進行映射。

如上所述:Retrofit 2中的接口定義對于同步和異步請求是相同的。 所需的返回類型被封裝到一個Call對象中,實際的請求執行定義它的類型(同步/異步)。

Get Results from Asynchronous Requests

使用異步請求必須實現兩個回調方法:成功和失敗。 當從服務類調用異步getTasks()方法時,必須實現回調,并定義一旦請求完成應該做什么:

TaskService taskService = ServiceGenerator.createService(TaskService.class);  
Call<List<Task>> call = taskService.getTasks();  
call.enqueue(new Callback<List<Task>>() {  
    @Override
    public void onResponse(Call<List<Task>> call, Response<List<Task>> response) {
        if (response.isSuccessful()) {
            // tasks available
        } else {
            // error response, no access to resource?
        }
    }

    @Override
    public void onFailure(Call<List<Task>> call, Throwable t) {
        // something went completely south (like no internet connection)
        Log.d("Error", t.getMessage());
    }
}

Get Raw HTTP Response

如果你需要原始的HTTP響應對象,只需要定義返回類型為Response.
你可以接收Retrofit 2中原始響應主體與定義請求類型(sync / async)的方式相同。 不需要將Response類定義為返回類型,但可以在onResponse()回調方法中捕獲它。 讓我們看看下面的代碼片段來說明如何獲得原始響應:

call.enqueue(new Callback<List<Task>>() {  
    @Override
    public void onResponse(Call<List<Task>> call, Response<List<Task>> response) {
        // get raw response
        Response raw = response.raw();
    }

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

在請求體中發送對象

Send Objects as Request Body

Retrofit提供了在請求體內發送對象的能力。 通過使用@Body注釋,可以指定對象用作HTTP請求主體。

public interface TaskService {  
    @POST("/tasks")
    Call<Task> createTask(@Body Task task);
}

定義的RestAdapter的轉換器(如Gson)會將對象映射到JSON,它將最終作為請求的主體發送到服務器。

Example

public class Task {  
    private long id;
    private String text;

    public Task(long id, String text) {
        this.id = id;
        this.text = text;
    }
}

創建一個新的Task對象。

Task task = new Task(1, "my task title");  
Call<Task> call = taskService.createTask(task);  
call.enqueue(new Callback<Task>() {});  

調用方法createTask會將Task轉換為JSON。Task的JSON將如下所示:

{
    "id": 1,
    "text": "my task title"
}

添加自定義頭信息

Retrofit提供了兩個定義HTTP請求標頭字段的選項:靜態和動態。 靜態請求頭不能更改。請求頭的的鍵和值是固定的,并與應用程序同時啟動。

相反,動態請求頭必須每次都設置。

靜態請求頭

將API方法的頭和相應的值定義為注解。 對于使用此方法的每個請求,頭信息會自動通過Retrofit添加。 注解必須是鍵值對,可以有一個或者多個:

public interface UserService {  
    @Headers("Cache-Control: max-age=640000")
    @GET("/tasks")
    Call<List<Task>> getTasks();
}

上面的示例顯示了靜態頭的鍵值定義。 此外,您可以將多個鍵值字符串作為封裝在大括號{}中的列表傳遞到@Headers注釋。

public interface UserService {  
    @Headers({
        "Accept: application/vnd.yourapi.v1.full+json",
        "User-Agent: Your-App-Name"
    })
    @GET("/tasks/{task_id}")
    Call<Task> getTask(@Path("task_id") long taskId);
}

此外,您可以通過Retrofit的RequestInterceptor的攔截方法(在Retrofit 2中自定義實現Interceptor接口)來定義靜態頭。
在Retrofit中必須在OkHttp中添加攔截器。

OkHttpClient.Builder httpClient = new OkHttpClient.Builder();  
httpClient.addInterceptor(new Interceptor() {  
    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        Request original = chain.request();

        Request request = original.newBuilder()
            .header("User-Agent", "Your-App-Name")
            .header("Accept", "application/vnd.yourapi.v1.full+json")
            .method(original.method(), original.body())
            .build();

        return chain.proceed(request);
    }
}

OkHttpClient client = httpClient.build();  
Retrofit retrofit = new Retrofit.Builder()  
    .baseUrl(API_BASE_URL)
    .addConverterFactory(GsonConverterFactory.create())
    .client(client)
    .build();

上面的示例將User-Agent和Accept頭字段設置為相應的值。 這些值與使用RestAdapter(Retrofit 2中的Retrofit)和集成的RequestInterceptor(Retrofit 2中的Interceptor)執行的請求一起傳遞。

Dynamic Header

動態header是作為參數傳進方法中的。 在執行請求之前,通過Retrofit映射提供的參數值:

public interface UserService {  
    @GET("/tasks")
    Call<List<Task>> getTasks(@Header("Content-Range") String contentRange);
}

動態請求頭允許你為每個請求設置不同的值。

復寫Retrofit2中的已經存在的請求頭

  • .header(key,value):如果已經存在由鍵標識的現有頭,則用值覆蓋相應的值
  • .addHeader(key,value):添加相應的標題鍵和值,即使存在具有相同鍵的現有標題字段

在攔截器中管理請求頭

添加請求頭

一個常見的例子是使用授權頭字段。如果幾乎每個請求都需要包含授權的頭字段,則可以使用攔截器來添加這條信息。 這樣,就不需要為每個端點聲明添加@Header注釋。

類似于前邊介紹的,可以直接添加頭信息,也可以覆蓋原有的頭信息。

How to Override Headers

使用OkHttp攔截器允許你修改實際請求。請求構建器具有一個.header(key,val)方法,它會將定義的頭添加到請求中。 如果已經存在具有相同鍵標識符的現有頭,則此方法將覆蓋先前定義的值。

OkHttpClient.Builder httpClient = new OkHttpClient.Builder();  
httpClient.addInterceptor(new Interceptor() {  
    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        Request original = chain.request();

        // Request customization: add request headers
        Request.Builder requestBuilder = original.newBuilder()
                .header("Authorization", "auth-value"); // <-- this is the important line

        Request request = requestBuilder.build();
        return chain.proceed(request);
    }
});

OkHttpClient client = httpClient.build();  

Retrofit和OkHttp允許你添加多個具有相同key的頭信息,這將會覆蓋原有的頭信息。

How to Not Override Headers

有時會使用具有相同名稱的多個頭。實際上,我們只知道一個具體的實例:Cache-Control頭。 HTTP RFC2616指定允許具有相同名稱的多個標頭值,如果它們可以表示為逗號分隔列表。

Cache-Control: no-cache  
Cache-Control: no-store  

等同于

Cache-Control: no-cache, no-store  

此時可以調用.addHeader()方才來添加頭,而不是覆蓋。

OkHttpClient.Builder httpClient = new OkHttpClient.Builder();  
httpClient.addInterceptor(new Interceptor() {  
    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        Request original = chain.request();

        // Request customization: add request headers
        Request.Builder requestBuilder = original.newBuilder()
                    .addHeader("Cache-Control", "no-cache")
                    .addHeader("Cache-Control", "no-store");

        Request request = requestBuilder.build();
        return chain.proceed(request);
    }
});

OkHttpClient client = httpClient.build();  
  • .header(key,value):如果已經存在由鍵標識的現有頭,則用值覆蓋相應的值
  • .addHeader(key,value):添加相應的標題鍵和值,即使存在具有相同鍵的現有標題字段

使用@HeaderMap添加請求頭

Dynamic Request Headers

前邊文章中顯示的方法都是靜態的。 雖然你可以更改請求頭的值,但無法動態選擇實際發送的請求頭。 ,@HeaderMap可以讓你在運行時決定哪些頭被添加到你的請求。

與@Header注釋類似,需要將@HeaderMap聲明為接口參數之一。 參數的類型需要實現Java Map接口:

public interface TaskService {  
    @GET("/tasks")
    Call<List<Task>> getTasks(
        @HeaderMap Map<String, String> headers
    );
}

使用我們上面聲明的接口非常簡單。 您可以創建一個Map實例,并根據您的需要使用值填充它。 Retrofit將@HeaderMap的每個非空元素添加為請求標頭。

TaskService taskService = ServiceGenerator.createService(TaskService.class);

Map<String, String> map = new HashMap<>();  
map.put("Page", String.valueOf(page));

if (BuildConfig.DEBUG) {  
    map.put("Accept", "application/vnd.yourapi.v1.full+json");
    map.put("User-Agent", "Future Studio Debug");
}
else {  
    map.put("Accept", "application/json");
    map.put("Accept-Charset", "utf-8");
    map.put("User-Agent", "Future Studio Release");
}

Call<List<Task>> call = taskService.getTasks(map);  
// Use it like any other Retrofit call

多個具有相同名稱的查詢參數

Query Parameters

Query parameters 是從客戶端傳遞數據到服務器的最常見的方式。

https://api.example.com/tasks?id=123  

上面的例子是通過路由"tasks"傳遞數據"id=123"到服務器。

在Retrofit中可以如下寫

public interface TaskService {  
    @GET("/tasks")
    Call<Task> getTask(@Query("id") long taskId);
}

在getTask方法中需要傳入參數"taskId",Retrofit會自動轉換為"/task?id=<taskid>"形式。

Multiple Query Parameters

一些情況下需要傳遞多個相同名字的參數到服務器,類似于下面的樣子:

https://api.example.com/tasks?id=123&id=124&id=125  

此時期望服務器的返回值應該是一個任務列表對應的id是ids=[123,124,125]。

在Retrofit中可以很簡單的做到,只需要傳遞一個List即可。

public interface TaskService {  
    @GET("/tasks")
    Call<List<Task>> getTask(@Query("id") List<Long> taskIds);
}

可選查詢參數

根據API的設計,有時候我們需要傳遞可選的參數到服務器。如果你不想傳遞參數,就傳遞null即可。
service.getTasks(null);
Retrofit在重新編譯請求時會自動忽略null的參數。記住,你不能使用原始數據類型來傳遞null,例如:int,float,long等,你必須使用它們的包裝類:Integer,Float,Long等,這樣編譯器不會報錯:

public interface TaskService {  
    @GET("/tasks")
    Call<List<Task>> getTasks(
        @Query("sort") String order,
        @Query("page") Integer page);
}

現在你可以為getTasks方法傳遞null參數了。

service.getTasks(null, null);  

傳遞URL表單數據

表單請求

在Retrofit添加表單請求只需要添加另外一個注解將會直接將你的請求類型轉換為"application/x-www-form-urlencoded".如下邊的例子:

public interface TaskService {  
    @FormUrlEncoded
    @POST("tasks")
    Call<Task> createTask(@Field("title") String title);
}

重要的部分是@FormUrlEncoded這個注解。你不能使用get方法時候添加這個注解,表單的目的是傳遞數據到服務器。
此外,必須使用@Field注釋來與您的請求一起發送的參數。 將所需的鍵放在@Field(“key”)注釋中以定義參數名稱。 此外,將您的值的類型添加為方法參數。 如果不使用String,Retrofit將使用Java的String.valueOf(yourObject)方法創建一個字符串值。

service.createTask("Research Retrofit form encoded requests");  

生成的結果是

title=Research+Retrofit+form+encoded+requests  

假如需要傳遞多個參數,你只需要繼續添加@Field即可。

Form Encoded Requests Using an Array of Values

在上邊的例子中使用@Field注解可以添加字符串數據。但是如果你想要使用對象而不是字符串類型,Retrofit將會把你的對象轉換為字符串。你也可以使用同一個key傳遞一個字符串數組。

public interface TaskService {  
    @FormUrlEncoded
    @POST("tasks")
    Call<List<Task>> createTasks(@Field("title") List<String> titles);
}

現在來看一下如何使用

List<String> titles = new ArrayList<>();  
titles.add("Research Retrofit");  
titles.add("Retrofit Form Encoded")

service.createTasks(titles);  

生成的結果如下:
title=Research+Retrofit&title=Retrofit+Form+Encoded

每個條目都被轉換成了類似于map的鍵值對形式,鍵值對與鍵值對之間用&連接,鍵值對內部用=連接。

Field Options

@Filed注解有一個編碼選項encoded,是布爾值。默認是false。

這個encoded定義的內容是你是否已經為鍵值對編碼為url:
@Field(value = "title", encoded = true) String title

Form-Urlencoded vs. Query Parameter

這兩種傳遞數據的方式有什么不同:
form-urlencoded: POST
query parameter: GET
使用form-urlencoded方式傳遞數據的時候數據存放在請求體內,不會再url中顯示,使用query parameter方式傳遞參數時會在url中顯示,多用于篩選或指定區域數據查詢。

使用FieldMap發送表單數據

What Is @Fieldmap in Retrofit?

假如有多個注解,例如添加查詢參數或者路徑參數,使用給定對象請求數據,創建已經編碼好的請求體。舉一個簡單的例子,你現在想更新程序中用戶的數據,你需要請求一個接口要求是鍵值對形式上傳的。接口接受一個JSON字段,你可以這樣寫:

public interface UserService {  
    @FormUrlEncoded
    @PUT("user")
    Call<User> update(
            @Field("username") String username,
            @Field("name") String name,
            @Field("email") String email,
            @Field("homepage") String homepage,
            @Field("location") String location
    );
}

PUT方法需要多個參數,例如usernam,email,homepage等。

缺點:每次我們使用新的數據發送更新的時候,我們必須要提供每一個參數的值,即使他們沒有更改,這樣做很繁瑣。

Retrofit提供了一個解決上述問題的方案:@FieldMap。

Form Encoded Requests Using FieldMap

有時候你只需要為某個用戶更新指定字段,你可以使用Retrofit的@FieldMao。你可以使用java標準的Map格式來添加你的鍵值對。

public interface UserService {  
    @FormUrlEncoded
    @PUT("user")
    Call<User> update(@FieldMap Map<String, String> fields);
}

Note:假如你只需要更新你的username字段,那就沒有必要添加username以外的字段,你的請求只包含單個字段。

@FiledMap在應用程序中使用的非常廣泛,但是又一點不好的地方:你不知道哪些字段是允許添加的,你也不知道字段名稱,這就需要額外的文檔來說明。

FieldMap Options

同@Field一樣,@FieldMap也包含一個布爾值encoded,默認是false。

使用方法如下:
@FieldMap(encoded = true) Map<String, String> fields

encode定義了每個鍵值對是否已經被編碼。

來看一個簡單的例子。要更新username字段的值為marcus-poehls,默認情況下會變成username=marcus-poehls,使用編碼后會變成username=marcus%2Dpoehls。

為每個請求添加Query Parameters

你可以通過向OkHttpClient添加一個新的請求攔截器來實現。 攔截實際請求并獲取HttpUrl。 http url是添加查詢參數所必需的,因為它將通過附加查詢參數名稱及其值來更改先前生成的請求網址。

OkHttpClient.Builder httpClient =  
    new OkHttpClient.Builder();
httpClient.addInterceptor(new Interceptor() {  
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request original = chain.request();
        HttpUrl originalHttpUrl = original.url();

        HttpUrl url = originalHttpUrl.newBuilder()
                .addQueryParameter("apikey", "your-actual-api-key")
                .build();

        // Request customization: add request headers
        Request.Builder requestBuilder = original.newBuilder()
                .url(url);

        Request request = requestBuilder.build();
        return chain.proceed(request);
    }
});

一旦您擁有HttpUrl對象,就可以基于原始的http url對象創建一個新的構建器。 該構建器將允許您使用.addQueryParameter(key,val)方法添加其他查詢參數。 添加查詢參數后,使用.build()方法創建新的HttpUrl對象,該對象通過使用Request.Builder添加到請求中。 上面的代碼使用基于原始請求的附加參數構建新請求,并且只是將網址與新創建的網址進行交換。

使用@QueryMap添加多個查詢參數

Retrofit允許使用@Query注解來添加查詢參數。假如有多個參數需要傳遞,類似于下面:

public interface NewsService() {  
    @GET("/news")
    Call<List<News>> getNews(
            @Query("page") int page,
            @Query("order") String order,
            @Query("author") String author,
            @Query("published_at") Date date,
            …
    );
}

你可以使用null來傳給getNews方法。但是Retrofit提供了一個更好的方法。

How to Use QueryMap

@QueryMap注解使用Map<String,String>來作為參數,每個非空的key的value都會被添加進去。

public interface NewsService() {  
    @GET("/news")
    Call<List<News>> getNews(
        @QueryMap Map<String, String> options
    );
}

如果你請求作者是Marcus第二頁的新聞,你可以只添加page和author字段,而沒必要再去添加其他字段。

private void fetchNews() {  
    Map<String, String> data = new HashMap<>();
    data.put("author", "Marcus");
    data.put("page", String.valueOf(2));

    // simplified call to request the news with already initialized service
    Call<List<News>> call = newsService.getNews(data);
    call.enqueue(…);
}

最后的結果是

http://your.api.url/news?page=2&author=Marcus

QueryMap Options

同前面的一樣,它也擁有布爾值encoded,默認是false。此處不講解詳細用法了。

怎樣在請求中使用動態URL

Use-Case Scenarios

說明一下這個動態URl是不使用BaseURL的網址。

  1. 上傳文件:假如你的APP允許用戶上傳自己的圖片,你可能回把它們保存到自己的服務器上。
  2. 下載文件:文件能在不同于BaseURL的網址保存

How to Use Dynamic Urls

你可使用@Url注解為請求方法添加動態網址:

public interface UserService {  
    @GET
    public Call<ResponseBody> profilePicture(@Url String url);
}

上邊的@GET注解后邊并沒有添加路由參數,它會自己添加@Url注解的參數作為請求地址。

How Urls Resolve Against Your Base Url

Retrofit2使用OkHttp的httpurl解析站點。
來看第一個例子:

Retrofit retrofit = Retrofit.Builder()  
    .baseUrl("https://your.api.url/");
    .build();

UserService service = retrofit.create(UserService.class);  
service.profilePicture("https://s3.amazon.com/profile-picture/path");

// request url results in:
// https://s3.amazon.com/profile-picture/path

因為你使用了一個完全的地址(https://s3.amazon.com/profile-picture/path),Retrofit會替換掉BaseUrl然后使用你輸入的地址。

來看第二個例子

Retrofit retrofit = Retrofit.Builder()  
    .baseUrl("https://your.api.url/");
    .build();

UserService service = retrofit.create(UserService.class);  
service.profilePicture("profile-picture/path");

// request url results in:
// https://your.api.url/profile-picture/path

這個例子中你添加的參數被拼接到BaseURL后邊。

來看第三個例子

Retrofit retrofit = Retrofit.Builder()  
    .baseUrl("https://your.api.url/v2/");
    .build();

UserService service = retrofit.create(UserService.class);  
service.profilePicture("/profile-picture/path");

// request url results in:
// https://your.api.url/profile-picture/path

第二個和第三個例子之間的區別是:我們添加了v2 /到BaseURl,并使用/在路徑前面。 實際上,這將導致相同的最終請求url,因為以開頭的斜杠開頭的端點url將僅附加到基本url的主機url。 當對端點網址使用前導斜杠時,將忽略主機網址后面的所有內容。 您可以通過從您的端點刪除前導/來解決您的問題。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,828評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,752評論 25 708
  • 整體Retrofit內容如下: 1、Retrofit解析1之前哨站——理解RESTful2、Retrofit解析2...
    隔壁老李頭閱讀 15,130評論 4 39
  • 〖各種繞〗?「簡化」 一些看起來復雜的圖樣 只要按解構步驟 一筆一劃畫出來 過程其實幾簡單
    肥鴿子麻麻閱讀 314評論 4 2
  • 《今天的花店沒有丁香》 金色的陽光穿過頭頂灰色的天空,落在這一座鋼筋水泥的森林,鏤空他的掌心,鋪在他的眼睛上,熠熠...
    臨江渚雨閱讀 234評論 0 0