WebView调用原生相册和拍照

关于uri部分未适配7.0,需要的可以添加

图片工具类
public class ImageUtil {

private static final String TAG ="ImageUtil";

/**
 * go for Album.    相册
 */
public static final Intent choosePicture() {
    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    intent.setType("image/*");
    return Intent.createChooser(intent, null);
}

/**
 * go for camera.   相机
 */
public static final Intent takeBigPicture() {
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, newPictureUri(getNewPhotoPath()));
    return intent;
}

public static final String getDirPath() {
    return Environment.getExternalStorageDirectory().getPath() + "/UploadImage";
}

private static final String getNewPhotoPath() {
    return getDirPath() + "/" + System.currentTimeMillis() + ".jpg";
}

public static final String retrievePath(Context context, Intent sourceIntent, Intent dataIntent) {
    String picPath = null;
    try {
        Uri uri;
        if (dataIntent != null) {
            uri = dataIntent.getData();
            if (uri != null) {
                picPath = ContentUtil.getPath(context, uri);
            }
            if (isFileExists(picPath)) {
                return picPath;
            }

            Log.w(TAG, String.format("retrievePath failed from dataIntent:%s, extras:%s", dataIntent, dataIntent.getExtras()));
        }

        if (sourceIntent != null) {
            uri = sourceIntent.getParcelableExtra(MediaStore.EXTRA_OUTPUT);
            if (uri != null) {
                String scheme = uri.getScheme();
                if (scheme != null && scheme.startsWith("file")) {
                    picPath = uri.getPath();
                }
            }
            if (!TextUtils.isEmpty(picPath)) {
                File file = new File(picPath);
                if (!file.exists() || !file.isFile()) {
                    Log.w(TAG, String.format("retrievePath file not found from sourceIntent path:%s", picPath));
                }
            }
        }
        return picPath;
    } finally {
        Log.d(TAG, "retrievePath(" + sourceIntent + "," + dataIntent + ") ret: " + picPath);
    }
}

private static final Uri newPictureUri(String path) {
    return Uri.fromFile(new File(path));
}

private static final boolean isFileExists(String path) {
    if (TextUtils.isEmpty(path)) {
        return false;
    }
    File f = new File(path);
    if (!f.exists()) {
        return false;
    }
    return true;
}

}

