OkHttp文件上傳與下載的進(jìn)度監(jiān)聽

說在前面


? ? ? ?要實(shí)現(xiàn)進(jìn)度的監(jiān)聽譬巫,需要使用到OkHttp的依賴包Okio里的兩個(gè)類捣鲸,一個(gè)是Source,一個(gè)是Sink甸箱,至于Okio的東西育叁,這里也不多說,其實(shí)OKHttp底層的實(shí)現(xiàn)就是基于Socket芍殖,大家都知道Socket網(wǎng)絡(luò)編程豪嗽,差不多就是操作Stream,所以O(shè)KHttp所有的操作都是依賴OKio這個(gè)IO操作庫來做的豌骏,那么我們要實(shí)現(xiàn)一個(gè)下載監(jiān)聽龟梦,就應(yīng)該對(duì)連接中的Stream監(jiān)聽,在OKHttp中網(wǎng)絡(luò)請(qǐng)求基本都是RequestBody和ResponseBody這兩個(gè)類封裝了Stream的操作窃躲,所以下文就基于這兩個(gè)類來介紹了变秦。
? ? ? ?首先我們實(shí)現(xiàn)文件下載的進(jìn)度監(jiān)聽。OkHttp給我們的只是一個(gè)回調(diào)框舔,里面有Response返回結(jié)果蹦玫,我們需要繼承一個(gè)類,對(duì)結(jié)果進(jìn)行監(jiān)聽刘绣,這個(gè)類就是ResponseBody樱溉,但是如何將它設(shè)置到OkHttp中去呢,答案是攔截器纬凤。攔截器的部分后面再敘述福贞,這里先實(shí)現(xiàn)ResponseBody的子類ProgressResponseBody。
要監(jiān)聽進(jìn)度停士,我們必然需要一個(gè)監(jiān)聽器挖帘,也就是一個(gè)接口,在其實(shí)現(xiàn)類中完成回調(diào)內(nèi)容的處理恋技,該接口聲明如下:

/**   
* 包裝的響體拇舀,處理進(jìn)度
 */
 public interface ProgressResponseListener {
  void   onResponseProgress(long bytesRead, long contentLength,boolean done);
}

然后會(huì)使用到該接口:

 /**
  * 包裝的響體,處理進(jìn)度
 */
public class ProgressResponseBody extends ResponseBody {
//實(shí)際的待包裝響應(yīng)體
private final ResponseBody responseBody;
//進(jìn)度回調(diào)接口
private final ProgressResponseListener progressListener;
//包裝完成的BufferedSource
private BufferedSource bufferedSource;

/**
 * 構(gòu)造函數(shù)蜻底,賦值
 * @param responseBody 待包裝的響應(yīng)體
 * @param progressListener 回調(diào)接口
 */
public ProgressResponseBody(ResponseBody responseBody, ProgressResponseListener progressListener) {
    this.responseBody = responseBody;
    this.progressListener = progressListener;
}


/**
 * 重寫調(diào)用實(shí)際的響應(yīng)體的contentType
 * @return MediaType
 */
@Override public MediaType contentType() {
    return responseBody.contentType();
}

/**
 * 重寫調(diào)用實(shí)際的響應(yīng)體的contentLength
 * @return contentLength
 * @throws IOException 異常
 */
@Override public long contentLength() throws IOException {
    return responseBody.contentLength();
}

/**
 * 重寫進(jìn)行包裝source
 * @return BufferedSource
 * @throws IOException 異常
 */
@Override public BufferedSource source() throws IOException {
    if (bufferedSource == null) {
        //包裝
        Source source = source(responseBody.source())骄崩;
        bufferedSource = Okio.buffer();
    }
    return bufferedSource;
}

/**
 * 讀取,回調(diào)進(jìn)度接口
 * @param source Source
 * @return Source
 */
private Source source(Source source) {

    return new ForwardingSource(source) {
        //當(dāng)前讀取字節(jié)數(shù)
        long totalBytesRead = 0L;
        @Override public long read(Buffer sink, long byteCount) throws IOException {
            long bytesRead = super.read(sink, byteCount);
            //增加當(dāng)前讀取的字節(jié)數(shù)薄辅,如果讀取完成了bytesRead會(huì)返回-1
            totalBytesRead += bytesRead != -1 ? bytesRead : 0;
            //回調(diào)要拂,如果contentLength()不知道長度,會(huì)返回-1
            progressListener.onResponseProgress(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
            return bytesRead;
        }
    };
}
}

? ? ? ?類似裝飾器站楚,我們對(duì)原始的ResponseBody 進(jìn)行了一層包裝脱惰。并在其讀取數(shù)據(jù)的時(shí)候設(shè)置了回調(diào),回調(diào)的接口由構(gòu)造函數(shù)傳入窿春,此外構(gòu)造函數(shù)還傳入了原始的ResponseBody拉一,當(dāng)系統(tǒng)內(nèi)部調(diào)用了ResponseBody 的source方法的時(shí)候采盒,返回的便是我們包裝后的Source。然后我們還重寫了幾個(gè)方法調(diào)用原始的ResponseBody對(duì)應(yīng)的函數(shù)返回結(jié)果舅踪。

? ? ? ?同理既然下載是這樣纽甘,那么上傳也應(yīng)該是這樣良蛮,我們乘熱打鐵完成上傳的部分抽碌,下載是繼承ResponseBody ,上傳就是繼承RequestBody决瞳,同時(shí)也應(yīng)該還有一個(gè)監(jiān)聽器货徙。

/**
 * 請(qǐng)求體進(jìn)度回調(diào)接口,比如用于文件上傳中
*/
public interface ProgressRequestListener {
    void onRequestProgress(long bytesWritten, long contentLength,     boolean done); 
}

RequestBody的子類實(shí)現(xiàn)類比ResponseBody 皮胡,基本上復(fù)制一下稍加修改即可使用:

