Build Apps with Multidata

創建實現用戶期望行為的富媒體應用

Capturing Photos

Taking Photos Simply

Taking Photos Simply

使用已有的拍照程序拍照.

Request Camera Permission

如果你的app必須有攝像硬件支持,則需要在Google Play上限制其安裝渠道.

<manifest ... >
    <uses-feature android:name="android.hardware.camera"
                  android:required="true" />
</manifest>

如果你的app對于攝像硬件是可選的,則需要在運行時的時候檢測系統是否有攝像硬件:hasSystemFeature(PackageManager.FEATURE_CAMERA)

Take a Photo with the Camera App

Android請求其他app來完成一個action通常會使用Intent.

static final int REQUEST_IMAGE_CAPTURE = 1;

private void dispatchTakePictureIntent() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);    
    }
}

Get the Thumbnail

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
        Bundle extras = data.getExtras();
        Bitmap imageBitmap = (Bitmap) extra.get("data");
        mImageView.setImageBitmap(imageBitmap);
    }    
}

Save the Full-size Photo

如果提供一個具體的文件對象,Android拍照軟件會保存未壓縮的照片. 你必須提供一個完全限定的文件名稱.

通常,用戶拍攝的任何照片都應該存儲在外部公共存儲區域,以便所有app進行訪問.
getExternalStorageDirectory()+DIRECTORY_PICTURES: 共享照片的適宜存儲位置.

權限請求:<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

然而,如果你只想要照片可被你個人的app訪問,你需要改變存儲區域:getExternalFilesDir().
在Android 4.3及以下,將照片寫入該文件位置需要WRITE_EXTERNAL_STORAGE權限.
從Android 4.4開始,不再需要該權限是因為該文件位置不能再被其它app訪問,所以你可以聲明讀權限的最高sdk版本: <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" />

你存儲在getExternalFilesDir()或者getFilesDir()的文件會在用戶卸載你的app以后一同被刪除。

一旦你決定了存儲文件的位置,你需要創建沒有沖突的文件名稱。以下為一個簡單的小例子,演示如何生成獨一無二的文件名稱.

String mCurrentPhotoPath;

private File createImageFile() throws IOException {
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    String imageFileName = "JPEG_" + timeStamp + "_";
    File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
    File image = File.createTempFile(imageFileName, ".jpg", storageDir);

    mCurrentPhotoPath = image.getAbsolutePath();
    return image;
}

通過結合該方法,你可以通過Intent來創建一個圖片文件:

static final int REQUEST_TAKE_PHOTO = 1;

private void dispatchPictureIntent() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        File photoFile = null;
        try {
            photoFile = createImageFile();    
        } catch (IOException ex) {}
       if (photoFile != null) {            
            Uri photoURI = FileProvider.getUriForFile(this, 
                    "com.example.android.fileprovider", photoFile);
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
            startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);    
        }
    }
}

getUriForFile(Context, String, File) => content://URI.在Android 7.0及以上的版本,使用file://URI將會拋出FileUriExposedException.因此,我們現在更常用FileProvider來生成圖片的URI.

配置FileProvider => 在配置文件中添加一個provider

<application>
    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.example.android.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.name.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths">
        </meta-data>
    <provider>
</application>

確保android:authoritiesgetUriForFile(Context, String, File)第二個參數匹配.
在provider定義的meta-data部分,你可以看到provider提供了一個xml文件來配置合格的路徑:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="my_images" path="Android/data/com.example.package.name/files/Pictures" />
</paths>

當調用帶有Environment.DIRECTORY_PICTURESgetExternalFilesDir()時,路徑組件會返回定義好的路徑.

Add the Photo to a Gallery

當你通過intent創建了一張照片時,你應該知道圖片的存儲位置. 對于其他的人來說,訪問你創建的照片最簡單的方式就是使其可被系統的Media Provider訪問.

如果你將圖片存儲在了getExternalFilesDir(),media scanner不能訪問該圖片,因為其只對你的app可見.

private void galleryAddPic() {
    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    File f = new File(mCurrentPhotoPath);
    Uri contentUri = Uri.fromFile(f);
    mediaScanIntent.setData(contentUri);
    this.sendBroadcast(mediaScanIntent);
}

