android webview 文件上传

1、唤出系统文件管理器

开启文件上传,可使用HTML5标签 <input type="file"> 唤出系统文件管理器或自定义文件管理器,然后选择文件。

MainActivity.java:

private WebView webView;
private WVChromeClient wv = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);
    webView = (WebView) findViewById(R.id.wv_webview);
    WebSettings settings = webView.getSettings();
    settings.setUseWideViewPort(true);
    settings.setJavaScriptEnabled(true);
    wv = new WVChromeClient(this,MainActivity.this);
    webView.setWebChromeClient(wv);
}

 @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == WVChromeClient.CHOOSER_REQUEST) { // 处理返回的文件
        wv.onActivityResultFileChooser(requestCode, resultCode, data); // 调用 WVChromeClient 类中的 回调方法
    }
}

WVChromeClient.java:

public class WVChromeClient extends WebChromeClient {
    private static final String TAG = "WebChromeClient:";
    public final static int CHOOSER_REQUEST = 0x33;
    private ValueCallback<Uri[]> uploadFiles = null;
    Context context;
    MainActivity _m;
    public WVChromeClient(Context _context, MainActivity mainActivity)
    {
        context = _context;
        _m = mainActivity;
    }

    // 第一种方式
    @Override
    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
                                     FileChooserParams fileChooserParams) {
        uploadFiles = filePathCallback;
        Intent i = fileChooserParams.createIntent();
        i.addCategory(Intent.CATEGORY_OPENABLE);
        i.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); // 设置多选
        _m.startActivityForResult(Intent.createChooser(i, "Image Chooser"), CHOOSER_REQUEST);
        return true;
    }

    // 第二种方式(过滤文件格式)
    @Override
    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
                                     FileChooserParams fileChooserParams) {
        uploadFiles = filePathCallback;
        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
        i.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
        i.setType("*/*"); // 设置文件类型
        String[] mimeTypes = { "image/*,audio/*,video/*,*/*" };
        i.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); // 设置多种类型
        i.addCategory(Intent.CATEGORY_OPENABLE);
        _m.startActivityForResult(Intent.createChooser(i, "Image Chooser"), CHOOSER_REQUEST);
        return true;
    }

    // 文件选择回调(在 MainActivity.java 的 onActivityResult中调用此方法)
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public void onActivityResultFileChooser(int requestCode, int resultCode, Intent intent) {
        if (requestCode != CHOOSER_REQUEST || uploadFiles == null)
            return;
        Uri[] results = null;
        if (resultCode == Activity.RESULT_OK) {
            if (intent != null) {
                String dataString = intent.getDataString();
                ClipData clipData = intent.getClipData();
                if (clipData != null) {
                    results = new Uri[clipData.getItemCount()];
                    for (int i = 0; i < clipData.getItemCount(); i++) {
                        ClipData.Item item = clipData.getItemAt(i);
                        results[i] = item.getUri();
                    }
                }
                if (dataString != null)
                    results = new Uri[]{Uri.parse(dataString)};
            }
        }
        uploadFiles.onReceiveValue(results);
        uploadFiles = null;
    }
}

2、唤出自定义文件管理器

2.1 使用第三方插件

这里使用 AndroidFilePicker 插件可自定义文件管理器,见详细使用

(1)添加依赖

在项目 build.gradle 配置文件添加仓库:

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

在子模块(app)的配置文件添加依赖:

dependencies {
    implementation 'me.rosuh:AndroidFilePicker:0.8.2'
}

此库需要一个权限:

android.permission.READ_EXTERNAL_STORAGE

如果您没有提前授予,这个库会自动申请该权限的。

修改上文 WVChromeClient 类中的 onShowFileChooser() 方法:

private ValueCallback<Uri[]> uploadFiles = null;
// 重写选择文件
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
                                    FileChooserParams fileChooserParams) {
    uploadFiles = filePathCallback;
    String uDiskUrl = getUDisk(); // 检测U盘
    if(uDiskUrl != null) {
        showSingleAlertDialog(uDiskUrl); // 弹出选择框
    } else {
        showFilePickerManager(""); // 直接打开本地路径,若无需支持U盘则可以直接调用此方法唤出自定义文件管理器
    }
    return true;
}

// 判断是否存在U盘
private String getUDisk() {
    String path = "/mnt/usb/"; // u盘路径
    File storage = new File(path);
    File[] files = storage.listFiles();
    if(files != null && files.length != 0) {
        return path + files[0].getName() + "/";
    }
    return null;
}

