android——文件斷點續(xù)傳下載(二)

GIF.gif

上篇博客寫的是單個文件單個線程的斷點續(xù)傳下載(文件斷點續(xù)傳下載(一))侨颈,這里是在之前的基礎(chǔ)上進行了擴展,變成了多文件芯义,多線程同時下載/暫停哈垢,同時添加了通知欄下載狀態(tài)顯示,雖然代碼是基于之前的扛拨,還是做了不少改動耘分。
1、activity中顯示的是一個列表绑警,點擊事件的觸發(fā)是在列表的條目中求泰,就需要在點擊開始時,將點擊事件通過接口回調(diào)到activity中计盒,進行權(quán)限的申請
2渴频、當多任務(wù)、多線程同時操作時北启,利用BroadcastReceiver進行通信會導致界面不有點卡卜朗,就改用了Messenger+Handler進行通信
3拔第、需要對Notification做8.0的適配
4、利用TimerTask聊替、Timer進行消息的輪詢
5楼肪、改用線程池對線程的管理
先看下列表適配器中的邏輯,很簡單,

public class FileListAdapter extends BaseAdapter {
    private Context mContext;
    private List<FileInfo> list;

    public FileListAdapter(Context context, List<FileInfo> list) {
        this.mContext = context;
        this.list = list;
    }
    @Override
    public int getCount() {
        return list == null ? 0 : list.size();
    }

    @Override
    public Object getItem(int position) {
        return list.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder;
        final FileInfo fileInfo = list.get(position);
        if (convertView == null) {
            viewHolder = new ViewHolder();
            convertView = LayoutInflater.from(mContext).inflate(R.layout.file_list_adapter_item, parent, false);
            viewHolder.tvFileName = convertView.findViewById(R.id.tv_file_name);
            viewHolder.progressBar = convertView.findViewById(R.id.progress_bar);
            viewHolder.btnStop = convertView.findViewById(R.id.btn_stop);
            viewHolder.btnStart = convertView.findViewById(R.id.btn_start);

            viewHolder.progressBar.setMax(100);
            viewHolder.tvFileName.setText(fileInfo.fileName);
            viewHolder.btnStart.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (listener != null) {
                        listener.doStart(position, fileInfo);
                    }
                }
            });
            viewHolder.btnStop.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (listener != null) {
                        listener.doStop(position, fileInfo);
                    }
                }
            });
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }

        viewHolder.progressBar.setProgress(fileInfo.finished);

        return convertView;
    }

    /**
     * 更新列表項中條目下載進度
     */
    public void updateProgress(List<FileInfo> fileInfos) {
        this.list = fileInfos;
        notifyDataSetChanged();
    }

    class ViewHolder {
        TextView tvFileName;
        ProgressBarView progressBar;
        Button btnStop, btnStart;
    }

    private onItemClick listener;

    public void setOnItemClick(onItemClick listener) {
        this.listener = listener;
    }

    public interface onItemClick {
        /**
         * 開始下載
         *
         * @param position
         */
        void doStart(int position, FileInfo fileInfo);

        /**
         * 暫停下載
         *
         * @param position
         */
        void doStop(int position, FileInfo fileInfo);
    }
}

不過有點細節(jié)需要注意的掺冠,賦值一次后不需要變動的數(shù)據(jù)淤井,就不需要數(shù)據(jù)復用了,這樣會提高一些性能铺罢,這里文件名稱的賦值、ProgressBar的初始化、開始/暫停的點擊事件就沒有進行復用当纱,然后在回調(diào)事件中去做權(quán)限的申請、開啟下載/暫停下載的邏輯處理踩窖。
之前是通過startService的方式開啟一個Service的坡氯,這里沒有采用該方式,用的是綁定服務(wù)的方式洋腮,在onCreate()方法中綁定一個Service箫柳;

//綁定service
Intent intent = new Intent(this, DownLoadService.class);
bindService(intent, serviceConnection, Service.BIND_AUTO_CREATE);

Service綁定后,就會回調(diào)ServiceConnection中的onServiceConnected()方法啥供,在該方法中利用Messenger同時在Service的onBind()方法中也利用Messenger進行activity和Service數(shù)據(jù)通信雙向綁定悯恍;

ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //綁定回調(diào)
            //獲得service中的Messenger
            mServiceMessenger = new Messenger(service);
            //創(chuàng)建activity中的Messenger
            Messenger messenger = new Messenger(mHandler);
            //創(chuàng)建一個消息
            Message message = new Message();
            message.what = DownLoadService.MSG_BIND;
            message.replyTo = messenger;
            //使用service的messenger發(fā)送activity中的Messenger
            try {
                mServiceMessenger.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            //解綁回調(diào)
        }
    };
