Android 系統(tǒng)原生 API 實(shí)現(xiàn)分享功能

一田度、前言:

對于 App 的分享功能默怨,基本上是一個(gè)剛需,本文主要介紹運(yùn)用系統(tǒng)原生分享功能時(shí)候需要注意的一些問題久锥。對于某些特定平臺(tái)的一些高級(jí)分享特性家淤,比如微信或者微博之類的分享來源標(biāo)注,需要在其開放平臺(tái)注冊應(yīng)用再接入其 sdk 才可以瑟由,這里不予以討論絮重。打算借助第三方庫類似 ShareSDK 實(shí)現(xiàn)的同學(xué)們,這篇文章可能也幫不上你歹苦。

GitHub 項(xiàng)目地址:LocalShare-master

二青伤、什么是 Android 系統(tǒng)的原生分享

直接上圖,這是一個(gè)典型的調(diào)用系統(tǒng)原生分享場景下的界面殴瘦,相信大家應(yīng)該都很熟悉狠角。

圖片.png

系統(tǒng)內(nèi)建的分享機(jī)制,參照官方的教程蚪腋,基本上可以滿足你的一般需求:Android-training-building-content-sharing

1丰歌、簡單描述下創(chuàng)建分享的主要過程:

  • 創(chuàng)建一個(gè) Intent ,指定其 Action 為 Intent.ACTION_SEND屉凯,這表示要?jiǎng)?chuàng)建一個(gè)發(fā)送指定內(nèi)容的隱式意圖立帖。
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
  • 指定需要發(fā)送的內(nèi)容和類型。
// 比如發(fā)送文本形式的數(shù)據(jù)內(nèi)容
// 指定發(fā)送的內(nèi)容
sendIntent.putExtra(Intent.EXTRA_TEXT, "This is my text to send.");
// 指定發(fā)送內(nèi)容的類型
sendIntent.setType("text/plain"); 
// 比如發(fā)送二進(jìn)制文件數(shù)據(jù)流內(nèi)容(比如圖片悠砚、視頻晓勇、音頻文件等等)
// 指定發(fā)送的內(nèi)容 (EXTRA_STREAM 對于文件 Uri )
shareIntent.putExtra(Intent.EXTRA_STREAM, uriToImage);
// 指定發(fā)送內(nèi)容的類型 (MIME type)
shareIntent.setType("image/jpeg");
  • 向系統(tǒng)發(fā)送隱式意圖,打開系統(tǒng)分享選擇器,出現(xiàn)如上圖所示界面宵蕉。
startActivity(Intent.createChooser(shareIntent, “Share to...”));

那下面說一下遇到的一些問題酝静,特別針對是 7.0 以后的系統(tǒng),以及兼容一些主流 app 時(shí)遇到的坑羡玛。

2别智、獲取文件類型(MimeType)

前面說到分享文件時(shí)需要知道文件的類型,不然的指定類型為 / 稼稿,這樣分享到某些 App 會(huì)因?yàn)闊o法判斷文件類型而導(dǎo)致失敗薄榛,所以最好先根據(jù)文件路徑獲取其文件類型。

  • 下面是一些常見文件的mimeType
{".3gp", "video/3gpp"},  
{".apk", "application/vnd.android.package-archive"},  
{".asf", "video/x-ms-asf"},  
{".avi", "video/x-msvideo"},  
{".bin", "application/octet-stream"},  
{".bmp", "image/bmp"},  
{".c", "text/plain"},  
{".class", "application/octet-stream"},  
{".conf", "text/plain"},  
{".cpp", "text/plain"},  
{".doc", "application/msword"},  
{".exe", "application/octet-stream"},  
{".gif", "image/gif"},  
{".gtar", "application/x-gtar"},  
{".gz", "application/x-gzip"},  
{".h", "text/plain"},  
{".htm", "text/html"},  
{".html", "text/html"},  
{".jar", "application/java-archive"},  
{".java", "text/plain"},  
{".jpeg", "image/jpeg"},  
{".jpg", "image/jpeg"},  
{".js", "application/x-javascript"},  
{".log", "text/plain"},  
{".m3u", "audio/x-mpegurl"},  
{".m4a", "audio/mp4a-latm"},  
{".m4b", "audio/mp4a-latm"},  
{".m4p", "audio/mp4a-latm"},  
{".m4u", "video/vnd.mpegurl"},  
{".m4v", "video/x-m4v"},  
{".mov", "video/quicktime"},  
{".mp2", "audio/x-mpeg"},  
{".mp3", "audio/x-mpeg"},  
{".mp4", "video/mp4"},  
{".mpc", "application/vnd.mpohun.certificate"},  
{".mpe", "video/mpeg"},  
{".mpeg", "video/mpeg"},  
{".mpg", "video/mpeg"},  
     {".mpg4", "video/mp4"},  
{".mpga", "audio/mpeg"},  
{".msg", "application/vnd.ms-outlook"},  
{".ogg", "audio/ogg"},  
{".pdf", "application/pdf"},  
{".png", "image/png"},  
{".pps", "application/vnd.ms-powerpoint"},  
{".ppt", "application/vnd.ms-powerpoint"},  
{".prop", "text/plain"},  
{".rar", "application/x-rar-compressed"},  
{".rc", "text/plain"},  
{".rmvb", "audio/x-pn-realaudio"},  
{".rtf", "application/rtf"},  
{".sh", "text/plain"},  
{".tar", "application/x-tar"},  
{".tgz", "application/x-compressed"},  
{".txt", "text/plain"},  
{".wav", "audio/x-wav"},  
{".wma", "audio/x-ms-wma"},  
{".wmv", "audio/x-ms-wmv"},  
{".wps", "application/vnd.ms-works"},  
//{".xml", "text/xml"},  
{".xml", "text/plain"},  
{".z", "application/x-compress"},  
{".zip", "application/zip"},  
{"", "*/*"}  