 /**
   * 包裝的請(qǐng)求體痴颊,處理進(jìn)度
 */
public  class ProgressRequestBody extends RequestBody {
//實(shí)際的待包裝請(qǐng)求體
private final RequestBody requestBody;
//進(jìn)度回調(diào)接口
private final ProgressRequestListener progressListener;
//包裝完成的BufferedSink
private BufferedSink bufferedSink;

/**
 * 構(gòu)造函數(shù),賦值
 * @param requestBody 待包裝的請(qǐng)求體
 * @param progressListener 回調(diào)接口
 */
public ProgressRequestBody(RequestBody requestBody, ProgressRequestListener progressListener) {
    this.requestBody = requestBody;
    this.progressListener = progressListener;
}

/**
 * 重寫調(diào)用實(shí)際的響應(yīng)體的contentType
 * @return MediaType
 */
@Override
public MediaType contentType() {
    return requestBody.contentType();
}

/**
 * 重寫調(diào)用實(shí)際的響應(yīng)體的contentLength
 * @return contentLength
 * @throws IOException 異常
 */
@Override
public long contentLength() throws IOException {
    return requestBody.contentLength();
}

/**
 * 重寫進(jìn)行寫入
 * @param sink BufferedSink
 * @throws IOException 異常
 */
@Override
public void writeTo(BufferedSink sink) throws IOException {
    if (bufferedSink == null) {
        //包裝
        Sink sk = sink(sink)屡贺;
        bufferedSink = Okio.buffer(sk );
    }
    //寫入
    requestBody.writeTo(bufferedSink);
    //必須調(diào)用flush蠢棱,否則最后一部分?jǐn)?shù)據(jù)可能不會(huì)被寫入
    bufferedSink.flush();

}

/**
 * 寫入,回調(diào)進(jìn)度接口
 * @param sink Sink
 * @return Sink
 */
private Sink sink(Sink sink) {
    return new ForwardingSink(sink) {
        //當(dāng)前寫入字節(jié)數(shù)
        long bytesWritten = 0L;
        //總字節(jié)長度甩栈,避免多次調(diào)用contentLength()方法
        long contentLength = 0L;

        @Override
        public void write(Buffer source, long byteCount) throws IOException {
            super.write(source, byteCount);
            if (contentLength == 0) {
                //獲得contentLength的值泻仙,后續(xù)不再調(diào)用
                contentLength = contentLength();
            }
            //增加當(dāng)前寫入的字節(jié)數(shù)
            bytesWritten += byteCount;
            //回調(diào)
            progressListener.onRequestProgress(bytesWritten, contentLength, bytesWritten == contentLength);
        }
    };
}}

? ? ? ?內(nèi)部維護(hù)了一個(gè)原始的RequestBody 以及一個(gè)監(jiān)聽器,同樣的也是由構(gòu)造函數(shù)傳入量没。當(dāng)然也是要重寫幾個(gè)函數(shù)調(diào)用原始的RequestBody 對(duì)應(yīng)的函數(shù)玉转,文件的下載是read函數(shù)中進(jìn)行監(jiān)聽的設(shè)置,毫無疑問文件的上傳就是write函數(shù)了殴蹄,我們?cè)趙rite函數(shù)中進(jìn)行了類似的操作究抓,并回調(diào)了接口中的函數(shù)。當(dāng)系統(tǒng)內(nèi)部調(diào)用了RequestBody 的writeTo函數(shù)時(shí)袭灯,我們對(duì)BufferedSink 進(jìn)行了一層包裝刺下,即設(shè)置了進(jìn)度監(jiān)聽,并返回了我們包裝的BufferedSink 稽荧。于是乎怠李,上傳于下載的進(jìn)度監(jiān)聽就完成了。

