OkHttp的使用

PREVIEW

  • 導(dǎo)入
  • 簡單使用
  • 使用HttpUrl
  • Header處理
  • Post提交String
  • Post提交表單
  • Post提交文件
  • Post提交流
  • 使用Gson解析response
  • 設(shè)置超時
  • 配置新client
  • 攔截器
    ? 應(yīng)用攔截器
    ? 網(wǎng)絡(luò)攔截器
    ? 兩者區(qū)別
  • 緩存處理
    ? 緩存策略
    ? 緩存流程
    ? 緩存總結(jié)

導(dǎo)入

在build.gradle下添加依賴,具體版本號參考OkHttp更新日志

dependencies {
    ...
    implementation 'com.squareup.okhttp3:okhttp:4.7.2'
}

簡單使用

本文使用的OkHttp版本為4.7.2。OkHttp的核心類主要有OkHttpClient荸型,Dispatcher贺待,CallRequest蝙叛,Response撮弧,Interceptor饲做,Chain飒箭。其中OkHttpClient是負(fù)責(zé)管理多個Call的組織者,而每個Call又包含一個RequestResponse蜒灰,并且Call中的回調(diào)用于提供響應(yīng)結(jié)果弦蹂。要完成一次網(wǎng)絡(luò)請求,我們需要告訴Call需要處理的Request是什么樣的强窖,例如它的URL是什么凸椿,然后將Call交給OkHttpClientOkHttpClient僅對本次請求做一些配置翅溺,例如指定緩存路徑脑漫,它會讓Dispatcher去決定何時執(zhí)行Call。而Dispatcher的底層實現(xiàn)就是一個由OkHttp默認(rèn)實現(xiàn)的線程池咙崎,它將最終執(zhí)行Call中的.run()方法优幸。最后的InterceptorChain將用于數(shù)據(jù)的攔截處理。OkHttp提供兩種方式提交網(wǎng)絡(luò)請求褪猛,分別是Call.execute()Call.enqueue(Callback)网杆,前者會阻塞線程,后者加入隊列異步執(zhí)行。通過調(diào)用response.body().string()我們可以得到響應(yīng)的body部分并以String形式返回碳却,但值得注意的是.string()只能調(diào)用一次队秩。

  • 同步調(diào)用

一般來說要在得到結(jié)果的第一時間修改UI,我們可能會使用Call.execute()AsyncTask完成提交請求昼浦。但AsyncTask通常會導(dǎo)致context內(nèi)存泄漏馍资,因為它是非靜態(tài)嵌套類,所以不推薦使用同步調(diào)用关噪。以下例子使用https://reqres.in測試請求:

public class MainActivity extends AppCompatActivity {
    private TextView mTextView;
    private OkHttpClient mClient = new OkHttpClient();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextView = findViewById(R.id.textView);

        String url = "https://reqres.in/api/users/2";

        OkHttpHandler okHttpHandler = new OkHttpHandler();
        okHttpHandler.execute(url);
    }

    private class OkHttpHandler extends AsyncTask<String, String, String> {
        @Override
        protected String doInBackground(String... params) {
            Request request = new Request.Builder()
                    .url(params[0])
                    .build();
            try {
                Response response = mClient.newCall(request).execute();
                return response.body().string();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            mTextView.setText(s);
        }
    }
}
  • 異步調(diào)用

拋開同步調(diào)用鸟蟹,使用Call.enqueue(Callback)Activity.runOnUiThread(Runnable)的方式是提交請求的最佳方案。其中Activity.runOnUiThread(Runnable)方法傳入Runnable色洞,這個Runnable將插入到UI線程的事件隊列末尾戏锹,等待執(zhí)行run()方法。以下例子使用https://reqres.in測試請求:

public class MainActivity extends AppCompatActivity {
    private TextView mTextView;
    private OkHttpClient mClient = new OkHttpClient();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextView = findViewById(R.id.textView);

        String url = "https://reqres.in/api/users/2";

        try {
            get(url);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void get(String url) {
        Request request = new Request.Builder()
                .url(url)
                .build();

        mClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                call.cancel();
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                final String responseString = response.body().string();

                MainActivity.this.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mTextView.setText(responseString);
                    }
                });
            }
        });
    }
}

使用HttpUrl

HttpUrl用于生成含參的URL火诸,以下例子使用https://resttesttest.com測試請求:

HttpUrl.Builder urlBuilder = HttpUrl.parse("https://httpbin.org/get").newBuilder();
urlBuilder.addQueryParameter("category", "android");
urlBuilder.addQueryParameter("title", "okhttp");
String url2 = urlBuilder.build().toString();

Header處理

  • 設(shè)置請求頭

.header()設(shè)置唯一的請求頭锦针,舊值會被替換。.addHeader()新增請求頭置蜀,可以添加多值

Request request = new Request.Builder()
    .url(url)
    .addHeader("Accept","application/json; charset=utf-8")
    .header("Accept","application/json; charset=utf-8")
    .post(requestBody)
    .build();
  • 獲得響應(yīng)頭

.header()返回單值奈搜,.headers()返回多值的響應(yīng)頭

String headerString=response.header("Server");
List<String> headerStrings=response.headers("Vary");

Log.i(TAG,headerString);
Iterator<String> it=headerStrings.iterator();
while (it.hasNext()) {
    Log.i(TAG,it.next());
}

Post提交String

以下例子使用https://reqres.in測試請求:

private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
String url = "https://reqres.in/api/users/";
String jsonString = "{\n" +
        "    \"name\": \"morpheus\",\n" +
        "    \"job\": \"leader\"\n" +
        "}";
private void post(String url, final String requestString) {
    RequestBody requestBody = RequestBody.create(JSON, requestString);

    Request request = new Request.Builder()
            .url(url)
            .post(requestBody)
            .build();

    mClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            call.cancel();
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            final String responseString = response.body().string();
            MainActivity.this.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mTextView.setText(responseString);
                }
            });
        }
    });
}

Post提交表單

final String url = "https://tieba.baidu.com/f"; 
RequestBody requestBody = new FormBody.Builder()
    .add("ie", "utf-8")
    .add("kw", "minecraft")
    .build();

Request request = new Request.Builder()
    .url(url)
    .post(requestBody)
    .build();

Post提交文件

public static final MediaType JPEG = MediaType.parse("image/jpeg");
File file = new File(Environment.getExternalStoragePublicDirectory(
    Environment.DIRECTORY_DCIM), "building.jpg");

RequestBody requestBody = RequestBody.create(JPEG, file);

Request request = new Request.Builder()
    .url(url)
    .post(requestBody)
    .build();

Post提交流

RequestBody requestBody = new RequestBody() {
    @Nullable
    @Override
    public MediaType contentType() {
        return null;
    }

    @Override
    public void writeTo(@NotNull BufferedSink bufferedSink) throws IOException {
        bufferedSink.writeUtf8(requestString);
    }
};

Request request = new Request.Builder()
    .url(url)
    .post(requestBody)
    .build();

使用Gson解析response

String url = "https://api.github.com/gists/c2a7c39532239ff261be";
class Gist{
    Map<String,GistFile> files;
}

class GistFile{
    String content;
}
Gson gson = new Gson();
Gist gist = gson.fromJson(response.body().charStream(),Gist.class);
for(Map.Entry<String,GistFile> entry:gist.files.entrySet()){
    Log.i(TAG,entry.getKey()+ " "+entry.getValue().content);
}

設(shè)置超時

OkHttpClient client = new OkHttpClient.Builder()
    .connectTimeout(3, TimeUnit.SECONDS)
    .build();

配置新client

.newBuilder()會返回一個配置相同的buidler

OkHttpClient client = new OkHttpClient.Builder()
        .connectTimeout(3, TimeUnit.SECONDS)
        .build();

OkHttpClient client2 = client.newBuilder()
        .connectTimeout(5, TimeUnit.SECONDS)
        .build();

攔截器

攔截器(Interceptor)是OkHttp的概念,也是核心功能盯荤。OkHttp有兩種攔截器馋吗,分別是應(yīng)用攔截器和網(wǎng)絡(luò)攔截器。攔截器的主要目的在于重寫requestresponse秋秤,可以在發(fā)出request前修改headers或body宏粤,也可以在收到response前修改headers或body。我們完全可以在用戶收到reponse前將其修改成一個完全不一樣的新response灼卢,這一功能使得我們可以進(jìn)行后續(xù)的緩存策略修改或是使用gzip壓縮requestBody等操作绍哎。應(yīng)用攔截器在用戶發(fā)出一次請求后的全過程中僅調(diào)用一次,而網(wǎng)絡(luò)攔截器可能因為重定向等問題多次調(diào)用鞋真,例如有一次重定向就會調(diào)用兩次崇堰。攔截器可以設(shè)置多個,并按添加順序進(jìn)行攔截涩咖。下圖來自OkHttp文檔:

攔截器圖解

兩種攔截器區(qū)別如下海诲,參考OkHttp文檔原文:

Application interceptors

  • Don’t need to worry about intermediate responses like redirects and retries.
  • Are always invoked once, even if the HTTP response is served from the cache.
  • Observe the application’s original intent. Unconcerned with OkHttp-injected headers like If-None-Match.
  • Permitted to short-circuit and not call Chain.proceed().
  • Permitted to retry and make multiple calls to Chain.proceed().
  • Can adjust Call timeouts using withConnectTimeout, withReadTimeout,withWriteTimeout.

Network Interceptors

  • Able to operate on intermediate responses like redirects and retries.
  • Not invoked for cached responses that short-circuit the network.
  • Observe the data just as it will be transmitted over the network.
  • Access to the Connection that carries the request.

個人翻譯如下:

應(yīng)用攔截器

  • 使用時不需要考慮例如重定向、重試等中轉(zhuǎn)請求帶來的影響
  • 全過程只攔截一次檩互,即使攔截的response來自緩存
  • 可處理來自Applcation(參考攔截器圖解)的本意特幔。(例如no-cache)不涉及OkHttp的頭部注入例如If-None-Match頭部(這是在core注入的)
  • 可以不調(diào)用Chain.proceed()(例如return一個來自緩存的response,但不能return null
  • 可以重試和多次調(diào)用Chain.proceed()
  • 可通過withConnectTimeout,withReadTimeout, withWriteTimeout調(diào)整Call的超時時間.

網(wǎng)絡(luò)攔截器

  • 可處理例如重定向闸昨、重試等中轉(zhuǎn)請求
  • 不涉及緩存的調(diào)用
  • 可處理來自服務(wù)器的原始響應(yīng)
  • 可對最終發(fā)出前的請求做讀寫
  • 實現(xiàn)攔截器

以上所說攔截器可對處于中間時期的requestresponse做修改敬辣,就是在chain.proceed(request)的前后完成的雪标。
chain.proceed(request)會返回通過core或服務(wù)器處理后得到的response,這個方法會阻塞線程溉跃。

String url = "http://publicobject.com/helloworld.txt";
class LoggingInterceptor implements Interceptor {
    @Override public Response intercept(Interceptor.Chain chain) throws IOException {
        Request request = chain.request();

        //do something to rewrite request

        long t1 = System.nanoTime();
        Log.i(TAG,String.format("Sending request %s on %s%n%s",
                request.url(), chain.connection(), request.headers()));

        Response response = chain.proceed(request);

        long t2 = System.nanoTime();
        Log.i(TAG,String.format("Received response for %s in %.1fms%n%s",
                response.request().url(), (t2 - t1) / 1e6d, response.headers()));

        //do something to rewrite response

        return response;
    }
}
  • 設(shè)置應(yīng)用攔截器

兩種攔截器在實現(xiàn)的時候沒有區(qū)別村刨,充當(dāng)那種攔截器取決于調(diào)用的方法是.addInterceptor()或是.addNetworkInterceptor().addInterceptor()表示設(shè)置應(yīng)用攔截器撰茎,.addNetworkInterceptor()則是網(wǎng)絡(luò)攔截器嵌牺。

OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(new LoggingInterceptor())
    .build();
  • 設(shè)置網(wǎng)絡(luò)攔截器
OkHttpClient client = new OkHttpClient.Builder()
    .addNetworkInterceptor(new LoggingInterceptor())
    .build();

緩存處理

OkHttp默認(rèn)不使用緩存,可以調(diào)用.cache()開啟龄糊,但.cache()僅能設(shè)置緩存區(qū)大小和緩存讀寫的位置逆粹。Cache-Control頭部是Http協(xié)議定義的,而OkHttp完全遵循Http協(xié)議炫惩,所以O(shè)kHttp的緩存策略是由請求頭或響應(yīng)頭中的Cache-Control頭部而定的僻弹。如果服務(wù)器返回的response已經(jīng)帶有Cache-Control響應(yīng)頭,在buidler中調(diào)用.cache()即可使用緩存他嚷。反之當(dāng)收到的response沒有設(shè)置Cache-Control時蹋绽,可以在攔截器里手動添加,不同參數(shù)對應(yīng)不同的緩存策略筋蓖。不論response是否有Cache-Control卸耘,始終可以在發(fā)出request時添加例如Cache-control: no-cache來控制緩存使用與否。

啟用緩存

String url = "http://publicobject.com/helloworld.txt";
int _10MB = 10 * 1024 * 1024;
File cacheDir = getCacheDir();
Cache cache = new Cache(cacheDir, _10MB);

OkHttpClient client = new OkHttpClient.Builder()
    .cache(cache)
    .build();

緩存策略

Http協(xié)議的Cache-Control的參數(shù)有很多粘咖,可設(shè)置多個參數(shù)蚣抗,多個參數(shù)間用逗號分隔開。以下主要介紹其中幾種的含義

  • Cache-Control: max-age=3600
    設(shè)置緩存過期時間為一小時瓮下。單位為秒翰铡,用于response

  • Cache-Control: max-stale=3600
    表示接受使用過期的緩存,最長時間為過期后的一小時讽坏,單位為秒两蟀。用于request

  • Cache-control: no-cache
    先不使用本地緩存,向服務(wù)器驗證緩存是否過期后決定緩存使用與否震缭,且并不取消本次response的緩存。用于request

  • Cache-control: no-store
    本次請求不緩存得到的response战虏,也表示本次請求不讀取緩存拣宰。用于request

  • Cache-control: only-if-cached
    僅嘗試使用緩存。用于request

  • Cache-control: public
    表明響應(yīng)可以被任何對象(包括:發(fā)送請求的客戶端烦感,代理服務(wù)器巡社,等等)緩存,即使是通常不可緩存的內(nèi)容手趣。(例如該響應(yīng)沒有max-age指令或Expires消息頭晌该, 該響應(yīng)對應(yīng)的請求方法是 POST )

  • Cache-control: private
    表明響應(yīng)只能被單個用戶緩存肥荔,不能作為共享緩存(即代理服務(wù)器不能緩存它)。私有緩存可以緩存響應(yīng)內(nèi)容朝群,比如:對應(yīng)用戶的本地瀏覽器燕耿。

  • Cache-Control: min-fresh=3600
    表示客戶端希望獲取一個能在指定的秒數(shù)內(nèi)保持其最新狀態(tài)的響應(yīng)。

  • Cache-control: must-revalidate
    一旦資源過期(比如已經(jīng)超過max-age)誉帅,在成功向原始服務(wù)器驗證之前,緩存不能用該資源響應(yīng)后續(xù)請求蚜锨。

此外與緩存有關(guān)的header可能還有ExpiresPragma,這里暫不介紹

  • 直接修改Cache-Control頭部定義緩存策略
class CacheInterceptor implements Interceptor {
    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        Request request = chain.request();

        request = request.newBuilder()
                .header("Cache-Control", "max-stale=3600")
                .build();

        return chain.proceed(request);
    }
}
Interceptor interceptor = new CacheInterceptor();