第二个工具类
public class ContentUtil {

@SuppressLint("NewApi")
public static final String getPath(final Context context, final Uri uri) {

    final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

    // DocumentProvider
    if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
        // ExternalStorageProvider
        if (isExternalStorageDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            if ("primary".equalsIgnoreCase(type)) {
                return String.format("%s/%s", Environment.getExternalStorageDirectory().getPath(), split[1]);
            }

            // TODO handle non-primary volumes
        }
        // DownloadsProvider
        else if (isDownloadsDocument(uri)) {

            final String id = DocumentsContract.getDocumentId(uri);
            final Uri contentUri = ContentUris.withAppendedId(
                    Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

            return getDataColumn(context, contentUri, null, null);
        }
        // MediaProvider
        else if (isMediaDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            Uri contentUri = null;
            if ("image".equals(type)) {
                contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            } else if ("video".equals(type)) {
                contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
            } else if ("audio".equals(type)) {
                contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
            }

            final String selection = "_id=?";
            final String[] selectionArgs = new String[]{split[1]};

            return getDataColumn(context, contentUri, selection, selectionArgs);
        }
    }
    // MediaStore (and general)
    else if ("content".equalsIgnoreCase(uri.getScheme())) {

        // Return the remote address
        if (isGooglePhotosUri(uri)) {
            return uri.getLastPathSegment();
        }

        return getDataColumn(context, uri, null, null);
    }
    // File
    else if ("file".equalsIgnoreCase(uri.getScheme())) {
        return uri.getPath();
    }

    return null;
}

/**
 * Get the value of the data column for this Uri. This is useful for
 * MediaStore Uris, and other file-based ContentProviders.
 *
 * @param context       The context.
 * @param uri           The Uri to query.
 * @param selection     (Optional) Filter used in the query.
 * @param selectionArgs (Optional) Selection arguments used in the query.
 * @return The value of the _data column, which is typically a file path.
 */
public static final String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {

    Cursor cursor = null;

    try {
        cursor = context.getContentResolver().query(uri, new String[]{MediaStore.Images.Media.DATA}
                , selection, selectionArgs, null);
        if (cursor != null && cursor.moveToFirst()) {
            final int index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
            return cursor.getString(index);
        }
    } finally {
        if (cursor != null) {
            cursor.close();
        }
    }
    return null;
}


/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is ExternalStorageProvider.
 */
public static final boolean isExternalStorageDocument(Uri uri) {
    return "com.android.externalstorage.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is DownloadsProvider.
 */
public static final boolean isDownloadsDocument(Uri uri) {
    return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is MediaProvider.
 */
public static final boolean isMediaDocument(Uri uri) {
    return "com.android.providers.media.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is Google Photos.
 */
public static final boolean isGooglePhotosUri(Uri uri) {
    return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}

}

权限处理工具类 这个可以使用其他的
/**
 * 权限管理工具 (针对Android 6.0 系统)
 * Created by AlexTam on 2016/10/14.
 */
public class PermissionUtil {
    private static PermissionUtil permissionUtil = null;
    private static final String PERMISSIONS_CAMERA = Manifest.permission.CAMERA;
    private static final String PERMISSIONS_WRITE_STORAGE = Manifest.permission.WRITE_EXTERNAL_STORAGE;
    private static final String PERMISSIONS_READ_STORAGE = Manifest.permission.READ_EXTERNAL_STORAGE;
    private static final String PERMISSIONS_PHONE = Manifest.permission.READ_PHONE_STATE;
    private static final String PERMISSIONS_ACCOUNTS = Manifest.permission.GET_ACCOUNTS;
    private static final String PERMISSIONS_LOCATION = Manifest.permission.ACCESS_FINE_LOCATION;
    private static final String PERMISSIONS_AUDIO = Manifest.permission.RECORD_AUDIO;


    public static final boolean isOverMarshmallow() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
    }


    /**
     * @param activity
     * @param permissionName such as Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE.
     * @return
     */
    public static final boolean isPermissionValid(Activity activity, String permissionName) {
        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                int checkCallPhonePermission = ContextCompat.checkSelfPermission(activity, permissionName);
                if (checkCallPhonePermission == PackageManager.PERMISSION_GRANTED) {
                    return true;
                } else {
                    return false;
                }
            } else {
                return true;
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        return false;
    }


    /**
     * to find the permissions which were denied in this device.
     */
    @TargetApi(value = Build.VERSION_CODES.M)
    public static final List<String> findDeniedPermissions(Activity activity, List<String> permissions) {
        if (permissions == null || permissions.size() == 0) {
            return null;
        } else {
            List<String> denyPermissions = new ArrayList<>();

            for (String value : permissions) {
                try {
                    if (activity.checkSelfPermission(value) != PackageManager.PERMISSION_GRANTED) {
                        denyPermissions.add(value);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            return denyPermissions;
        }
    }

    /**
     * request Permissions.
     *
     * @param activity
     * @param requestCode
     * @param mListPermissions
     */
    @TargetApi(value = Build.VERSION_CODES.M)
    public static final void requestPermissions(Activity activity, int requestCode, List<String> mListPermissions) {
        if (mListPermissions == null || mListPermissions.size() == 0) {
            return;
        }

        if (!isOverMarshmallow()) {
            // should not be invoked when it is below Android 6.0.
            return;

        } else {
            List<String> deniedPermissionList = findDeniedPermissions(activity, mListPermissions);

            if (deniedPermissionList != null && deniedPermissionList.size() > 0) {
                activity.requestPermissions(deniedPermissionList.toArray(new String[deniedPermissionList.size()]),
                        requestCode);

            }
        }

    }
}
自定义WebChomeClient
public class MyWebChomeClient extends WebChromeClient {

    private OpenFileChooserCallBack mOpenFileChooserCallBack;

    public MyWebChomeClient(OpenFileChooserCallBack openFileChooserCallBack) {
        mOpenFileChooserCallBack = openFileChooserCallBack;
    }

    /**
     * 4.4X以下回调             android 3.0以上,android4.0以下:
     * @param uploadMsg
     * @param acceptType
     */
    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
        mOpenFileChooserCallBack.openFileChooserCallBack(uploadMsg, acceptType);
    }

    public void openFileChooser(ValueCallback<Uri> uploadMsg) {
        openFileChooser(uploadMsg, "");
    }
    //android 4.0 - android 4.3  && android4.4.4
    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
        openFileChooser(uploadMsg, acceptType);
    }

    /**
     *  该方法的作用:告诉当前app 打开一个文件选择器 如相册,启动拍照或打开本地文件管理器
     *  webView加载包含上传文件的表单按钮,html定义了input标签,同同时input的type为file,手指点击该按钮
     *  回调onShowFileChooser方法,在这个重写的方法里打开相册 启动相机 或打开本地文件管理器,甚至做其他逻辑操作
     *  点击一次回调一次的前提是请求被取消,而取消请求回调的方法:给ValueCallBack接口的onReceiveValue抽象方法传入null
     *  同时onShowFileChooser方法返回true
     *
     *  4.4X以上回调onShowFileChooser(替代)4.4X以下回调openFileChooser(隐藏);只重写某一个会造成有的系统点击没有反应
     *  区别:
     *      1:前者ValueCallback接口回传一个Uri数组,后者回传一个Uri对象,在onActivityResult回调方法中调用
     *        ValueCallback接口方法onReceiveValue传入参数特别注意
     *          for android 5.0+ 回调onShowFileChooser方法,onReceiveVlue传入Uri对象数组
     *          for android 5.0- 回调openFileChooser方法,onReceiveVlue传入Uri对象
     *
     *      2:前者将后者的 acceptType、capture封装成FileChooserParams抽象类
     */
    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
                                     FileChooserParams fileChooserParams) {
        return mOpenFileChooserCallBack.openFileChooserCallBackAndroid5(webView, filePathCallback, fileChooserParams);
    }

    public interface OpenFileChooserCallBack {
        // for API - Version below 5.0.
        void openFileChooserCallBack(ValueCallback<Uri> uploadMsg, String acceptType);

        // for API - Version above 5.0 (contais 5.0).
        boolean openFileChooserCallBackAndroid5(WebView webView, ValueCallback<Uri[]> filePathCallback,
                                                FileChooserParams fileChooserParams);
    }    

将以上四个工具类放在同一包下,使用方法示例:

成员变量:

private WebView mWebView;
private static final int REQUEST_CODE_PICK_IMAGE = 0;
private static final int REQUEST_CODE_IMAGE_CAPTURE = 1;

private Intent mSourceIntent;
private ValueCallback<Uri> mUploadMsg;
public ValueCallback<Uri[]> mUploadMsgForAndroid5;

// permission Code
private static final int P_CODE_PERMISSIONS = 101;

onCreate()方法中:

   mWebView.getSettings().setAllowContentAccess(true);
    mWebView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);     //设置js可以直接打开窗口,如window.open(),默认为false
    mWebView.getSettings().setJavaScriptEnabled(true);     //是否允许执行js,默认为false。设置true时,会提醒可能造成XSS漏洞
    mWebView.getSettings().setSupportZoom(true);           //是否可以缩放,默认true
    mWebView.getSettings().setAllowFileAccess(true);       // 设置允许访问文件数据
    mWebView.getSettings().setBuiltInZoomControls(false);   //是否显示缩放按钮,默认false
    mWebView.getSettings().setUseWideViewPort(true);       //设置此属性,可任意比例缩放。大视图模式
    mWebView.getSettings().setLoadWithOverviewMode(true);  //和setUseWideViewPort(true)一起解决网页自适应问题
    mWebView.getSettings().setAppCacheEnabled(true);       //是否使用缓存
    mWebView.getSettings().setDomStorageEnabled(true);     //DOM Storage


    mWebView.setWebViewClient(new android.webkit.WebViewClient() {
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            view.loadUrl(url);
            return true;
        }
    });
    mWebView.addJavascriptInterface(new JsInterface(mActivity), "PartnerHome");
    mWebView.setWebChromeClient(new MyWebChomeClient(this));


