Android-FileProvider-輕松掌握

前言

存儲(chǔ)適配系列文章:

Android-存儲(chǔ)基礎(chǔ)
Android-10隐砸、11-存儲(chǔ)完全適配(上)
Android-10票摇、11-存儲(chǔ)完全適配(下)
Android-FileProvider-輕松掌握

之前在分析Android 存儲(chǔ)相關(guān)知識(shí)點(diǎn)的時(shí)候,有同學(xué)提出希望也分析一下FileProvider鱼填,那時(shí)忙于總結(jié)線程并發(fā)知識(shí)點(diǎn),并沒(méi)有立即著手分享。本次驶沼,將著重分析Android 應(yīng)用之間如何使用第三方應(yīng)用打開(kāi)文件,如何分享文件給第三方應(yīng)用争群。
通過(guò)本篇文章回怜,你將了解到:

1、Android 應(yīng)用間共享文件
2换薄、FileProvider 應(yīng)用與原理
3玉雾、FileProvider Uri構(gòu)造與解析

1翔试、Android 應(yīng)用間共享文件

共享基礎(chǔ)

提到文件共享,首先想到就是在本地磁盤上存放一個(gè)文件抹凳,多個(gè)應(yīng)用都可以訪問(wèn)它遏餐,如下:


image.png

理想狀態(tài)下只要知道了文件的存放路徑,那么各個(gè)應(yīng)用都可以讀寫它赢底。
比如相冊(cè)里的圖片存放目錄:/sdcard/DCIM/失都、/sdcard/Pictures/ 。
再比如相冊(cè)里的視頻存放目錄:/sdcard/DCIM/幸冻、/sdcard/Movies/粹庞。

共享方式

一個(gè)常見(jiàn)的應(yīng)用場(chǎng)景:
應(yīng)用A里檢索到一個(gè)文件my.txt,它無(wú)法打開(kāi)洽损,于是想借助其它應(yīng)用打開(kāi)庞溜,這個(gè)時(shí)候它需要把待打開(kāi)的文件路徑告訴其它應(yīng)用。
假設(shè)應(yīng)用B可以打開(kāi)my.txt碑定,那么應(yīng)用A如何把路徑傳遞給應(yīng)用B呢流码,這就涉及到了進(jìn)程間通信。我們知道Android進(jìn)程間通信主要手段是Binder延刘,而四大組件的通信也是依靠Binder漫试,因此我們應(yīng)用間傳遞路徑可以依靠四大組件。


image.png

可以看出碘赖,Activity/Service/Broadcast 可以傳遞Intent驾荣,而ContentProvider傳遞Uri,實(shí)際上Intent 里攜帶了Uri變量普泡,因此四大組件之間可以傳遞Uri播掷,而路徑就可以存放在Uri里。

2撼班、FileProvider 應(yīng)用與原理

以使用其它應(yīng)用打開(kāi)文件為例歧匈,分別闡述Android 7.0 前后的不同點(diǎn)。

Android 7.0 之前使用

上面說(shuō)到了傳遞路徑可以通過(guò)Uri砰嘁,來(lái)看看如何使用:

    private void openByOtherForN() {
        Intent intent = new Intent();
        //指定Action眯亦,使用其它應(yīng)用打開(kāi)
        intent.setAction(Intent.ACTION_VIEW);
        //通過(guò)路徑,構(gòu)造Uri
        Uri uri = Uri.fromFile(new File(external_filePath));
        //設(shè)置Intent般码,附帶Uri
        intent.setData(uri);
        //跨進(jìn)程傳遞Intent
        startActivity(intent);
    }

其中

  • external_filePath="/storage/emulated/0/fish/myTxt.txt"
  • 構(gòu)造為uri 后uriString="file:///storage/emulated/0/fish/myTxt.txt"

可以看出,文件路徑前多了"file:///"字符串乱顾。
而接收方在收到Intent后板祝,拿出Uri,通過(guò):

filePath = uri.getEncodedPath() 拿到發(fā)送方發(fā)送的原始路徑后走净,即可讀寫文件券时。

然而此種構(gòu)造Uri方式在Android7.0(含)之后被禁止了孤里,若是使用則拋出異常:


image.png

可以看出,Uri.fromFile 構(gòu)造方式的缺點(diǎn):