mClient = new OkHttpClient.Builder()
        .cache(cache)
        .addInterceptor(interceptor)
        .build();
  • 使用CacheControl.Builder()定義緩存策略

CacheControl類只能在攔截器中使用亚再,其實質(zhì)只是在請求頭或響應(yīng)頭為Cache-Control添加不同的參數(shù)而已晨抡,并沒有其他作用

class ForceCacheInterceptor implements Interceptor {
    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        Request request = chain.request();

        CacheControl cacheControl = new CacheControl.Builder()
                .onlyIfCached()
                .build();

        request = request.newBuilder()
                .cacheControl(cacheControl)
                .build();

        return chain.proceed(request);
    }
}
Interceptor interceptor = new ForceCacheInterceptor();

mClient = new OkHttpClient.Builder()
        .cache(cache)
        .addInterceptor(interceptor)
        .build();

CacheControl.Builder的常用方法

  • .maxAge(3600, TimeUnit.SECONDS);
  • .maxStale(3600, TimeUnit.SECONDS);
  • .noCache();
  • .noStore();
  • .onlyIfCached();

含義參考Cache-Control參數(shù)介紹

  • 使用CacheControl的伴生對象定義緩存策略

CacheControl的伴生對象有兩個,CacheControl.FORCE_CACHECacheControl.FORCE_NETWORK圆雁,分別表示強(qiáng)制使用緩存和強(qiáng)制使用網(wǎng)絡(luò)。

