zxing源码分析

基本使用

添加依赖
compile 'com.journeyapps:zxing-android-embedded:3.5.0'

扫描二维码:

new IntentIntegrator(this).initiateScan();

仅仅添加这一行就可以开启扫描二维码了,非常简单。

看看内部做了什么操作把,initiateScan()这个方法就一行代码,咱瞅瞅

startActivityForResult(createScanIntent(), REQUEST_CODE);

这个就很熟悉了啊,传入Intent和requestcode,开启一个可以但会结果的界面。在瞅瞅createScanIntent这个方法。

public Intent createScanIntent() {
        Intent intentScan = new Intent(activity, getCaptureActivity());
        intentScan.setAction(Intents.Scan.ACTION);

        // check which types of codes to scan for
        //检查要扫码的二维码类型
        if (desiredBarcodeFormats != null) {
            // set the desired barcode types
            StringBuilder joinedByComma = new StringBuilder();
            for (String format : desiredBarcodeFormats) {
                if (joinedByComma.length() > 0) {
                    joinedByComma.append(',');
                }
                joinedByComma.append(format);
            }
            intentScan.putExtra(Intents.Scan.FORMATS, joinedByComma.toString());
        }

        intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
        attachMoreExtras(intentScan);
        return intentScan;
    }

这个Intent需要传入一个activity,getCaptureActivity(),如果你设置了activity就是用你传递进来的activity,如果你没有设置就使用默认的activity,这个activity是CaptureActivity.class,那么在哪里设置自己的Activity呢。

new IntentIntegrator(this).setCaptureActivity(ToolbarCaptureActivity.class).initiateScan();    

在开启扫描的时候设置咱自己的Activity,就可以修改咱们自己想要的界面了。
接着上面的intent说,desiredBarcodeFormats直接检查支持二维码支的格式有那些。

public static final Collection<String> PRODUCT_CODE_TYPES = list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "RSS_14");
public static final Collection<String> ONE_D_CODE_TYPES =
            list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "CODE_39", "CODE_93", "CODE_128",
                    "ITF", "RSS_14", "RSS_EXPANDED");
public static final Collection<String> QR_CODE_TYPES = Collections.singleton("QR_CODE");
public static final Collection<String> DATA_MATRIX_TYPES = Collections.singleton("DATA_MATRIX");

把设置的参数存入的Intent中。见方法
**private void attachMoreExtras(Intent intent) **
这时候就已经开启了二维码扫描界面了。

看看可以设置那些参数

IntentIntegrator integrator = new IntentIntegrator(this);
integrator.setCaptureActivity(AnyOrientationCaptureActivity.class);
integrator.setDesiredBarcodeFormats(IntentIntegrator.ONE_D_CODE_TYPES);
integrator.setPrompt("Scan something");
integrator.setOrientationLocked(false);
integrator.setBeepEnabled(false);
integrator.setBarcodeImageEnabled(true)
integrator.initiateScan();
  • setCaptureActivity
    设置自定义的Activity
  • setDesiredBarcodeFormats
    设置要扫描的所需条形码格式。
  • setPrompt
    设置扫面界面的提示语
  • setOrientationLocked
  • setBeepEnabled
    设置扫描完成声音提示 false 表示没有提示音
  • setBarcodeImageEnabled
    表示二维码图片是否保存,true表示保存。
  • setCameraId
    设置前后摄像头滴
  • setOrientationLocked()
    是否旋转锁定
    如果你的androidManifest文件扫描界面的屏幕旋转方向改成
        <activity
            android:name="com.journeyapps.barcodescanner.CaptureActivity"
            android:screenOrientation="fullSensor"
            tools:replace="screenOrientation" />

然后

   integrator.setOrientationLocked(false);

结果就是:调整手机方向时,扫描布局也会重新布置

获取返回的结果

IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
        if(result != null) {
            if(result.getContents() == null) {
                Log.d("MainActivity", "Cancelled scan");
                Toast.makeText(this, "Cancelled", Toast.LENGTH_LONG).show();
            } else {
                Log.d("MainActivity", "Scanned");
                Toast.makeText(this, "Scanned: " + result.getContents(), Toast.LENGTH_LONG).show();
            }
        } else {
            // This is important, otherwise the result will not be passed to the fragment
            super.onActivityResult(requestCode, resultCode, data);
        }

来看看这个是怎么解析的:

IntentIntegrator.parseActivityResult(requestCode, resultCode, data);

上代码:

 public static IntentResult parseActivityResult(int requestCode, int resultCode, Intent intent) {
        if (requestCode == REQUEST_CODE) {
            if (resultCode == Activity.RESULT_OK) {
                String contents = intent.getStringExtra(Intents.Scan.RESULT);
                String formatName = intent.getStringExtra(Intents.Scan.RESULT_FORMAT);
                byte[] rawBytes = intent.getByteArrayExtra(Intents.Scan.RESULT_BYTES);
                int intentOrientation = intent.getIntExtra(Intents.Scan.RESULT_ORIENTATION, Integer.MIN_VALUE);
                Integer orientation = intentOrientation == Integer.MIN_VALUE ? null : intentOrientation;
                String errorCorrectionLevel = intent.getStringExtra(Intents.Scan.RESULT_ERROR_CORRECTION_LEVEL);
                String barcodeImagePath = intent.getStringExtra(Intents.Scan.RESULT_BARCODE_IMAGE_PATH);
                return new IntentResult(contents,
                        formatName,
                        rawBytes,
                        orientation,
                        errorCorrectionLevel,
                        barcodeImagePath);
            }
            return new IntentResult();
        }
        return null;
    }

其实就是获取Intent中的信息,二维码的信息结果就在Intent中,当然IntentResult中有许多信息。

public final class IntentResult {

    private final String contents;
    private final String formatName;
    private final byte[] rawBytes;
    private final Integer orientation;
    private final String errorCorrectionLevel;
    private final String barcodeImagePath;

    ......

}
  • contents
    二维码扫描的信息
  • formatName
    扫描类型
  • rawBytes
    条形码内容的原始字节
  • orientation
    图片的旋转度数
  • errorCorrectionLevel
    纠错级别
  • barcodeImagePath
    二维码图片路径(我的是在缓存中)
    /data/user/0/cn.projects.com.projectsdemo/cache/barcodeimage371491556.jpg

CaptureActivity

这个类加载自定义布局 :
xml文件

<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!--
    This Activity is typically full-screen. Therefore we can safely use centerCrop scaling with
    a SurfaceView, without fear of weird artifacts. -->
    <com.journeyapps.barcodescanner.DecoratedBarcodeView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/zxing_barcode_scanner"
        app:zxing_preview_scaling_strategy="centerCrop"
        app:zxing_use_texture_view="false"/>

</merge>

xml文件使用merge标签来减少布局的层次结果,不明白的自行度娘。

这里最重要的就是这个DecoratedBarcodeView自定义控件了,封装BarcodeView,ViewfinderView和状态文本。创建CaptureManger 管理CaptureActivity条形码扫描,从Intent初始化(通过IntentIntegrator)并开始解码capture.decode();(并不是真正意义上的解码)真正解码的事BarcodeView。

  • BarcodeView
    继承CameraPreview继承ViewGroup,用于扫描条形码的视图
  • ViewfinderView
    此view覆盖在相机预览的顶部。 它添加取景器矩形和部分透明度以外,以及激光扫描仪的动画和结果点(一条横线的那个玩意)
  • 状态文本
    扫描时的提示信息

如果想要自定义界面,有两种方法:

  • 创建一个类,复制CaptureActivity类的内容,修改initializeContent方法中的布局。
  • 继承CaptureActivity类重写initializeContent方法。

例如:

public class SmallCaptureActivity extends CaptureActivity {
    @Override
    protected DecoratedBarcodeView initializeContent() {
        setContentView(R.layout.capture_small);
        return (DecoratedBarcodeView)findViewById(R.id.zxing_barcode_scanner);
    }
}