3让歼、獲取文件類型的方法:

  • 方式一(方便但不穩(wěn)定):通過 ContentResolver 查詢 Android 系統(tǒng)提供的 ContentProvider 獲取

當(dāng) targetSdkVersion >= 24 時(shí)使用 Uri.fromFile(File file) 獲取文件
uri 會(huì)報(bào) android.os.FileUriExposedException 異常 敞恋,應(yīng)該要使用
FileProvider ,具體請參考 Android 7.0 FileProvider 適配相關(guān)谋右,這里不再展開說明硬猫。關(guān)于 FileProvider 推薦一篇總結(jié)比較好的文章。

// 獲取文件的 url
File shareFile = new File(shareFilePath);
Uri fileUri = Uri.fromFile(shareFile);

// 獲取系統(tǒng)的提供的 ContentResolver
ContentResolver contentResolver = getApplicationContext().getContentResolver();
// 獲取文件MimeType改执,如 image/png
String fileMimeType = contentResolver.getType(fileUri);

// 獲取文件Type啸蜜,如 png
MimeTypeMap mime = MimeTypeMap.getSingleton();
String fileType = mime.getExtensionFromMimeType(fileMimeType);

使用這種方法獲取文件類型,一定要注意 ContentResolver 獲取返回為 null 的情況辈挂,不然空指針異常的崩潰率可能會(huì)讓你笑不出來衬横。實(shí)際測試中,發(fā)現(xiàn)在某些國產(chǎn)機(jī)型下终蒂,這個(gè)方法可以說直接是不可用蜂林,查詢返回一直都是空,所以單純依賴這一個(gè)方法會(huì)很不可靠拇泣。具體問題原因請看:What causes Android's ContentResolver.query() to return null?

  • 方式二 解析文件信息噪叙,通過匹配識(shí)別判斷:
    在好用的方法卻不可靠的情況下,只能配合看起來蠢一點(diǎn)的方法霉翔。目前大致的思路有兩種:
    -- 1.識(shí)別文件后綴构眯,根據(jù)后綴名來判斷文件類型。
    -- 2.獲取文件頭信息早龟,轉(zhuǎn)成十六進(jìn)制字符串后判斷文件類型惫霸。
    這兩種都是根據(jù)特點(diǎn)信息去做匹配,因此需要先保存一份文件特點(diǎn)信息和文件類型的對應(yīng)參照表葱弟。

