Android網絡請求心路歷程

網絡請求是android客戶端很重要的部分。下面從入門級開始介紹下自己Android網絡請求的實踐歷程。希望能給剛接觸Android網絡部分的朋友一些幫助。

本文包含:

HTTP請求&響應

Get&Post

HttpClient & HttpURLConnection

同步&異步

HTTP緩存機制

Volley&OkHttp

Retrofit&RestAPI

網絡圖片加載優化

Fresco&Glide

圖片管理方案

HTTP請求&響應

既然說從入門級開始就說說Http請求包的結構。

一次請求就是向目標服務器發送一串文本。什么樣的文本?有下面結構的文本。

HTTP請求包結構

請求包

例子:

POST /meme.php/home/user/login HTTP/1.1? ? Host: 114.215.86.90Cache-Control:no-cachePostman-Token: bd243d6b-da03-902f-0a2c-8e9377f6f6edContent-Type: application/x-www-form-urlencoded? ? tel=13637829200&password=123456

請求了就會收到響應包(如果對面存在HTTP服務器)

HTTP響應包結構

響應包

例子:


Http請求方式有

方法描述

GET請求指定url的數據,請求體為空(例如打開網頁)。

POST請求指定url的數據,同時傳遞參數(在請求體中)。

HEAD類似于get請求,只不過返回的響應體為空,用于獲取響應頭。

PUT從客戶端向服務器傳送的數據取代指定的文檔的內容。

DELETE請求服務器刪除指定的頁面。

CONNECTHTTP/1.1協議中預留給能夠將連接改為管道方式的代理服務器。

OPTIONS允許客戶端查看服務器的性能。

TRACE回顯服務器收到的請求,主要用于測試或診斷。

常用只有Post與Get。

Get&Post

網絡請求中我們常用鍵值對來傳輸參數(少部分api用json來傳遞,畢竟不是主流)。

通過上面的介紹,可以看出雖然Post與Get本意一個是表單提交一個是請求頁面,但本質并沒有什么區別。下面說說參數在這2者的位置。

Get方式

在url中填寫參數:

http://xxxx.xx.com/xx.php?params1=value1?ms2=value2

甚至使用路由

http://xxxx.xx.com/xxx/value1/value2/value3

這些就是web服務器框架的事了。

Post方式

參數是經過編碼放在請求體中的。編碼包括x-www-form-urlencoded與form-data。

x-www-form-urlencoded的編碼方式是這樣:

tel=13637829200&password=123456

form-data的編碼方式是這樣:

----WebKitFormBoundary7MA4YWxkTrZu0gWContent-Disposition: form-data;name="tel"13637829200----WebKitFormBoundary7MA4YWxkTrZu0gWContent-Disposition: form-data;name="password"123456----WebKitFormBoundary7MA4YWxkTrZu0gW

x-www-form-urlencoded的優越性就很明顯了。不過x-www-form-urlencoded只能傳鍵值對,但是form-data可以傳二進制

因為url是存在于請求行中的。

所以Get與Post區別本質就是參數是放在請求行中還是放在請求體

當然無論用哪種都能放在請求頭中。一般在請求頭中放一些發送端的常量。

有人說:

Get是明文,Post隱藏

移動端不是瀏覽器,不用https全都是明文。

Get傳遞數據上限XXX

胡說。有限制的是瀏覽器中的url長度,不是Http協議,移動端請求無影響。Http服務器部分有限制的設置一下即可。

Get中文需要編碼

是真的...要注意。URLEncoder.encode(params, "gbk");

還是建議用post規范參數傳遞方式。并沒有什么更優秀,只是大家都這樣社會更和諧。

上面說的是請求。下面說響應。

請求是鍵值對,但返回數據我們常用Json。

對于內存中的結構數據,肯定要用數據描述語言將對象序列化成文本,再用Http傳遞,接收端并從文本還原成結構數據。

對象(服務器)<-->文本(Http傳輸)<-->對象(移動端) 。

服務器返回的數據大部分都是復雜的結構數據,所以Json最適合。

Json解析庫有很多Google的Gson,阿里的FastJson

Gson的用法看這里

HttpClient & HttpURLConnection

HttpClient早被廢棄了,誰更好這種問題也只有經驗落后的面試官才會問。具體原因可以看這里

下面說說HttpURLConnection的用法。

最開始接觸的就是這個。