? ? ? ?我們還需要一個(gè)Helper類蛤克,對(duì)上傳或者下載進(jìn)行監(jiān)聽設(shè)置捺癞。文件的上傳其實(shí)很簡單,將我們的原始RequestBody和監(jiān)聽器 傳入构挤,返回我們的包裝的ProgressRequestBody 髓介,使用包裝后的ProgressRequestBody 進(jìn)行請(qǐng)求即可,但是文件的下載呢筋现,OkHttp給我們返回的是Response唐础,我們?nèi)绾螌⑽覀儼b的ProgressResponseBody設(shè)置進(jìn)去呢箱歧,答案之前已經(jīng)說過了,就是攔截器一膨,具體見代碼吧呀邢。

/**
   * 進(jìn)度回調(diào)輔助類
   */
public class ProgressHelper {
/**
 * 包裝OkHttpClient,用于下載文件的回調(diào)
 * @param client 待包裝的OkHttpClient
 * @param progressListener 進(jìn)度回調(diào)接口
 * @return 包裝后的OkHttpClient豹绪,使用clone方法返回
 */
public static OkHttpClient addProgressResponseListener(OkHttpClient client,final ProgressResponseListener progressListener){
    //克隆
    OkHttpClient clone = client.clone();
    //增加攔截器
    clone.networkInterceptors().add(new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            //攔截
            Response originalResponse = chain.proceed(chain.request());
            //包裝響應(yīng)體并返回
            return originalResponse.newBuilder()
                    .body(new ProgressResponseBody(originalResponse.body(), progressListener))
                    .build();
        }
    });
    return clone;
}

/**
 * 包裝請(qǐng)求體用于上傳文件的回調(diào)
 * @param requestBody 請(qǐng)求體RequestBody
 * @param progressRequestListener 進(jìn)度回調(diào)接口
 * @return 包裝后的進(jìn)度回調(diào)請(qǐng)求體
 */
public static ProgressRequestBody addProgressRequestListener(RequestBody requestBody,ProgressRequestListener progressRequestListener){
    //包裝請(qǐng)求體
    return new ProgressRequestBody(requestBody,progressRequestListener);
}}

? ? ? ?對(duì)于文件下載的監(jiān)聽器我們?yōu)榱瞬挥绊懺瓉淼腛kHttpClient 實(shí)例价淌,我們調(diào)用clone方法進(jìn)行了克隆,之后對(duì)克隆的方法設(shè)置了響應(yīng)攔截瞒津,并返回該克隆的實(shí)例蝉衣。而文件的上傳則十分簡單,直接包裝后返回即可巷蚪。

? ? ? ?但是你別忘記了病毡,我們的目的是在UI層進(jìn)行回調(diào),而OkHttp的所有請(qǐng)求都不在UI層屁柏。于是我們還要實(shí)現(xiàn)我們寫的接口啦膜,進(jìn)行UI操作的回調(diào)。由于涉及到消息機(jī)制淌喻,我們對(duì)之前的兩個(gè)接口回調(diào)傳的參數(shù)進(jìn)行封裝僧家,封裝為一個(gè)實(shí)體類便于傳遞。

/**
 * UI進(jìn)度回調(diào)實(shí)體類
 */
public class ProgressModel implements Serializable {
//當(dāng)前讀取字節(jié)長度
private long currentBytes;
//總字節(jié)長度
private long contentLength;
//是否讀取完成
private boolean done;

public ProgressModel(long currentBytes, long contentLength, boolean done) {
    this.currentBytes = currentBytes;
    this.contentLength = contentLength;
    this.done = done;
}

public long getCurrentBytes() {
    return currentBytes;
}

public void setCurrentBytes(long currentBytes) {
    this.currentBytes = currentBytes;
}

public long getContentLength() {
    return contentLength;
}

public void setContentLength(long contentLength) {
    this.contentLength = contentLength;
}

public boolean isDone() {
    return done;
}

public void setDone(boolean done) {
    this.done = done;
}

@Override
public String toString() {
    return "ProgressModel{" +
            "currentBytes=" + currentBytes +
            ", contentLength=" + contentLength +
            ", done=" + done +
            '}';
}}

