【Android】混合开发之WebView的介绍及使用

前言

Android项目中WebView是必不可少的,越来越开的迭代节奏导致越来越多的App采用混合开发,接着我们就介绍一下Android中WebView的使用。

一、混合开发的优缺点:

优点:
1.开发成本较低:Android和iOS使用一个地址就可以。
2.自动更新最新的web内容。
3.兼容平台较多。
缺点:
1.用户体验没有原生的炫酷。
2.连接网络等性能较差。
但瑕不掩瑜,对于不需要炫酷动效的简单页面如:用户协议、注册说明、banner跳转的一些推广页面、图文展示的文章等页面都可以用WebView来完成!

二、如何配置WebView:

常用的类:
WebSettings用于管理WebView状态配置

        webView.getSettings().setDisplayZoomControls(false);//是否使用内置缩放机制
        webView.getSettings().setSupportZoom(true);// 是否支持变焦
        wvSignin.getSettings().setBuiltInZoomControls(true);// 设置WebView是否应该使用其内置变焦机制,显示放大缩小 
        webView.getSettings().setUseWideViewPort(true);//是否开启控件viewport。默认false,自适应;true时标签中指定宽度值生效
        webView.getSettings().setLoadWithOverviewMode(true);
        webView.setInitialScale(100);// 初始化时缩放
        webView.getSettings().setJavaScriptEnabled(true);

WebViewClient :主要帮助WebView处理各种通知、请求事件的。
shouldOverrideUrlLoading(打开网页时不调用系统浏览器)
onLoadResource(在加载页面资源时会调用)
onPageStart(设定加载开始的操作)
onPageFinish(在页面加载结束时调用。我们可以关闭loading 条,切换程序动作)
onReceiveError(加载出错调用)

  //首先选择加载方式
  //方式1. 加载一个网页:
  webView.loadUrl("http://www.google.com/");
  //方式2:加载apk包中的html页面
  webView.loadUrl("file:///android_asset/test.html");
  //方式3:加载手机本地的html页面
   webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html");
webView.setWebViewClient(new WebViewClient() {
            @Override
            public void doUpdateVisitedHistory(WebView view, String url,
                                               boolean isReload) {
                super.doUpdateVisitedHistory(view, url, isReload);
            }

            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);// 点击超链接的时候重新在原来进程上加载URL
                return true;
            }

            @Override
            public void onPageFinished(WebView view, String url) {
            }

        });

WebChromeClien:WebChromeClient主要辅助WebView处理Javascript的对话框、网站图标、网站title、加载进度等
onCloseWindow(关闭WebView)
onCreateWindow()
onJsAlert (WebView上alert无效,需要定制WebChromeClient处理弹出)
onJsPrompt(支持javascript输入框)
onJsConfirm(支持javascript的确认框)
onProgressChanged(获得网页的加载进度并显示)
onReceivedIcon(获取WebView的icon)
onReceivedTitle(获取WebView的标题)
如果你的WebView只是用来处理一些html的页面内容,只用WebViewClient就行了,如果需要更丰富的处理效果,比如JS、进度条等,就要用到WebChromeClient。
进度条例子:

 webView.setWebChromeClient(webChromeClient)
 WebChromeClient webChromeClient = new WebChromeClient() {

        public void onProgressChanged(WebView view, int progress) {
            super.onProgressChanged(view, progress);
            wvProgressbar.setMax(100);
            if (progress < 100) {
                wvProgressbar.setVisibility(View.VISIBLE);
                if (progress < 10) {
                    wvProgressbar.setProgress(10);
                } else {
                    wvProgressbar.setProgress(progress);
                }
            } else {
                wvProgressbar.setProgress(100);
                wvProgressbar.setVisibility(View.GONE);
            }
        }

        @Override
        public void onReceivedTitle(WebView view, String title) {

            super.onReceivedTitle(view, title);
        }

    };
三、WebView和JS的交互:

之间的交互无非两种,Android调用Js,Js调用Android。
1.Android调用Js:

  • 通过WebView的loadUrl()
 webView.loadUrl(url);里面可以配置userId、page等信息。
  • 通过WebView的evaluateJavascript()
比第一种方法效率更高,有返回值。但只支持Android4.4以上
 mWebView.evaluateJavascript(url, new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String value) {
            //结果
        }
    });

2.Js调用Android:

  • 通过 addJavascriptInterface(存在严重的漏洞问题)