1橘洞、發(fā)送方傳遞的文件路徑接收方完全知曉捌袜,一目了然,沒(méi)有安全保障炸枣。
2虏等、發(fā)送方傳遞的文件路徑接收方可能沒(méi)有讀取權(quán)限,導(dǎo)致接收異常适肠。

Android 7.0(含)之后的使用

先想想霍衫,若是我們自己操刀,如何規(guī)避以上兩個(gè)問(wèn)題呢侯养?
針對(duì)第一個(gè)問(wèn)題:
可以將具體路徑替換為另一個(gè)字符串敦跌,類似以前密碼本的感覺(jué),比如:
"/storage/emulated/0/fish/myTxt.txt" 替換為"myfile/Txt.txt"逛揩,這樣接收方收到文件路徑完全不知道原始文件路徑是咋樣的柠傍。

不過(guò)這也引入了另一個(gè)額外的問(wèn)題:接收方不知道真實(shí)路徑,如何讀取文件呢?

針對(duì)第二個(gè)問(wèn)題
既然不確定接收方是否有打開(kāi)文件權(quán)限辩稽,那么是否由發(fā)送方打開(kāi)惧笛,然后將流傳遞給接收方就可以了呢?

Android 7.0(含)之后引入了FileProvider搂誉,可以解決上述兩個(gè)問(wèn)題徐紧。

FileProvider 應(yīng)用

先來(lái)看看如何使用FileProvider 來(lái)傳遞路徑。
細(xì)分為四個(gè)步驟:

一:定義FileProvider 子類

public class MyFileProvider extends FileProvider {

}

定義一個(gè)空的類炭懊,繼承自FileProvider并级,而FileProvider 繼承自ContentProvider。
注:FileProvider 需要引入AndroidX

二:AndroidManifest 里聲明FileProvider

既然是ContentProvider侮腹,那么需要像Activity一樣在AndroidManifest.xml里聲明:

        <provider
            android:authorities="com.fish.fileprovider"
            android:name=".fileprovider.MyFileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_path">
            </meta-data>
        </provider>

字段解釋如下:

1嘲碧、android:authorities 標(biāo)識(shí)ContentProvider的唯一性,可以自己任意定義父阻,最好是全局唯一的愈涩。
2、android:name 是指之前定義的FileProvider 子類加矛。
3履婉、android:exported="false" 限制其他應(yīng)用獲取Provider。
4斟览、android:grantUriPermissions="true" 授予其它應(yīng)用訪問(wèn)Uri權(quán)限毁腿。
5、meta-data 囊括了別名應(yīng)用表。
5.1已烤、android:name 這個(gè)值是固定的鸠窗,表示要解析file_path。
5.2胯究、android:resource 自己定義實(shí)現(xiàn)的映射表

三:路徑映射表

可以看出稍计,F(xiàn)ileProvider需要讀取映射表。
在/res/ 下建立xml 文件夾裕循,然后再創(chuàng)建對(duì)應(yīng)的映射表(xml)臣嚣,最終路徑如下:/res/xml/file_path.xml。
內(nèi)容如下:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <root-path name="myroot" path="." />
    <external-path name="external_file" path="fish" />
    <external-files-path name="external_app_file" path="myfile" />
    <external-cache-path name="external_app_cache" path="mycache/doc/" />
    <files-path name="inner_app_file" path="." />
    <cache-path name="inner_app_cache" path="." />
</paths>

字段解釋如下:

1费韭、root-path 標(biāo)簽表示要給根目錄下的子目錄取別名(包括內(nèi)部存儲(chǔ)茧球、自帶外部存儲(chǔ)、擴(kuò)展外部存儲(chǔ)星持,統(tǒng)稱用"/"表示)抢埋,path 屬性表示需要被更改的目錄名,其值為:"."督暂,表示不區(qū)分目錄揪垄,name 屬性表示將path 目錄更改后的別名。
2逻翁、假若有個(gè)文件路徑:/storage/emulated/0/fish/myTxt.txt饥努,而我們只配置了root-path 標(biāo)簽,那么最終該文件路徑被替換為:/myroot/storage/emulated/0/fish/myTxt.txt八回。
可以看出酷愧,因?yàn)閜ath=".",因此任何目錄前都被追加了myroot缠诅。

剩下的external-path等標(biāo)簽對(duì)應(yīng)的目錄如下:

1溶浴、external-path--->Environment.getExternalStorageDirectory(),如/storage/emulated/0/fish
2管引、external-files-path--->ContextCompat.getExternalFilesDirs(context, null)士败。
3、external-cache-path--->ContextCompat.getExternalCacheDirs(context)褥伴。
4谅将、files-path--->context.getFilesDir()。
5重慢、cache-path--->context.getCacheDir()饥臂。

你可能已經(jīng)發(fā)現(xiàn)了,這些標(biāo)簽所代表的目錄有重疊的部分似踱,在替換別名的時(shí)候如何選擇呢隅熙?答案是:選擇最長(zhǎng)匹配的志衣。
假設(shè)我們映射表里只定義了root-path與external-path,分別對(duì)應(yīng)的目錄為:

root-path--->/
external-path--->/storage/emulated/0/
現(xiàn)在要傳遞的文件路徑為:/storage/emulated/0/fish/myTxt.txt猛们。需要給這個(gè)文件所在目錄取別名,因此會(huì)遍歷映射表找到最長(zhǎng)匹配該目錄的標(biāo)簽狞洋,顯然external-path 所表示的/storage/emulated/0/ 與文件目錄最為匹配弯淘,因此最后文件路徑被替換為:/external_file/myTxt.txt

四:使用FileProvider 構(gòu)造路徑

映射表建立好之后,接著就需要構(gòu)造路徑吉懊。

    private void openByOther() {
        //取得文件擴(kuò)展名
        String extension = external_filePath.substring(external_filePath.lastIndexOf(".") + 1);
        //通過(guò)擴(kuò)展名找到mimeType
        String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
        //構(gòu)造Intent
        Intent intent = new Intent();
        //賦予讀寫權(quán)限
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        //表示用其它應(yīng)用打開(kāi)
        intent.setAction(Intent.ACTION_VIEW);
        File file = new File(external_filePath);
        //第二個(gè)參數(shù)表示要用哪個(gè)ContentProvider庐橙,這個(gè)唯一值在AndroidManifest.xml里定義了
        //若是沒(méi)有定義MyFileProvider,可直接使用FileProvider替代
        Uri uri = MyFileProvider.getUriForFile(this, "com.fish.fileprovider", file);
        //給Intent 賦值
        intent.setDataAndType(uri, mimeType);
        try {
            //交由系統(tǒng)處理
            startActivity(intent);
        } catch (Exception e) {
            //若是沒(méi)有其它應(yīng)用能夠接收打開(kāi)此種mimeType借嗽,則拋出異常
            Toast.makeText(this, e.getLocalizedMessage(),Toast.LENGTH_SHORT).show();
        }
    }

/storage/emulated/0/fish/myTxt.txt 最終構(gòu)造為:content://com.fish.fileprovider/external_file/myTxt.txt

對(duì)于私有目錄:/data/user/0/com.example.androiddemo/files/myTxt.txt 最終構(gòu)造為:
content://com.fish.fileprovider/inner_app_file/myTxt.txt

可以看出添加了:

content 作為scheme态鳖;
com.fish.fileprovider 即為我們定義的 authorities,作為host恶导;

如此構(gòu)造后浆竭,第三方應(yīng)用收到此Uri后,并不能從路徑看出我們傳遞的真實(shí)路徑惨寿,這就解決了第一個(gè)問(wèn)題:
發(fā)送方傳遞的文件路徑接收方完全知曉邦泄,一目了然,沒(méi)有安全保障裂垦。

3顺囊、FileProvider Uri構(gòu)造與解析

Uri 構(gòu)造輸入流

發(fā)送方將Uri交給系統(tǒng),系統(tǒng)找到有能力處理該Uri的應(yīng)用蕉拢。發(fā)送方A需要?jiǎng)e的應(yīng)用打開(kāi)myTxt.txt 文件特碳,假設(shè)應(yīng)用B具有能夠打開(kāi)文本文件的能力,并且也愿意接收別人傳遞過(guò)來(lái)的路徑晕换,那么它需要在AndroidManifest里做如下聲明:

        <activity android:name="com.fish.fileprovider.ReceiveActivity">
            <intent-filter>
                <action android:name="android.intent.action.VIEW"></action>
                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="android.intent.category.BROWSABLE"/>
                <data android:scheme="content"/>
                <data android:scheme="file"/>
                <data android:scheme="http"/>
                <data android:mimeType="text/*"></data>
            </intent-filter>
        </activity>