下面按照第二條思路壹店,按照文件頭信息簡單實(shí)現(xiàn)一個(gè)獲取文件類型的例子:

  /**
   * 獲取文件類型
   * @param filePath
   * @return
   */
   public static String getFileType(String filePath) {  
          return mFileTypes.get(getFileHeader(filePath));  
   }  

   private static final HashMap<String, String> mFileTypes = new HashMap<String, String>();  

   // judge file type by file header content  
   static {  
            mFileTypes.put("ffd8ffe000104a464946", "jpg"); //JPEG (jpg)         
            mFileTypes.put("89504e470d0a1a0a0000", "png"); //PNG (png)         
            mFileTypes.put("47494638396126026f01", "gif"); //GIF (gif)         
            mFileTypes.put("49492a00227105008037", "tif"); //TIFF (tif)         
            mFileTypes.put("424d228c010000000000", "bmp"); //16色位圖(bmp)         
            mFileTypes.put("424d8240090000000000", "bmp"); //24位位圖(bmp)         
            mFileTypes.put("424d8e1b030000000000", "bmp"); //256色位圖(bmp)         
            mFileTypes.put("41433130313500000000", "dwg"); //CAD (dwg)         
            mFileTypes.put("3c21444f435459504520", "html"); //HTML (html)    
            mFileTypes.put("3c21646f637479706520", "htm"); //HTM (htm)    
            mFileTypes.put("48544d4c207b0d0a0942", "css"); //css    
            mFileTypes.put("696b2e71623d696b2e71", "js"); //js    
            mFileTypes.put("7b5c727466315c616e73", "rtf"); //Rich Text Format (rtf)         
            mFileTypes.put("38425053000100000000", "psd"); //Photoshop (psd)         
            mFileTypes.put("46726f6d3a203d3f6762", "eml"); //Email [Outlook Express 6] (eml)           
            mFileTypes.put("d0cf11e0a1b11ae10000", "doc"); //MS Excel 注意:word、msi 和 excel的文件頭一樣         
            mFileTypes.put("d0cf11e0a1b11ae10000", "vsd"); //Visio 繪圖         
            mFileTypes.put("5374616E64617264204A", "mdb"); //MS Access (mdb)          
            mFileTypes.put("252150532D41646F6265", "ps");  
            mFileTypes.put("255044462d312e350d0a", "pdf"); //Adobe Acrobat (pdf)       
            mFileTypes.put("2e524d46000000120001", "rmvb"); //rmvb/rm相同      
            mFileTypes.put("464c5601050000000900", "flv"); //flv與f4v相同      
            mFileTypes.put("00000020667479706d70", "mp4");  
            mFileTypes.put("49443303000000002176", "mp3");  
            mFileTypes.put("000001ba210001000180", "mpg"); //         
            mFileTypes.put("3026b2758e66cf11a6d9", "wmv"); //wmv與asf相同        
            mFileTypes.put("52494646e27807005741", "wav"); //Wave (wav)      
            mFileTypes.put("52494646d07d60074156", "avi");  
            mFileTypes.put("4d546864000000060001", "mid"); //MIDI (mid)       
            mFileTypes.put("504b0304140000000800", "zip");  
            mFileTypes.put("526172211a0700cf9073", "rar");  
            mFileTypes.put("235468697320636f6e66", "ini");  
            mFileTypes.put("504b03040a0000000000", "jar");  
            mFileTypes.put("4d5a9000030000000400", "exe");//可執(zhí)行文件    
            mFileTypes.put("3c25402070616765206c", "jsp");//jsp文件    
            mFileTypes.put("4d616e69666573742d56", "mf");//MF文件    
            mFileTypes.put("3c3f786d6c2076657273", "xml");//xml文件    
            mFileTypes.put("494e5345525420494e54", "sql");//xml文件    
            mFileTypes.put("7061636b616765207765", "java");//java文件    
            mFileTypes.put("406563686f206f66660d", "bat");//bat文件    
            mFileTypes.put("1f8b0800000000000000", "gz");//gz文件    
            mFileTypes.put("6c6f67346a2e726f6f74", "properties");//bat文件    
            mFileTypes.put("cafebabe0000002e0041", "class");//bat文件    
            mFileTypes.put("49545346030000006000", "chm");//bat文件    
            mFileTypes.put("04000000010000001300", "mxp");//bat文件    
            mFileTypes.put("504b0304140006000800", "docx");//docx文件    
            mFileTypes.put("d0cf11e0a1b11ae10000", "wps");//WPS文字wps芝加、表格et硅卢、演示dps都是一樣的    
            mFileTypes.put("6431303a637265617465", "torrent");  
  
  
            mFileTypes.put("6D6F6F76", "mov"); //Quicktime (mov)      
            mFileTypes.put("FF575043", "wpd"); //WordPerfect (wpd)       
            mFileTypes.put("CFAD12FEC5FD746F", "dbx"); //Outlook Express (dbx)         
            mFileTypes.put("2142444E", "pst"); //Outlook (pst)          
            mFileTypes.put("AC9EBD8F", "qdf"); //Quicken (qdf)         
            mFileTypes.put("E3828596", "pwl"); //Windows Password (pwl)             
            mFileTypes.put("2E7261FD", "ram"); //Real Audio (ram)       
            mFileTypes.put("null", null); //null  
    }

    /**
     * 獲取文件頭信息
     * @param filePath
     * @return
     */
    public static String getFileHeader(String filePath) {
       File file=new File(filePath);  
            if(!file.exists() || file.length()<11){  
                return "null";  
            }  
            FileInputStream is = null;  
            String value = null;  
            try {  
                is = new FileInputStream(file);  
                byte[] b = new byte[10];  
                is.read(b, 0, b.length);  
                value = bytesToHexString(b);  
            } catch (Exception e) {  
            } finally {  
                if(null != is) {  
                    try {  
                        is.close();  
                    } catch (IOException e) {}  
                }  
            }  
            return value;  
    }

    /**
     * 將byte字節(jié)轉(zhuǎn)換為十六進(jìn)制字符串
     * @param src
     * @return
     */
    private static String bytesToHexString(byte[] src) {
        StringBuilder builder = new StringBuilder();
        if (src == null || src.length <= 0) {
            return null;
        }
        String hv;
        for (int i = 0; i < src.length; i++) {
            hv = Integer.toHexString(src[i] & 0xFF).toUpperCase();
            if (hv.length() < 2) {
                builder.append(0);
            }
            builder.append(hv);
        }
        return builder.toString();
    }