xml文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical">
    <com.journeyapps.barcodescanner.DecoratedBarcodeView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/zxing_barcode_scanner"
        app:zxing_preview_scaling_strategy="centerCrop"
        app:zxing_scanner_layout = "@layout/custom_layout_zxing"
        app:zxing_framing_rect_height = "200dp"
        app:zxing_framing_rect_width = "200dp"
        app:zxing_use_texture_view="false"/>

</LinearLayout>

DecoratedBarcodeView

自定义的Fragment,封装BarcodeView,ViewfinderView和状态文本。要自定义UI,请直接使用BarcodeView和ViewfinderView。

  • initialize()
    从xml中加载自定义属性,定义BarcodeView,ViewfinderView和statusView。

private void initialize(AttributeSet attrs) {
// Get attributes set on view
TypedArray attributes = getContext().obtainStyledAttributes(attrs, R.styleable.zxing_view);

    int scannerLayout = attributes.getResourceId(
            R.styleable.zxing_view_zxing_scanner_layout, R.layout.zxing_barcode_scanner);

    attributes.recycle();

    inflate(getContext(), scannerLayout, this);

    barcodeView = (BarcodeView) findViewById(R.id.zxing_barcode_surface);

    if (barcodeView == null) {
        throw new IllegalArgumentException(
            "There is no a com.journeyapps.barcodescanner.BarcodeView on provided layout " +
            "with the id \"zxing_barcode_surface\".");
    }

    // Pass on any preview-related attributes
    barcodeView.initializeAttributes(attrs);


    viewFinder = (ViewfinderView) findViewById(R.id.zxing_viewfinder_view);

    if (viewFinder == null) {
        throw new IllegalArgumentException(
            "There is no a com.journeyapps.barcodescanner.ViewfinderView on provided layout " +
            "with the id \"zxing_viewfinder_view\".");
    }

    viewFinder.setCameraPreview(barcodeView);

    // statusView is optional
    statusView = (TextView) findViewById(R.id.zxing_status_view);
}

如果想要自定义BarcodeView,ViewfinderView。在自定义的
xml文件中
com.journeyapps.barcodescanner.DecoratedBarcodeView
加入一行
app:zxing_scanner_layout = "@layout/custom_layout_zxing"
来添加你自己的layout布局。
例如:
我自己定义的布局custom_layout_zxing

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <com.journeyapps.barcodescanner.BarcodeView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/zxing_barcode_surface"/>

    <cn.projects.com.projectsdemo.zxing.CustomViewFindView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/zxing_viewfinder_view"/>

    <TextView android:id="@+id/zxing_status_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="120dp"
        android:background="@color/zxing_transparent"
        android:text="@string/zxing_msg_default_status"
        android:textColor="@color/zxing_status_text"/>
</FrameLayout>

这里我自己定义了ViewFindView,简单的绘制扫描先了四个角。

  • initializeFromIntent(Intent intent)
    从Intent去除消息,初始化摄像机ID,解码格式和是否反转。

  • onKeyDown
    重写onKeyDown主要两个功能,1.按音量+开启闪光灯。2.按音量-关闭闪光灯。

  • decodeSingle(BarcodeCallback callback)
    解码单个的(就是一次)条码,然后停止解码。回调在住线程

  • decodeContinuous(BarcodeCallback callback)
    连续解码条形码。 相同的条形码可以每秒返回多次。回调在住线程

  • pause
    停止预览和解码

  • pauseAndWait
    暂停扫描和预览; 等待相机关闭。会阻塞住线程

  • WrappedCallback

private class WrappedCallback implements BarcodeCallback {
        private BarcodeCallback delegate;

        public WrappedCallback(BarcodeCallback delegate) {
            this.delegate = delegate;
        }

        @Override
        public void barcodeResult(BarcodeResult result) {
            delegate.barcodeResult(result);
        }