android.intent.action.VIEW 表示接收別的應(yīng)用打開(kāi)文件的請(qǐng)求午乓。
android:mimeType 表示其具有打開(kāi)某種文件的能力,text/* 表示只接收文本類型的打開(kāi)請(qǐng)求届巩。
當(dāng)聲明了上述內(nèi)容后硅瞧,該應(yīng)用就會(huì)出現(xiàn)在系統(tǒng)的選擇彈框里,當(dāng)用戶點(diǎn)擊彈框里的該應(yīng)用時(shí)恕汇,ReceiveActivity 將會(huì)被調(diào)用腕唧。我們知道,傳遞過(guò)來(lái)的Uri被包裝在Intent里瘾英,因此ReceiveActivity 需要處理Intent枣接。

    private void handleIntent() {
        Intent intent = getIntent();
        if (intent != null) {
            if (intent.getAction().equals(Intent.ACTION_VIEW)) {
                //從Intent里獲取uri
                uri = intent.getData();
                String content = handleUri(uri);
                if (!TextUtils.isEmpty(content)) {
                    tvContent.setText("打開(kāi)文件內(nèi)容:" + content);
                }
            }
        }
    }

    private String handleUri(Uri uri) {
        if (uri == null)
            return null;

        String scheme = uri.getScheme();
        if (!TextUtils.isEmpty(scheme)) {
            if (scheme.equals("content")) {
                try {
                    //從uri構(gòu)造流
                    InputStream inputStream = getContentResolver().openInputStream(uri);
                    try {
                        //有流之后即可讀取內(nèi)容
                        byte[] content = new byte[inputStream.available()];
                        inputStream.read(content);
                        return new String(content);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

從Intent里拿到Uri,再通過(guò)Uri構(gòu)造輸入流缺谴,最終從輸入流里讀取文件內(nèi)容但惶。
至此,應(yīng)用A通過(guò)FileProvider可將其能夠訪問(wèn)的任意路徑的文件傳遞給應(yīng)用B,應(yīng)用B能夠讀取文件并展示膀曾。
看到這里县爬,你可能已經(jīng)發(fā)現(xiàn)了:還沒(méi)有解決第二個(gè)問(wèn)題呢:發(fā)送方傳遞的文件路徑接收方可能沒(méi)有讀取權(quán)限,導(dǎo)致接收異常添谊。
這就需要從getContentResolver().openInputStream(uri)說(shuō)起:

    #ContentResolver.java
    public final @Nullable InputStream openInputStream(@NonNull Uri uri)
            throws FileNotFoundException {
        Preconditions.checkNotNull(uri, "uri");
        String scheme = uri.getScheme();
        if (SCHEME_ANDROID_RESOURCE.equals(scheme)) {
            ...
        } else if (SCHEME_FILE.equals(scheme)) {
            //file開(kāi)頭
        } else {
            //content開(kāi)頭 走這
            AssetFileDescriptor fd = openAssetFileDescriptor(uri, "r", null);
            try {
                //從文件描述符獲取輸入流
                return fd != null ? fd.createInputStream() : null;
            } catch (IOException e) {
                throw new FileNotFoundException("Unable to create stream");
            }
        }
    }

    public final @Nullable AssetFileDescriptor openAssetFileDescriptor(@NonNull Uri uri,
            @NonNull String mode, @Nullable CancellationSignal cancellationSignal)
                    throws FileNotFoundException {
        ...

        //根據(jù)scheme 區(qū)分不同的協(xié)議
        String scheme = uri.getScheme();
        if (SCHEME_ANDROID_RESOURCE.equals(scheme)) {
            //資源文件
        } else if (SCHEME_FILE.equals(scheme)) {
            //file 開(kāi)頭
        } else {
            //content 開(kāi)頭
            if ("r".equals(mode)) {
                return openTypedAssetFileDescriptor(uri, "*/*", null, cancellationSignal);
            } else {
                ...
            }
        }
    }

    public final @Nullable AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull Uri uri,
            @NonNull String mimeType, @Nullable Bundle opts,
            @Nullable CancellationSignal cancellationSignal) throws FileNotFoundException {

        ...
        //找到FileProvider IPC 調(diào)用
        IContentProvider unstableProvider = acquireUnstableProvider(uri);

        try {
            try {
                //IPC 調(diào)用财喳,返回文件描述符
                fd = unstableProvider.openTypedAssetFile(
                        mPackageName, uri, mimeType, opts, remoteCancellationSignal);
                if (fd == null) {
                    // The provider will be released by the finally{} clause
                    return null;
                }
            } catch (DeadObjectException e) {
                ...
            }
            ...
            //構(gòu)造AssetFileDescriptor
            return new AssetFileDescriptor(pfd, fd.getStartOffset(),
                    fd.getDeclaredLength());

        } catch (RemoteException e) {
            ...
        } 
    }