publicclassNetUtils{publicstaticStringpost(String url, String content){? ? ? ? ? ? HttpURLConnection conn =null;try{// 創建一個URL對象URL mURL =newURL(url);// 調用URL的openConnection()方法,獲取HttpURLConnection對象conn = (HttpURLConnection) mURL.openConnection();? ? ? ? ? ? ? ? conn.setRequestMethod("POST");// 設置請求方法為postconn.setReadTimeout(5000);// 設置讀取超時為5秒conn.setConnectTimeout(10000);// 設置連接網絡超時為10秒conn.setDoOutput(true);// 設置此方法,允許向服務器輸出內容// post請求的參數String data = content;// 獲得一個輸出流,向服務器寫數據,默認情況下,系統不允許向服務器輸出內容OutputStreamout= conn.getOutputStream();// 獲得一個輸出流,向服務器寫數據out.write(data.getBytes());out.flush();out.close();intresponseCode = conn.getResponseCode();// 調用此方法就不必再使用conn.connect()方法if(responseCode ==200) {? ? ? ? ? ? ? ? ? ? InputStreamis= conn.getInputStream();? ? ? ? ? ? ? ? ? ? String response = getStringFromInputStream(is);returnresponse;? ? ? ? ? ? ? ? }else{thrownewNetworkErrorException("response status is "+responseCode);? ? ? ? ? ? ? ? }? ? ? ? ? ? }catch(Exception e) {? ? ? ? ? ? ? ? e.printStackTrace();? ? ? ? ? ? }finally{if(conn !=null) {? ? ? ? ? ? ? ? ? ? conn.disconnect();// 關閉連接}? ? ? ? ? ? }returnnull;? ? ? ? }publicstaticStringget(String url){? ? ? ? ? ? HttpURLConnection conn =null;try{// 利用string url構建URL對象URL mURL =newURL(url);? ? ? ? ? ? ? ? conn = (HttpURLConnection) mURL.openConnection();? ? ? ? ? ? ? ? conn.setRequestMethod("GET");? ? ? ? ? ? ? ? conn.setReadTimeout(5000);? ? ? ? ? ? ? ? conn.setConnectTimeout(10000);intresponseCode = conn.getResponseCode();if(responseCode ==200) {? ? ? ? ? ? ? ? ? ? InputStreamis= conn.getInputStream();? ? ? ? ? ? ? ? ? ? String response = getStringFromInputStream(is);returnresponse;? ? ? ? ? ? ? ? }else{thrownewNetworkErrorException("response status is "+responseCode);? ? ? ? ? ? ? ? }? ? ? ? ? ? }catch(Exception e) {? ? ? ? ? ? ? ? e.printStackTrace();? ? ? ? ? ? }finally{if(conn !=null) {? ? ? ? ? ? ? ? ? ? conn.disconnect();? ? ? ? ? ? ? ? }? ? ? ? ? ? }returnnull;? ? ? ? }privatestaticStringgetStringFromInputStream(InputStreamis)? ? ? ? ? ? ? ? throws IOException{? ? ? ? ? ? ByteArrayOutputStream os =newByteArrayOutputStream();// 模板代碼 必須熟練byte[] buffer =newbyte[1024];intlen =-1;while((len =is.read(buffer)) !=-1) {? ? ? ? ? ? ? ? os.write(buffer,0, len);? ? ? ? ? ? }is.close();? ? ? ? ? ? String state = os.toString();// 把流中的數據轉換成字符串,采用的編碼是utf-8(模擬器默認編碼)os.close();returnstate;? ? ? ? }? ? }

注意網絡權限!被坑了多少次。

同步&異步

這2個概念僅存在于多線程編程中。

android中默認只有一個主線程,也叫UI線程。因為View繪制只能在這個線程內進行。

所以如果你阻塞了(某些操作使這個線程在此處運行了N秒)這個線程,這期間View繪制將不能進行,UI就會卡。所以要極力避免在UI線程進行耗時操作。

網絡請求是一個典型耗時操作。

通過上面的Utils類進行網絡請求只有一行代碼。

NetUtils.get("http://www.baidu.com");//這行代碼將執行幾百毫秒。

如果你這樣寫

@OverrideprotectedvoidonCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);? ? ? ? setContentView(R.layout.activity_main);? ? ? ? String response = Utils.get("http://www.baidu.com");? ? }

就會死。。

這就是同步方式。直接耗時操作阻塞線程直到數據接收完畢然后返回。Android不允許的。

異步方式:

//在主線程new的Handler,就會在主線程進行后續處理。privateHandlerhandler=newHandler();privateTextView textView;@OverrideprotectedvoidonCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);? ? ? ? setContentView(R.layout.activity_main);? ? ? ? textView = (TextView) findViewById(R.id.text);newThread(newRunnable() {@Overridepublicvoidrun(){//從網絡獲取數據finalString response = NetUtils.get("http://www.baidu.com");//向Handler發送處理操作handler.post(newRunnable() {@Overridepublicvoidrun(){//在UI線程更新UItextView.setText(response);? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? });? ? ? ? ? ? }? ? ? ? }).start();? ? }

在子線程進行耗時操作,完成后通過Handler將更新UI的操作發送到主線程執行。這就叫異步。Handler是一個Android線程模型中重要的東西,與網絡無關便不說了。關于Handler不了解就先去Google一下。

關于Handler原理一篇不錯的文章

但這樣寫好難看。異步通常伴隨者他的好基友回調。

這是通過回調封裝的Utils類。

publicclassAsynNetUtils{publicinterfaceCallback{voidonResponse(String response);? ? ? ? }publicstaticvoidget(finalString url,finalCallback callback){finalHandlerhandler=newHandler();newThread(newRunnable() {@Overridepublicvoidrun(){finalString response = NetUtils.get(url);handler.post(newRunnable() {@Overridepublicvoidrun(){? ? ? ? ? ? ? ? ? ? ? ? ? ? callback.onResponse(response);? ? ? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? ? ? });? ? ? ? ? ? ? ? }? ? ? ? ? ? }).start();? ? ? ? }publicstaticvoidpost(finalString url,finalString content,finalCallback callback){finalHandlerhandler=newHandler();newThread(newRunnable() {@Overridepublicvoidrun(){finalString response = NetUtils.post(url,content);handler.post(newRunnable() {@Overridepublicvoidrun(){? ? ? ? ? ? ? ? ? ? ? ? ? ? callback.onResponse(response);? ? ? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? ? ? });? ? ? ? ? ? ? ? }? ? ? ? ? ? }).start();? ? ? ? }? ? }

然后使用方法。

privateTextView textView;@OverrideprotectedvoidonCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);? ? ? ? setContentView(R.layout.activity_main);? ? ? ? textView = (TextView) findViewById(R.id.webview);? ? ? ? AsynNetUtils.get("http://www.baidu.com",newAsynNetUtils.Callback() {@OverridepublicvoidonResponse(String response){? ? ? ? ? ? ? ? textView.setText(response);? ? ? ? ? ? }? ? ? ? });

是不是優雅很多。

嗯,一個蠢到哭的網絡請求方案成型了。

愚蠢的地方有很多:

每次都new Thread,new Handler消耗過大

沒有異常處理機制

沒有緩存機制

沒有完善的API(請求頭,參數,編碼,攔截器等)與調試模式

沒有Https

HTTP緩存機制

緩存對于移動端是非常重要的存在。

減少請求次數,減小服務器壓力.

本地數據讀取速度更快,讓頁面不會空白幾百毫秒。

在無網絡的情況下提供數據。

緩存一般由服務器控制(通過某些方式可以本地控制緩存,比如向過濾器添加緩存控制信息)。通過在請求頭添加下面幾個字端:

Request

請求頭字段意義

If-Modified-Since: Sun, 03 Jan 2016 03:47:16 GMT緩存文件的最后修改時間。

If-None-Match: "3415g77s19tc3:0"緩存文件的Etag(Hash)值

Cache-Control: no-cache不使用緩存

Pragma: no-cache不使用緩存

Response

響應頭字段意義

Cache-Control: public響應被共有緩存,移動端無用

Cache-Control: private響應被私有緩存,移動端無用

Cache-Control:no-cache不緩存

Cache-Control:no-store不緩存

Cache-Control: max-age=6060秒之后緩存過期(相對時間)

Date: Sun, 03 Jan 2016 04:07:01 GMT當前response發送的時間

Expires: Sun, 03 Jan 2016 07:07:01 GMT緩存過期的時間(絕對時間)

Last-Modified: Sun, 03 Jan 2016 04:07:01 GMT服務器端文件的最后修改時間

ETag: "3415g77s19tc3:0"服務器端文件的Etag[Hash]值

正式使用時按需求也許只包含其中部分字段。

客戶端要根據這些信息儲存這次請求信息。

然后在客戶端發起請求的時候要檢查緩存。遵循下面步驟:

瀏覽器緩存機制

注意服務器返回304意思是數據沒有變動滾去讀緩存信息。

曾經年輕的我為自己寫的網絡請求框架添加完善了緩存機制,還沾沾自喜,直到有一天我看到了下面2個東西。(/TДT)/

Volley&OkHttp

Volley&OkHttp應該是現在最常用的網絡請求庫。用法也非常相似。都是用構造請求加入請求隊列的方式管理網絡請求。

先說Volley:

Volley可以通過這個庫進行依賴.

Volley在Android 2.3及以上版本,使用的是HttpURLConnection,而在Android 2.2及以下版本,使用的是HttpClient。

Volley的基本用法,網上資料無數,這里推薦郭霖大神的博客

Volley存在一個緩存線程,一個網絡請求線程池(默認4個線程)。

Volley這樣直接用開發效率會比較低,我將我使用Volley時的各種技巧封裝成了一個庫RequestVolly.

我在這個庫中將構造請求的方式封裝為了函數式調用。維持一個全局的請求隊列,拓展一些方便的API。

不過再怎么封裝Volley在功能拓展性上始終無法與OkHttp相比。

Volley停止了更新,而OkHttp得到了官方的認可,并在不斷優化。

因此我最終替換為了OkHttp

OkHttp用法見這里

很友好的API與詳盡的文檔。

這篇文章也寫的很詳細了。

OkHttp使用Okio進行數據傳輸。都是Square家的。

但并不是直接用OkHttp。Square公司還出了一個Retrofit庫配合OkHttp戰斗力翻倍。

Retrofit&RestAPI

Retrofit極大的簡化了網絡請求的操作,它應該說只是一個Rest API管理庫,它是直接使用OKHttp進行網絡請求并不影響你對OkHttp進行配置。畢竟都是Square公司出品。

RestAPI是一種軟件設計風格。

服務器作為資源存放地。客戶端去請求GET,PUT, POST,DELETE資源。并且是無狀態的,沒有session的參與。

移動端與服務器交互最重要的就是API的設計。比如這是一個標準的登錄接口。

Paste_Image.png

你們應該看的出這個接口對應的請求包與響應包大概是什么樣子吧。

請求方式,請求參數,響應數據,都很清晰。

使用Retrofit這些API可以直觀的體現在代碼中。

Paste_Image.png

然后使用Retrofit提供給你的這個接口的實現類 就能直接進行網絡請求獲得結構數據。

注意Retrofit2.0相較1.9進行了大量不兼容更新。google上大部分教程都是基于1.9的。這里有個2.0的教程。

教程里進行異步請求是使用Call。Retrofit最強大的地方在于支持RxJava。就像我上圖中返回的是一個Observable。RxJava上手難度比較高,但用過就再也離不開了。Retrofit+OkHttp+RxJava配合框架打出成噸的輸出,這里不再多說。

網絡請求學習到這里我覺得已經到頂了。。

網絡圖片加載優化

對于圖片的傳輸,就像上面的登錄接口的avatar字段,并不會直接把圖片寫在返回內容里,而是給一個圖片的地址。需要時再去加載。

如果你直接用HttpURLConnection去取一張圖片,你辦得到,不過沒優化就只是個BUG不斷demo。絕對不能正式使用。

注意網絡圖片有些特點:

它永遠不會變

一個鏈接對應的圖片一般永遠不會變,所以當第一次加載了圖片時,就應該予以永久緩存,以后就不再網絡請求。

它很占內存

一張圖片小的幾十k多的幾M高清無碼。尺寸也是64*64到2k圖。你不能就這樣直接顯示到UI,甚至不能直接放進內存。

它要加載很久

加載一張圖片需要幾百ms到幾m。這期間的UI占位圖功能也是必須考慮的。

說說我在上面提到的RequestVolley里做的圖片請求處理(沒錯我做了,這部分的代碼可以去github里看源碼)。

三級緩存

網上常說三級緩存--服務器,文件,內存。不過我覺得服務器不算是一級緩存,那就是數據源嘛。

內存緩存

首先內存緩存使用LruCache。LRU是Least Recently Used 近期最少使用算法,這里確定一個大小,當Map里對象大小總和大于這個大小時將使用頻率最低的對象釋放。我將內存大小限制為進程可用內存的1/8.

內存緩存里讀得到的數據就直接返回,讀不到的向硬盤緩存要數據。

硬盤緩存

硬盤緩存使用DiskLruCache。這個類不在API中。得復制使用。

看見LRU就明白了吧。我將硬盤緩存大小設置為100M。

@OverridepublicvoidputBitmap(Stringurl, Bitmap bitmap) {? ? ? put(url, bitmap);//向內存Lru緩存存放數據時,主動放進硬盤緩存里try{? ? ? ? ? Editor editor = mDiskLruCache.edit(hashKeyForDisk(url));? ? ? ? ? bitmap.compress(Bitmap.CompressFormat.JPEG,100, editor.newOutputStream(0));? ? ? ? ? editor.commit();? ? ? }catch(IOException e) {? ? ? ? ? e.printStackTrace();? ? ? }? }//當內存Lru緩存中沒有所需數據時,調用創造。@OverrideprotectedBitmap create(Stringurl) {//獲取keyStringkey= hashKeyForDisk(url);//從硬盤讀取數據Bitmap bitmap =null;try{? ? ? ? ? DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);if(snapShot!=null){? ? ? ? ? ? ? bitmap = BitmapFactory.decodeStream(snapShot.getInputStream(0));? ? ? ? ? }? ? ? }catch(IOException e) {? ? ? ? ? e.printStackTrace();? ? ? }returnbitmap;? }

DiskLruCache的原理不再解釋了(我還解決了它存在的一個BUG,向Log中添加的數據增刪記錄時,最后一條沒有輸出,導致最后一條緩存一直失效。)

硬盤緩存也沒有數據就返回空,然后就向服務器請求數據。

這就是整個流程。

但我這樣的處理方案還是有很多局限。

圖片未經壓縮處理直接存儲使用

文件操作在主線程

沒有完善的圖片處理API

以前也覺得這樣已經足夠好直到我遇到下面倆。

Fresco&Glide

不用想也知道它們都做了非常完善的優化,重復造輪子的行為很蠢。

Fresco是Facebook公司的黑科技。光看功能介紹就看出非常強大。使用方法官方博客說的夠詳細了。

真三級緩存,變換后的BItmap(內存),變換前的原始圖片(內存),硬盤緩存。

在內存管理上做到了極致。對于重度圖片使用的APP應該是非常好的。

它一般是直接使用SimpleDraweeView來替換ImageView,呃~侵入性較強,依賴上它apk包直接大1M。代碼量驚人。

所以我更喜歡Glide,作者是bumptech。這個庫被廣泛的運用在google的開源項目中,包括2014年google I/O大會上發布的官方app。

這里有詳細介紹。直接使用ImageView即可,無需初始化,極簡的API,豐富的拓展,鏈式調用都是我喜歡的。

豐富的拓展指的就是這個

另外我也用過Picasso。API與Glide簡直一模一樣,功能略少,且有半年未修復的BUG。

圖片管理方案

再說說圖片存儲。不要存在自己服務器上面,徒增流量壓力,還沒有圖片處理功能。

推薦七牛阿里云存儲(沒用過其它 π__π )。它們都有很重要的一項圖片處理。在圖片Url上加上參數來對圖片進行一些處理再傳輸。

于是(七牛的處理代碼)

publicstaticStringgetSmallImage(Stringimage){if(image==null)returnnull;if(isQiniuAddress(image))image+="?imageView2/0/w/"+IMAGE_SIZE_SMALL;returnimage;? ? }publicstaticStringgetLargeImage(Stringimage){if(image==null)returnnull;if(isQiniuAddress(image))image+="?imageView2/0/w/"+IMAGE_SIZE_LARGE;returnimage;? ? }publicstaticStringgetSizeImage(Stringimage,intwidth){if(image==null)returnnull;if(isQiniuAddress(image))image+="?imageView2/0/w/"+width;returnimage;? ? }

既可以加快請求速度,又能減少流量。再配合Fresco或Glide。完美的圖片加載方案。

不過這就需要你把所有圖片都存放在七牛或阿里云,這樣也不錯。

圖片/文件上傳也都是使用它們第三方存儲,它們都有SDK與官方文檔教你。

不過圖片一定要壓縮過后上傳。上傳1-2M大的高清照片沒意義。

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

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,785評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,660評論 25 708
  • 參考Android網絡請求心路歷程Android Http接地氣網絡請求(HttpURLConnection) 一...
    合肥黑閱讀 21,317評論 7 63
  • 筆者也是初入Git的小白,寫這篇Git入門教程的目的更多的是分享一些學習心得和經驗,不足之處懇請批評和指正。 因...
    TW_實驗室_白彬楠閱讀 930評論 1 1
  • 死亡是來去天堂與地獄之間選擇? 一切還沒有停止。 別試著去影響別的靈魂, 那是愚蠢的想法。 唯有擁抱才能夠真正體會...
    蔡振源閱讀 353評論 0 2