在WebView中调用android系统相册

好久没写博客了,以来因为琐事很多,二来自己也没有特别想写的东西,总觉得知识有点停滞不前,确实略表遗憾。

刚好今天碰到个问题,刚刚搞定,问题是有个web写的代码去调用系统的图片,PC端,IOS端的都没有问题,我用android手机里面的某些浏览器也都是正常的,就是我使用WebView的时候怎么都没有效果。我还以为是我哪个WebView的Setting没有设置好,于是弄了好半天,可是到头来还是没用,一筹莫展了,只能借助搜索工具去看看人家是怎么弄的这个问题。

看了好多人写的,大体上就是重写WebChromClient,但是这块接触的少,有点不太明白。因为思想都不懂,看代码能看出什么?后来找了很多文章,看到了一篇文章大致的讲解了下这个过程,就大致上明白了这个调用的过程。

1. 调用过程

点击WebView中按钮,会触发WebChromeClient中的特定函数,从特定的函数,我们再调用系统的拍照和图片选择功能,得到返回的数据后再通过特定函数中的参数回调到WebView。


2. 代码编写

a.html界面

<html>

	<head>
		<title>test</title>
	</head>

	<body>
		<input type="file" accept="image/*">
	</body>

</html>
b.总体的Activity
package cn.zhoudl.selectimg;

import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebView;

public class MainActivity extends AppCompatActivity {

    private WebView wv;
    private ValueCallback<Uri> mFilePathCallback;
    private ValueCallback<Uri[]> mFilePathCallbackArray;
    private ImagePick ip;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        wv = (WebView) findViewById(R.id.wv);
        wv.getSettings().setAllowFileAccess(true);
        wv.getSettings().setJavaScriptEnabled(true);
        wv.loadUrl("file:///android_asset/test.html");

        wv.setWebChromeClient(new WebChromeClient() {
            // file upload callback (Android 2.2 (API level 8) -- Android 2.3 (API level 10)) (hidden method)
            public void openFileChooser(ValueCallback<Uri> filePathCallback) {
                handle(filePathCallback);
            }

            // file upload callback (Android 3.0 (API level 11) -- Android 4.0 (API level 15)) (hidden method)
            public void openFileChooser(ValueCallback filePathCallback, String acceptType) {
                handle(filePathCallback);
            }

            // file upload callback (Android 4.1 (API level 16) -- Android 4.3 (API level 18)) (hidden method)
            public void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
                handle(filePathCallback);
            }

            // for Lollipop
            @Override
            public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
                // Double check that we don't have any existing callbacks
                if (mFilePathCallbackArray != null) {
                    mFilePathCallbackArray.onReceiveValue(null);
                }
                mFilePathCallbackArray = filePathCallback;
                showDialog();
                return true;
            }

            /**
             * 处理5.0以下系统回调
             * @param filePathCallback
             */
            private void handle(ValueCallback<Uri> filePathCallback) {
                if (filePathCallback != null) {
                    mFilePathCallback.onReceiveValue(null);
                }
                mFilePathCallback = filePathCallback;
                showDialog();
            }

            /**
             * 显示照片选取Dialog
             */
            public void showDialog() {
                if (ip == null) {
                    ip = new ImagePick(MainActivity.this);
                }
                ip.setCancel(new ImagePick.MyDismiss() {
                    @Override
                    public void dismiss() {
                        handleCallback(null);
                    }
                });
                ip.show();
            }
        });

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == RESULT_OK) { // 正常照片选取的时候调用
            ip.onActivityResult(requestCode, resultCode, data, new ImagePick.MyUri() {
                @Override
                public void getUri(Uri uri) {
                    handleCallback(uri);
                }
            });
        } else {
            // 取消了照片选取的时候调用
            handleCallback(null);
        }
    }

    /**
     * 处理WebView的回调
     * @param uri
     */
    private void handleCallback(Uri uri) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            if (mFilePathCallbackArray != null) {
                if (uri != null) {
                    mFilePathCallbackArray.onReceiveValue(new Uri[]{uri});
                } else {
                    mFilePathCallbackArray.onReceiveValue(null);
                }
                mFilePathCallbackArray = null;
            }
        } else {
            if (mFilePathCallback != null) {
                if (uri != null) {
                    mFilePathCallback.onReceiveValue(uri);
                } else {
                    mFilePathCallback.onReceiveValue(null);
                }
                mFilePathCallback = null;
            }
        }
    }
}
c. 图片选取工具
package cn.zhoudl.selectimg;