Decode a Scaled Image

在低內存下管理多個未壓縮圖片會很復雜. 如果你發現應用在展示了很少的圖片就內存溢出,你可以通過將JPEG擴展到已經縮放到與目標視圖大小匹配的內存數組,大大減少使用的動態堆的數量.

private void setPic() {
    int targetW = mImageView.getWidth();
    int targetH = mImageView.getHeight();

    BitmapFactory.Options bmOptions = new BitmapFactory.Options();
    bmOptions.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
    int photoW = bmOptions.outWidth;
    int photoH = bmOptions.outHeight;

    int scaleFactor = Math.min(photoW/targetW, photoH/targetH);

    bmOptions.inJustDecodeBounds = false;
    bmOptions.isSampleSize = scaleFactor;
    bmOptions.inPurgeable = true;

    Bitmap bitmap = BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
    mImageView.setImageBitmap(bitmap);
}

Recording Videos Simply

使用已有的相機應用錄制音頻.

Request Camera Permission

在 manifest 文件中使用<uses-feature>標簽.

<manifest ... >
    <uses-feature android:name="android.hardware.camera"
                  android:required="true" />
</manifest>

Record a Video with a Camera App

static final int REQUEST_VIDEO_CAPTURE = 1;

private void dispatchTakeVideoIntent() {
    Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
    if (takeVideoIntent.resolveActivity(getPackageManager()) != null) {
        startActivityForResult(takeVideoIntent, REQUEST_VIDEO_CAPTURE);    
    }
}

View the Video

onActivityResult()中Android相機應用將會返回視頻的Uri.

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    if (requestCode == REQUEST_VIDEO_CAPTURE && resultCode == RESULT_OK) {
        Uri videoUri = intent.getData();
        mVideoView.setVideoURI(videoUri);
    }    
}

Controlling the Camera

Open the Camera Object

  1. 獲取Camera對象的一個實例,推薦在onCreate()時創建.
  2. onResume()方法中打開camera

如果在調用Camera.open()時相機正在被使用,會拋出一個異常.

Private boolean safeCameraOpen(int id) {
    boolean qOpened = false;

    try {
        releaseCameraAndPreview();
        mCamera = Camera.open(id);
        qOpened = (mCamera != null);
    } catch (Exception e) {
        Log.e(getString(R.string.app_name), "failed to open Camera");
        e.printStackTrace();
    }
    return qOpened;
}

private void releaseCameraAndPreview() {
    mPreview.setCamera(null);
    if (mCamera != null) {
        mCamera.release();
        mCamera = null;
    }
}

在API 9 及以上,camera framework 開始支持多個相機. 如果你調用open()不攜帶任何參數,你將會獲取到后置攝像頭的Camera對象.

Create the Camera Preview

使用SurfaceView顯示相機傳感器檢測到的圖像.

Preview Class

實現android.view.SurfaceHolder.Callback 接口—— 接收相機硬件的圖片數據到app上.

class Preview extends ViewGroup implements SurfaceHolder.Callback {
    
    SurfaceView mSurfaceView;
    SufaceHolder mHolder;

    Preview(Context context) {
        super(context);
        
        mSurfaceView = new SurfaceView(context);
        addView(mSurfaceView);

        mHolder = mSurfaceView.getHolder();
        mHolder.addCallback(this);
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }
}

Set and Start the Preview

Camera實例和其相關的界面展示必須有確切的創建時序,先創建Camera實例.

在下面的代碼段中,初始化相機的過程被封裝. 任何時間用戶做了一些改變相機狀態(setCamera())的操作,Camera.startPreview()都會被調用. 預覽也應該在surfaceChanged()中重啟.

public void setCamera(Camera camera) {
    if (mCamera == camera) { return; }

    stopPreviewAndFreeCamera();

    mCamera = camera;

    if (mCamera != null) {
        mCamera.setPreviewDisplay(mHolder);    
    } catch (IOException e) {
        e.printStackTrace();    
    }

    mCamera.startPreview();
}

Modify Camera Setting