        @Override
        public void possibleResultPoints(List<ResultPoint> resultPoints) {
            for (ResultPoint point : resultPoints) {
                viewFinder.addPossibleResultPoint(point);
            }
            delegate.possibleResultPoints(resultPoints);
        }

WrappedCallback实现了BarcodeCallback,对解码的结果进行包装,实际上把结果传递到了BarcodeView的resultCallback中。这里只是对
possibleResultPoints进行处理,这个东西到底是啥呢,官方解释:

检测到结果点。 无论扫描是否成功,都可以进行调用。 这对于在扫描时向用户给出一些反馈主要是有用的。 不要依赖于在解码周期的任何特定点被调用。 @param resultPoints可能会识别条形码

这是啥意思呢,看看谁用到他了吧!

for (ResultPoint point : resultPoints) {
                viewFinder.addPossibleResultPoint(point);
            }

除了viewFinder没人使用它,看看viewFinder是怎么用的吧

if (!lastPossibleResultPoints.isEmpty()) {
                paint.setAlpha(CURRENT_POINT_OPACITY / 2);
                paint.setColor(resultPointColor);
                float radius = POINT_SIZE / 2.0f;
                for (final ResultPoint point : lastPossibleResultPoints) {
                    canvas.drawCircle(
                            frameLeft + (int) (point.getX() * scaleX),
                            frameTop + (int) (point.getY() * scaleY),
                            radius, paint
                    );
                }
                lastPossibleResultPoints.clear();

哦哦哦,原来激光线旁边的小点点就是可能的结果点啊!原来乳此!

CaptureManager

管理CaptureActivity条形码扫描。 这是针对专门用于捕获单个条形码并返回结果通过setResult()。以下由类程管理:

  • 方向锁
  • 静止计时器
  • BeepManager
  • 从Intent初始化(通过IntentIntegrator)
  • 设置结果并完成扫描条形码的Activity
  • 显示相机错误

initializeFromIntent(Intent intent, Bundle savedInstanceState)
根据Intent初始化参数

这里只主要是调用DecoratedBarcodeView的decodeSingle开始解码,

/**
     * Start decoding.
     */
    public void decode() {
        barcodeView.decodeSingle(callback);
    }

这个callback就是解码的结果的回调。

private BarcodeCallback callback = new BarcodeCallback() {
        @Override
        public void barcodeResult(final BarcodeResult result) {
            barcodeView.pause();
            beepManager.playBeepSoundAndVibrate();

            handler.post(new Runnable() {
                @Override
                public void run() {
                    returnResult(result);
                }
            });

        }

        @Override
        public void possibleResultPoints(List<ResultPoint> resultPoints) {

        }
    };

暂停预览并播放声音。
handler发送结果到 returnResult(result);

protected void returnResult(BarcodeResult rawResult) {
        Intent intent = resultIntent(rawResult, getBarcodeImagePath(rawResult));
        activity.setResult(Activity.RESULT_OK, intent);
        closeAndFinish();
    }

把结果返回到Activity并关闭,这个Activity,就是CaptureActivity或者自定义的Activity,就可以拿到结果了。

  • InactivityTimer是对电池的状态进行监听,
final boolean onBatteryNow = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) <= 0;

这里应该是使用电池本身的电量(没有USB,或者充电)5分钟就关闭了Activity。

BarcodeView

BarcodeView是用于扫描条形码的视图。
使用decodeSingle()或decodeContinuous()开始解码。
停止使用stopDecoding()解码。
调用两个方法来管理状态:

    1. resume() 初始化相机并开始预览。 从Activity的onResume()调用。
    1. pause() 停止预览并释放任何资源。 从Activity的onPause()调用。

对外开放的开始解码代码

public void decodeSingle(BarcodeCallback callback) {
        this.decodeMode = DecodeMode.SINGLE;
        this.callback = callback;
        startDecoderThread();
    }

开启连续解码,相同的条形码可以每秒返回多次

public void decodeContinuous(BarcodeCallback callback) {
        this.decodeMode = DecodeMode.CONTINUOUS;
        this.callback = callback;
        startDecoderThread();
    }

开启解码