// 打开文件管理器
private void showFilePickerManager(String path) {
    FilePickerManager
            .from((Activity) context) // context 为上文实例化 WVChromeClient 类时传入
            .setCustomRootPath(path)
            .forResult(CHOOSER_REQUEST);
}

private int checkedItem = 0;
private boolean isNotOK = true;
// 选择框
private void showSingleAlertDialog(String path) {
    String[] items = {"本地存储", "U盘"};
    AlertDialog.Builder alertBuilder = new AlertDialog.Builder(context);
    alertBuilder.setTitle("请选择");
    alertBuilder.setSingleChoiceItems(items, 0, (dialogInterface, i) -> {
        checkedItem = i;
    });

    alertBuilder.setPositiveButton("确定", (dialogInterface, i) -> {
        isNotOK = false;
        dialogInterface.dismiss();
        String paths = "";
        if(checkedItem == 1) {
            paths = path; // 当前选择U盘,默认为本地存储
        }
        showFilePickerManager(paths);
    });

    alertBuilder.setNegativeButton("取消", (dialogInterface, i) -> dialogInterface.dismiss());

    alertBuilder.setOnDismissListener(dialog -> {
        if(isNotOK) { // 若没有选择确定按钮则取消文件上传
            uploadFiles.onReceiveValue(null);
            uploadFiles = null;
        }
    });
    alertBuilder.show();
}

// 文件选择回调
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void onActivityResultFileChooser(int requestCode, int resultCode, Intent intent) {
    if (requestCode != Config.CHOOSER_REQUEST_CODE || uploadFiles == null)
        return;
    Uri[] results = null;
    if (resultCode == Activity.RESULT_OK) {
        List<String> list = FilePickerManager.obtainData(); // 取到选择的文件列表
        results = new Uri[list.size()];
        for (int i = 0; i < list.size(); i++) {
            String item = list.get(i);
            Uri uri = getUriForFile(new File(item));
            results[i] = uri;
        }
    }
    uploadFiles.onReceiveValue(results);
    uploadFiles = null;
    isNotOK = true;
}

// File 转 Uri
private Uri getUriForFile(File file) {
    String packageName = getPackage(context).packageName;
    Uri contentUri = FileProvider.getUriForFile(context,packageName+".fileProvider", file); // 需要 FileProvider,详细使用见下文
    return contentUri;
}

// 获取当前包名
public static PackageInfo getPackage(Context context) {
    PackageManager manager = context.getPackageManager();
    try {
        PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0);
        return  info;
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
        return null;
    }
}

FileProvider 使用详见

2.2 手写文件管理页面

手写文件管理页面有两个个步骤:获取文件列表、展示文件列表

(1)获取文件列表

按一般文件管理器大致有几个目录:文档、音频、视频、图片、下载、所有文件目录

public class FileManage {
    private Context _c;
    private static final String TAG = "FileManage:";
    public FileManage(Context context) {
        _c = context;
    }