Camera Settings 更改相機拍攝照片的方式,從縮放級別到曝光補償. 此示例僅更改預覽大小.

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    Camera.Parameters parameters = mCamera.getParameters();
    parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
    requestLayout();
    mCamera.setParameters(parameters);

    mCamera.startPreview();
}

Set the Preview Orientation

大多數相機應用將顯示鎖定為橫向模式,因為這是相機傳感器的自然方向. 此設置不會阻止你拍攝人像模式的照片,因為設備的方向記錄在EXIF標頭中.setCameraDisplayOrientation()允許你修改預覽的顯示方式,而不影響圖像的記錄方式. 但是在API 14以下,如果要改變方向,必須先停止預覽,然后再更改方向,再重新啟動.

Take a Picture

調用Camera.takePicture()拍攝照片在preview已經開始的前提下. 你可以創建Camera.PictureCallbackCamera.ShutterCallback,并將他們傳遞給Camera.takePicture().

如果你想延遲拍照,你可以創建Camera.PreviewCallback,實現onPreviewFrame(). 對于捕捉到的鏡頭數據,你可以僅捕獲所選的預覽幀,或設置一個延遲操作來調用takePicture().

Restart the Preview

@Override
public void onClick(View v) {
    switch(mPreviewState) {
        case K_STATE_FROZEN:
            mCamera.startPreview();
            mPreviewState = K_STATE_PREVIEW;
            break;

        default:
            mCamera.takePicture(null, rawCallback, null);
            mPreviewState = K_STATE_BUSY;
    }
    shutterBtnConfig();
}

Stop the Preview and Release the Camera

一旦你的app不再使用相機,就應該將其處理掉. 尤其需要釋放掉Camera對象,否則將可能造成其他app的crash,當然包括你自己的app.

你什么時候需要停止預覽釋放相機資源?當你的預覽窗口被摧毀的時候.

public void surfaceDestroyed(SurfaceHolder holder) {
    if (mCamera != null) {
        mCamera.stopPreview();
    }    
}

private void stopPreviewAndFreeCamera() {
    if (mCamera != null) {
        mCamera.stopPreview();
        mCamera.release();

        mCamera = null;
    }    
}

Printing Content

Android 4.4 以上提供了直接顯示圖片和文件的框架.

Photos

使用PrintHelper類來顯示一張圖片.

PrintHelper: 來自Android v4 support library

Print an Image

PrintHelper提供一種簡單的方式來顯示圖片. 該類只有一個顯示設置:setScaleMode(),有如下兩種選擇:

  • SCALE_MODE_FIT: 按照圖片的大小平鋪在界面上
  • SCALE_MODE_FILL: 默認值. 按照屏幕大小平鋪整個圖片.

以下代碼示例怎樣顯示圖片的全過程.

private void doPhotoPrint() {
    PrintHelper photoPrinter = new PrintHelper(getActivity());
    photoPrinter.setScaleMode(PrintHelper.SCALE_MODE_FIT);
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.droids);
    photoPrint.printBitmap("droids.jpg - test print", bitmap);
}

HTML Documents

在Android 4.4以上,WebView類做了更新,支持顯示HTML內容. 該類允許你加載本地HTML或者從web端下載一個頁面并顯示.

Load an HTML Document

創建本地輸出視圖的主要步驟如下:

  1. 在HTML資源家在以后創建一個WebViewClient對象
  2. 使用WebView對象加載HTML資源

示例.

private WebView mWebView;

private void doWebViewPrint() {
    WebView webView = new WebView(getActivity());
    webView.setWebViewContent(new WebViewClient() {
        public boolean shouldOverrideUriLoading(Webview webView, String url) {
            return false;    
        }    

        @Override
        public void onPageFinished(WebView view, String url) {
            Log.i(TAG, "page finished loading " + url);
            createWebPrintJob(view);
            mWebView = null;
        }
    });

    String htmlDocument = "<html><body><h1>Test Content</h1><p>Testing, testing, testing ...</p></body></html>";
    webView.loadDataWithBaseURL(null, htmlDocument, "text/HTML", "utf-8", null);
    mWebView = webView;
}

