好久没写博客了,以来因为琐事很多,二来自己也没有特别想写的东西,总觉得知识有点停滞不前,确实略表遗憾。
刚好今天碰到个问题,刚刚搞定,问题是有个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>
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. 要是有什么问题,欢迎留言。