? ? ? ?再實(shí)現(xiàn)我們的UI回調(diào)接口似嗤,對(duì)于文件的上傳啸臀,我們需要實(shí)現(xiàn)的是ProgressRequestListener接口,文件的下載需要實(shí)現(xiàn)的是ProgressResponseListener接口烁落,但是內(nèi)部的邏輯處理是完全一樣的乘粒。我們使用抽象類,提供一個(gè)抽象方法伤塌,該抽象方法用于UI層回調(diào)的處理灯萍,由具體開發(fā)去實(shí)現(xiàn)。涉及到消息機(jī)制就涉及到Handler類每聪,在Handler的子類中維護(hù)一個(gè)弱引用指向外部類(用到了static防止內(nèi)存泄露旦棉,但是需要調(diào)用外部類的一個(gè)非靜態(tài)函數(shù),所以將外部類引用直接由構(gòu)造函數(shù)傳入药薯,在內(nèi)部通過調(diào)用該引用的方法去實(shí)現(xiàn))绑洛,然后將主線程的Looper傳入,調(diào)用父類構(gòu)造函數(shù)童本。在onRequestProgress中發(fā)送進(jìn)度更新的消息真屯,在handleMessage函數(shù)中回調(diào)我們的抽象方法。我們只需要實(shí)現(xiàn)抽象方法穷娱,編寫對(duì)應(yīng)的UI更新代碼即可绑蔫。具體代碼如下运沦。

/**
* 請(qǐng)求體回調(diào)實(shí)現(xiàn)類,用于UI層回調(diào)
*/
public abstract class UIProgressRequestListener implements ProgressRequestListener {
private static final int REQUEST_UPDATE = 0x01;

//處理UI層的Handler子類
private static class UIHandler extends Handler {
    //弱引用
    private final WeakReference<UIProgressRequestListener> mUIProgressRequestListenerWeakReference;

    public UIHandler(Looper looper, UIProgressRequestListener uiProgressRequestListener) {
        super(looper);
        mUIProgressRequestListenerWeakReference = new WeakReference<UIProgressRequestListener>(uiProgressRequestListener);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case REQUEST_UPDATE:
                UIProgressRequestListener uiProgressRequestListener = mUIProgressRequestListenerWeakReference.get();
                if (uiProgressRequestListener != null) {
                    //獲得進(jìn)度實(shí)體類
                    ProgressModel progressModel = (ProgressModel) msg.obj;
                    //回調(diào)抽象方法
                    uiProgressRequestListener.onUIRequestProgress(progressModel.getCurrentBytes(), progressModel.getContentLength(), progressModel.isDone());
                }
                break;
            default:
                super.handleMessage(msg);
                break;
        }
    }
}
//主線程Handler
private final Handler mHandler = new UIHandler(Looper.getMainLooper(), this);

@Override
public void onRequestProgress(long bytesRead, long contentLength, boolean done) {
    //通過Handler發(fā)送進(jìn)度消息
    Message message = Message.obtain();
    message.obj = new ProgressModel(bytesRead, contentLength, done);
    message.what = REQUEST_UPDATE;
    mHandler.sendMessage(message);
}

/**
 * UI層回調(diào)抽象方法
 * @param bytesWrite 當(dāng)前寫入的字節(jié)長度
 * @param contentLength 總字節(jié)長度
 * @param done 是否寫入完成
 */
public abstract void onUIRequestProgress(long bytesWrite, long contentLength, boolean done);}

另一個(gè)實(shí)現(xiàn)類代碼雷同配深,不做敘述携添。

/**
* 請(qǐng)求體回調(diào)實(shí)現(xiàn)類,用于UI層回調(diào)
 */
public abstract class UIProgressResponseListener implements ProgressResponseListener {
private static final int RESPONSE_UPDATE = 0x02;
//處理UI層的Handler子類
private static class UIHandler extends Handler {
    //弱引用
    private final WeakReference<UIProgressResponseListener> mUIProgressResponseListenerWeakReference;