    // 获取视频
    public JSONObject getVideos() { // 这里返回一个 JSONObject,返回格式可以自行定义
        Cursor c = null;
        JSONArray array = new JSONArray();
        try {
            Log.e(TAG,MediaStore.Video.Media.EXTERNAL_CONTENT_URI.toString());
            c = _c.getContentResolver().query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Video.Media.DEFAULT_SORT_ORDER);
            while (c.moveToNext()) {
                String path = c.getString(c.getColumnIndexOrThrow(MediaStore.Video.Media.DATA));// 路径
                if (!new File(path).exists()) {
                    continue;
                }

                int id = c.getInt(c.getColumnIndexOrThrow(MediaStore.Video.Media._ID));// 视频的id
                String name = c.getString(c.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME)); // 视频名称
                String resolution = c.getString(c.getColumnIndexOrThrow(MediaStore.Video.Media.RESOLUTION)); //分辨率
                long size = c.getLong(c.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE));// 大小
                long duration = c.getLong(c.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION));// 时长
                long date = c.getLong(c.getColumnIndexOrThrow(MediaStore.Video.Media.DATE_MODIFIED));//修改时间

                JSONObject obj = new JSONObject();
                obj.put("id",id);
                obj.put("name",name);
                obj.put("url",path);
                obj.put("resolution",resolution);
                obj.put("size",size);
                obj.put("duration",duration);
                obj.put("time",getDateToString(date*1000));
                obj.put("timestamp", date*1000);
                array.put(obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (c != null) {
                c.close();
            }
        }
        File file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
        JSONObject obj = new JSONObject();
        try {
            obj.put("name","视频");
            obj.put("svg","#icon-file_video");
            obj.put("url", file.getPath());
            if(array != null && array.length() != 0) {
                obj.put("child", array);
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return obj;
    }

    // 获取音频
    public JSONObject getMusics() {
        Cursor c = null;
        JSONArray array = new JSONArray();
        try {
            c = _c.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null,
                    MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
            while (c.moveToNext()) {
                String path = c.getString(c.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA));// 路径
                if (!new File(path).exists()) {
                    continue;
                }

                int id = c.getInt(c.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)); // 歌曲的id
                String name = c.getString(c.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME)); // 歌曲名
                String album = c.getString(c.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM)); // 专辑
                String artist = c.getString(c.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)); // 作者
                long size = c.getLong(c.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE));// 大小
                int duration = c.getInt(c.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION));// 时长
                long date = c.getLong(c.getColumnIndexOrThrow(MediaStore.Audio.Media.DATE_MODIFIED));//修改时间
                int albumId = c.getInt(c.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID));

                JSONObject obj = new JSONObject();
                obj.put("id",id);
                obj.put("name",name);
                obj.put("url",path);
                obj.put("album",album);
                obj.put("artist",artist);
                obj.put("size",size);
                obj.put("duration",duration);
                obj.put("time",getDateToString(date*1000));
                obj.put("timestamp", date*1000);
                obj.put("albumId",albumId);
                array.put(obj);
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (c != null) {
                c.close();
            }
        }
        File file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
        JSONObject obj = new JSONObject();
        try {
            obj.put("name","音频");
            obj.put("svg","#icon-file_music");
            obj.put("url", file.getPath());
            if(array != null && array.length() != 0) {
                obj.put("child", array);
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return obj;
    }

    // 获取图片
    public JSONObject getImages() {
        // 扫描图片
        Cursor c = null;
        JSONArray array = new JSONArray();
        try {
            c = _c.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null,
                    MediaStore.Images.Media.MIME_TYPE + "= ? or " + MediaStore.Images.Media.MIME_TYPE + "= ?",
                    new String[]{"image/jpeg", "image/png"}, MediaStore.Images.Media.DATE_MODIFIED);
            while (c.moveToNext()) {
                String path = c.getString(c.getColumnIndexOrThrow(MediaStore.Images.Media.DATA));// 路径
//                @SuppressLint("Range") String path = c.getString(c.getColumnIndex(MediaStore.Images.Media.DATA));// 路径
                File parentFile = new File(path).getParentFile();
                if (parentFile == null)
                    continue;
                int id = c.getInt(c.getColumnIndexOrThrow(MediaStore.Images.Media._ID)); // 图片的id
                String name = c.getString(c.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)); // 图片名
                long size = c.getLong(c.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE));// 大小
                long date = c.getLong(c.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_MODIFIED));//修改时间

                JSONObject obj = new JSONObject();
                obj.put("id",id);
                obj.put("name",name);
                obj.put("url",path);
                obj.put("size",size);
                obj.put("time",getDateToString(date*1000));
                obj.put("timestamp", date*1000);
                array.put(obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (c != null) {
                c.close();
            }
        }
        File file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
        JSONObject obj = new JSONObject();
        try {
            obj.put("name","图片");
            obj.put("svg","#icon-file_img");
            obj.put("url", file.getPath());
            if(array != null && array.length() != 0) {
                obj.put("child", array);
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return obj;
    }

    // 获取文档
    public JSONObject getDocuments() {
        File file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
        JSONObject obj = getFileList(file);
        try {
            obj.put("name","文档");
            obj.put("svg","#icon-file1");
            obj.put("isFilter",true);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return obj;
    }

    // 获取下载
    public JSONObject getDownloads() {
        File file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
        JSONObject obj = getFileList(file);
        try {
            obj.put("name","下载");
            obj.put("svg","#icon-file_download");
            obj.put("isFilter",true);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return obj;
    }

    // 获取根文件
    public JSONObject getRoots() {
        File file = Environment.getExternalStorageDirectory();
        return getFileList(file);
    }

    // 获取本地文件
    public JSONObject getLocalStore() {
        JSONObject obj = getRoots();
        try {
            obj.put("name","本地");
            JSONArray array = new JSONArray();

            array.put(getDocuments());
            array.put(getMusics());
            array.put(getVideos());
            array.put(getImages());
            array.put(getDownloads());

            obj.put("typeList", array);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return obj;
    }

    // 获取U盘文件
    public JSONObject getDiskFiles() {
        JSONObject obj = null;
        String path = "/mnt/usb/";
        File file = new File(path);
        File[] files = file.listFiles();
        if(files != null && files.length != 0) {
            obj = getFileList(file);
            try {
                obj.put("name","U盘");
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
        return obj;
    }

    // 获取目录文件下所有文件列表
    private JSONObject getFileList(File file) {
        JSONObject message = new JSONObject();
        try {
            if(file.exists()){
                message.put("name", file.getName());
                message.put("url", file.getPath());
                message.put("timestamp", file.lastModified());
                message.put("time", getDateToString(file.lastModified()));
                message.put("size", file.length());
                File[] list = file.listFiles();
                if(list != null && list.length != 0) {
                    List fileList = Arrays.asList(list);
                    Collections.sort(fileList, (Comparator<File>) (o1, o2) -> {
                        if (o1.isDirectory() && o2.isFile())
                            return -1;
                        if (o1.isFile() && o2.isDirectory())
                            return 1;
                        return o1.getName().compareTo(o2.getName());
                    });
                    JSONArray arr = new JSONArray();
                    for(File item : list) {
                        arr.put(getFileList(item));
                    }
                    message.put("child", arr);
                } else {
                    if(file.isDirectory()) {
                        message.put("child",new JSONArray());
                    }
                }
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return message;
    }

    // File 转 Uri
    public Uri getUriForFile(File file) {
        String packageName = GetDevice.getPackage(_c).packageName;
        Uri contentUri = FileProvider.getUriForFile(_c,packageName+".fileProvider", file);
        return contentUri;
    }

    private String getDateToString(long milSecond) {
        String pattern = "yyyy-MM-dd HH:mm:ss";
        Date date = new Date(milSecond);
        SimpleDateFormat format = new SimpleDateFormat(pattern);
        return format.format(date);
    }

    // 获取视频缩略图
    public Bitmap getVideoThumbnail(int id) {
        Bitmap bitmap = null;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inDither = false;
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        bitmap = MediaStore.Video.Thumbnails.getThumbnail(_c.getContentResolver(), id, MediaStore.Images.Thumbnails.MICRO_KIND, options);
        return bitmap;
    }
}

(2)展示文件列表

通过上面的 FileManage 里面的方法就可以获取到 Android 系统里面大部分文件列表了

展示可以使用 Android 的 Activity 布局展示,这里使用的是 H5 写的文件管理,实现逻辑一致(取到文件列表->展示->选择文件->得到文件URI)。

在 WChromeClient.java 中编写

private ValueCallback<Uri[]> uploadFiles = null;
// 重写选择文件
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
                                    FileChooserParams fileChooserParams) {
    uploadFiles = filePathCallback; // 取到 filePathCallback 回调之后不做处理
    return true;
}

// 文件选择回调(这个方法提供给js调用)
public void resultFileChoose(String json) { // 参数是前端js选择了一项或多项的列表
    Uri[] results = null;
    try {
        JSONArray jsonArray = new JSONArray(json);
        results = new Uri[jsonArray.length()];
        for(int i=0; i<jsonArray.length(); i++) {
            JSONObject obj = jsonArray.getJSONObject(i);
            String url = obj.getString("url");
            File file = new File(url);
            results[i] = getUriForFile(file);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    receiveFile(results);
}

public void receiveFile(Uri[] results) {
    uploadFiles.onReceiveValue(results);
    uploadFiles = null;
}

提供给js的方法:

FileManage fileManage = new FileManage(context); // context 就是 MainActivity 的 this
// 获取本地文件列表
@JavascriptInterface
public String getLocalStore() {
    String str = fileManage.getLocalStore().toString();
    return str;
}

// 获取U盘文件列表
@JavascriptInterface
public String getDiskFiles() {
    JSONObject obj = fileManage.getDiskFiles();
    if(obj!=null) {
        String str = fileManage.getDiskFiles().toString();
        return str;
    }else{
        return "null";
    }
}

// 选择文件
@JavascriptInterface
public void getFileList(String json) {
    wChromeClient.resultFileChoose(json); // resultFileChoose 就是前面 WChromeClient.java 里面的
}

// 取消选择(注意取消选择文件必须置空回调)
@JavascriptInterface
public void cancelFile() { wChromeClient.receiveFile(null); }

js如何调用:

// 获取设备根目录
toAndroid({ methods: 'getLocalStore' }, val => {
    const json = JSON.parse(val);
    this.fileList.push(json); // 文件列表
});

// 获取设备U盘目录
toAndroid({ methods: 'getDiskFiles' }, val => {
    if (val !== 'null') {
    const json = JSON.parse(val);
    this.fileList.push(json);
    }
});

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

推荐阅读更多精彩内容