public class ForceNetworkInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request.Builder builder = chain.request().newBuilder();
        if (!NetworkUtils.internetAvailable()) {
            builder.cacheControl(CacheControl.FORCE_NETWORK);
        }
        
        return chain.proceed(builder.build());
    }
}
Interceptor interceptor = new ForceNetworkInterceptor();

mClient = new OkHttpClient.Builder()
        .cache(cache)
        .addInterceptor(interceptor)
        .build();

CacheControl.FORCE_CACHE的本質(zhì)是將Cache-Control請求頭設(shè)為"max-stale=2147483647, only-if-cached"伪朽。
重新查看上圖就可以知道汛蝙,Application發(fā)出的請求是被OkHttp core處理,而OkHttp core發(fā)出的請求將提交給服務(wù)器窖剑,如果我們希望本次請求強(qiáng)制使用緩存坚洽,就應(yīng)該使用應(yīng)用攔截器而不是網(wǎng)絡(luò)攔截器,這段請求頭告訴OkHttp本次請求僅使用緩存的響應(yīng)西土。

CacheControl.FORCE_NETWORK的本質(zhì)是將Cache-Control請求頭設(shè)為"no-cache"讶舰。與FORCE_CACHE同理,它也應(yīng)該使用應(yīng)用攔截器需了,這段請求頭告訴OkHttp本次請求僅使用來自網(wǎng)絡(luò)的響應(yīng)跳昼。

緩存流程

決定一次請求是否使用緩存的流程,主要的幾個步驟如下(任何一步?jīng)Q定使用網(wǎng)絡(luò)時將不再檢查后續(xù)步驟)

  1. 檢查response是否包含Date肋乍,Expires鹅颊,Last-ModifiedETag墓造,Age這些響應(yīng)頭堪伍。若都不包含則使用網(wǎng)絡(luò)锚烦。

  2. 檢查requestCache-Control請求頭,"no-cache"使用網(wǎng)絡(luò)帝雇,"only-if-cached"使用緩存涮俄。

  3. 檢查responseETag響應(yīng)頭,若存在則使用網(wǎng)絡(luò)摊求,并且本次請求會帶有與ETag值相同的If-None-Match請求頭禽拔。若實際數(shù)據(jù)沒有變化,服務(wù)器處理后會給出304 Not Modified狀態(tài)碼室叉,表示資源沒有修改睹栖,并且不會返回body,指示客戶端使用緩存茧痕,所以此時OkHttp也會使用緩存野来。

  4. 檢查responseLast-Modified響應(yīng)頭,若存在則使用網(wǎng)絡(luò)踪旷,并且本次請求會帶有與Last-Modified值相同的If-Modified-Since請求頭曼氛。后續(xù)同ETag

  5. 檢查responseDate響應(yīng)頭令野,若存在則使用網(wǎng)絡(luò)舀患,并且本次請求會帶有與Date值相同的If-Modified-Since請求頭。后續(xù)同ETag气破。

  6. 檢查responsemax-age,如果過期則使用網(wǎng)絡(luò)现使,否則使用緩存。但也可能因為其他參數(shù)如max-stale等影響最終計算結(jié)果顽冶。

緩存總結(jié)

一次完整的涉及緩存的網(wǎng)絡(luò)請求大致如下圖强重,其中成功的結(jié)果有兩個(綠框)间景,分別是使用緩存和使用服務(wù)器的新數(shù)據(jù)。在Force cache后找不到緩存就會失敱取(紅框)。從初始階段向下看哮缺,第一步判斷是否調(diào)用.cache()開啟了緩存功能甲喝。第二步檢查之前是否緩存過埠胖,兩者任意一者不滿足則使用網(wǎng)絡(luò)直撤。第三步判斷是否需要驗證,與ETag等有關(guān)红柱,存在則使用網(wǎng)絡(luò)向服務(wù)器驗證锤悄,服務(wù)器若返回304則response完全從緩存中取出零聚。這步操作同普通請求一樣握牧,可能涉及無網(wǎng)絡(luò)問題沿腰。當(dāng)無網(wǎng)絡(luò)時可以Force cache進(jìn)行處理狈定,最后則是成功或失敗時的異常處理纽什。下圖來自Medium