@Override
    public IBinder onBind(Intent intent) {
        //創(chuàng)建一個Messenger對象
        Messenger messenger = new Messenger(mHandler);
        //返回Messenger的binder
        return messenger.getBinder();
    }
Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case MSG_INIT:
                    mSGINIT(msg);
                    break;
                case MSG_BIND:
                    //處理綁定的Messenger
                    mActivityMessenger = msg.replyTo;
                    break;
                case MSG_START:
                    mSGSTART(msg);
                    break;
                case MSG_STOP:
                    mSGSTOP(msg);
                    break;
            }
        }
    };

實現(xiàn)了activity和service的通信綁定,在點擊開始或者暫停時就可以Messenger給service發(fā)送消息了伙狐;

private void doStart(FileInfo fileInfo) {
        //將開始下載的文件id存儲在集合中
        if (fileIds.contains(fileInfo.id)) {
            //正在下載提示用戶 并返回
            Toast.makeText(this, "該文件正在下載中...", Toast.LENGTH_LONG).show();
            return;
        }
        //將文件id添加到集合中
        fileIds.add(fileInfo.id);
        Message message = new Message();
        message.what = DownLoadService.MSG_START;
        message.obj = fileInfo;
        try {
            mServiceMessenger.send(message);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        Log.e("doStart-->", "doStart");
    }
private void doStop(FileInfo fileInfo) {
        //暫停下載時將存儲的下載文件id移除
        for (int i = 0; i < fileIds.size(); i++) {
            Integer integer = fileIds.get(i);
            if (integer == fileInfo.id) {
                fileIds.remove(integer);
                break;
            }
        }
        Log.e("doStop-->", "doStop");
        Message message = new Message();
        message.what = DownLoadService.MSG_STOP;
        message.obj = fileInfo;
        try {
            mServiceMessenger.send(message);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

在開始下載時需要將下載文件的id緩存的集合中涮毫,用于管理用戶多次點擊同一個正在下載的文件,在暫停下載或下載完成后在從集合中移除對應(yīng)的文件id贷屎,消息發(fā)送后罢防,在Service中Handler的handerMessage()方法中根據(jù)msg.whate進行處理。
開始下載時還是和之前一樣唉侄,在子線程中獲取文件的大小咒吐,在本地創(chuàng)建文件夾,然后通過handler通知進行下載美旧,只是改成了利用線程池進行線程的管理渤滞;

/**
     * 開始下載
     *
     * @param msg
     */
    private void mSGSTART(Message msg) {
        FileInfo fileInfo = (FileInfo) msg.obj;
        Log.e(TAG, ACTION_START);
        //開啟線程
        InitThread thread = new InitThread(fileInfo);
        DownLoadTask.sExecutorService.execute(thread);
    }

在下載的時候需要對多線程、多任務(wù)進行管理榴嗅,同時開始時通過消息告訴activity去開啟消息通知欄妄呕,顯示下載進度;

/**
     * 初始化處理
     *
     * @param msg
     */
    private void mSGINIT(Message msg) {
        FileInfo fileInfo = (FileInfo) msg.obj;
        Log.e(TAG, MSG_INIT + "");
        //啟動下載任務(wù)
        DownLoadTask task = new DownLoadTask(DownLoadService.this, fileInfo, 1, mActivityMessenger);
        task.downLoad();
        //把下載任務(wù)添加到集合中
        mTasks.put(fileInfo.id, task);
        //給Activity發(fā)送消息
        Message message = new Message();
        message.what = MSG_START;
        message.obj = fileInfo;
        try {
            mActivityMessenger.send(message);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

在activity的handler中就可以通知開啟通知狀態(tài)欄嗽测,

/**
     * 開始下載通知通知欄
     *
     * @param msg
     */
    private void mSGSTART(Message msg) {
        //顯示一個通知
        FileInfo info = (FileInfo) msg.obj;
        if (info != null) {
            mNotificationUtil.showNotification(info);
        }
    }
/**
 * 通知的工具類
 */
public class NotificationUtil {
    //實例化Notification通知管理類
    private NotificationManager mNotificationManager;
    //管理Notification的集合
    private Map<Integer, Notification.Builder> mNotification;
    private Context mContext;

    public NotificationUtil(Context context) {
        mContext = context;
        mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        mNotification = new HashMap<>();
    }

    /**
     * 顯示notification
     */
    public void showNotification(FileInfo fileInfo) {
        //是否存在該文件下載通知
        if (!mNotification.containsKey(fileInfo.id)) {
            //android8.0 notification適配
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                NotificationChannel channel = new NotificationChannel("channel_id", "channel_name", NotificationManager.IMPORTANCE_DEFAULT);
                channel.canBypassDnd();//可否繞過請勿打擾模式
                channel.enableLights(true);//有新消息時手機有閃光
                channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);//鎖屏顯示通知
                channel.setLightColor(Color.argb(100, 100, 100, 100));//指定閃光時的燈光顏色
                channel.canShowBadge();//有新消息時桌面角標顯示
                channel.enableVibration(true);//振動
                AudioAttributes audioAttributes = channel.getAudioAttributes();//獲取系統(tǒng)響鈴
                channel.getGroup();//獲取通知渠道組
                channel.setBypassDnd(true);//設(shè)置可以繞過請勿打擾模式
                channel.setVibrationPattern(new long[]{100, 100, 200});//振動模式
                channel.shouldShowLights();//是否會閃燈

                mNotificationManager.createNotificationChannel(channel);

            }
            //不存在就新增
            //創(chuàng)建通知的對象
            Notification.Builder notification;
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
                //android8.0 notification適配
                notification = new Notification.Builder(mContext)
                        .setChannelId("channel_id");
            } else {
                notification = new Notification.Builder(mContext);
            }
            //設(shè)置滾動文字
            notification.setTicker(fileInfo.fileName + "開始下載")
                    .setWhen(System.currentTimeMillis())//設(shè)置通知顯示的時間
                    .setSmallIcon(R.mipmap.ic_launcher_round)//設(shè)置圖標
                    .setContentTitle(fileInfo.fileName)
                    .setContentText(fileInfo.fileName + "下載中...")
                    .setAutoCancel(true);//設(shè)置通知的特性  FLAG_AUTO_CANCEL點擊通知欄自動消失

            //設(shè)置點擊通知欄的操作
            Intent intent = new Intent(mContext, MainActivity.class);
            PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
            notification.setContentIntent(pendingIntent);
            //發(fā)出通知廣播
            mNotificationManager.notify(fileInfo.id, notification.build());
            //把通知加到集合中
            mNotification.put(fileInfo.id, notification);
        }
    }

    /**
     * 取消通知
     *
     * @param id
     */
    public void cancleNotification(int id) {
        //取消通知
        mNotificationManager.cancel(id);
        //同時刪除集合中的通知
        mNotification.remove(id);
    }

    /**
     * 更新通知進度
     *
     * @param id
     * @param progress
     */
    public void updateNotification(int id, int progress) {
        Notification.Builder notification = mNotification.get(id);
        if (notification != null) {
            //更新進度條
            notification.setProgress(100, progress, false);
            //重新發(fā)送通知
            mNotificationManager.notify(id, notification.build());
        }
    }
}

消息通知欄是通過NotificationUtil工具類來管理的绪励,調(diào)用showNotification()方法創(chuàng)建并顯示一個通知狀態(tài)欄肿孵,在進度更新時調(diào)用updateNotification()方法,下載完成或者取消通知狀態(tài)欄調(diào)用cancleNotification()就可以了疏魏,NotificationUtil類中已經(jīng)對Notification做了8.0的適配處理停做,關(guān)于Notification的8.0適配網(wǎng)上很多資料,不過需要注意在調(diào)用setChannelId()時設(shè)置的id要和NotificationChannel創(chuàng)建時傳入的id一致大莫;

NotificationChannel channel = new NotificationChannel("channel_id", "channel_name", NotificationManager.IMPORTANCE_DEFAULT);

在DownLoadTask中的downLoad()中還是要根據(jù)數(shù)據(jù)庫查詢的結(jié)果進行下載蛉腌,數(shù)據(jù)庫中如果有下載記錄,直接將查詢到的下載記錄傳入到下載線程中只厘,如果沒有就需要新增一個下載任務(wù)實例烙丛,在新增的時候需要注意,要根據(jù)傳入的單個任務(wù)的下載線程個數(shù)來弄羔味,要處理好每個線程的開始下載位置和結(jié)束下載位置河咽;

public void downLoad() {
        //讀取數(shù)據(jù)庫的線程信息
        List<ThreadInfo> threadInfos = dao.queryThread(mFileInfo.url);
        if (threadInfos.size() == 0) {
            //獲得每個線程下載的長度 分線程下載,多個線程下載一個文件
            int length = mFileInfo.length / mThreadCount;
            for (int i = 0; i < mThreadCount; i++) {
                ThreadInfo threadInfo = new ThreadInfo();
                threadInfo.id = i;
                threadInfo.url = mFileInfo.url;
                threadInfo.thread_start = length * i;
                threadInfo.thread_end = (i + 1) * length - 1;
                threadInfo.finished = 0;
                if (i == mThreadCount - 1) {
                    //最后一個線程下載的結(jié)束位置
                    threadInfo.thread_end = mFileInfo.length;
                }
                //添加到線程集合信息
                threadInfos.add(threadInfo);
                //向數(shù)據(jù)庫中插入一條信息
                if (!dao.isExists(threadInfo.url, threadInfo.id)) {
                    dao.insertThread(threadInfo);
                }
            }
        }
        threadList = new ArrayList<>();
        //啟動多個線程進行下載
        for (ThreadInfo threadInfo : threadInfos) {
            DownLoadThread downLoadThread = new DownLoadThread(threadInfo);
            DownLoadTask.sExecutorService.execute(downLoadThread);
            threadList.add(downLoadThread);
        }
        //啟動定時任務(wù)
        mTimer.schedule(timerTask, 1000, 1000);
    }

在開始的同時開啟一個Timer進行輪詢赋元;

TimerTask timerTask = new TimerTask() {
        @Override
        public void run() {
            if (isPause) {
                //暫停時要將定時器清除掉
                cancelTimer();
            }
            //發(fā)送消息修改activity進度
            Message message = new Message();
            message.what = DownLoadService.MSG_UPDATE;
            message.arg1 = mFinished * 100 / mFileInfo.length;
            message.arg2 = mFileInfo.id;
            Log.e("DownLoadService", mFinished + "");
            try {
                messenger.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    };

    /**
     * 清除定時器
     */
    private void cancelTimer(){
        //取消定時器
        timerTask.cancel();
        timerTask = null;
        mTimer.cancel();
        mTimer = null;
    }

在下載任務(wù)進行的同時會每隔一段時間回調(diào)TimerTask中的run方法忘蟹,通過Messenger向activity傳遞下載的進度,在暫停下載是需要將Timer移除并置為null搁凸。
在DownLoadThread線程中下載完畢后做法和之前一樣媚值,多了要將Timer移除并置為null;

/**
     * 下載線程
     */
    class DownLoadThread extends Thread {
        private ThreadInfo mThreadInfo;
        //線程是否執(zhí)行完畢
        public boolean isFinished = false;

        public DownLoadThread(ThreadInfo threadInfo) {
            mThreadInfo = threadInfo;
        }

        @Override
        public void run() {
            super.run();
            HttpURLConnection conn = null;
            RandomAccessFile raf = null;
            InputStream input = null;
            try {
                URL url = new URL(mThreadInfo.url);
                conn = (HttpURLConnection) url.openConnection();
                conn.setReadTimeout(6000);
                conn.setRequestMethod("GET");
                //設(shè)置下載位置
                int start = mThreadInfo.thread_start + mThreadInfo.finished;
                conn.setRequestProperty("Range", "bytes=" + start + "-" + mThreadInfo.thread_end);
                //設(shè)置一個文件寫入位置
                File file = new File(DownLoadService.DOWNLOAD_PATH, mFileInfo.fileName);
                raf = new RandomAccessFile(file, "rwd");
                //設(shè)置文件寫入位置
                raf.seek(start);
                mFinished += mThreadInfo.finished;
                //開始下載了
                if (conn.getResponseCode() == HttpURLConnection.HTTP_PARTIAL) {
                    //讀取數(shù)據(jù)
                    input = conn.getInputStream();
                    byte[] buffer = new byte[1024 * 4];
                    int len = -1;
                    while ((len = input.read(buffer)) != -1) {
                        //寫入文件
                        raf.write(buffer, 0, len);
                        //下載進度發(fā)送廣播給activity
                        //累加整個文件的進度
                        mFinished += len;
                        //累加每個線程下載的進度
                        int currentFinished = mThreadInfo.finished;
                        mThreadInfo.finished = currentFinished + len;

                        //下載暫停是要把進度保存在數(shù)據(jù)庫中
                        if (isPause) {
                            dao.updateThread(mThreadInfo.url, mThreadInfo.id, mThreadInfo.finished);
                            return;
                        }
                    }
                    //標識線程執(zhí)行完畢
                    isFinished = true;
                    //檢查下載任務(wù)是否執(zhí)行完畢
                    checkAllThreadsFinished();
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if (conn != null) {
                        conn.disconnect();
                    }
                    if (raf != null) {
                        raf.close();
                    }
                    if (input != null) {
                        input.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
/**
     * 判斷是否所有線程執(zhí)行完畢
     */
    private synchronized void checkAllThreadsFinished() {
        boolean allFinished = true;
        //遍歷線程集合判斷線程是否執(zhí)行完畢
        for (DownLoadThread downLoadThread : threadList) {
            if (!downLoadThread.isFinished) {
                allFinished = false;
                break;
            }
        }
        if (allFinished) {
            cancelTimer();
            //發(fā)送消息告訴activity下載完畢
            Message message = new Message();
            message.what = DownLoadService.MSG_FINISHED;
            message.obj = mFileInfo;
            try {
                messenger.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            //刪除線程信息
            dao.deleteThread(mFileInfo.url);

        }
    }

在activity更新下載進度時要根據(jù)對應(yīng)的文件id去更新進度坪仇;

/**
     * 下載進度更新
     *
     * @param msg
     */
    private void mSGUPDATE(Message msg) {
        int finished = msg.arg1;
        int id = msg.arg2;
        updataItem(id, finished);
        //更新通知進度
        mNotificationUtil.updateNotification(id, finished);
    }

    /**
     * 下載完成后更新
     *
     * @param msg
     */
    private void mSGFINISHED(Message msg) {
        //更新進度為0
        FileInfo fileInfo = (FileInfo) msg.obj;
        //下載完成后移除存儲的文件id
        for (int i = 0; i < fileIds.size(); i++) {
            Integer integer = fileIds.get(i);
            if (integer == fileInfo.id) {
                fileIds.remove(integer);
                break;
            }
        }
        if (fileInfo != null) {
            updataItem(fileInfo.id, fileInfo.length);
        }
        Log.e("fileInfo.length-->", fileInfo.length + "");
        Toast.makeText(MainActivity.this, fileInfo.fileName + "下載完畢", Toast.LENGTH_LONG).show();
        //下載完畢后取消通知
        mNotificationUtil.cancleNotification(fileInfo.id);
    }

    private void updataItem(int id, int finished) {
        for (int i = 0; i < lists.size(); i++) {
            FileInfo fileInfo = lists.get(i);
            if (fileInfo.id == id) {
                lists.remove(i);
                fileInfo.finished = finished;
                lists.add(i, fileInfo);
                break;
            }
        }
        adapter.updateProgress(lists);
    }

源碼地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末杂腰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子椅文,更是在濱河造成了極大的恐慌喂很,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件皆刺,死亡現(xiàn)場離奇詭異少辣,居然都是意外死亡,警方通過查閱死者的電腦和手機羡蛾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門漓帅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人痴怨,你說我怎么就攤上這事忙干。” “怎么了浪藻?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵捐迫,是天一觀的道長。 經(jīng)常有香客問我爱葵,道長施戴,這世上最難降的妖魔是什么反浓? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮赞哗,結(jié)果婚禮上雷则,老公的妹妹穿的比我還像新娘。我一直安慰自己肪笋,他們只是感情好月劈,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涂乌,像睡著了一般艺栈。 火紅的嫁衣襯著肌膚如雪英岭。 梳的紋絲不亂的頭發(fā)上湾盒,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天,我揣著相機與錄音诅妹,去河邊找鬼罚勾。 笑死,一個胖子當著我的面吹牛吭狡,可吹牛的內(nèi)容都是我干的尖殃。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼划煮,長吁一口氣:“原來是場噩夢啊……” “哼送丰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起弛秋,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤器躏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蟹略,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體登失,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年挖炬,在試婚紗的時候發(fā)現(xiàn)自己被綠了揽浙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡意敛,死狀恐怖馅巷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情草姻,我是刑警寧澤钓猬,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站碴倾,受9級特大地震影響逗噩,放射性物質(zhì)發(fā)生泄漏掉丽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一异雁、第九天 我趴在偏房一處隱蔽的房頂上張望捶障。 院中可真熱鬧,春花似錦纲刀、人聲如沸项炼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锭部。三九已至,卻和暖如春面褐,著一層夾襖步出監(jiān)牢的瞬間拌禾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工展哭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留湃窍,地道東北人。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓匪傍,卻偏偏與公主長得像您市,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子役衡,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

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