让人闹心的H5(Android与H5交互)
2018-11-12 本文已影响0人
mt大山
Android在开发过程中避免不了会与H5进行交互。在开发过程中有一个需要在H5中调用相机和相册来选取相片的功能,所以在此记录一下。
我的项目中使用的是腾讯的X5内核,链接如下:
腾讯X5内核
先来一个Android与Html交互的小栗子
先在main文件下创建Directory取名assets
image.png
html代码:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<input type="text" id="tv">
<input type="button" id="btn" onclick="giveAndroid()" value="给安卓发数据啦">
<script type="text/javascript">
function showAndroid(msg){
alert(msg);
}
function giveAndroid(){
var msg = document.getElementById("tv").value;
Android.show(msg);
}
</script>
</body>
</html>
Activity的代码
WebSettings webSettings = web.getSettings();
webSettings.setUseWideViewPort(true);
webSettings.setLoadWithOverviewMode(true);
webSettings.setDomStorageEnabled(true);
webSettings.setDefaultTextEncodingName("UTF-8");
webSettings.setAllowContentAccess(true); // 是否可访问Content Provider的资源,默认值 true
webSettings.setAllowFileAccess(true); // 是否可访问本地文件,默认值 true
//开启JavaScript支持
webSettings.setJavaScriptEnabled(true);
// 支持缩放
webSettings.setSupportZoom(true);
web.setWebViewClient(new WebViewClient());
web.setWebChromeClient(new WebChromeClient());
web.loadUrl("file:///android_asset/test.html");
web.addJavascriptInterface(TwoActivity.this,"Android");
}
//一定要加 方法名字必须是public的
@JavascriptInterface
public void show(String msg){
Toast.makeText(this,msg,Toast.LENGTH_SHORT).show();
};
@OnClick(R.id.btn)
public void onViewClicked() {
web.loadUrl("javascript:showAndroid('"+edit.getText().toString()+"');");
}
Activity的布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toLeftOf="@+id/btn" />
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="给html发数据" />
<WebView
android:id="@+id/web"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/btn"></WebView>
</RelativeLayout>
上我自己要做的需求
1、新建一个xml
image.png
2、在Activity应用
@ContentView(R.layout.activity_rights_and_interests_aty)
public class RightsAndInterestsAty extends BActivity {
@ViewInject(R.id.web)
private WebView webView;
@ViewInject(R.id.progressBar1)
private ProgressBar progressBar1;
private ValueCallback<Uri> mUploadMessage;
private ValueCallback<Uri[]> mUploadCallbackAboveL;
private final static int FILECHOOSER_RESULTCODE = 1;
private Dialog dialog;
private View view;
@Override
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_CANCELED) {
onshowPopAgain();
return;
}
if (requestCode == FILECHOOSER_RESULTCODE) {
if (null == mUploadMessage && null == mUploadCallbackAboveL) return;
if (data == null) {
Uri result = Uri.fromFile(fileUri);
if (mUploadCallbackAboveL != null) {
onActivityResultAboveL(requestCode, resultCode, data);
} else if (mUploadMessage != null) {
mUploadMessage.onReceiveValue(result);
mUploadMessage = null;
}
} else {
Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
if (mUploadCallbackAboveL != null) {
onActivityResultAboveL(requestCode, resultCode, data);
} else if (mUploadMessage != null) {
mUploadMessage.onReceiveValue(result);
mUploadMessage = null;
}
}
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void onActivityResultAboveL(int requestCode, int resultCode, Intent data) {
if (requestCode != FILECHOOSER_RESULTCODE || mUploadCallbackAboveL == null) {
return;
}
Uri[] results = null;
if (resultCode == Activity.RESULT_OK) {
if (data == null) {
results = new Uri[]{imageUri};
} else {
String dataString = data.getDataString();
ClipData clipData = data.getClipData();
if (clipData != null) {
results = new Uri[clipData.getItemCount()];
for (int i = 0; i < clipData.getItemCount(); i++) {
ClipData.Item item = clipData.getItemAt(i);
results[i] = item.getUri();
}
}
if (dataString != null)
results = new Uri[]{Uri.parse(dataString)};
}
}
mUploadCallbackAboveL.onReceiveValue(results);
mUploadCallbackAboveL = null;
return;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mActionBar.hide();
WebSettings webSettings = this.webView.getSettings();
//将图片调整到适合webView大小
webSettings.setUseWideViewPort(true);
// 缩放至屏幕的大小
webSettings.setLoadWithOverviewMode(true);
// 设置DOM Storage缓存
webSettings.setDomStorageEnabled(true);
//设置编码格式
webSettings.setDefaultTextEncodingName("UTF-8");
// 是否可访问Content Provider的资源,默认值 true
webSettings.setAllowContentAccess(true);
// 是否可访问本地文件,默认值 true
webSettings.setAllowFileAccess(true);
//开启JavaScript支持
webSettings.setJavaScriptEnabled(true);
//支持缩放,默认为true。
webSettings.setSupportZoom(true);
// 设置webview无缓存
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
//图片不显示
webSettings.setBlockNetworkImage(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
webSettings.setMixedContentMode(0);
}
webView.setWebViewClient(new WebViewClient() {
//加载完成调用
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
}
//开始加载调用
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
});
webView.loadUrl("url");
webView.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress == 100) {
progressBar1.setVisibility(View.GONE);//加载完网页进度条消失
} else {
progressBar1.setVisibility(View.VISIBLE);//开始加载网页时显示进度条
progressBar1.setProgress(newProgress);//设置进度值
}
}
//当js的input的type属性,属性值为file时
// For Android 3.0+
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
mUploadMessage = uploadMsg;
show();
}
// For Android 3.0+
public void openFileChooser(ValueCallback uploadMsg, String acceptType) {
mUploadMessage = uploadMsg;
show();
}
//For Android 4.1
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
mUploadMessage = uploadMsg;
show();
}
// For Android 5.0+
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
show();
mUploadCallbackAboveL = filePathCallback;
return true;
}
});
}
public static final String BASE_PATH = Environment.getExternalStorageDirectory().getPath() + File.separator + "kefuimg/";
private File fileUri = null;
private Uri imageUri;
/**
* 拍照
*/
private void takePhoto() {
fileUri = new File(BASE_PATH, System.currentTimeMillis() + ".jpg");
if (!fileUri.getParentFile().exists()) {
fileUri.getParentFile().mkdirs();
} else {
deleteAllFiles(new File(BASE_PATH));
}
imageUri = Uri.fromFile(fileUri);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
imageUri = FileProvider.getUriForFile(RightsAndInterestsAty.this, getPackageName() + ".fileprovider", fileUri);//通过FileProvider创建一个content类型的Uri
}
PhotoUtils.takePicture(RightsAndInterestsAty.this, imageUri, FILECHOOSER_RESULTCODE);
}
private void deleteAllFiles(File root) {
File files[] = root.listFiles();
if (files != null)
for (File f : files) {
if (f.isDirectory()) { // 判断是否为文件夹
deleteAllFiles(f);
try {
f.delete();
} catch (Exception e) {
}
} else {
if (f.exists()) { // 判断是否存在
deleteAllFiles(f);
try {
f.delete();
} catch (Exception e) {
}
}
}
}
}
private void getPhoto() {
fileUri = new File(BASE_PATH, System.currentTimeMillis() + ".jpg");
if (!fileUri.getParentFile().exists()) {
fileUri.getParentFile().mkdirs();
}
imageUri = Uri.fromFile(fileUri);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
imageUri = FileProvider.getUriForFile(RightsAndInterestsAty.this, getPackageName() + ".fileprovider", fileUri);//通过FileProvider创建一个content类型的Uri
}
PhotoUtils.openPic(this, FILECHOOSER_RESULTCODE);
}
public void show() {
dialog = new Dialog(this, R.style.ActionSheetDialogStyle);
//填充对话框的布局
view = LayoutInflater.from(this).inflate(R.layout.head_popupwindows, null);
TextView bt1 = view.findViewById(R.id.item_popupwindows_camera);
TextView bt2 = view
.findViewById(R.id.item_popupwindows_Photo);
TextView bt3 = view
.findViewById(R.id.item_popupwindows_cancel);
bt1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
takePhoto();
}
});
bt2.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
dialog.dismiss();
getPhoto();
}
});
bt3.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
dialog.dismiss();
onshowPopAgain();
}
});
//初始化控件
//将布局设置给Dialog
dialog.setContentView(view);
dialog.setCanceledOnTouchOutside(false);
//获取当前Activity所在的窗体
Window dialogWindow = dialog.getWindow();
//设置Dialog从窗体底部弹出
dialogWindow.setGravity(Gravity.BOTTOM);
dialogWindow.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
//获得窗体的属性
WindowManager.LayoutParams lp = dialogWindow.getAttributes();
// 将属性设置给窗体
dialogWindow.setAttributes(lp);
dialog.show();//显示对话框
dialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
@Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
onshowPopAgain();
}
return false;
}
});
}
//解决点击按钮一次时 在此点击失效问题
private void onshowPopAgain() {
if (mUploadCallbackAboveL != null) {
mUploadCallbackAboveL.onReceiveValue(null);
mUploadCallbackAboveL = null;
return;
}
if (mUploadMessage != null) {
mUploadMessage.onReceiveValue(null);
mUploadMessage = null;
return;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (webView != null) {
webView.setWebViewClient(null);
webView.setWebChromeClient(null);
webView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
webView.clearHistory();
webView.destroy();
webView = null;
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
if (webView.canGoBack()) {
webView.goBack();
}
} else {
finish();
}
}
return true;
}
}
说明
WebViewClient就是帮助WebView处理各种通知、请求事件的,具体来说包括:
onLoadResource
onPageStart
onPageFinish
onReceiveError
onReceivedHttpAuthRequest
WebChromeClient是辅助WebView处理Javascript的对话框,网站图标,网站title,加载进度等
onCloseWindow(关闭WebView)
onCreateWindow()
onJsAlert (WebView上alert是弹不出来东西的,需要定制你的WebChromeClient处理弹出)
onJsPrompt
onJsConfirm
onProgressChanged
onReceivedIcon
onReceivedTitle
隐藏弹窗动画
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="200"
android:fromYDelta="0"
android:toYDelta="100%"
/>
显示弹窗动画
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="200"
android:fromYDelta="100%"
android:toYDelta="0"
/>
dialog样式
<style name="ActionSheetDialogStyle" parent="@android:style/Theme.Dialog">
<!-- 背景透明 -->
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowContentOverlay">@null</item>
<!-- 浮于Activity之上 -->
<item name="android:windowIsFloating">true</item>
<!-- 边框 -->
<item name="android:windowFrame">@null</item>
<!-- Dialog以外的区域模糊效果 -->
<item name="android:backgroundDimEnabled">true</item>
<!-- 无标题 -->
<item name="android:windowNoTitle">true</item>
<!-- 半透明 -->
<item name="android:windowIsTranslucent">true</item>
<!-- Dialog进入及退出动画 -->
<item name="android:windowAnimationStyle">@style/ActionSheetDialogAnimation</item>
</style>
<!-- ActionSheet进出动画 -->
<style name="ActionSheetDialogAnimation" parent="@android:style/Animation.Dialog">
<item name="android:windowEnterAnimation">@anim/show</item>
<item name="android:windowExitAnimation">@anim/out</item>
</style>
3、photoUtils
public class PhotoUtils {
private static final String TAG = "PhotoUtils";
/**
* @param activity 当前activity
* @param imageUri 拍照后照片存储路径
* @param requestCode 调用系统相机请求码
*/
public static void takePicture(Activity activity, Uri imageUri, int requestCode) {
//调用系统相机
Intent intentCamera = new Intent();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intentCamera.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //添加这一句表示对目标应用临时授权该Uri所代表的文件
}
intentCamera.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
//将拍照结果保存至photo_file的Uri中,不保留在相册中
intentCamera.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
if (activity != null) {
activity.startActivityForResult(intentCamera, requestCode);
}
}
/**
* @param activity 当前activity
* @param requestCode 打开相册的请求码
*/
public static void openPic(Activity activity, int requestCode) {
Intent photoPickerIntent = new Intent(Intent.ACTION_GET_CONTENT);
photoPickerIntent.setType("image/*");
activity.startActivityForResult(photoPickerIntent, requestCode);
}
/**
* @param activity 当前activity
* @param orgUri 剪裁原图的Uri
* @param desUri 剪裁后的图片的Uri
* @param aspectX X方向的比例
* @param aspectY Y方向的比例
* @param width 剪裁图片的宽度
* @param height 剪裁图片高度
* @param requestCode 剪裁图片的请求码
*/
public static void cropImageUri(Activity activity, Uri orgUri, Uri desUri, int aspectX, int aspectY, int width, int height, int requestCode) {
Intent intent = new Intent("com.android.camera.action.CROP");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
intent.setDataAndType(orgUri, "image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", aspectX);
intent.putExtra("aspectY", aspectY);
intent.putExtra("outputX", width);
intent.putExtra("outputY", height);
intent.putExtra("scale", true);
//将剪切的图片保存到目标Uri中
intent.putExtra(MediaStore.EXTRA_OUTPUT, desUri);
intent.putExtra("return-data", false);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true);
activity.startActivityForResult(intent, requestCode);
}
/**
* 读取uri所在的图片
*
* @param uri 图片对应的Uri
* @param mContext 上下文对象
* @return 获取图像的Bitmap
*/
public static Bitmap getBitmapFromUri(Uri uri, Context mContext) {
try {
// Bitmap bitmap = MediaStore.Images.Media.getBitmap(mContext.getContentResolver(), uri);
Bitmap bitmapFormUri = getBitmapFormUri(mContext, uri);
return bitmapFormUri;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 通过uri获取图片并进行压缩
*
* @param uri
*/
public static Bitmap getBitmapFormUri(Context ac, Uri uri) throws FileNotFoundException, IOException {
InputStream input = ac.getContentResolver().openInputStream(uri);
BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
onlyBoundsOptions.inJustDecodeBounds = true;
onlyBoundsOptions.inDither = true;//optional
onlyBoundsOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional
BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
input.close();
int originalWidth = onlyBoundsOptions.outWidth;
int originalHeight = onlyBoundsOptions.outHeight;
if ((originalWidth == -1) || (originalHeight == -1)) {
return null;
}
//图片分辨率以480x800为标准
float hh = 800f;//这里设置高度为800f
float ww = 480f;//这里设置宽度为480f
//缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
int be = 1;//be=1表示不缩放
if (originalWidth > originalHeight && originalWidth > ww) {//如果宽度大的话根据宽度固定大小缩放
be = (int) (originalWidth / ww);
} else if (originalWidth < originalHeight && originalHeight > hh) {//如果高度高的话根据宽度固定大小缩放
be = (int) (originalHeight / hh);
}
if (be <= 0) {
be = 1;
}
//比例压缩
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
bitmapOptions.inSampleSize = be;//设置缩放比例
bitmapOptions.inDither = true;//optional
bitmapOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional
input = ac.getContentResolver().openInputStream(uri);
Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);
input.close();
return compressImage(bitmap);//再进行质量压缩
}
/**
* 质量压缩方法
*
* @param image
* @return
*/
public static Bitmap compressImage(Bitmap image) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
int options = 100;
while (baos.toByteArray().length / 1024 > 100) { //循环判断如果压缩后图片是否大于100kb,大于继续压缩
baos.reset();//重置baos即清空baos
//第一个参数 :图片格式 ,第二个参数: 图片质量,100为最高,0为最差 ,第三个参数:保存压缩后的数据的流
image.compress(Bitmap.CompressFormat.JPEG, options, baos);//这里压缩options%,把压缩后的数据存放到baos中
options -= 10;//每次都减少10
}
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把压缩后的数据baos存放到ByteArrayInputStream中
Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);//把ByteArrayInputStream数据生成图片
return bitmap;
}
/**
* @param context 上下文对象
* @param uri 当前相册照片的Uri
* @return 解析后的Uri对应的String
*/
@SuppressLint("NewApi")
public static String getPath(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
String pathHead = "file:///";
// 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 pathHead + Environment.getExternalStorageDirectory() + "/" + split[1];
}
}
// 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 pathHead + 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 pathHead + getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
return pathHead + getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return pathHead + 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.
*/
private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
Cursor cursor = null;
final String column = "_data";
final String[] projection = {column};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
if (cursor != null && cursor.moveToFirst()) {
final int column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return null;
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
private static 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.
*/
private static 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.
*/
private static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
}
调用Android的相机实在是坑,因为测试机有限,所以还不知道会有什么问题先记录到这吧。
最后非常感谢这篇博客,给了我很大的帮助。
深坑之Webview,解决H5调用android相机拍照和录像