    mWebView.setWebViewClient(new WebViewClient() {

        @Override
        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
            handler.proceed();
        }

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {//用于打开支付宝
            /*view.loadUrl(url);
            return true;*/
            if (url.startsWith("http:") || url.startsWith("https:")) {
                return false;
            }
            try {
                Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                startActivity(intent);
            } catch (Exception e) {
            }
            return true;
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                CookieSyncManager.getInstance().sync();
            } else {
                CookieManager.getInstance().flush();
            }
        }
    });

    fixDirPath();
    mWebView.loadUrl(url);

其他方法

 @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (resultCode != Activity.RESULT_OK) {         //==Activity.RESULT_CANCELED
        if (mUploadMsg != null) {
            mUploadMsg.onReceiveValue(null);
        }

        if (mUploadMsgForAndroid5 != null) {                     // for android 5.0+
            mUploadMsgForAndroid5.onReceiveValue(null);
        }
        return;
    }
    switch (requestCode) {
        case REQUEST_CODE_IMAGE_CAPTURE:
        case REQUEST_CODE_PICK_IMAGE: {
            try {
                //for android 5.0- 回调openFileChooser方法,onReceiveVlue传入Uri对象
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                    if (mUploadMsg == null) {
                        return;
                    }

                    String sourcePath = ImageUtil.retrievePath(mActivity, mSourceIntent, data);

                    if (TextUtils.isEmpty(sourcePath) || !new File(sourcePath).exists()) {
                        Log.e(TAG, "sourcePath empty or not exists.");
                        break;
                    }
                    Uri uri = null;
                    uri = Uri.fromFile(new File(sourcePath));
                    mUploadMsg.onReceiveValue(uri);

                } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    if (mUploadMsgForAndroid5 == null) {        // for android 5.0+
                        return;
                    }

                    String sourcePath = ImageUtil.retrievePath(mActivity, mSourceIntent, data);

                    if (TextUtils.isEmpty(sourcePath) || !new File(sourcePath).exists()) {
                        Log.e(TAG, "sourcePath empty or not exists.");
                        break;
                    }
                    Uri uri = null;
                    try {
                        uri = Uri.fromFile(new File(sourcePath));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    //for android 5.0+ 回调onShowFileChooser方法,onReceiveVlue传入Uri对象数组
                    mUploadMsgForAndroid5.onReceiveValue(new Uri[]{uri});
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            break;
        }
    }
}

