基于AndroidAsync框架搭建android http server

使用的框架是AndroidAsync
项目地址:https://github.com/koush/AndroidAsync/tree/master/AndroidAsync/src/com/koushikdutta/async
参考的文章是:
http://programminglife.io/android-http-server-with-androidasync/
https://github.com/reneweb/AndroidAsyncSocketExamples
WifiTransfer

项目中的代码如下:

package com.duotin.car.widget.wifiTransfer;

import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Environment;
import android.text.TextUtils;


import com.duotin.car.BaseApplication;
import com.duotin.car.R;
import com.duotin.car.constant.Constants;
import com.duotin.car.scan.AlbumManager;
import com.duotin.car.scan.ResultFile;
import com.duotin.car.scan.ResultFolder;
import com.duotin.car.util.FileUtils;
import com.duotin.car.util.Log;
import com.duotin.car.util.Tool;
import com.duotin.lib.api2.Resource;
import com.duotin.lib.api2.model.Track;
import com.duotin.lib.util.AsyncTask;
import com.koushikdutta.async.AsyncServer;
import com.koushikdutta.async.AsyncServerSocket;
import com.koushikdutta.async.AsyncSocket;
import com.koushikdutta.async.ByteBufferList;
import com.koushikdutta.async.DataEmitter;
import com.koushikdutta.async.Util;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.DataCallback;
import com.koushikdutta.async.future.Future;
import com.koushikdutta.async.future.FutureCallback;
import com.koushikdutta.async.http.AsyncHttpClient;
import com.koushikdutta.async.http.AsyncHttpRequest;
import com.koushikdutta.async.http.AsyncHttpResponse;
import com.koushikdutta.async.http.body.MultipartFormDataBody;
import com.koushikdutta.async.http.body.Part;
import com.koushikdutta.async.http.callback.HttpConnectCallback;
import com.koushikdutta.async.http.server.AsyncHttpServer;
import com.koushikdutta.async.http.server.AsyncHttpServerRequest;
import com.koushikdutta.async.http.server.AsyncHttpServerRequestImpl;
import com.koushikdutta.async.http.server.AsyncHttpServerResponse;
import com.koushikdutta.async.http.server.HttpServerRequestCallback;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONObject;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * * Created by hongjunmin on 15/8/26
 *
 * @author hongjunmin
 */
public class WifiTransferServerHolder {
    private static final String TEXT_CONTENT_TYPE = "text/html;charset=utf-8";
    private static final String CSS_CONTENT_TYPE = "text/css;charset=utf-8";
    private static final String BINARY_CONTENT_TYPE = "application/octet-stream";
    private static final String JS_CONTENT_TYPE = "application/javascript";
    private static final String PNG_CONTENT_TYPE = "application/x-png";
    private static final String WOFF_CONTENT_TYPE = "application/x-font-woff";
    private static final String TTF_CONTENT_TYPE = "application/x-font-truetype";
    private static final String SVG_CONTENT_TYPE = "image/svg+xml";
    private static final String EOT_CONTENT_TYPE = "image/vnd.ms-fontobject";
    private static final String MP3_CONTENT_TYPE = "audio/mp3";
    private static final String MP4_CONTENT_TYPE = "video/mpeg4";
    public static final int AUDIO_FILE_DURATION_THRESHOLD_IN_SECOND = 600;
    public static final String MY_IMPORTED_AUDIO = "导入的音频";
    public static final String MY_IMPORTED_MUSIC = "导入的音乐";

    private static final String TAG = "WifiTransferServerHolder";
    private static final int DEFAULT_SOCKET_PORT = 34510;
    private AsyncHttpServer mHttpServer;
    private String defaultDirectory;
    private WifiTransferAdapter mWifiTransferAdapter;
    private AsyncServer mAsyncServer = AsyncServer.getDefault();


    private static final int BufferSize = 8190;

    public WifiTransferServerHolder(WifiTransferAdapter wifiTransferAdapter) {
        mWifiTransferAdapter = wifiTransferAdapter;
    }