網(wǎng)絡(luò)請求完整流程圖
  • 當(dāng)存儲緩存時

如果此時要修改response的頭部企巢,應(yīng)該使用網(wǎng)絡(luò)攔截器修改response让蕾。

只要在構(gòu)建client的時候調(diào)用了.cache(),那么通過這個client得到的響應(yīng)一定會被緩存誉裆,但之后不一定會被使用缸濒。存儲緩存時與Cache-Control請求頭或響應(yīng)頭都無關(guān)庇配,Cache-Control只有當(dāng)讀取緩存時才會用到讨永。

  • 當(dāng)讀取緩存時

應(yīng)該使用應(yīng)用攔截器修改request卿闹。

想要強(qiáng)制使用緩存,有以下3種方式:

  • 使用CacheControl.FORCE_CACHE
  • 調(diào)用CacheControl.Builder().onlyIfCached()
  • 直接添加Cache-Control: "only-if-cached"請求頭

但如果緩存不存在著角,這次請求就會失敗并拋出IOException吏口,并且得到一個帶有504 Gateway Timeout的response产徊。

想要強(qiáng)制使用網(wǎng)絡(luò)舟铜,有以下3種方式:

  • 使用CacheControl.FORCE_NETWORK
  • 調(diào)用CacheControl.Builder().noCache()
  • 直接添加Cache-Control: "no-cache"請求頭

如果你在請求頭沒有指定任何有關(guān)緩存的參數(shù)谆刨,OkHttp將按照緩存中response的數(shù)個響應(yīng)頭進(jìn)行不同的處理痊夭,可能使用緩存脏里,也可能向服務(wù)器驗證response后決定是否使用緩存,或是進(jìn)行一次普通的請求根吁。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拴事,隨后出現(xiàn)的幾起案子刃宵,更是在濱河造成了極大的恐慌牲证,老刑警劉巖坦袍,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捂齐,死亡現(xiàn)場離奇詭異奠宜,居然都是意外死亡瞻想,警方通過查閱死者的電腦和手機(jī)蘑险,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進(jìn)店門嘴高,熙熙樓的掌柜王于貴愁眉苦臉地迎上來和屎,“玉大人柴信,你說我怎么就攤上這事√蜒模” “怎么了唆鸡?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵燃逻,是天一觀的道長臂痕。 經(jīng)常有香客問我,道長姆怪,這世上最難降的妖魔是什么稽揭? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任淀衣,我火速辦了婚禮召调,結(jié)果婚禮上唠叛,老公的妹妹穿的比我還像新娘。我一直安慰自己册舞,他們只是感情好调鲸,可當(dāng)我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布藐石。 她就那樣靜靜地躺著于微,像睡著了一般。 火紅的嫁衣襯著肌膚如雪驱证。 梳的紋絲不亂的頭發(fā)上抹锄,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天祈远,我揣著相機(jī)與錄音,去河邊找鬼谋减。 笑死,一個胖子當(dāng)著我的面吹牛庄吼,可吹牛的內(nèi)容都是我干的总寻。 我是一名探鬼主播渐行,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼祟印,長吁一口氣:“原來是場噩夢啊……” “哼蕴忆!你這毒婦竟也來了套鹅?” 一聲冷哼從身側(cè)響起汰具,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤留荔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后拔疚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年吸占,在試婚紗的時候發(fā)現(xiàn)自己被綠了矾屯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片初厚。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡产禾,死狀恐怖亚情,靈堂內(nèi)的尸體忽然破棺而出楞件,到底是詐尸還是另有隱情,我是刑警寧澤障簿,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站毅舆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏岂津。R本人自食惡果不足惜吮成,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一泳叠、第九天 我趴在偏房一處隱蔽的房頂上張望茶宵。 院中可真熱鬧乌庶,春花似錦、人聲如沸螃征。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽顾瞻。三九已至荷荤,卻和暖如春蕴纳,著一層夾襖步出監(jiān)牢的瞬間古毛,已是汗流浹背都许。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工塞椎, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留案狠,地道東北人。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像从铲,于是被迫代替她去往敵國和親名段。 傳聞我的和親對象是個殘疾皇子伸辟,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,507評論 2 359