    public UIHandler(Looper looper, UIProgressResponseListener uiProgressResponseListener) {
        super(looper);
        mUIProgressResponseListenerWeakReference = new WeakReference<UIProgressResponseListener>(uiProgressResponseListener);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case RESPONSE_UPDATE:
                UIProgressResponseListener uiProgressResponseListener = mUIProgressResponseListenerWeakReference.get();
                if (uiProgressResponseListener != null) {
                    //獲得進(jìn)度實(shí)體類
                    ProgressModel progressModel = (ProgressModel) msg.obj;
                    //回調(diào)抽象方法
                    uiProgressResponseListener.onUIResponseProgress(progressModel.getCurrentBytes(), progressModel.getContentLength(), progressModel.isDone());
                }
                break;
            default:
                super.handleMessage(msg);
                break;
        }
    }
}
//主線程Handler
private final Handler mHandler = new UIHandler(Looper.getMainLooper(), this);

@Override
public void onResponseProgress(long bytesRead, long contentLength, boolean done) {
    //通過Handler發(fā)送進(jìn)度消息
    Message message = Message.obtain();
    message.obj = new ProgressModel(bytesRead, contentLength, done);
    message.what = RESPONSE_UPDATE;
    mHandler.sendMessage(message);
}

/**
 * UI層回調(diào)抽象方法
 * @param bytesRead 當(dāng)前讀取響應(yīng)體字節(jié)長度
 * @param contentLength 總字節(jié)長度
 * @param done 是否讀取完成
 */
public abstract void onUIResponseProgress(long bytesRead, long contentLength, boolean done);}

? ? ? ?一個(gè)上傳操作篓叶,一個(gè)下載操作烈掠,分別提供了UI層與非UI層回調(diào)的示例。最終代碼中使用的監(jiān)聽器都是UI層的澜共,因?yàn)槲覀円逻M(jìn)度條向叉。

private void download() {
    //這個(gè)是非ui線程回調(diào)锥腻,不可直接操作UI
    final ProgressResponseListener progressResponseListener = new ProgressResponseListener() {
        @Override
        public void onResponseProgress(long bytesRead, long contentLength, boolean done) {
            Log.e("TAG", "bytesRead:" + bytesRead);
            Log.e("TAG", "contentLength:" + contentLength);
            Log.e("TAG", "done:" + done);
            if (contentLength != -1) {
                //長度未知的情況下回返回-1
                Log.e("TAG", (100 * bytesRead) / contentLength + "% done");
            }
            Log.e("TAG", "================================");
        }
    };


    //這個(gè)是ui線程回調(diào)嗦董,可直接操作UI
    final UIProgressResponseListener uiProgressResponseListener = new UIProgressResponseListener() {
        @Override
        public void onUIResponseProgress(long bytesRead, long contentLength, boolean done) {
            Log.e("TAG", "bytesRead:" + bytesRead);
            Log.e("TAG", "contentLength:" + contentLength);
            Log.e("TAG", "done:" + done);
            if (contentLength != -1) {
                //長度未知的情況下回返回-1
                Log.e("TAG", (100 * bytesRead) / contentLength + "% done");
            }
            Log.e("TAG", "================================");
            //ui層回調(diào)
            downloadProgeress.setProgress((int) ((100 * bytesRead) / contentLength));
            //Toast.makeText(getApplicationContext(), bytesRead + " " + contentLength + " " + done, Toast.LENGTH_LONG).show();
        }
    };

    //構(gòu)造請(qǐng)求
    final Request request1 = new Request.Builder()
            .url("http://121.41.119.107:81/test/1.doc")
            .build();

    //包裝Response使其支持進(jìn)度回調(diào)
    ProgressHelper.addProgressResponseListener(client, uiProgressResponseListener).newCall(request1).enqueue(new Callback() {
        @Override
        public void onFailure(Request request, IOException e) {
            Log.e("TAG", "error ", e);
        }

        @Override
        public void onResponse(Response response) throws IOException {
            Log.e("TAG", response.body().string());
        }
    });
}