    public void setUp() {
        mHttpServer = new AsyncHttpServer();
        AsyncServerSocket asyncServerSocket = mHttpServer.listen(mAsyncServer, DEFAULT_SOCKET_PORT);
        mHttpServer.post("/upload", new HttpServerRequestCallback() {
            @Override
            public void onRequest(final AsyncHttpServerRequest request, final AsyncHttpServerResponse response) {
                final MultipartFormDataBody body = (MultipartFormDataBody) request.getBody();
                final FileUploadHolder fileUploadHolder = new FileUploadHolder();
                body.setMultipartCallback(new MultipartFormDataBody.MultipartCallback() {
                    @Override
                    public void onPart(final Part part) {
                        if (part.isFile()) {
                            body.setDataCallback(new DataCallback() {
                                @Override
                                public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
                                    if (fileUploadHolder.getFileOutPutStream() != null)
                                    //已经开始传输文件
                                    {
                                        try {
                                            fileUploadHolder.getFileOutPutStream().write(bb.getAllByteArray());
                                            mWifiTransferAdapter.onRecievingFile();
                                        } catch (IOException e) {
                                            e.printStackTrace();
                                        }
                                        bb.recycle();
                                    }
                                }
                            });
                        } else {
                            if (body.getDataCallback() == null) {
                                body.setDataCallback(new DataCallback() {
                                    @Override
                                    public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
                                        String fileName = getFileNameFromHttpByteBufferList(bb);
                                        //还没设置文件名
                                        if (TextUtils.isEmpty(fileUploadHolder.getFileName()) && !TextUtils.isEmpty(fileName)) {
                                            generateReceivedFileAndFileStream(fileUploadHolder, fileName);
                                            mWifiTransferAdapter.onRecievingFile();
                                        }
                                    }
                                });
                            }
                        }
                        if (!TextUtils.isEmpty(part.getFilename())) {
                            if (!Track.isProgramFileName(part.getFilename())) {
                                mWifiTransferAdapter.formateNotsupported(part.getFilename());
                            }
                        }
                    }
                });
                request.setEndCallback(new CompletedCallback() {
                    @Override
                    public void onCompleted(Exception ex) {
                        response.send(new JSONObject());
                        if (fileUploadHolder.getFileOutPutStream() != null) {
                            try {
                                fileUploadHolder.getFileOutPutStream().close();
                                if (fileUploadHolder.getRecievedFile() != null && fileUploadHolder.getRecievedFile().exists()) {
                                    String filePath = fileUploadHolder.getRecievedFile().getPath();
                                    MediaMetadataRetriever retriever = new MediaMetadataRetriever();
                                    try {
                                        retriever.setDataSource(filePath);
                                    } catch (Exception e) {
                                        try {
                                            FileInputStream fI = new FileInputStream(filePath);
                                            FileDescriptor fd = fI.getFD();
                                            retriever.setDataSource(fd);
                                        } catch (Exception e1) {
                                            e1.printStackTrace();
                                        }
                                    } finally {
                                        mWifiTransferAdapter.fileReceived(fileUploadHolder.getFileName());
                                        locateProgramAndReadyToSync(retriever, fileUploadHolder.getFileName());
                                    }
                                }
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                });
            }
        });
        mHttpServer.get("/wf_images/.*", new HttpServerRequestCallback() {
            @Override
            public void onRequest(final AsyncHttpServerRequest request, final AsyncHttpServerResponse response) {
                sendResources(request, response);
            }
        });
        mHttpServer.get("/wf_resources/.*", new HttpServerRequestCallback() {
            @Override
            public void onRequest(final AsyncHttpServerRequest request, final AsyncHttpServerResponse response) {
                sendResources(request, response);
            }
        });
        mHttpServer.get("/wf_files/.*", new HttpServerRequestCallback() {
            @Override
            public void onRequest(final AsyncHttpServerRequest request, final AsyncHttpServerResponse response) {
                sendResources(request, response);
            }
        });
        mHttpServer.get("/list", new HttpServerRequestCallback() {
            @Override
            public void onRequest(final AsyncHttpServerRequest request, final AsyncHttpServerResponse response) {

                Log.d(TAG, request.getPath());
                Map<String, String> headMap = new HashMap<>();
                headMap.put("Expires:", "-1");
                headMap.put("Cache-Control:", "no-cache");
                headMap.put("Pragma:", "no-cache");
                printHead(response, TEXT_CONTENT_TYPE, headMap);
                response.send(getProgramFileNames());
            }
        });

        mHttpServer.get("/", new HttpServerRequestCallback() {
            @Override
            public void onRequest(final AsyncHttpServerRequest request, final AsyncHttpServerResponse response) {
                Log.d(TAG, request.getPath());
                try {
                    response.send(Resource.assetGet(BaseApplication.appContext, "wifiImport.html"));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        mHttpServer.get("/temp/.*?", new HttpServerRequestCallback() {
            @Override
            public void onRequest(AsyncHttpServerRequest request, AsyncHttpServerResponse response) {
                Log.d(TAG, request.getPath());
                Map<String, String> headMap = new HashMap<>();
                headMap.put("Expires:", "-1");
                headMap.put("Cache-Control:", "no-cache");
                headMap.put("Pragma:", "no-cache");
                headMap.put("Connection:", "keep-alive");
                printHead(response, TEXT_CONTENT_TYPE, headMap);
                try {
                    String statusLine = ((AsyncHttpServerRequestImpl) (request)).getStatusLine();
                    String[] parts = statusLine.split(" ");
                    String fullPath = parts[1];
                    fullPath = fullPath.replace("%20", " ");
                    String resourceName = fullPath.substring(fullPath.lastIndexOf("/") + 1);
                    if (resourceName.endsWith("?")) {
                        resourceName = resourceName.substring(0, resourceName.length() - 1);
                    }
                    if (!TextUtils.isEmpty(getContentTypeByResourceName(resourceName))) {
                        response.setContentType(getContentTypeByResourceName(resourceName));
                    }
                    String path = request.getMatcher().replaceAll("");
                    String storagePath = "/storage/emulated/0/";
                    File file = new File(storagePath, path);
                    FileInputStream ex = new FileInputStream(file);
                    response.sendStream(ex, ex.available());
                } catch (IOException e) {
                    e.printStackTrace();
                    return;
                }
            }
        });
        mHttpServer.setErrorCallback(new CompletedCallback() {
            @Override
            public void onCompleted(Exception ex) {
                Log.dMultiString("mHttpServer ErrorCallback onCompleted");
            }
        });
        if (asyncServerSocket != null) {
            mWifiTransferAdapter.setIpString(asyncServerSocket.getLocalPort());
        } else {
            mWifiTransferAdapter.setIpString(0);
        }

    }

    private void generateReceivedFileAndFileStream(FileUploadHolder fileUploadHolder, String fileName) {
        //取到文件名,生成目标文件和路径
        fileUploadHolder.setFileName(fileName);
        File exStorage = Environment.getExternalStorageDirectory();
        defaultDirectory = BaseApplication.getWifiMusicImportPath();
        if (TextUtils.isEmpty(defaultDirectory)) {
            mWifiTransferAdapter.showToast(R.string.no_external_cannot_import);
            return;
        }
        StringBuilder dir = new StringBuilder();
        dir.append(defaultDirectory);
        if (!dir.toString().contains(exStorage.getPath()))
            dir.insert(0, exStorage.getPath());
        File dirFile;
        dirFile = new File(dir.toString()); // convert spaces appropriately
        if (!dirFile.exists() || !dirFile.isDirectory()) // catch issues in the directory path
        {
            dir.replace(0, dir.length(), defaultDirectory); // replace it with defaultDirectory if invalid
            dirFile = new File(dir.toString());
        }
        File recievedFile = new File(dirFile, fileUploadHolder.getFileName());
        fileUploadHolder.setRecievedFile(recievedFile);
        BufferedOutputStream fs = null;
        try {
            fs = new BufferedOutputStream(new FileOutputStream(recievedFile));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        fileUploadHolder.setFileOutPutStream(fs);
    }

    private String getProgramFileNames() {
        List<File> importedProgramFiles = new ArrayList<>();
        importedProgramFiles.addAll(getFilesSortedByLastModified(BaseApplication.getWifiAudioImportPath()));
        importedProgramFiles.addAll(getFilesSortedByLastModified(BaseApplication.getWifiMusicImportPath()));
        JSONArray jsonArray = new JSONArray();
        for (File file : importedProgramFiles) {
            JSONObject jsonObject = new JSONObject();
            try {
                jsonObject.put("path", file.getAbsolutePath().toString());
                jsonObject.put("name", file.getName());
                jsonObject.put("size", file.length());
                jsonArray.put(jsonObject);
            } catch (Exception e) {

            }
        }
        return jsonArray.toString();
    }

    private List<File> getFilesSortedByLastModified(String path) {
        List<File> importedAudioFiles = new ArrayList<>();
        File importedAudioFolder = new File(path);
        if (importedAudioFolder.exists()) {
            File[] importedAudioArray = importedAudioFolder.listFiles();
            if (!Tool.isEmpty(importedAudioArray)) {
                importedAudioFiles = Arrays.asList(importedAudioArray);
                Collections.sort(importedAudioFiles,
                        new Comparator<File>() {
                            @Override
                            public int compare(File o1,
                                               File o2) {
                                return (int) (o1.lastModified() - o2.lastModified());
                            }
                        });
            }

        }
        return importedAudioFiles;
    }

    private void sendResources(final AsyncHttpServerRequest request, final AsyncHttpServerResponse response) {
        try {
            String statusLine = ((AsyncHttpServerRequestImpl) (request)).getStatusLine();
            String[] parts = statusLine.split(" ");
            String fullPath = parts[1];
            fullPath = fullPath.replace("%20", " ");
            String resourceName = fullPath.substring(fullPath.lastIndexOf("/") + 1);

            if (resourceName.endsWith("?")) {
                resourceName = resourceName.substring(0, resourceName.length() - 1);
            }
            if (!TextUtils.isEmpty(getContentTypeByResourceName(resourceName))) {
                response.setContentType(getContentTypeByResourceName(resourceName));
            }
            BufferedInputStream bInputStream = new BufferedInputStream(BaseApplication.appContext.getAssets().open(resourceName));
            response.sendStream(bInputStream, bInputStream.available());
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
    }

    private String getContentTypeByResourceName(String resourceName) {
        if (resourceName.endsWith(".css")) {
            return CSS_CONTENT_TYPE;
        } else if (resourceName.endsWith(".js")) {
            return JS_CONTENT_TYPE;
        } else if (resourceName.endsWith(".swf")) {
            return BINARY_CONTENT_TYPE;
        } else if (resourceName.endsWith(".png")) {
            return PNG_CONTENT_TYPE;
        } else if (resourceName.endsWith(".woff")) {
            return WOFF_CONTENT_TYPE;
        } else if (resourceName.endsWith(".ttf")) {
            return TTF_CONTENT_TYPE;
        } else if (resourceName.endsWith(".svg")) {
            return SVG_CONTENT_TYPE;
        } else if (resourceName.endsWith(".eot")) {
            return EOT_CONTENT_TYPE;
        } else if (resourceName.endsWith(".mp3")) {
            return MP3_CONTENT_TYPE;
        } else if (resourceName.endsWith(".mp4")) {
            return MP4_CONTENT_TYPE;
        }
        return "";
    }

    private void locateProgramAndReadyToSync(MediaMetadataRetriever retriever, String fileName) {
        String folderName = BaseApplication.getWifiMusicImportPath();
        String albumName = MY_IMPORTED_MUSIC;
        String duration = "0";
        Constants.TrackType trackType = Constants.TrackType.LOCAL;
        int albumId = MY_IMPORTED_MUSIC_ALBUM_ID;
        Log.dMultiString("synchronized (SingleSocketConnection.class) { 903");
        try {
            duration = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
            long durationInmillisec = Long.parseLong(duration);
            long durationInsec = durationInmillisec / 1000;
            if (durationInsec > AUDIO_FILE_DURATION_THRESHOLD_IN_SECOND) {
                FileUtils.moveFile(BaseApplication.getWifiMusicImportPath(), fileName, BaseApplication.getWifiAudioImportPath());
                folderName = BaseApplication.getWifiAudioImportPath();
                albumName = MY_IMPORTED_AUDIO;
                trackType = Constants.TrackType.LOCAL;
                albumId = MY_IMPORTED_AUDIO_ALBUM_ID;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readtToSync(folderName, fileName, duration, albumName, trackType, albumId);
        }
    }

    private void readtToSync(String folderName, String fileName, String duration, String albumName, Constants.TrackType trackType, int albumId) {
        if (TextUtils.isEmpty(folderName) || TextUtils.isEmpty(fileName) || TextUtils.isEmpty(albumName))
            return;
        File folder = new File(folderName);
        Map<String, String> fileNamesAndDurations = new HashMap<>();
        fileNamesAndDurations.put(fileName, duration);
        AutoAddResultFoler autoAddResultFoler = new AutoAddResultFoler(folder, folderName, fileNamesAndDurations, albumName, trackType, albumId);
        new ImportProgramesAsynTask().execute(autoAddResultFoler);
    }

    public void stop() {
        mHttpServer.stop();
        mAsyncServer.stop();
    }

    static class ImportProgramesAsynTask extends AsyncTask<ResultFolder, String, String> {

        @Override
        protected String doInBackground(ResultFolder... params) {
            ResultFolder resultFolder = params[0];
            if (resultFolder != null)
                AlbumManager.storeFolder(resultFolder);
            return null;
        }
    }

    class AutoAddResultFoler extends ResultFolder {

        public AutoAddResultFoler(File folderFile, String folderName, Map<String, String> fileNamesAndDurations, String albumName, Constants.TrackType trackType, int albumId) {
            super(folderFile);
            if (folderFile.exists() && !Tool.isEmpty(fileNamesAndDurations)) {
                setId(albumId);
                setSource(Constants.TrackSource.LOCAL_WIFI_IMPORT);
                setTrackType(trackType);
                setAlbumName(albumName);
                for (Map.Entry<String, String> entry : fileNamesAndDurations.entrySet()) {
                    File recievedFile = new File(folderName + entry.getKey());
                    if (recievedFile.exists()) {
                        ResultFile resultFile = new ResultFile(recievedFile);
                        resultFile.setToAdd(true);
                        resultFile.setSource(Constants.TrackSource.LOCAL_WIFI_IMPORT);
                        resultFile.setDuration(com.duotin.lib.api2.util.StringUtils.formatTime(entry.getValue()));
                        resultFile.setStatus(Constants.TrackState.DOWNLOAD_SUCCESSFUL);
                        addResultFile(resultFile);
                    }
                }
            }
        }

    }


    class FileUploadHolder {
        private String fileName;

        public File getRecievedFile() {
            return recievedFile;
        }

        public void setRecievedFile(File recievedFile) {
            this.recievedFile = recievedFile;
        }

        private File recievedFile;

        public BufferedOutputStream getFileOutPutStream() {
            return fileOutPutStream;
        }

        public void setFileOutPutStream(BufferedOutputStream fileOutPutStream) {
            this.fileOutPutStream = fileOutPutStream;
        }

        public String getFileName() {
            return fileName;
        }

        public void setFileName(String fileName) {
            this.fileName = fileName;
        }

        public void reset() {
            fileName = null;
            fileOutPutStream = null;
        }

        private BufferedOutputStream fileOutPutStream;
    }

    private String getFileNameFromHttpByteBufferList(ByteBufferList bb) {
        String s = bb.readString();
        Log.dMultiString(s);
        if (Track.isProgramFileName(s)) {
            return s;
        } else {
            if (s.length() > 2) {
                s = s.substring(0, s.length() - 2);
                if (Track.isProgramFileName(s)) {
                    return s;
                }
            }
        }
        return null;
    }

    private void printHead(AsyncHttpServerResponse response, String contentType, Map<String, String> customHeads) {
        response.setContentType(contentType);
        if (!Tool.isEmpty(customHeads)) {
            for (Map.Entry<String, String> entry : customHeads.entrySet()) {
                response.getHeaders().set(entry.getKey(), entry.getValue());
            }
        }
    }
}

代码中很多是业务,所以大家主要是参照setUp方法和get,post是怎么设置的。
另:
下载的jar包是2.1.6的。但用在项目中遇到
问题一:有乱码。解决方案:项目里的 Charsets.US_ASCII 都改成了 Charsets.UTF_8
问题二: 从客户端获取数据不完整。发现是LineEmitter 类的onDataAvailable(DataEmitter emitter, ByteBufferList bb)内的代码走不通。就把原来的:
@Override public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { while (bb.remaining() > 0) { byte b = bb.get(); if (b == '\n') { assert mLineCallback != null; mLineCallback.onStringAvailable(data.toString()); data = new StringBuilder(); return; } else { data.append((char)b); } } }
改成了:
@Override public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { String s = bb.readString(); if (s.indexOf("\n") > 0) mLineCallback.onStringAvailable(s); }}
就OK了
由于我用的是jar包的引用方式,所以是依照上面改动原项目后导出了jar包,导jar包方法:http://blog.csdn.net/u012319317/article/details/48133463
你可以直接下载我导出的jar包:http://download.csdn.net/detail/u012319317/9408485

获取的结果中,中文乱码 更简洁的方案

另:
推荐一篇NIO科普文:
Java NIO:浅析I/O模型

更多参考:
AndroidAsync方案:
An HTTP server inside your Android application using AndroidAsync
This project includes a few examples on how to create different types of sockets using AndroidAsync. It includes examples for a TCP client/server, TCP client with SSL and UDP client/server.
WifiTransfer

其他方案:
AndServer
android-http-server
nanohttpd

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容

  • afinalAfinal是一个android的ioc,orm框架 https://github.com/yangf...
    passiontim阅读 15,411评论 2 45
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,787评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 尊敬的邹老师,明天就是9月10日教师节了。这篇日记是我送给您的教师节礼物。 老师,您每天都会换各种各样,好看的衣服...
    彦彦公主阅读 389评论 0 1
  • 知乎里有人提了一个问题:专家级的用户和普通用户看待一个产品的感觉是否一样的?他还能从普通用户的角度去看待产...
    Smart熊大阅读 337评论 0 1