 private void startDecoderThread() {
        stopDecoderThread(); // To be safe

        if (decodeMode != DecodeMode.NONE && isPreviewActive()) {
            //启动线程条件:
            // 1.请求解码
            // 2.预览处于活动状态
            decoderThread = new DecoderThread(getCameraInstance(), createDecoder(), resultHandler);
            decoderThread.setCropRect(getPreviewFramingRect());
            decoderThread.start();
        }
    }

保证线程安全,设置矩形区域,然后开启解码。

这里的handler(resultHandler)就是返回解码结果的回调。这个回调是在DecoderThread类发出的.

private final Handler.Callback resultCallback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message message) {
            if (message.what == R.id.zxing_decode_succeeded) {
                BarcodeResult result = (BarcodeResult) message.obj;

                if (result != null) {
                    if (callback != null && decodeMode != DecodeMode.NONE) {
                        callback.barcodeResult(result);
                        if (decodeMode == DecodeMode.SINGLE) {
                            stopDecoding();
                        }
                    }
                }
                return true;
            } else if (message.what == R.id.zxing_decode_failed) {
                // Failed. Next preview is automatically tried.
                //失败 下一个预览会自动尝试。
                return true;
            } else if (message.what == R.id.zxing_possible_result_points) {
                //noinspection unchecked
                List<ResultPoint> resultPoints = (List<ResultPoint>) message.obj;
                if (callback != null && decodeMode != DecodeMode.NONE) {
                    callback.possibleResultPoints(resultPoints);
                }
                return true;
            }
            return false;
        }
    };

callback.barcodeResult(result);这一行代码中,Callback是CaptureManager类中callback。结果是一层一层向上传递的。

跟踪这个resultHandler。进入DecoderThread类。

DecoderThread

构造

public DecoderThread(CameraInstance cameraInstance, Decoder decoder, Handler resultHandler) {
        Util.validateMainThread();

        this.cameraInstance = cameraInstance;
        this.decoder = decoder;
        this.resultHandler = resultHandler;
    }

直接解码呗

/**
     * Start decoding.
     *
     * This must be called from the UI thread.
     */
    public void start() {
        Util.validateMainThread();

        thread = new HandlerThread(TAG);
        thread.start();
        handler = new Handler(thread.getLooper(), callback);
        running = true;
        requestNextPreview();
    }

这个callback返回的是图像信息,然后根据图像信息进行解码

private final Handler.Callback callback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message message) {
            if (message.what == R.id.zxing_decode) {
                decode((SourceData) message.obj);
            } else if(message.what == R.id.zxing_preview_failed) {
                // Error already logged. Try again.
                requestNextPreview();
            }
            return true;
        }
    };

最关键的是
requestNextPreview();
这个方法.

private void requestNextPreview() {
        if (cameraInstance.isOpen()) {
            cameraInstance.requestPreview(previewCallback);
        }
    }

这里有个CameraInstance类,这个类使用后台线程管理Camera实例,必须调用在主线程。

public void requestPreview(final PreviewCallback callback) {
        validateOpen();

        cameraThread.enqueue(new Runnable() {
            @Override
            public void run() {
                cameraManager.requestPreviewFrame(callback);
            }
        });
    }

这里cameraThread.enqueue()其实就是handler.post发送调用在主线程。

public void requestPreviewFrame(PreviewCallback callback) {
        Camera theCamera = camera;
        if (theCamera != null && previewing) {
            cameraPreviewCallback.setCallback(callback);
            theCamera.setOneShotPreviewCallback(cameraPreviewCallback);
        }
    }

单次预览框将图像返回到回调。

private final class CameraPreviewCallback implements Camera.PreviewCallback {
        private PreviewCallback callback;

        private Size resolution;

        public CameraPreviewCallback() {
        }

        public void setResolution(Size resolution) {
            this.resolution = resolution;
        }