import android.app.Activity;
import android.app.Dialog;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.view.LayoutInflater;
import android.view.View;
import android.webkit.ValueCallback;
import android.widget.Toast;

import java.io.File;

/**
 * Created by Dara on 2015/8/26 0026.
 */
public class ImagePick implements View.OnClickListener {

    private final int PICK_REQUEST = 0x1001;
    private final int TAKE_REQUEST = 0x1002;

    private static final String imgDir = Environment.getExternalStorageDirectory() + File.separator + "cloud";
    public static String imgFile = imgDir + File.separator + "tmp.jpg";

    private Activity activity;
    private Dialog dialog;

    public interface MyUri {
        void getUri(Uri uri);
    }

    public interface MyDismiss {
        void dismiss();
    }

    private MyDismiss md;

    public ImagePick(Activity activity) {
        this.activity = activity;

        dialog = new Dialog(activity, R.style.DialogStyle);
        View root = LayoutInflater.from(activity).inflate(R.layout.select_img, null);
        // 设置点击监听
        root.findViewById(R.id.take_photo).setOnClickListener(this);
        root.findViewById(R.id.album_photo).setOnClickListener(this);
        root.findViewById(R.id.cancel).setOnClickListener(this);

        dialog.setContentView(root);
    }

    public void setCancel(MyDismiss md) {
        this.md = md;
    }

