美文网首页Android
WebView调用原生相册和拍照

WebView调用原生相册和拍照

作者: _Sisyphus | 来源:发表于2017-07-24 10:41 被阅读0次

    关于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(...);
    }

    相关文章

      网友评论

        本文标题:WebView调用原生相册和拍照

        本文链接:https://www.haomeiwen.com/subject/uenvkxtx.html