        public void setCallback(PreviewCallback callback) {
            this.callback = callback;
        }

        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            Size cameraResolution = resolution;
            PreviewCallback callback = this.callback;
            if (cameraResolution != null && callback != null) {
                try {
                    if(data == null) {
                        throw new NullPointerException("No preview data received");
                    }
                    int format = camera.getParameters().getPreviewFormat();
                    SourceData source = new SourceData(data, cameraResolution.width, cameraResolution.height, format, getCameraRotation());
                    callback.onPreview(source);
                } catch (RuntimeException e) {
                    // Could be:
                    // java.lang.RuntimeException: getParameters failed (empty parameters)
                    // IllegalArgumentException: Image data does not match the resolution
                    Log.e(TAG, "Camera preview failed", e);
                    callback.onPreviewError(e);
                }
            } else {
                Log.d(TAG, "Got preview callback, but no handler or resolution available");
                if(callback != null) {
                    // Should generally not happen
                    callback.onPreviewError(new Exception("No resolution available"));
                }
            }
        }
    }

callback.onPreview(source);
图像给回调了。
回调是在DecoderThread中实现的

private final PreviewCallback previewCallback = new PreviewCallback() {
        @Override
        public void onPreview(SourceData sourceData) {
            // Only post if running, to prevent a warning like this:
            //   java.lang.RuntimeException: Handler (android.os.Handler) sending message to a Handler on a dead thread

            // synchronize to handle cases where this is called concurrently with stop()
            synchronized (LOCK) {
                if (running) {
                    // Post to our thread.
                    handler.obtainMessage(R.id.zxing_decode, sourceData).sendToTarget();
                }
            }
        }

        @Override
        public void onPreviewError(Exception e) {
            synchronized (LOCK) {
                if (running) {
                    // Post to our thread.
                    handler.obtainMessage(R.id.zxing_preview_failed).sendToTarget();
                }
            }
        }
    };

数据发送给了handler,在handler的Callback中

private final Handler.Callback callback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message message) {
            if (message.what == R.id.zxing_decode) {
                decode((SourceData) message.obj);
            } else if(message.what == R.id.zxing_preview_failed) {
                // Error already logged. Try again.
                requestNextPreview();
            }
            return true;
        }
    };

看看decoder()方法。

 private void decode(SourceData sourceData) {
        long start = System.currentTimeMillis();
        Result rawResult = null;
        sourceData.setCropRect(cropRect);
        LuminanceSource source = createSource(sourceData);

        if(source != null) {
            rawResult = decoder.decode(source);
        }

        if (rawResult != null) {
            // Don't log the barcode contents for security.
            long end = System.currentTimeMillis();
            Log.d(TAG, "Found barcode in " + (end - start) + " ms");
            if (resultHandler != null) {
                BarcodeResult barcodeResult = new BarcodeResult(rawResult, sourceData);
                Message message = Message.obtain(resultHandler, R.id.zxing_decode_succeeded, barcodeResult);
                Bundle bundle = new Bundle();
                message.setData(bundle);
                message.sendToTarget();
            }
        } else {
            if (resultHandler != null) {
                Message message = Message.obtain(resultHandler, R.id.zxing_decode_failed);
                message.sendToTarget();
            }
        }
        if (resultHandler != null) {
            List<ResultPoint> resultPoints = decoder.getPossibleResultPoints();
            Message message = Message.obtain(resultHandler, R.id.zxing_possible_result_points, resultPoints);
            message.sendToTarget();
        }
        requestNextPreview();
    }

在decoder.decode(source)这里解码图像,并且把结果都分发出来,
现在比较清晰了,resultHandler就是结果的回调,网上跟踪就可以了。
最后又开启了下一次预览。解码二进制图像是在Decode类中,可以自己去看看

ViewfinderView

此视图覆盖在相机预览的顶部。 它添加取景器矩形和部分透明度以外,以及激光扫描仪的动画和结果点。
没啥说的啊。

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

推荐阅读更多精彩内容