input 标签在Android中的使用小记
最近遇到个需求,html页面有个<input type="file" />
标签需要调用客户端的拍照跟选择照片功能,在iOS上面可以正常调起拍照,选择照片。但是在Android上却没有反应,当然这也是我意料之中的事情。
遇到问题当然就想办法去解决了,我先打开了biying搜索,全都是爬的别人写的博客,千篇一律没有任何价值,于是上了stackoverflow,找到了一个这个回答 。接下来我打开了webChormeClient(sdk25)的源码,看到了这个方法,当页面的input标签点击之后,我们可以拦截,然后自己处理并将数据返回,返回什么样的数据格式呢?当时我也在想这个问题,后来看到了这个得到了答案,
base64
。
/**
* Tell the client to show a file chooser.
*
* This is called to handle HTML forms with 'file' input type, in response to the
* user pressing the "Select File" button.
* To cancel the request, call <code>filePathCallback.onReceiveValue(null)</code> and
* return true.
*
* @param webView The WebView instance that is initiating the request.
* @param filePathCallback Invoke this callback to supply the list of paths to files to upload,
* or NULL to cancel. Must only be called if the
* <code>showFileChooser</code> implementations returns true.
* @param fileChooserParams Describes the mode of file chooser to be opened, and options to be
* used with it.
* @return true if filePathCallback will be invoked, false to use default handling.
*
* @see FileChooserParams
*/
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
FileChooserParams fileChooserParams) {
return false;
}
思路:
- 当监控到html页面的input标签点击的时候由我们自己弹出对话框让用户选择拍照,选择照片等
- 用户拍照选择照片处理完成之后转成base64后将数据返回前端
- 前端拿到客户端返回的base64数据显示在对应的控件上
Html的处理
Html代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<title></title>
<style>
.header {
height: 44px;
line-height: 44px;
text-align: center;
}
#capture_img {
width: 200px;
height: 200px;
}
#video {
width: 100%;
height: 200px;
}
</style>
</head>
<body>
<header class="header">
<input type="file" accept="image/*" capture="camera" />
</header>
<img id="capture_img" />
<video id="video" controls="controls" />
</body>
<script>
//***Android端拍照、选择照片之后会调用这个方法***
function onCaptureFinished(base64String) {
var img = document.getElementById('capture_img');
if(base64String) {
img.src = 'data:image/png;base64,' + base64String;
}
}
//***Android端录像之后调用这个方法***
function onRecordFinished(base64String) {
var video = document.getElementById('video');
video.src = "data:video/mp4;base64," + base64String;
setTimeout(function() {
video.play();
}, 1000);
}
</script>
</html>
Android的处理
- 初始化WebView
private void initWebView() {
mWebView = (WebView) findViewById(R.id.wv_content);
WebSettings mWebSettings = mWebView.getSettings();
mWebSettings.setAppCacheEnabled(false);//禁止APP缓存
mWebSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);//禁止网页缓存
mWebSettings.setJavaScriptEnabled(true);
mWebSettings.setSupportZoom(false);//不允许缩放
mWebSettings.setAllowFileAccess(true);
mWebView.setWebViewClient(new WebViewClient());
mWebView.setWebChromeClient(new FileWebChromeClient());
jsObject = new JavaScriptObject(this);
//这里是我们要执行js方法的对象
mWebView.addJavascriptInterface(jsObject, "qfxl");
mWebView.loadUrl("file:///android_asset/index.html");
}
- 在使用webView加载这个网页之后重写WebChromeClient中的onShowFileChooser方法
class FileWebChromeClient extends WebChromeClient {
// 当页面有input type=file 标签的时候会调用这个方法
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
WebChromeClient.FileChooserParams fileChooserParams) {
//这里弹框让用户选择拍照录像选择照片等
showFileChooser();
return false;
}
}
- 监听到Html的input事件,弹窗
private void showFileChooser() {
String[] selectPicTypeStr = {"拍照", "录像", "从相册中选"};
AlertDialog mAlertDialog = new AlertDialog.Builder(MainActivity.this)
.setItems(selectPicTypeStr,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
switch (which) {
// 拍照
case 0:
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
File file = new File(IMG_PATH);
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
startActivityForResult(captureIntent, CAPTURE_TAG);
break;
//录像
case 1:
File videoFile = new File(VIDEO_PATH);
if (!videoFile.exists()) {
try {
videoFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
Intent videoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
videoIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(videoFile));
startActivityForResult(videoIntent, CAMERA_TAG);
break;
// 手机相册
case 2:
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
startActivityForResult(Intent.createChooser(i, "选择您要添加的照片"), CHOOSE_TAG);
break;
default:
break;
}
}
}).setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialogInterface) {
//DO NOTHING
}
}).show();
}
- 处理拍照录像选择照片的回调onActivityResult
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case CAPTURE_TAG://拍照
if (data != null) {//指定了uri则data 为空
} else {
Bitmap bitmap = BitmapFactory.decodeFile(IMG_PATH);
if (bitmap != null) {
//调用js方法,将Bitmap转成base64String
jsObject.onCaptureFinished(mWebView, castBitmapToBase64(bitmap));
} else {
Toast.makeText(MainActivity.this, "bitmap is null", Toast.LENGTH_SHORT).show();
}
}
break;
case CAMERA_TAG://录像
if (data != null) {
File file = new File(VIDEO_PATH);
if (file.exists()) {
try {
FileInputStream fin = new FileInputStream(file);
byte[] b = new byte[(int) file.length()];
fin.read(b);
fin.close();
String base64String = Base64.encodeToString(b, Base64.DEFAULT);
//调用js方法
jsObject.onRecordFinished(mWebView, base64String);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
break;
case CHOOSE_TAG://从相册中选
if (data != null) {
Uri uri = data.getData();
try {
Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uri);
//调用js方法
jsObject.onCaptureFinished(mWebView, castBitmapToBase64(bitmap));
} catch (IOException e) {
e.printStackTrace();
}
}
break;
}
}
- JavaScriptObject
public class JavaScriptObject {
private Context context;
public JavaScriptObject(Context context) {
this.context = context;
}
/**
*
* 拍照或者选择照片结束
* @param webview
* @param base64String
*/
public void onCaptureFinished(WebView webview, String base64String) {
webview.loadUrl("javascript:onCaptureFinished('" + base64String + "')");
}
/**
* 录屏结束
* @param webView
*/
public void onRecordFinished(WebView webView,String base64String){
webView.loadUrl("javascript:onRecordFinished('" + base64String + "')");
}
}
完整代码
public class MainActivity extends AppCompatActivity {
private WebView mWebView;
private JavaScriptObject jsObject;
private final int CAPTURE_TAG = 1;
private final int CAMERA_TAG = 2;
private final int CHOOSE_TAG = 3;
private final String IMG_PATH = Environment.getExternalStorageDirectory() + File.separator + "upload.jpg";
private final String VIDEO_PATH = Environment.getExternalStorageDirectory() + File.separator + "upload.mp4";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initWebView();
}
private void initWebView() {
mWebView = (WebView) findViewById(R.id.wv_content);
WebSettings mWebSettings = mWebView.getSettings();
mWebSettings.setAppCacheEnabled(false);//禁止APP缓存
mWebSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);//禁止网页缓存
mWebSettings.setJavaScriptEnabled(true);
mWebSettings.setSupportZoom(false);//不允许缩放
mWebSettings.setAllowFileAccess(true);
mWebView.setWebViewClient(new WebViewClient());
mWebView.setWebChromeClient(new FileWebChromeClient());
jsObject = new JavaScriptObject(this);
mWebView.addJavascriptInterface(jsObject, "qfxl");
mWebView.loadUrl("file:///android_asset/index.html");
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case CAPTURE_TAG://拍照
if (data != null) {//指定了uri则data 为空
} else {
Bitmap bitmap = BitmapFactory.decodeFile(IMG_PATH);
if (bitmap != null) {
jsObject.onCaptureFinished(mWebView, castBitmapToBase64(bitmap));
} else {
Toast.makeText(MainActivity.this, "bitmap is empty", Toast.LENGTH_SHORT).show();
}
}
break;
case CAMERA_TAG://录像
if (data != null) {
File file = new File(VIDEO_PATH);
if (file.exists()) {
try {
FileInputStream fin = new FileInputStream(file);
byte[] b = new byte[(int) file.length()];
fin.read(b);
fin.close();
String base64String = Base64.encodeToString(b, Base64.DEFAULT);
jsObject.onRecordFinished(mWebView, base64String);
Toast.makeText(MainActivity.this, "finished", Toast.LENGTH_SHORT).show();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
break;
case CHOOSE_TAG://从相册中选
if (data != null) {
Uri uri = data.getData();
try {
Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uri);
jsObject.onCaptureFinished(mWebView, castBitmapToBase64(bitmap));
} catch (IOException e) {
e.printStackTrace();
}
}
break;
}
}
/**
* 将bitmap转成base64
*
* @param bitmap
* @return
*/
private String castBitmapToBase64(Bitmap bitmap) {
String base64String = "";
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 30, bos);
byte[] bitmapBytes = bos.toByteArray();
bos.flush();
bos.close();
base64String = Base64.encodeToString(bitmapBytes, Base64.DEFAULT);
} catch (IOException e) {
e.printStackTrace();
}
return base64String;
}
class FileWebChromeClient extends WebChromeClient {
// 当页面有input type=file 标签的时候会调用这个方法
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
WebChromeClient.FileChooserParams fileChooserParams) {
showFileChooser();
return false;
}
}
private void showFileChooser() {
String[] selectPicTypeStr = {"拍照", "录像", "从相册中选"};
AlertDialog mAlertDialog = new AlertDialog.Builder(MainActivity.this)
.setItems(selectPicTypeStr,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
switch (which) {
// 拍照
case 0:
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
File file = new File(IMG_PATH);
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
startActivityForResult(captureIntent, CAPTURE_TAG);
break;
//录像
case 1:
File videoFile = new File(VIDEO_PATH);
if (!videoFile.exists()) {
try {
videoFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
Intent videoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
videoIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(videoFile));
startActivityForResult(videoIntent, CAMERA_TAG);
break;
// 手机相册
case 2:
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
startActivityForResult(Intent.createChooser(i, "选择您要添加的照片"), CHOOSE_TAG);
break;
default:
break;
}
}
}).setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialogInterface) {
//DO NOTHING
}
}).show();
}
}
流程梳理一下:
- 监听到html的file事件
- 客户端弹出对话框让用户选择具体操作
- 处理用户操作的回调
- 将回调的数据返回给html
总结
博客不能停止更新,好记性不如烂笔头。