    /**
     * 显示Dialog
     */
    public void show() {
        dialog.show();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.take_photo:
                takePhoto();
                break;
            case R.id.album_photo:
                getAlbum();
                break;
            case R.id.cancel:
                if (md != null) {
                    md.dismiss();
                }
                break;
        }
        dialog.dismiss();
    }

    /**
     * 拍照
     */
    private void takePhoto() {
        String sdState = Environment.getExternalStorageState();
        // 如果SD卡可读写
        if (sdState.equals(Environment.MEDIA_MOUNTED)) {
            new File(imgDir).mkdirs();
            File file = new File(imgFile);
            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
            activity.startActivityForResult(intent, TAKE_REQUEST);
        } else {
            Toast.makeText(activity, "请确认已经插入SD卡", Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 从相册获取
     */
    private void getAlbum() {
        Intent intent = new Intent(Intent.ACTION_PICK, null);
        intent.setType("image/*");
        activity.startActivityForResult(intent, PICK_REQUEST);
    }

    public void onActivityResult(int requestCode, int resultCode, Intent data, MyUri uri) {
        if (resultCode != activity.RESULT_OK) {
            return;
        }
        if (requestCode == PICK_REQUEST) {
            if (data.getData() != null) {
                uri.getUri(data.getData());
            }
        } else if (requestCode == TAKE_REQUEST) {
            if (data != null) {
                Uri myUri;
                if (data.getData() != null) {
                    uri.getUri(data.getData());
                    return;
                }
                if (data.hasExtra("data")) {
                    Bitmap bitmap = data.getParcelableExtra("data");
                    try {
                        myUri = Uri.parse(MediaStore.Images.Media.insertImage(activity.getContentResolver(), bitmap, null, null));
                        uri.getUri(myUri);
                    } catch (Exception e) {
                        e.printStackTrace();
                        // 大神手机这行代码报异常:MediaStore.Images.Media.insertImage(activity.getContentResolver(), bitmap, null, null)
                        getUri(uri);
                    }
                } else {
                    // Nexus 6 返回Intent{}得不到数据
                    getUri(uri);
                }
            } else {
                getUri(uri);
            }
        }
    }

    private void getUri(MyUri uri) {
        File file = new File(imgFile);
        if (file != null && file.exists()) {
            uri.getUri(Uri.fromFile(file));
        }
    }

    /**
     * 裁剪图片,并按照给定的长宽输出
     *
     * @param activity
     * @param uri          拍照或者选择照片后获得的URI
     * @param outputWidth  输出的宽度
     * @param outputHeight 输出的高度
     * @param requestCode  请求码
     */
    public void cropImg(Activity activity, Uri uri, int outputWidth, int outputHeight, int requestCode) {
        Intent intent = new Intent("com.android.camera.action.CROP");
        intent.setDataAndType(uri, "image/*");
        intent.putExtra("crop", "true");
        intent.putExtra("aspectX", outputWidth);
        intent.putExtra("aspectY", outputHeight);
        intent.putExtra("outputX", outputWidth);
        intent.putExtra("outputY", outputHeight);
        intent.putExtra("outputFormat", "JPEG");
        intent.putExtra("noFaceDetection", true);
        intent.putExtra("return-data", true);
        activity.startActivityForResult(intent, requestCode);
    }
}
d. 图片选取所要的界面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal"
    android:orientation="vertical">

    <TextView
        android:layout_width="200dp"
        android:layout_height="43dp"
        android:gravity="center"
        android:text="请选择上传方式"
        android:textColor="#fff"
        android:background="#f00"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#FFFFFF"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/take_photo"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:gravity="center"
            android:padding="20dp"
            android:text="拍照" />

        <TextView
            android:id="@+id/album_photo"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:padding="20dp"
            android:text="相册"/>
    </LinearLayout>

    <View
        android:layout_width="match_parent"
        android:layout_height="0.33dp"
        android:background="#DDDDDD" />

    <TextView
        android:id="@+id/cancel"
        android:layout_width="match_parent"
        android:layout_height="43dp"
        android:gravity="center"
        android:background="#FF0"
        android:text="取消" />
</LinearLayout>

<!-- Dialog样式 -->
    <style name="DialogStyle" parent="@android:style/Theme.Dialog">
        <item name="android:windowFrame">@null</item>
        <!-- 边框 -->
        <item name="android:windowIsFloating">true</item>
        <!-- 是否浮现在activity之上 -->
        <item name="android:windowIsTranslucent">false</item>
        <!-- 半透明 -->
        <item name="android:windowNoTitle">true</item>
        <!-- 无标题 -->
        <item name="android:windowBackground">@android:color/transparent</item>
        <!-- 自己想要的背景 -->
    </style>


3. 遇到的坑

a. 无论什么操作(取消dialog,取消选取等),最后一定要回调

 private void handleCallback(Uri uri) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            if (mFilePathCallbackArray != null) {
                if (uri != null) {
                    mFilePathCallbackArray.onReceiveValue(new Uri[]{uri});
                } else {
                    mFilePathCallbackArray.onReceiveValue(null);
                }
                mFilePathCallbackArray = null;
            }
        } else {
            if (mFilePathCallback != null) {
                if (uri != null) {
                    mFilePathCallback.onReceiveValue(uri);
                } else {
                    mFilePathCallback.onReceiveValue(null);
                }
                mFilePathCallback = null;
            }
        }
    }

这部分代码,否则webview会被阻塞,造成点击不了按钮选取图片。


b. 防止这部分代码:

 // file upload callback (Android 2.2 (API level 8) -- Android 2.3 (API level 10)) (hidden method)
            public void openFileChooser(ValueCallback<Uri> filePathCallback) {
                handle(filePathCallback);
            }

            // file upload callback (Android 3.0 (API level 11) -- Android 4.0 (API level 15)) (hidden method)
            public void openFileChooser(ValueCallback filePathCallback, String acceptType) {
                handle(filePathCallback);
            }

            // file upload callback (Android 4.1 (API level 16) -- Android 4.3 (API level 18)) (hidden method)
            public void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
                handle(filePathCallback);
            }

            // for Lollipop
            @Override
            public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
                // Double check that we don't have any existing callbacks
                if (mFilePathCallbackArray != null) {
                    mFilePathCallbackArray.onReceiveValue(null);
                }
                mFilePathCallbackArray = filePathCallback;
                showDialog();
                return true;
            }

多次调用的情况,这四个函数就是我们文章最开始所讲的特定函数,分别对应了不同的android版本。其中所有的形参filePathCallback都是我们最后要回调。可以参考上一个代码块。

c. 混淆打包的时候要注意不要混淆openFileChooser这个函数,否则会造成release版本无法调用的情况,这个函数是隐藏函数,所以之前理解这块的时候,还从父类里去找,但是发现找不到,对当时的理解造成了困惑。

d. 要是有什么问题,欢迎留言。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值