Note 一定要確認你在WebViewClient.onPageFinished()方法被調用以后再做資源輸出的工作.
Note 以上的示例代碼保存了對一個WebView對象實例的引用,所以在進行資源輸出之前不會對其進行垃圾回收. 你需要確保在自己的實現中也要使用同樣的思路來執行代碼,否則打印過程可能會失敗.

如果要在頁面中包含圖形,請將圖形文件放在項目的assets/目錄中,并在loadDataWithBaseURL()方法的第一個參數中指定基本URL.

webView.loadDataWithBaseURL("file://android_asset/images", htmlBody,
        "text/HTML", "utf-8", null);

你也可以通過調用loadUrl()加載網頁.

webView.loadUrl("http://developer.android.com/about/index.html");

當使用WebView來創建輸出文件時,有如下幾條注意事項:

  • 不能添加頭惑尾,包括頁碼等.
  • HTML 文檔的打印選項不包括打印頁面范圍的功能,例如:不支持打印10頁HTML文檔的第2頁到第4頁
  • 一個WebView一次只能支持一個輸出工作
  • 不支持包含CSS打印屬性的HTML文檔
  • 不能在HTML中使用 JavaScript

Note 包含在布局中的WebView對象的內容也可以在加載文檔后打印.

Create a Print Job

做完以上事情以后,終于要做最后一步工作了:訪問PrintManager,創建打印適配器,最后創建打印作業.

private void createWebPrintJob(WebView webView) {
    PrintManager printManager = (PrintManager) getActivity()
            .getSystemService(Context.PRINT_SERVICE);

            PrintDocumentAdapter printAdapter = webView.createPrintDocumentAdapter();

            String jobName = getString(R.string.app_name) + "Document";
            PrintJob printJob = printManager.print(jobName, printAdapter,
                    new PrintAttributes.Builder().build());

            mPrintJobs.add(printJob);
}   

Custom Documents

Connect to the Print Manager

當應用程序直接管理打印過程時,從用戶收到打印請求后的第一步是連接到Android打印框架并獲取PrintManager類的實例. 以下代碼示例顯示如何獲取打印管理器并開始打印過程.

private void doPrint() {
    PrintManager printManager = (PrintManager) getActivity().getSystemService(Context.PRINT_SERVICE);
    String jobName = getActivity().getString(R.string.app_name) + " Document";
    printManager.print(jobName, new MyPrintDocumentAdapter(getActivity(), null));
}

Note print() 最后一個參數需要是PrintAttribute對象. 你可以使用此參數向打印框架提供提示,并根據先前的打印周期提供預設選項,從而改善用戶體驗. 你還可以使用此參數設置更適合正在打印的內容的選項,例如在打印處于該方向的照片時將方向設置為橫向.

Create a Print Adapter

輸出適配器與Android輸出框架交互,處理輸出流程. 該過程需要用戶選擇打印機和打印選項. 在打印過程中,用戶可以選擇取消打印行為,所以你的打印適配器需要監聽和處理取消請求。

PrintDocumentAdapter 抽象類有4個主要的回調函數,被用來設計處理打印生命周期.

  • onStart(): 一次調用. 如果你的應用有任何一次性的準備工作,都可以寫在這里. 不是必須實現的方法.
  • onLayout(): 任何時間用戶的行為影響了輸出都會被調用.
  • onWrite(): 調用將打印的頁面轉換為要打印的文件. 在每次onLayout()被調用后都必須必須被調用至少一次.
  • onFinish(): 在打印任務結束時被調用一次.

Note 這些適配器的方法在主線程中被調用. 如果你期望在執行這些方法時消耗大量的時間,需要在非UI線程執行實現函數.

Compute print document info

在實現PrintDocumentAdapter的過程中,app必須能夠在打印作業中確認文件類型以及需要打印的頁數,和打印頁面的大小. 實現onLayout(),計算打印作業相關信息,將期望參數通過PrintDocumentInfo類傳輸.