/**
 * 选择完文件之后调用
 *
 * @param uploadMsg
 * @param acceptType
 */
@Override
public void openFileChooserCallBack(ValueCallback<Uri> uploadMsg, String acceptType) {
    mUploadMsg = uploadMsg;
    showOptions();
}

/**
 * 选择完文件之后调用    5.0以上
 */
@Override
public boolean openFileChooserCallBackAndroid5(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
    mUploadMsgForAndroid5 = filePathCallback;       //选中的图片uri数组
    showOptions();
    return true;
}

public void showOptions() {

    AlertDialog.Builder alertDialog = new AlertDialog.Builder(mActivity);
    alertDialog.setOnCancelListener(new DialogOnCancelListener());

    alertDialog.setTitle("请选择操作");
    // gallery, camera.
    String[] options = {"相册", "拍照"};

    alertDialog.setItems(options, new DialogInterface.OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {

                    if (which == 0) {

//点击了相册
                        if (PermissionUtil.isOverMarshmallow()) {

                            if (!PermissionUtil.isPermissionValid(mActivity, Manifest.permission.READ_EXTERNAL_STORAGE)) {

                                ToastUtils.showToast("请去\"设置\"中开启本应用的图片媒体访问权限");
                                restoreUploadMsg();
                                requestPermissionsAndroidM();
                                return;
                            }
                        }

                        try {
                            mSourceIntent = ImageUtil.choosePicture();
                            startActivityForResult(mSourceIntent, REQUEST_CODE_PICK_IMAGE);
                        } catch (Exception e) {
                            e.printStackTrace();

                            ToastUtils.showToast("请去\"设置\"中开启本应用的图片媒体访问权限");
                            restoreUploadMsg();
                        }

                    } else {
//点击了拍照
                        if (PermissionUtil.isOverMarshmallow()) {

                            if (!PermissionUtil.isPermissionValid(mActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {

                                ToastUtils.showToast("请去\"设置\"中开启本应用的图片媒体访问权限");
                                restoreUploadMsg();
                                requestPermissionsAndroidM();
                                return;
                            }

                            if (!PermissionUtil.isPermissionValid(mActivity, Manifest.permission.CAMERA)) {

                                ToastUtils.showToast("请去\"设置\"中开启本应用的相机权限");
                                restoreUploadMsg();
                                requestPermissionsAndroidM();
                                return;
                            }
                        }

                        try {
                            mSourceIntent = ImageUtil.takeBigPicture();
                            startActivityForResult(mSourceIntent, REQUEST_CODE_IMAGE_CAPTURE);

                        } catch (Exception e) {
                            e.printStackTrace();
                            ToastUtils.showToast("请去\"设置\"中开启本应用的相机和图片媒体访问权限");
                            restoreUploadMsg();
                        }


                    }
                }
            }
    );

    alertDialog.show();
}

private void fixDirPath() {
    String path = ImageUtil.getDirPath();
    File file = new File(path);
    if (!file.exists()) {
        file.mkdirs();
    }
}

private class DialogOnCancelListener implements DialogInterface.OnCancelListener {
    @Override
    public void onCancel(DialogInterface dialogInterface) {
        restoreUploadMsg();
    }
}

//重置  解决无法重复选择
private void restoreUploadMsg() {

    if (mUploadMsg != null) {
        mUploadMsg.onReceiveValue(null);
        mUploadMsg = null;

    } else if (mUploadMsgForAndroid5 != null) {
        mUploadMsgForAndroid5.onReceiveValue(null);
        mUploadMsgForAndroid5 = null;
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    switch (requestCode) {
        case P_CODE_PERMISSIONS:
            requestResult(permissions, grantResults);
            restoreUploadMsg();
            break;

        default:
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

private void requestPermissionsAndroidM() {             //请求权限

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

        List<String> needPermissionList = new ArrayList<>();
        needPermissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
        needPermissionList.add(Manifest.permission.READ_EXTERNAL_STORAGE);
        needPermissionList.add(Manifest.permission.CAMERA);

        PermissionUtil.requestPermissions(mActivity, P_CODE_PERMISSIONS, needPermissionList);

    } else {
        return;
    }
}

public void requestResult(String[] permissions, int[] grantResults) {
    ArrayList<String> needPermissions = new ArrayList<String>();

    for (int i = 0; i < grantResults.length; i++) {
        if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
            if (PermissionUtil.isOverMarshmallow()) {

                needPermissions.add(permissions[i]);
            }
        }
    }

    if (needPermissions.size() > 0) {
        StringBuilder permissionsMsg = new StringBuilder();

        for (int i = 0; i < needPermissions.size(); i++) {
            String strPermissons = needPermissions.get(i);

            if (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(strPermissons)) {
                permissionsMsg.append("," + getString(R.string.permission_storage));

            } else if (Manifest.permission.READ_EXTERNAL_STORAGE.equals(strPermissons)) {
                permissionsMsg.append("," + getString(R.string.permission_storage));

            } else if (Manifest.permission.CAMERA.equals(strPermissons)) {
                permissionsMsg.append("," + getString(R.string.permission_camera));
            }
        }

        String strMessage = "请允许使用\"" + permissionsMsg.substring(1).toString() + "\"权限, 以正常使用APP的所有功能.";

    } 
}

最后,如果打包发布时进行了混淆,应避免将这些类混淆,主要是集成WebChromeClient的那个类,加上这句话:

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