被JS调用的方法必须加入@JavascriptInterface注解
 @JavascriptInterface
    public void getData(String msg) {
        System.out.println("JS调用了Android的getData方法");
    }
  • 通过WebViewClient 的方法回调拦截,常用的如 shouldOverrideUrlLoading ()方法回调拦截 url(不存在漏洞,但使用麻烦)
    Android通过 WebViewClient 的回调方法shouldOverrideUrlLoading ()拦截 url
    解析该 url 的协议
    如果检测到是预先约定好的协议,就调用相应方法
四、封装一下WebView让使用更简单~
public class MyWebView extends WebView {
    private int currentY = 0;
    private Context context;

    public MyWebView(Context context) {
        super(context);
        init(context);
    }

    public  MyWebView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    public  MyWebView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        this.context = context;
        initWebSettings();
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return super.onTouchEvent(ev);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        currentY = t;
        super.onScrollChanged(l, t, oldl, oldt);
    }

    public int getCurrentY() {
        return currentY;
    }

    public boolean canZoomIn() {
        boolean canZoomIn = true;
        try {
            Field mActualScale = WebView.class.getDeclaredField("mActualScale");
            Field mMaxZoomScale = WebView.class
                    .getDeclaredField("mMaxZoomScale");
            mActualScale.setAccessible(true);
            mMaxZoomScale.setAccessible(true);
            canZoomIn = mActualScale.getFloat(this) < mMaxZoomScale
                    .getFloat(this);
        } catch (Exception e) {
            try {
                Field mZoomManager = WebView.class
                        .getDeclaredField("mZoomManager");
                if (mZoomManager != null) {
                    mZoomManager.setAccessible(true);
                    Object zoomManager = mZoomManager.get(this);
                    if (zoomManager != null) {
                        Field mEmbeddedZoomControl = zoomManager.getClass()
                                .getDeclaredField("mEmbeddedZoomControl");
                        if (mEmbeddedZoomControl != null) {
                            mEmbeddedZoomControl.setAccessible(true);
                            Object zoomControlEmbedded = mEmbeddedZoomControl
                                    .get(zoomManager);
                            if (zoomControlEmbedded != null) {
                                mZoomManager = zoomControlEmbedded.getClass()
                                        .getDeclaredField("mZoomManager");
                                if (mZoomManager != null) {
                                    mZoomManager.setAccessible(true);
                                    zoomManager = mZoomManager
                                            .get(zoomControlEmbedded);
                                    Method canZoomInMethod = zoomManager
                                            .getClass().getDeclaredMethod(
                                                    "canZoomIn");
                                    if (canZoomInMethod != null) {
                                        canZoomInMethod.setAccessible(true);
                                        Object canZoomInObj = canZoomInMethod
                                                .invoke(zoomManager,
                                                        new Object[0]);
                                        if (canZoomInObj != null
                                                && canZoomInObj instanceof Boolean) {
                                            return (Boolean) canZoomInObj;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            } catch (Exception e2) {
            }
        }
        return canZoomIn;
    }

    public boolean canZoomOut() {
        boolean canZoomOut = true;
        try {
            Field mActualScale = WebView.class.getDeclaredField("mActualScale");
            Field mMinZoomScale = WebView.class
                    .getDeclaredField("mMinZoomScale");
            Field mInZoomOverview = WebView.class
                    .getDeclaredField("mInZoomOverview");
            mActualScale.setAccessible(true);
            mMinZoomScale.setAccessible(true);
            mInZoomOverview.setAccessible(true);
            canZoomOut = mActualScale.getFloat(this) > mMinZoomScale
                    .getFloat(this) && !mInZoomOverview.getBoolean(this);
        } catch (Exception e) {
            try {
                Field mZoomManager = WebView.class
                        .getDeclaredField("mZoomManager");
                if (mZoomManager != null) {
                    mZoomManager.setAccessible(true);
                    Object zoomManager = mZoomManager.get(this);
                    if (zoomManager != null) {
                        Field mEmbeddedZoomControl = zoomManager.getClass()
                                .getDeclaredField("mEmbeddedZoomControl");
                        if (mEmbeddedZoomControl != null) {
                            mEmbeddedZoomControl.setAccessible(true);
                            Object zoomControlEmbedded = mEmbeddedZoomControl
                                    .get(zoomManager);
                            if (zoomControlEmbedded != null) {
                                mZoomManager = zoomControlEmbedded.getClass()
                                        .getDeclaredField("mZoomManager");
                                if (mZoomManager != null) {
                                    mZoomManager.setAccessible(true);
                                    zoomManager = mZoomManager
                                            .get(zoomControlEmbedded);
                                    Method canZoomOutMethod = zoomManager
                                            .getClass().getDeclaredMethod(
                                                    "canZoomOut");
                                    if (canZoomOutMethod != null) {
                                        canZoomOutMethod.setAccessible(true);
                                        Object canZoomOutObj = canZoomOutMethod
                                                .invoke(zoomManager,
                                                        new Object[0]);
                                        if (canZoomOutObj != null
                                                && canZoomOutObj instanceof Boolean) {
                                            return (Boolean) canZoomOutObj;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            } catch (Exception e2) {
            }
        }
        return canZoomOut;
    }

    @SuppressLint("SetJavaScriptEnabled")
    @SuppressWarnings("deprecation")
    private void initWebSettings() {
        WebSettings webSettings = getSettings();
        webSettings.setRenderPriority(RenderPriority.HIGH);
        webSettings.setTextSize(TextSize.NORMAL);// 设置字体
        
        // 设置支持缩放
        webSettings.setAllowFileAccess(true);// 设置可以访问文件
        webSettings.setDomStorageEnabled(true);
        // 设置屏幕自适应
        if (Integer.parseInt(Build.VERSION.SDK) <= 10) {
            webSettings.setLayoutAlgorithm(LayoutAlgorithm.NARROW_COLUMNS);
        } else {
            webSettings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
        }
        // 启用插件
        // webSettings.setPluginsEnabled(true);

        // 设置缓存
        webSettings.setAppCacheEnabled(true);
        webSettings.setAppCacheMaxSize(10 * 1204 * 1024);
        webSettings.setDatabaseEnabled(true);
        webSettings.setBlockNetworkImage(false);//延迟加载图片,首先加载文字
        
        this.setBackgroundColor(context.getResources().getColor(
                R.color.white));
        webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
        // 去掉缩放按钮
        webSettings.setDisplayZoomControls(false);
        webSettings.setSupportZoom(true);// 是否支持变焦
        webSettings.setBuiltInZoomControls(true);// 设置WebView是否应该使用其内置变焦机制,显示放大缩小 
        webSettings.setUseWideViewPort(true);
        webSettings.setLoadWithOverviewMode(true);
        this.setInitialScale(100);// 初始化时缩放
        webSettings.setJavaScriptEnabled(true);
        this.addJavascriptInterface(new JavaScriptinterface(context), "android");
        this.setWebViewClient(new WebViewClient() {
            @Override
            public void doUpdateVisitedHistory(WebView view, String url,
                                               boolean isReload) {
                super.doUpdateVisitedHistory(view, url, isReload);
            }

            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                DfheWebView.this.loadUrl(url);// 点击超链接的时候重新在原来进程上加载URL
                return true;
            }

            @Override
            public void onPageFinished(WebView view, String url) {
            }

        });
    }
    
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        // TODO Auto-generated method stub
        return super.onKeyDown(keyCode, event);
    }
    

    public interface OnScrollListener {
        void onScroll();
    }
}
628180

五、WebView 踩过的坑:

1.WebView内存泄漏问题
解决方案:
1.展示webview的activity可以另开一个进程,这样就能和我们app的主进程分开了,即使webview产生了oom崩溃等问题也不会影响到主程序,在Androidmanifest.xml的activity标签里加上Android:process=”packagename.web”就可以了,并且当这个 进程结束时,请手动调用System.exit(0)。

  1. 如果实在不想用开额外进程的方式解决webview 内存泄露的问题,那么下面的方法很大程度上可以避免这种情况,在webview的 destroy方法里 调用这个方法就行了。
public void releaseAllWebViewCallback() {
         if (android.os.Build.VERSION.SDK_INT < 16) {
             try {
                 Field field = WebView.class.getDeclaredField("mWebViewCore");
                 field = field.getType().getDeclaredField("mBrowserFrame");
                 field = field.getType().getDeclaredField("sConfigCallback");
                 field.setAccessible(true);
                 field.set(null, null);
             } catch (NoSuchFieldException e) {
                 if (BuildConfig.DEBUG) {
                     e.printStackTrace();
                 }
             } catch (IllegalAccessException e) {
                 if (BuildConfig.DEBUG) {
                     e.printStackTrace();
                 }
             }
         } else {
             try {
                 Field sConfigCallback = Class.forName("android.webkit.BrowserFrame").getDeclaredField("sConfigCallback");
                 if (sConfigCallback != null) {
                     sConfigCallback.setAccessible(true);
                     sConfigCallback.set(null, null);
                 }
             } catch (NoSuchFieldException e) {
                 if (BuildConfig.DEBUG) {
                     e.printStackTrace();
                 }
             } catch (ClassNotFoundException e) {
                 if (BuildConfig.DEBUG) {
                     e.printStackTrace();
                 }
             } catch (IllegalAccessException e) {
                 if (BuildConfig.DEBUG) {
                     e.printStackTrace();
                 }
             }
         }
     }

2.getSettings().setBuiltInZoomControls(true) 引发的crash。
这个方法调用以后 如果你触摸屏幕 弹出的提示框还没消失的时候 你如果activity结束了 就会报错了。3.0以上 4.4以下很多手机会出现这种情况。解决方法是在activity的onDestroy方法里手动的将webiew设置成 setVisibility(View.GONE)
3.WebView后台耗电问题。
WebView会自己开启一些线程,如果没有正确的销毁,这些残留的线程会一直在后台运行,导致耗费电量。还有在有的手机里,你如果webview加载的html里 有一些js 一直在执行比如动画之类的东西,如果此刻webview 挂在了后台,这些资源是不会被释放 用户也无法感知,导致一直占有cpu 耗电特别快。
解决方案:在Activity.onDestroy()中直接调用System.exit(0),使得应用程序完全被移出虚拟机。在Activity的onstop和onresume里分别把setJavaScriptEnabled();给设置成false和true。

六、WebView进阶,缓存原理:

原文链接:WebView缓存原理分析和应用
减少流量和资源的占用,加载完一次后Js没有变化就不再发起网络请求去加载网页

  • 浏览器自带的网页数据缓存:浏览器自带
  • H5缓存:由web页面的开发者设置
    利用App Cache 来缓存js文件
    浏览器缓存机制是通过HTTP协议Header里的Cache-Control和Last-Modified等字段来控制的。
    接受响应时: 加载文件时,浏览器是否发出请求字段:
    Cache-Control:max-age=36000**,这表示缓存时长为36000秒。如果36000秒内需要再次请求这个文件,那么浏览器不会发出请求,直接使用本地的缓存的文件。这是HTTP/1.1标准中的字段。
    发起请求时:服务器决定文件是否需要更新的字段:
    Last-Modified:Wed, 28 Sep 2016 09:24:35 GMT,表示这个文件最后的修改时间是2016年9月28日9点24分35秒。这个字段对于浏览器来说,会在下次请求的时候作为Request Header的If-Modified-Since字段带上。例如浏览器缓存的文件已经超过了Cache-Control,那么需要加载这个文件时,就会发出请求,请求的Header有一个字段为If-Modified-Since:Wed, 28 Sep 2016 09:24:35 GMT,服务器接收到请求后,会把文件的Last-Modified时间和这个时间对比,如果时间没变,那么浏览器将返回304 Not Modified给浏览器,且content-length肯定是0个字节。如果时间有变化,那么服务器会返回200 OK,并返回相应的内容给浏览器。
    WebView 如何设置:设置WebView的Cache Mode:
  • LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据。
  • LOAD_DEFAULT: 根据cache-control决定是否从网络上取数据。
  • LOAD_CACHE_NORMAL: API level 17中已经废弃,从API level 11开始作用同LOAD_DEFAULT模式
  • LOAD_NO_CACHE: 不使用缓存,只从网络获取数据。
  • LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。本地没有缓存时才从网络上获取。
    例子
WebSettings settings = webView.getSettings();
settings.setCacheMode(WebSettings.LOAD_DEFAULT);

浏览器默认缓存的路径
WebView自带的浏览器协议支持的缓存,在不同的系统版本上,位置是不一样的。
H5的缓存
这个Cache是由开发Web页面的开发者控制的,而不是由Native去控制的,但是Native里面的WebView也需要我们做一下设置才能支持H5的这个特性
1.工作原理
写Web页面代码时,指定manifest属性即可让页面使用App Cache。通常html页面代码会这么写:

<html manifest="xxx.appcache">
</html>

xxx.appcache文件用的是相对路径,这时appcache文件的路径是和页面一样的。也可以使用的绝对路径,但是域名要保持和页面一致。
完整的xxx.appcache文件一般包括了3个section,基本格式如下:

CACHE MANIFEST
# 2017-05-13 v1.0.0
/bridge.js
NETWORK:
*
FALLBACK:
/404.html
  • CACHE MANIFEST下面文件就是要被浏览器缓存的文件

  • NETWORK下面的文件就是要被加载的文件

  • FALLBACK下面的文件是目标页面加载失败时的显示的页面
    AppCache工作的原理:
    当一个设置了manifest文件的html页面被加载时,CACHE MANIFEST指定的文件就会被缓存到浏览器的App Cache目录下面。当下次加载这个页面时,会首先应用通过manifest已经缓存过的文件,然后发起一个加载xxx.appcache文件的请求到服务器,如果xxx.appcache文件没有被修改过,那么服务器会返回304 Not Modified给到浏览器,如果xxx.appcache文件被修改过,那么服务器会返回200 OK,并返回新的xxx.appcache文件的内容给浏览器,浏览器收到之后,再把新的xxx.appcache文件中指定的内容加载过来进行缓存。
    可以看到,AppCache缓存需要在每次加载页面时都发出一个xxx.appcache的请求去检查manifest文件是不是有更新(byte by byte)。根据这篇文章(H5 缓存机制浅析 移动端 Web 加载性能优化)的介绍,AppCache有一些坑的地方,且官方已经不推荐使用了,但目前主流的浏览器依然是支持的。文章里主要提到下面这些坑:

  • 要更新缓存的文件,需要更新包含它的 manifest 文件,那怕只加一个空格。常用的方法,是修改 manifest 文件注释中的版本号。如:# 2012-02-21 v1.0.0

  • 被缓存的文件,浏览器是先使用,再通过检查 manifest 文件是否有更新来更新缓存文件。这样缓存文件可能用的不是最新的版本。

  • 在更新缓存过程中,如果有一个文件更新失败,则整个更新会失败。

  • manifest 和引用它的HTML要在相同 HOST。

  • manifest 文件中的文件列表,如果是相对路径,则是相对 manifest 文件的相对路径。

  • manifest 也有可能更新出错,导致缓存文件更新失败。

  • 没有缓存的资源在已经缓存的 HTML 中不能加载,即使有网络。例如:http://appcache-demo.s3-website-us-east-1.amazonaws.com/without-network/

  • manifest 文件本身不能被缓存,且 manifest 文件的更新使用的是浏览器缓存机制。所以 manifest 文件的 Cache-Control 缓存时间不能设置太长。

2.WebView如何设置才能支持AppCache

WebView默认是没有开启AppCache支持的,需要添加下面这几行代码来设置:

WebSettings webSettings = webView.getSettings();
webSettings.setAppCacheEnabled(true);
String cachePath = getApplicationContext().getCacheDir().getPath(); // 把内部私有缓存目录'/data/data/包名/cache/'作为WebView的AppCache的存储路径
webSettings.setAppCachePath(cachePath);
webSettings.setAppCacheMaxSize(5 * 1024 * 1024);

注意:WebSettings的setAppCacheEnabled和setAppCachePath都必须要调用才行。
3.存储AppCache的路径
按照Android SDK的API说明,setAppCachePath是可以用来设置AppCache路径的,但是我实际测试发现,不管你怎么设置这个路径,设置到应用自己的内部私有目录还是外部SD卡,都无法生效。AppCache缓存文件最终都会存到/data/data/包名/app_webview/cache/Application Cache这个文件夹下面,在上面的Android 4.4和5.1系统目录截图可以看得到,但是如果你不调用setAppCachePath方法,WebView将不会产生这个目录。这里有点让我觉得奇怪,我猜测可能从某一个系统版本开始,为了缓存文件的完整性和安全性考虑,SDK实现的时候就吧AppCache缓存目录设置到了内部私有存储。

关于WebView的基础用法基本就这么多,了解本篇足以应付项目中的场景,但使用WebView也会遇到很多小问题,我会结合以往总结一下,再拜读一下大家的博客遇到的问题总结一下~
如果喜欢请点个赞再走~
github地址:https://github.com/bigeyechou

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

推荐阅读更多精彩内容