4射窒、 獲取分享文件的Uri進(jìn)行分享

前面也有提到,在 Android 7.0 以后将塑,系統(tǒng)對 scheme 為 file:// 的 uri 進(jìn)行了限制脉顿,所以之前進(jìn)行文件分享的一些接口就不能用了,此時(shí)就得使用其他的URI scheme 來代替 file://点寥,比如 MediaStore 的 content:// 或者FileProvider 艾疟。

public static void shareFile(Context context, String filePath) {
        if (context == null || TextUtils.isEmpty(filePath)){
            LogUtil.e("shareFile context is null or filePath is empty.");
            return;
        }

        File file = new File(filePath);
        if (file != null && file.exists()){
            Intent intent = new Intent();
            intent.setAction(Intent.ACTION_SEND);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.addCategory("android.intent.category.DEFAULT");
           
// 如果需要指定分享到某個(gè)app,配置 componentName 即可
           if (!TextUtils.isEmpty(componentName) && "com.tencent.mm".equals(componentName)){
                // 分享精確到微信的頁面敢辩,朋友圈頁面蔽莱,或者選擇好友分享頁面
                 ComponentName comp = new ComponentName("com.tencent.mm", "com.tencent.mm.ui.tools.ShareToTimeLineUI");
                 intent.setComponent(comp);
            }

            intent.putExtra(Intent.EXTRA_STREAM, uri);
            // 授予目錄臨時(shí)共享權(quán)限
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

            String fileType;
            Uri fileUri = getFileUri(context, file);
            if (fileUri != null && !TextUtils.isEmpty(fileUri.toString())) {
                ContentResolver contentResolver = context.getContentResolver();
                fileType = contentResolver.getType(fileUri);
            }

            if (TextUtils.isEmpty(fileType)){
                fileType = getFileType(filePath); // 使用上面的根據(jù)文件頭信息獲取文件類型的方法
            }

           if (TextUtils.isEmpty(fileType)){
               fileType =  "*/*"
           }

            LogUtil.d("shareFile fileType " + fileType);
            LogUtil.d("shareFile uri: " + uri);

            intent.setDataAndType(uri, fileType);

            try {
                context.startActivity(Intent.createChooser(intent, file.getName()));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

// 獲取文件Uri

public static Uri getFileUri(Context context, File file){
        Uri uri;
       // 低版本直接用 Uri.fromFile
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
            uri = Uri.fromFile(file);
        }else {
            //  使用 FileProvider 會(huì)在某些 app 下不支持(在使用FileProvider 方式情況下QQ不能支持圖片、視頻分享戚长,微信不支持視頻分享)
            uri = FileProvider.getUriForFile(context,
                            "gdut.bsx.videoreverser.fileprovider",
                    file);

            ContentResolver cR = context.getContentResolver();
            if (uri != null && !TextUtils.isEmpty(uri.toString())) {
                String fileType = cR.getType(uri);
// 使用 MediaStore 的 content:// 而不是自己 FileProvider 提供的uri盗冷,不然有些app無法適配
                if (!TextUtils.isEmpty(fileType)){
                    if (fileType.contains("video/")){
                        uri = getVideoContentUri(context, file);
                    }else if (fileType.contains("image/")){
                        uri = getImageContentUri(context, file);
                    }else if (fileType.contains("audio/")){
                        uri = getAudioContentUri(context, file);
                    }
                }
            }
        }
        return uri;
    }
  /**
     * Gets the content:// URI from the given corresponding path to a file
     *
     * @param context
     * @param imageFile
     * @return content Uri
     */
    public static Uri getImageContentUri(Context context, File imageFile) {
        String filePath = imageFile.getAbsolutePath();
        Cursor cursor = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                new String[] { MediaStore.Images.Media._ID }, MediaStore.Images.Media.DATA + "=? ",
                new String[] { filePath }, null);
        if (cursor != null && cursor.moveToFirst()) {
            int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
            Uri baseUri = Uri.parse("content://media/external/images/media");
            return Uri.withAppendedPath(baseUri, "" + id);
        } else {
            if (imageFile.exists()) {
                ContentValues values = new ContentValues();
                values.put(MediaStore.Images.Media.DATA, filePath);
                return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
            } else {
                return null;
            }
        }
    }

    /**
     * Gets the content:// URI from the given corresponding path to a file
     *
     * @param context
     * @param videoFile
     * @return content Uri
     */
    public static Uri getVideoContentUri(Context context, File videoFile) {
        String filePath = videoFile.getAbsolutePath();
        Cursor cursor = context.getContentResolver().query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
                new String[] { MediaStore.Video.Media._ID }, MediaStore.Video.Media.DATA + "=? ",
                new String[] { filePath }, null);
        if (cursor != null && cursor.moveToFirst()) {
            int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
            Uri baseUri = Uri.parse("content://media/external/video/media");
            return Uri.withAppendedPath(baseUri, "" + id);
        } else {
            if (videoFile.exists()) {
                ContentValues values = new ContentValues();
                values.put(MediaStore.Video.Media.DATA, filePath);
                return context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
            } else {
                return null;
            }
        }
    }

    /**
     * Gets the content:// URI from the given corresponding path to a file
     *
     * @param context
     * @param audioFile
     * @return content Uri
     */
    public static Uri getAudioContentUri(Context context, File audioFile) {
        String filePath = audioFile.getAbsolutePath();
        Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                new String[] { MediaStore.Audio.Media._ID }, MediaStore.Audio.Media.DATA + "=? ",
                new String[] { filePath }, null);
        if (cursor != null && cursor.moveToFirst()) {
            int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
            Uri baseUri = Uri.parse("content://media/external/audio/media");
            return Uri.withAppendedPath(baseUri, "" + id);
        } else {
            if (audioFile.exists()) {
                ContentValues values = new ContentValues();
                values.put(MediaStore.Audio.Media.DATA, filePath);
                return context.getContentResolver().insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
            } else {
                return null;
            }
        }
    }