以上是應(yīng)用B的調(diào)用流程,最終拿到應(yīng)用A的FileProvider斩狱,拿到FileProvider 后即可進(jìn)行IPC調(diào)用耳高。

應(yīng)用B發(fā)起了IPC,來(lái)看看應(yīng)用A如何響應(yīng)這動(dòng)作的:

        #ContentProviderNative.java
      //Binder調(diào)用此方法
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
            throws RemoteException {
                case OPEN_TYPED_ASSET_FILE_TRANSACTION:
                {
                    ...
                    fd = openTypedAssetFile(callingPkg, url, mimeType, opts, signal);
                }
            }

        #ContentProvider.java
        @Override
        public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri uri, String mimeType,
                Bundle opts, ICancellationSignal cancellationSignal) throws FileNotFoundException {
                ...
            try {
                return mInterface.openTypedAssetFile(
                        uri, mimeType, opts, CancellationSignal.fromTransport(cancellationSignal));
            } catch (RemoteException e) {
                ...
            } finally {
                ...
            }
        }

        public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode)
            throws FileNotFoundException {
        ParcelFileDescriptor fd = openFile(uri, mode);
        return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
         }

可以看出所踊,最后調(diào)用了openFile()方法泌枪,而FileProvider重寫了該方法:

        #ParcelFileDescriptor.java
        @Override
        public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
            throws FileNotFoundException {
        //解析uri,從里面拿出對(duì)應(yīng)的路徑
        final File file = mStrategy.getFileForUri(uri);
        final int fileMode = modeToMode(mode);
        //構(gòu)造ParcelFileDescriptor
        return ParcelFileDescriptor.open(file, fileMode);
        }

ParcelFileDescriptor 持有FileDescriptor秕岛,可以跨進(jìn)程傳輸碌燕。
重點(diǎn)是mStrategy.getFileForUri(uri),如何通過(guò)Uri找到path瓣蛀,代碼很簡(jiǎn)單陆蟆,就不貼了,僅用圖展示惋增。

關(guān)于IPC與四大組件相關(guān)可移步以下文章:
Android 四大組件通信核心
Android IPC 之Binder基礎(chǔ)

Uri與Path互轉(zhuǎn)

Path 轉(zhuǎn)Uri
回到最初應(yīng)用A如何將path構(gòu)造為Uri:
應(yīng)用A在啟動(dòng)的時(shí)候叠殷,會(huì)掃描AndroidManifest.xml 里的FileProvider,并讀取映射表構(gòu)造為一個(gè)Map:

image.png

這個(gè)Map的Key 為映射表里的別名诈皿,而Value對(duì)應(yīng)需要替換的目錄林束。
還是以/storage/emulated/0/fish/myTxt.txt 為例:

當(dāng)調(diào)用MyFileProvider.getUriForFile(xx)時(shí),遍歷Map稽亏,找到最匹配條目壶冒,最匹配的即為external_file。因此會(huì)用external_file 代替/storage/emulated/0/fish/截歉,最終形成的Uri為:content://com.fish.fileprovider/external_file/myTxt.txt

Uri 轉(zhuǎn)Path
構(gòu)造了Uri傳遞給應(yīng)用B胖腾,應(yīng)用B又通過(guò)Uri構(gòu)造輸入流,構(gòu)造輸入流的過(guò)程由應(yīng)用A完成瘪松,因此A需要將Uri轉(zhuǎn)為Path:

A先將Uri分離出external_file/myTxt.txt咸作,然后通過(guò)external_file 從Map里找到對(duì)應(yīng)Value 為:/storage/emulated/0/fish/,最后將myTxt.txt拼接宵睦,形成的路徑為:
/storage/emulated/0/fish/myTxt.txt