private void upload() {
    File file = new File("/sdcard/1.doc");
    //此文件必須在手機(jī)上存在,實(shí)際情況下請(qǐng)自行修改瘦黑,這個(gè)目錄下的文件只是在我手機(jī)中存在京革。


    //這個(gè)是非ui線程回調(diào),不可直接操作UI
    final ProgressRequestListener progressListener = new ProgressRequestListener() {
        @Override
        public void onRequestProgress(long bytesWrite, long contentLength, boolean done) {
            Log.e("TAG", "bytesWrite:" + bytesWrite);
            Log.e("TAG", "contentLength" + contentLength);
            Log.e("TAG", (100 * bytesWrite) / contentLength + " % done ");
            Log.e("TAG", "done:" + done);
            Log.e("TAG", "================================");
        }
    };


    //這個(gè)是ui線程回調(diào)幸斥,可直接操作UI
    final UIProgressRequestListener uiProgressRequestListener = new UIProgressRequestListener() {
        @Override
        public void onUIRequestProgress(long bytesWrite, long contentLength, boolean done) {
            Log.e("TAG", "bytesWrite:" + bytesWrite);
            Log.e("TAG", "contentLength" + contentLength);
            Log.e("TAG", (100 * bytesWrite) / contentLength + " % done ");
            Log.e("TAG", "done:" + done);
            Log.e("TAG", "================================");
            //ui層回調(diào)
            uploadProgress.setProgress((int) ((100 * bytesWrite) / contentLength));
            //Toast.makeText(getApplicationContext(), bytesWrite + " " + contentLength + " " + done, Toast.LENGTH_LONG).show();
        }
    };

    //構(gòu)造上傳請(qǐng)求匹摇,類似web表單
    RequestBody requestBody = new MultipartBuilder().type(MultipartBuilder.FORM)
            .addFormDataPart("hello", "android")
            .addFormDataPart("photo", file.getName(), RequestBody.create(null, file))
            .addPart(Headers.of("Content-Disposition", "form-data; name=\"another\";filename=\"another.dex\""), RequestBody.create(MediaType.parse("application/octet-stream"), file))
            .build();

    //進(jìn)行包裝,使其支持進(jìn)度回調(diào)
    final Request request = new Request.Builder().url("").post(ProgressHelper.addProgressRequestListener(requestBody, uiProgressRequestListener)).build();
    //開始請(qǐng)求
    client.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Request request, IOException e) {
            Log.e("TAG", "error ", e);
        }

        @Override
        public void onResponse(Response response) throws IOException {
            Log.e("TAG", response.body().string());
        }
    });

}

? ? ? ?還有一個(gè)細(xì)節(jié)需要注意就是連接的超時(shí)甲葬,讀取與寫入數(shù)據(jù)的超時(shí)時(shí)間的設(shè)置廊勃,在讀取或者寫入一些大文件的時(shí)候如果不設(shè)置這個(gè)參數(shù)可能會(huì)報(bào)異常,這里就隨便設(shè)置了一下值经窖,設(shè)得有點(diǎn)大坡垫,實(shí)際情況按需設(shè)置。

 //設(shè)置超時(shí)画侣,不設(shè)置可能會(huì)報(bào)異常
private void initClient() {
    client.setConnectTimeout(1000, TimeUnit.MINUTES);
    client.setReadTimeout(1000, TimeUnit.MINUTES);
    client.setWriteTimeout(1000, TimeUnit.MINUTES);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末冰悠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子配乱,更是在濱河造成了極大的恐慌溉卓,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搬泥,死亡現(xiàn)場離奇詭異桑寨,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)忿檩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門尉尾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人休溶,你說我怎么就攤上這事代赁∪潘” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵芭碍,是天一觀的道長徒役。 經(jīng)常有香客問我,道長窖壕,這世上最難降的妖魔是什么忧勿? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮瞻讽,結(jié)果婚禮上鸳吸,老公的妹妹穿的比我還像新娘。我一直安慰自己速勇,他們只是感情好晌砾,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著烦磁,像睡著了一般养匈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上都伪,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天呕乎,我揣著相機(jī)與錄音,去河邊找鬼陨晶。 笑死猬仁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的先誉。 我是一名探鬼主播湿刽,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼谆膳!你這毒婦竟也來了叭爱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤漱病,失蹤者是張志新(化名)和其女友劉穎买雾,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杨帽,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡漓穿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了注盈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晃危。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出僚饭,到底是詐尸還是另有隱情震叮,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布鳍鸵,位于F島的核電站苇瓣,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏偿乖。R本人自食惡果不足惜击罪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贪薪。 院中可真熱鬧媳禁,春花似錦、人聲如沸画切。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽槽唾。三九已至丧枪,卻和暖如春光涂,著一層夾襖步出監(jiān)牢的瞬間庞萍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國打工忘闻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留钝计,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓齐佳,卻偏偏與公主長得像私恬,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子炼吴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

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