android webview拍照,让 Android 的 WebView 支持 type 为 file 的 input,同时支持拍照

Android 的 WebView 组件默认是不启用 type 为 file 的 input 的,需要在代码中做一些类似 hack 的编码(因为解决问题的目标对象的方法都是加了@hide注解的)才能召唤神龙。

目标对象:WebChromeClient

实例化一个目标对象,并重写它的几个隐藏方法(针对不同的Android系统版本,方法名和入参都不一样,所以方法有多个),然后将目标对象作为参数传递给 WebView 对象的 setWebChromeClient 方法。

目标对象隐藏方法的重写

// For Android 3.0+

public void openFileChooser( ValueCallback uploadMsg, String acceptType )...

// For Android 3.0+

public void openFileChooser(ValueCallback uploadMsg)...

//For Android 4.1

public void openFileChooser(ValueCallback uploadMsg, String acceptType, String capture)...

// For Lollipop 5.0+ Devices

public boolean onShowFileChooser(WebView mWebView, ValueCallback filePathCallback,  WebChromeClient.FileChooserParams fileChooserParams)...

代码如下:

private WebChromeClient mWebChromeClient = new WebChromeClient(){

// For Android 3.0+

@SuppressWarnings({ "rawtypes" })

public void openFileChooser( ValueCallback uploadMsg, String acceptType ) {

vCbFileChooser = uploadMsg;

/*Intent i = new Intent(Intent.ACTION_GET_CONTENT);

i.addCategory(Intent.CATEGORY_OPENABLE);

i.setType("image/*");

MainActivity.this.startActivityForResult(

Intent.createChooser(i,"文件选择"),

FILECHOOSER_RESULTCODE

);*/

selPic();

}

// For Android 3.0+

@SuppressWarnings({ "unused", "rawtypes" })

public void openFileChooser(ValueCallback uploadMsg) {

openFileChooser(uploadMsg, "");

}

//For Android 4.1

@SuppressWarnings({ "unused", "rawtypes" })

public void openFileChooser(ValueCallback uploadMsg, String acceptType, String capture){

openFileChooser(uploadMsg, acceptType);

}

// For Lollipop 5.0+ Devices

@SuppressWarnings("unchecked")

@TargetApi(Build.VERSION_CODES.LOLLIPOP)

public boolean onShowFileChooser(

WebView mWebView, ValueCallback filePathCallback,

WebChromeClient.FileChooserParams fileChooserParams

) {

if (vCbFileChooser != null) {

vCbFileChooser.onReceiveValue(null);

vCbFileChooser = null;

}

vCbFileChooser = filePathCallback;

selPic();

return true;

}

};

在上面的代码中:

1、所有被重写的方法最后都会调用 selPic 方法,这个方法会显示一个对话框,让用户选择是拍照选取照片还是直接从已保存的文件中选取图片。

2、vCbFileChooser 变量维持着向页面回传值的 ValueCallback 对象,直到 onActivityResult。

selPic 方法实现

/**

* 弹出对话框,提示拍照或者选择照片文件

*/

@SuppressWarnings("unused")

protected final void selPic() {

if (!checkSDcard()){return;}

String[] selectPicTypeStr = { "拍照","选择照片" };

AlertDialog alertDialog = new AlertDialog.Builder(this)

.setItems(

selectPicTypeStr,

new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

switch (which) {

case 0://拍照

chkPrivBeforeTakePhoto();

break;

case 1://选择图片文件

choosePicFile();

break;

default:

break;

}

}

}

).setOnCancelListener(

new DialogInterface.OnCancelListener() {

@SuppressWarnings("unchecked")

@Override

public void onCancel(DialogInterface dialog) {

if (null != vCbFileChooser) {

vCbFileChooser.onReceiveValue(null);

vCbFileChooser = null;

}

}

}

).show();

}

上述代码:

1、chkPrivBeforeTakePhoto 方法执行拍照选取流程(之所以这样取名,是因为在拍照之前,还要考虑到Android 6.0以上版本权限系统机制的变化);

2、choosePicFile 方法执行直接从已保存文件中选取图片的流程;

3、如果两中流程都没有,而是执行了取消操作(按下返回键或者点击了界面空白处),那么 vCbFileChooser 变量也必须调用 onReceivValue 方法回传空值,保证type=file的input能反复使用。

4、checkSDcard 方法的作用是在拍照以前判断有没有存储。

/**

* 检查SD卡是否存在

*/

public final boolean checkSDcard() {

boolean flag = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);

if(!flag){Toast.makeText(this, "请插入手机存储卡再使用本功能", Toast.LENGTH_SHORT).show();}

return flag;

}

chkPrivBeforeTakePhoto 方法

private static final int PERMISSIONS_REQUEST_CODE_TAKE_PHOTO = 1;

@SuppressWarnings("unchecked")