可以看出记罚,Uri成功轉(zhuǎn)為了Path。

現(xiàn)在來(lái)梳理整個(gè)流程:

1壳嚎、應(yīng)用A使用FileProvider通過(guò)Map(映射表)將Path轉(zhuǎn)為Uri桐智,通過(guò)IPC 傳遞給應(yīng)用B末早。
2、應(yīng)用B使用Uri通過(guò)IPC獲取應(yīng)用A的FileProvider说庭。
3然磷、應(yīng)用A使用FileProvider通過(guò)映射表將Uri轉(zhuǎn)為Path,并構(gòu)造出文件描述符刊驴。
4样屠、應(yīng)用A將文件描述符返回給應(yīng)用B,應(yīng)用B就可以讀取應(yīng)用A發(fā)送的文件了缺脉。

image.png

由以上可知,不管應(yīng)用B是否有存儲(chǔ)權(quán)限悦穿,只要應(yīng)用A有權(quán)限就行攻礼,因?yàn)閷?duì)文件的訪問(wèn)都是通過(guò)應(yīng)用A完成的,這就回答了第二個(gè)問(wèn)題:發(fā)送方傳遞的文件路徑接收方可能沒(méi)有讀取權(quán)限栗柒,導(dǎo)致接收異常礁扮。

以上以打開(kāi)文件為例闡述了FileProvider的應(yīng)用,實(shí)際上分享文件也是類似的過(guò)程瞬沦。

當(dāng)然太伊,從上面可以看出FileProvider構(gòu)造需要好幾個(gè)步驟,還需要區(qū)分不同Android版本的差異逛钻,因此將這幾個(gè)步驟抽象為一個(gè)簡(jiǎn)單的庫(kù)僚焦,外部直接調(diào)用對(duì)應(yīng)的方法即可。
引入庫(kù)步驟:

1曙痘、project build.gradle 里加入:
allprojects {
    repositories {
        ...
        //庫(kù)是發(fā)布在jitpack上芳悲,因此需要指定位置
        maven { url 'https://jitpack.io' }
    }
}

2、在module build.gradle 里加入:
    dependencies {
    ...
    //引入EasyStorage庫(kù)
    implementation 'com.github.fishforest:EasyStorage:1.0.1'
}

3边坤、使用方式:
EasyFileProvider.fillIntent(this, new File(filePath), intent, true);

如上一行代碼搞定名扛。
效果如下:


gif.jj.gif

本文基于Android 10.0
演示代碼與庫(kù)源碼 若是有幫助,給github 點(diǎn)個(gè)贊唄~

您若喜歡茧痒,請(qǐng)點(diǎn)贊肮韧、關(guān)注,您的鼓勵(lì)是我前進(jìn)的動(dòng)力

持續(xù)更新中旺订,和我一起步步為營(yíng)系統(tǒng)弄企、深入學(xué)習(xí)Android/Java

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市耸峭,隨后出現(xiàn)的幾起案子桩蓉,更是在濱河造成了極大的恐慌,老刑警劉巖劳闹,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件院究,死亡現(xiàn)場(chǎng)離奇詭異洽瞬,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)业汰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門伙窃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人样漆,你說(shuō)我怎么就攤上這事为障。” “怎么了放祟?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵鳍怨,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我跪妥,道長(zhǎng)鞋喇,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任眉撵,我火速辦了婚禮侦香,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘纽疟。我一直安慰自己罐韩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布污朽。 她就那樣靜靜地躺著散吵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蟆肆。 梳的紋絲不亂的頭發(fā)上错蝴,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音颓芭,去河邊找鬼顷锰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛亡问,可吹牛的內(nèi)容都是我干的官紫。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼州藕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼束世!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起床玻,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤毁涉,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后锈死,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體贫堰,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡穆壕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了其屏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片喇勋。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖偎行,靈堂內(nèi)的尸體忽然破棺而出川背,到底是詐尸還是另有隱情,我是刑警寧澤蛤袒,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布熄云,位于F島的核電站,受9級(jí)特大地震影響妙真,放射性物質(zhì)發(fā)生泄漏皱碘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一隐孽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧健蕊,春花似錦菱阵、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至嫡锌,卻和暖如春虑稼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背势木。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工蛛倦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人啦桌。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓溯壶,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親甫男。 傳聞我的和親對(duì)象是個(gè)殘疾皇子且改,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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