@Override
public void onLayout(PrintAttributes oldAttributes,
                    PrintAttributes newAttributes,
                    CancellationSignal cancellationSignal,
                    Bundle metadata) {
    mPdfDocument = new PrintedPdfDocument(getActivity(), newAttributes);
    if (cancellationSignal.isCancelled()) {
        callback.onLayoutCancelled();
        return;
    }

    int pages = computPageCount(newAttributes);
    if (pages > 0) {
        PrintDocumentInfo info = new PrintDocumentInfo()
                    .Builder("print_output.pdf")
                    .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
                    .setPageCount(pages)
                    .build();
        callback.onLayoutFinished(info, true);
    } else {
        callback.onLayoutFailed("Page count calculation failed.");
    }
}

執行onLayout()方法會有三種可能的執行結果:完成、取消或者失敗. 你必須通過實現PrintDocumentAdapter.LayoutResultCallback 來處理執行結果.

Note onLayoutFinished() 方法的布爾參數指示自上次請求以來布局內容是否實際已更改. 正確設置此參數允許打印框架避免不必要地調用onWrite()方法,緩存先前寫入的打印文檔并提高性能.

以下的代碼示例展示如何根據打印方向計算頁面頁數:

private int computePageCount(PrintAttributes printAttributes) {
    int itemsPerPage = 4;

    MediaSize pageSize = printAttributes.getMediaSize();
    if (!pageSize.isPortrait()) {
        itemsPerPage = 6;    
    }

    int printItemCount = getPrintItemCount();

    return (int) Math.ceil(printItemCount / itemsPerPage);
}

Write a print document file

PrintDocumentAdapter.onWrite() 在要進行打印作業時調用.
onWrite(PageRange[] pageRanges, ParcelFileDescriptor destination, CancellationSignal cancellationSignal, WriteResultCallback callback)

當打印任務完成時,需要調用callback.onWriteFinished().

Note 每次onLayout()被調用以后,onWrite()都會被調用至少一次. 因此如果輸出內容沒有改變的話,需要設置onLayoutFinished()false,以避免不必要地重新打印.

Note onLayoutFinished()的boolean型參數指示輸出內容在上次調用時有沒有改變.

@Override
public void onWrite(final PageRange[] pageRanges,
                    final ParcelFileDescriptor destination,
                    final CancellationSignal cancellationSignal,
                    final WriteResultCallback callback) {
    for (int i = 0; i < totalPages; i ++) {
        if (containsPage(pageRanges, i)) {
            writtenPagesArray.append(writtenPagesArray.size(), i);
            PdfDocument.Page page = mPdfDocument.startPage(i);

            if (cancellationSignal.isCancelled()) {
                callback.onWriteCancelled();
                mPdfDocument.close();
                mPdfDocument = null;
                return;
            }

            drawPage(page);

            mPdfDocument.finishPage(page);
        }    
    }

    try {
        mPdfDocument.writeTo(new FileOutputStream(
                destination.getFileDescriptor()));
    } catch (IOException e) {
        callback.onWriteFailed(e.toString());
        return;
    } finally {
        mPdfDocument.close();
        mPdfDocument = null;
    }

    PageRange[] writtenPages = computeWrittenPages();
    callback.onWriteFinished(writtenPages);
    ...
}

PrintDocumentAdapter.WriteResultCallback 監聽onWrite() 結果.

Drawing PDF Page Content

你的app在打印時必須生成一個PDF文件,并將其傳輸給Android打印框架. 你可以使用PrintedPdfDocument來收入能夠承認那個PDF文件.

PrintedPdfDocument使用Canvas繪出PDF頁面.

private void drawPage(PdfDocument.Page page) {
    Canvas canvas = page.getCanvas();

    int titleBaseLine = 72;
    int leftMargin = 54;

    Paint paint = new Paint();
    paint.setColor(Color.BLACK);
    paint.setTextSize(36);
    canvas.drawText("Test Title", leftMargin, titleBaseLine, paint);

    paint.setTextSize(11);
    canvas.drawText("Test paragragh", leftMargin, titleBaseLine + 25, paint);

    paint.setColor(Color.BLUE);
    canvas.drawRect(100, 100, 172, 172, paint);
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,076評論 2 375

推薦閱讀更多精彩內容