要向在 MediaStore 中查詢到文件,要不就是通知媒體庫更新查詢或則往里面插入一條新記錄(會(huì)比較耗時(shí))

/**
     * 刪除或增加圖片同廉、視頻等媒體資源文件時(shí) 通知系統(tǒng)更新媒體庫仪糖,重新掃描
     * @param filePath 文件路徑,包括后綴
     */
    public static void notifyScanMediaFile(Context context, String filePath)
    {
        if (context == null || TextUtils.isEmpty(filePath)){
            LogUtil.e("notifyScanMediaFile context is null or filePath is empty.");
            return;
        }
        MediaScannerConnection.scanFile(context,
                new String[] { filePath }, null,
                new MediaScannerConnection.OnScanCompletedListener() {
                    public void onScanCompleted(String path, Uri uri) {
                        LogUtil.i("notifyScanMediaFile Scanned " + path);
                        LogUtil.i("notifyScanMediaFile -> uri=" + uri);
                    }
                });
    }

5迫肖、 具體實(shí)現(xiàn)

可以參考我的另外一篇文章:Android 系統(tǒng)原生 API 實(shí)現(xiàn)分享功能(2)


參考:http://www.reibang.com/p/1d4bd2c5ef69

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(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
  • 文/蒼蘭香墨 我猛地睜開眼蛆橡,長吁一口氣:“原來是場噩夢啊……” “哼舌界!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起泰演,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤呻拌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(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
  • 正文 我出身青樓阶界,卻偏偏與公主長得像虹钮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子膘融,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

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