private void chkPrivBeforeTakePhoto(){

if(

ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED ||

ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED

) {

if (null != vCbFileChooser) {

vCbFileChooser.onReceiveValue(null);

vCbFileChooser = null;

}

new AlertDialog

.Builder(this)

.setTitle("提示信息")

.setMessage("该功能需要您接受应用对一些关键权限(拍照)的申请,如之前拒绝过,可到手机系统的应用管理授权设置界面再次设置。")

.setPositiveButton("确认", new OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

ActivityCompat.requestPermissions(MainActivity.this, new String[]{

Manifest.permission.CAMERA,

Manifest.permission.WRITE_EXTERNAL_STORAGE

}, PERMISSIONS_REQUEST_CODE_TAKE_PHOTO);

}

})

.show();

} else {

chooseTakePhoto();

}

}

private void chooseTakePhoto(){

pathTakePhoto = Environment.getExternalStorageDirectory().getPath()

+ "/mbossclient/camera/temp/"

+ (System.currentTimeMillis() + ".jpg");

File vFile = new File(pathTakePhoto);

if (!vFile.exists()) {//必须确保文件夹路径存在,否则拍照后无法完成回调

File vDirPath = vFile.getParentFile();

vDirPath.mkdirs();

} else {

if (vFile.exists()) {

vFile.delete();

}

}

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

uriTakePhoto = Uri.fromFile(vFile);

intent.putExtra(MediaStore.EXTRA_OUTPUT, uriTakePhoto);

startActivityForResult(intent, TAKEPHOTO_RESULTCODE);

}

上述代码:

1、Android 6.0 及以上版本都需要就权限进行询问操作;

2、chooseTakePhoto 方法执行实际的拍照流程;

3、TAKEPHOTO_RESULTCODE 用于在 onActivityResult 方法中识别出是执行了拍照选取的流程。

choosePicFile 方法

/**

* 选择文件

*/

private void choosePicFile(){

Intent i = new Intent(Intent.ACTION_GET_CONTENT);

i.addCategory(Intent.CATEGORY_OPENABLE);

i.setType("image/*");

MainActivity.this.startActivityForResult(

Intent.createChooser(i,"文件选择"),

FILECHOOSER_RESULTCODE

);

}

FILECHOOSER_RESULTCODE 用于在onActivityResult方法中识别出是执行了从已保存文件中选取图片文件的流程。

onActivityResult 方法

@SuppressLint("NewApi")

@SuppressWarnings("unchecked")

@Override

protected void onActivityResult(int requestCode, int resultCode, Intent intent) {

if (requestCode == FILECHOOSER_RESULTCODE) {//从文件选择器选择照片

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

if(null == vCbFileChooser) {return;}

vCbFileChooser.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, intent));

vCbFileChooser = null;

} else {

if(null == vCbFileChooser) {return;}

Uri result = (intent == null || resultCode != RESULT_OK)? null:intent.getData();

vCbFileChooser.onReceiveValue(result);

vCbFileChooser = null;

}

} else if(requestCode == TAKEPHOTO_RESULTCODE){

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

if(null == vCbFileChooser) {return;}

if(null == uriTakePhoto) {

vCbFileChooser.onReceiveValue(null);

vCbFileChooser = null;

return;

}

addImageGallery(pathTakePhoto);

Uri[] uris = new Uri[1];

uris[0] = uriTakePhoto;

vCbFileChooser.onReceiveValue(uris);

vCbFileChooser = null;

uriTakePhoto = null;

pathTakePhoto = null;

} else {

if(null == vCbFileChooser) {return;}

if(null == uriTakePhoto) {

vCbFileChooser.onReceiveValue(null);

vCbFileChooser = null;

return;

}

addImageGallery(pathTakePhoto);

vCbFileChooser.onReceiveValue(uriTakePhoto);

vCbFileChooser = null;

uriTakePhoto = null;

pathTakePhoto = null;

}

}

super.onActivityResult(requestCode, resultCode, intent);

}

上述代码:

1、以Android Lollipop版本为届,低于该版本的系统与等于或高于该版本的系统处理方式不一样,表面上看主要是使用API获取uri数据的方法不同;

2、无论取没取到 uri 数据,只要 vCbFileChooser 变量不为空,都必须调用一次 onReceiveValue 方法,而且这之后要将它以及相关变量置为null,以保证type=file的input能反复使用。

3、addImageGallery 方法的作用是将拍照生成的图片(不是缩略图)添加到相册,保证后续还能从系统中索取到。

/**

* 解决拍照后在相册中找不到的问题

*/

private void addImageGallery(String path) {

if (null == path || "".equals(path)) {

return;

}

File file = new File(pathTakePhoto);

ContentValues values = new ContentValues();

values.put(MediaStore.Images.Media.DATA, file.getAbsolutePath());

values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");

getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值