WebView的一些神坑
最近在开发过程中遇到一个问题,主要是WebView页面,需要调用本地相机拍照及图库,遇到一系列的神坑,这里结合本人所查阅的资料给大家一一说明。
进入正题,首先来了解webview,这里我分享两篇大佬的博客
本人也是首次使用H5页面调用相机及图库,本以为只要将页面展示就可以,其他的都是前端的事情,我还能偷偷懒,可是在测试的时候就遇到了
第一个神坑:权限的添加
Android 6.0以后对于一些危险权限都需要动态的添加权限,这些对于大家来说都已经很容易实现了,大家可参考:
https://blog.youkuaiyun.com/m0_37959831/article/details/77854548
但是,如果你已经添加了权限,调用相机之后,文件回调不了,其实根本就是没有回调.这个时候你要去检查你的webview的Settings了关键代码,是否给足了权限;
WebSettings settings = webView.getSettings();
settings.setUseWideViewPort(true);
settings.setLoadWithOverviewMode(true);
settings.setDomStorageEnabled(true);
settings.setDefaultTextEncodingName("UTF-8");
settings.setAllowContentAccess(true); // 是否可访问Content Provider的资源,默认值 true
settings.setAllowFileAccess(true); // 是否可访问本地文件,默认值 true
// 是否允许通过file url加载的Javascript读取本地文件,默认值 false
settings.setAllowFileAccessFromFileURLs(false);
// 是否允许通过file url加载的Javascript读取全部资源(包括文件,http,https),默认值 false
settings.setAllowUniversalAccessFromFileURLs(false);
//开启JavaScript支持
settings.setJavaScriptEnabled(true);
// 支持缩放
settings.setSupportZoom(true);
其中
settings.setDomStorageEnabled(true);
这个方法当时我没加,血崩了一次;
第二个神坑:Android端 webview根本不能让h5自己调用,ios是可以的。
这里是为什么呢?查了资料发现Android端H5页面对于相机及图库的调用有一套专门的代码:这里给大家推荐两篇文章:
https://blog.youkuaiyun.com/m0_37959831/article/details/77854548
https://www.cnblogs.com/nmdzwps/p/5841509.html
大家啊先参考文档,最后我会贴上本人的代码供大家参考
第三个神坑:WebchromClient的openFileChooser()只调用了一次
首先了解为什么这个方法只调用了一次,看这篇博客就可
Android开发深入理解WebChromeClient之onShowFileChooser或openFileChooser使用说明
看了他基本你就了解怎么回事了
第四个神坑 :android 7.0的FileProvider的坑
看洪阳大佬的这篇博客基本就了解了
Android 7.0 行为变更 通过FileProvider在应用间共享文件吧
第五个神坑:WebView软键盘冲突
对于H5页面中的输入框与软件盘冲突的问题,或者输入框被软键盘遮盖问题,相信大部分人都遇到过,这里就和大家说下解决方案:
第六个神坑:H5页面加载地图
https://blog.youkuaiyun.com/jyz_2015/article/details/52776195
https://blog.youkuaiyun.com/csdndouniwan/article/details/51159901
贴上自己的代码:
public class WebViewInstallationActivity extends BaseActivity {
// 定位
private static final int REQ_SET_LOCATION = 300;
private static final int MY_PERMISSIONS_REQUEST_CALL_PHONE = 1;//打电话
private WebView webView;
private String dataMap;
private String order_type;
private String out_trade_no;
private int code;
private String userid;
private String installer_phone;
//H5调用Android 相册 相机的
private ValueCallback<Uri> mUploadMessage;// 表单的数据信息
private ValueCallback<Uri[]> mUploadCallbackAboveL;
private final static int FILECHOOSER_RESULTCODE = 1;// 表单的结果回调</span>
private Uri imageUri;
private WaitDialog waitDialog;
private String h5_notice_url;
private String url;
private String gobackurl;
private Uri bitmap2uri;
@Override
public int getLayoutResId() {
waitDialog = new WaitDialog(activity, "", false, null);
String user_id = SpUtils.getUserId(activity);
h5_notice_url = getIntent().getStringExtra("h5_notice_url");
if (StringUtils.isEmpty(h5_notice_url)) {
if (StringUtils.isEmpty(user_id)) {
user_id = "";
}
url = ServiceApi.getWebView(user_id);
} else if (!StringUtils.isEmpty(h5_notice_url)) {
url = h5_notice_url;
}
return R.layout.activity_webview;
}
@Override
protected void initView() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
webView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);//支持js
webSettings.setAppCacheEnabled(true);
webSettings.setDomStorageEnabled(true);//自适应屏幕
webSettings.setLoadWithOverviewMode(true);
webSettings.setUseWideViewPort(false); //将图片调整到适合webview的大小
webSettings.setSupportZoom(false); // 设置可以支持缩放
webSettings.setBuiltInZoomControls(false); //设置出现缩放工具
webSettings.setTextZoom(100); //禁止缩放,按照百分百显示
//缓存模式
boolean networkAvailable = MySystemUtils.isNetworkAvailable(activity);
if (networkAvailable) {
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT); //缓存模式
} else {
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //缓存模式
}
webSettings.setAllowContentAccess(true); // 是否可访问Content Provider的资源,默认值 true
webSettings.setAllowFileAccess(true); //设置可以访问本地文件
webSettings.setNeedInitialFocus(true); //当webview调用requestFocus时为webview设置节点
webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持通过JS打开新窗口
//启用数据库
webSettings.setDatabaseEnabled(true);
String dir = this.getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
//webview定位相关设置
webSettings.setGeolocationEnabled(true);
//设置定位的数据库路径
webSettings.setGeolocationDatabasePath(dir);
webView.setWebChromeClient(new MyWebChromeClient());
webView.setWebViewClient(new MyWebViewClient());//对webview页面加载管理、如url重定向
}
@Override
protected void initListener() {
}
@Override
protected void initData() {
//添加定位权限 相机图库权限
initPermission();
}
private void initPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//SDK>=23
if (!(checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED)) {
if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_COARSE_LOCATION)) {
Toast.makeText(this, "请确认是否开启定位的相关权限", Toast.LENGTH_LONG).show();
MySystemUtils.goToSetPermission(activity, "在设置-应用-权限中开启定位的权限,以保证功能的正常使用", REQ_SET_LOCATION);
} else {
requestPermissions(new String[]{Manifest.permission.CHANGE_WIFI_STATE, Manifest.permission.ACCESS_COARSE_LOCATION}, REQ_SET_LOCATION);
}
} else {
Log.i("--", "onClick granted");
onLoadData();
}
} else {
onLoadData();
}
}
private void onLoadData() {
webView.loadUrl(url);
if (!isFinishing()) {
waitDialog.show();
}
doSpecialiSomethingAsVirtualBar();
}
/**
* 虚拟按键-tangZd
*/
private void doSpecialiSomethingAsVirtualBar() {
//webview-软键盘-冲突
//KeyBoardListenerManager.newInstance(this).init();
//华为虚拟按键冲突问题:
//是否存在导航栏(虚拟功能键)
if (PhoneSystemManager.AndroidWorkaround.checkDeviceHasNavigationBar(this)) {
PhoneSystemManager.AndroidWorkaround.assistActivity(findViewById(android.R.id.content));
ViewStub stub = (ViewStub) findViewById(R.id.view_stub);
stub.inflate();
View enuiStubView = this.findViewById(R.id.enuiNatView);
LinearLayout.LayoutParams zLayoutParams = (LinearLayout.LayoutParams) enuiStubView.getLayoutParams();
//获取虚拟功能键高度
int virtualBarHeigh = PhoneSystemManager.AndroidWorkaround.getVirtualBarHeigh(this);
zLayoutParams.height = virtualBarHeigh;
enuiStubView.setLayoutParams(zLayoutParams);
} else {
//webview-软键盘-冲突
KeyBoardListenerManager.newInstance(this).init();
}
}
private class MyWebChromeClient extends WebChromeClient {
//定位
@Override
public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
callback.invoke(origin, true, true);
super.onGeolocationPermissionsShowPrompt(origin, callback);
}
//android 5.0以后google做了支持
@Override
public boolean onShowFileChooser(WebView webView,
ValueCallback<Uri[]> filePathCallback,
FileChooserParams fileChooserParams) {
mUploadCallbackAboveL = filePathCallback;
//take();
getPermissions();
return true;
}
//下面的这些方法会根据android的版本自动选择
//android3.0以下
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
mUploadMessage = uploadMsg;
getPermissions();
}
//android3.0-4.0
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
mUploadMessage = uploadMsg;
getPermissions();
}
//4.0-4.3 4.4.4(android 4.4无方法)
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
mUploadMessage = uploadMsg;
getPermissions();
}
}
private void getPermissions() {//拍照权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//checkSelfPermission用来检测应用是否已经具有权限
if (!(checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED)) {
if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
//去设置权限(申请权限) dialog
MySystemUtils.goToSetPermission(activity, getResources().getString(R.string.permission_camera), JHConstants.REQ_PERMISSION_CAMERA);
} else {
//进行请求单个或多个权限
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA}, JHConstants.REQ_PERMISSION_CAMERA);
}
} else {
//具有权限
take();
}
} else {// < 23
take();
}
}
private class MyWebViewClient extends MyWebViewClientUtils {
@Override
protected boolean initShouldOverrideUrlLoading(WebView view, String url) {
if (url.contains("installPhoneMyOrderPF/installerOrderCallPhone.do")) {//截取 电话
Map urlParamMap = UIutils.getUrlParamMap(url);
installer_phone = urlParamMap.get("INSTALLER_PHONE").toString();
//web 打电话
webViewCall();
} else if (url.contains("iosIndex/gotoIndexPage.do")) {//返回
waitDialog.dismiss();
WebViewInstallationActivity.this.finish();
} else if (url.contains("installPhoneIndexPF/phoneLoginPage.do")) {//去登录页面
startActivity(new Intent(activity, LoginActivity.class));
} else if (url.contains("installPhoneIndexPF/phoneRegisterPage.do")) {//去认证页面
startActivity(new Intent(activity, IdentifyActivity.class));
} else if (url.contains("kuaiQianJAZH5Pay/toPayPage.do")) {//去快钱支付页面
Map urlParamMap = UIutils.getUrlParamMap(url);
String order_id = urlParamMap.get("ORDER_ID").toString();
//快钱支付
Intent webviewkuaiqian = new Intent(activity, WebViewKuaiqianPayActivity.class);
webviewkuaiqian.putExtra("pay_type", "web");
webviewkuaiqian.putExtra("out_trade_no", order_id);
startActivity(webviewkuaiqian);
} else if (url.contains("submitToPayOrder")) {//待支付 去支付
Map urlParamMap = UIutils.getUrlParamMap(url);
userid = urlParamMap.get("USER_ID").toString();
ServiceApi.getUrlParammap(NetUtils.H5_URL_WEBTOPAYPAGE, urlParamMap, new MyString2Callback() {
@Override
public void onError(Call call, Exception e) {
ToastUtils.showInternetErrorToast();
}
@Override
public void onResponse(Call call, String s) {
String PAY_user_id = "PAY_" + userid;
try {
JSONObject jsonObject = new JSONObject(s);
code = jsonObject.getInt("code");
dataMap = jsonObject.getString("dataMap");
JSONObject pay_user_id = jsonObject.getJSONObject(PAY_user_id);
out_trade_no = pay_user_id.getString("out_trade_no");
order_type = jsonObject.getString("order_type");
} catch (JSONException e) {
e.printStackTrace();
}
if (0 == code) {
ZFBUtils zfbUtils = new ZFBUtils(WebViewInstallationActivity.this, dataMap);
zfbUtils.payV2(new ZFBUtils.Pay_ZFB_Pay_Listener() {
@Override
public void onPaySuccess(PayResult payResult) {
ToastUtils.showToast("支付成功");
webView.loadUrl(ServiceApi.getPaySuccessToDetail(userid, out_trade_no));
}
@Override
public void onPayError(PayResult payResult) {
String s1 = payResult.toString();
ToastUtils.showToast("支付失败");
}
});
} else if (-1 == code) {
ToastUtils.showToast("操作失败");
}
}
});
} else {
return false;
}
return true;
}
@Override
protected void initOnPageStarted(WebView view, String url, Bitmap favicon) {
gobackurl = url;
if (!isFinishing()) {
waitDialog.show();
}
}
@Override
protected void initOnPageFinished(WebView view, String url) {
waitDialog.dismiss();
}
@Override
protected void initOnReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
waitDialog.dismiss();
}
}
//web 打电话
private void webViewCall() {
// 检查是否获得了权限(Android6.0运行时权限)
if (ContextCompat.checkSelfPermission(WebViewInstallationActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
// 没有获得授权,申请授权
if (ActivityCompat.shouldShowRequestPermissionRationale(WebViewInstallationActivity.this, Manifest.permission.CALL_PHONE)) {
// 返回值:
//如果app之前请求过该权限,被用户拒绝, 这个方法就会返回true.
//如果用户之前拒绝权限的时候勾选了对话框中”Don’t ask again”的选项,那么这个方法会返回false.
// 如果设备策略禁止应用拥有这条权限, 这个方法也返回false.
// 弹窗需要解释为何需要该权限,再次请求授权
Toast.makeText(WebViewInstallationActivity.this, "请授权!", Toast.LENGTH_LONG).show();
// 帮跳转到该应用的设置界面,让用户手动授权
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);
} else {
// 不需要解释为何需要该权限,直接请求授权
ActivityCompat.requestPermissions(WebViewInstallationActivity.this, new String[]{Manifest.permission.CALL_PHONE}, MY_PERMISSIONS_REQUEST_CALL_PHONE);
}
} else {
// 已经获得授权,可以打电话
CallPhone();
}
}
private void CallPhone() {
if (TextUtils.isEmpty(installer_phone)) {
// 提醒用户
// 注意:在这个匿名内部类中如果用this则表示是View.OnClickListener类的对象,
// 所以必须用MainActivity.this来指定上下文环境。
Toast.makeText(WebViewInstallationActivity.this, "号码不能为空!", Toast.LENGTH_SHORT).show();
} else {
// 拨号:激活系统的拨号组件
Intent intent = new Intent(); // 意图对象:动作 + 数据
intent.setAction(Intent.ACTION_CALL); // 设置动作
Uri data = Uri.parse("tel:" + installer_phone); // 设置数据
intent.setData(data);
startActivity(intent); // 激活Activity组件
}
}
// 处理权限申请的回调
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_CALL_PHONE: {//电话
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 授权成功,继续打电话
CallPhone();
} else {
// 授权失败!
Toast.makeText(this, "授权失败!", Toast.LENGTH_LONG).show();
}
break;
}
case REQ_SET_LOCATION: {//定位
Boolean grantResultBoolean = false;
for (int grantResult : grantResults) {
grantResultBoolean = (grantResult == PackageManager.PERMISSION_GRANTED);
if (!grantResultBoolean) {
break;
}
}
if (grantResultBoolean) {
//通过
onLoadData();
} else {
MySystemUtils.goToSetPermission(activity, "在设置-应用-权限中开启定位的权限,以保证功能的正常使用", REQ_SET_LOCATION);
}
break;
}
case JHConstants.REQ_PERMISSION_CAMERA: {//相机相册
boolean isAllow = false;
for (int result : grantResults) {
if (PackageManager.PERMISSION_GRANTED == result) {
isAllow = true;
} else {
isAllow = false;
}
}
if (isAllow) {
take();
} else {
MySystemUtils.goToSetPermission(activity, getResources().getString(R.string.permission_notice_storage_and_camera), JHConstants.REQ_PERMISSION_CAMERA);
}
break;
}
}
}
/**
* 以下是webview的照片上传时候,用于在网页打开android图库文件
*/
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data); //图库的图片 data != null, 拍照的图片时 data == null
if (requestCode == FILECHOOSER_RESULTCODE) {
if (null == mUploadMessage && null == mUploadCallbackAboveL)
return;
Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
if (mUploadCallbackAboveL != null) {//android 5.0以后
try {
onActivityResultAboveL(requestCode, resultCode, data);
} catch (IOException e) {
e.printStackTrace();
}
} else if (mUploadMessage != null) {//android 5.0以前
if (result != null) {//图库的图片
try {
String path = getPath(getApplicationContext(), result);
Uri uri = Uri.fromFile(new File(path));
Bitmap bitmapformuri = PhotoUtils.getBitmapFormUri(activity, uri);
bitmap2uri = PhotoUtils.bitmap2uri(activity, bitmapformuri);
mUploadMessage.onReceiveValue(bitmap2uri);
} catch (IOException e) {
e.printStackTrace();
}
} else {//拍照的图片
//此处要设为null,否则在未选择图片的情况下,依然会出现上传空白文件,后台做了Uri 路径流是否可用判断
// imageUri = null;
mUploadMessage.onReceiveValue(imageUri);
}
mUploadMessage = null;
}
}
}
//针对5.0的结果回调
@SuppressWarnings("null")
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void onActivityResultAboveL(int requestCode, int resultCode, Intent data) throws IOException {
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)};
}
}
if (results != null) {
Uri[] compressResults = new Uri[results.length];
for (int i = 0; i < results.length; i++) {
Uri result = results[i];
Bitmap bitmapFormUri = PhotoUtils.getBitmapFormUri(activity, result);
bitmap2uri = PhotoUtils.bitmap2uri(activity, bitmapFormUri);
compressResults[i] = bitmap2uri;
}
mUploadCallbackAboveL.onReceiveValue(compressResults);
mUploadCallbackAboveL = null;
} else {
//此处要设为null,否则在未选择图片的情况下,依然会出现上传空白文件,
results = null;
mUploadCallbackAboveL.onReceiveValue(results);
mUploadCallbackAboveL = null;
}
return;
}
//take方法
private void take() {
File imageStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "MyApp");
// Create the storage directory if it does not exist
if (!imageStorageDir.exists()) {
imageStorageDir.mkdirs();
}
File file = new File(imageStorageDir + File.separator + "IMG_" + String.valueOf(System.currentTimeMillis()) + ".jpg");
//设定图片的uri路径
imageUri = Uri.fromFile(file);
final List<Intent> cameraIntents = new ArrayList<Intent>();
//调用相机的intent
final Intent captureIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
//获取包管理器(就是包含了整个清单文件,其中包括application,activity)
final PackageManager packageManager = getPackageManager();
//查询相机intent的activity,ResolveInfo其实就是activity节点
final List<ResolveInfo> listCam = packageManager.queryIntentActivities(captureIntent, 0);
//进行遍历
for (ResolveInfo res : listCam) {
//获取list中的元素,就activity,就是根据activity拿到相机的包名
final String packageName = res.activityInfo.packageName;
//将相机的intent 赋给新的intent
final Intent i = new Intent(captureIntent);
//重新设置当前intent的Component (写全包名)
i.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name));
i.setPackage(packageName);
i.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
cameraIntents.add(i);
}
//这个是文档内容,包含image,音视频
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
//添加分类
i.addCategory(Intent.CATEGORY_OPENABLE);
//类型为图片
i.setType("image/*");
//开始创建跳转应用的对话框,可以自己设置dialog样式,也可以像下面这样,同时创建包含多个
//intent的对话框,但样式不好控制,只能由系统默认,比如在4.4.4的模拟器上样式,图片见末尾
Intent chooserIntent = Intent.createChooser(i, "选择图片");//Image Chooser
//添加额外初始相机intent,但要序列化
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toArray(new Parcelable[]{}));
WebViewInstallationActivity.this.startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE);
}
//由于4.4以后content后面不再表示路径,以下方法专为4.4以后路径问题的解决
@TargetApi(Build.VERSION_CODES.KITKAT)
public static String getPath(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// 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 Environment.getExternalStorageDirectory() + "/" + split[1];
}
// TODO handle non-primary volumes
}
// 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 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 getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return 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.
*/
public 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.
*/
public 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.
*/
public 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.
*/
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
@Override
public Resources getResources() {
Resources res = super.getResources();
Configuration config = new Configuration();
config.setToDefaults();
res.updateConfiguration(config, res.getDisplayMetrics());
return res;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// TODO Auto-generated method stub
if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {
if (gobackurl.contains("installPhoneIndexPF/phoneIndexPage.do")) {
waitDialog.dismiss();
WebViewInstallationActivity.this.finish();
} else if (gobackurl.contains("installPhoneMyOrderPF/phoneMyOrderPage.do")) {
waitDialog.dismiss();
WebViewInstallationActivity.this.finish();
} else if (gobackurl.contains("installPhonePersonCenterPF/phonePersonIndexPage.do")) {
waitDialog.dismiss();
WebViewInstallationActivity.this.finish();
} else {
webView.goBack();
}
return true;
} else {
waitDialog.dismiss();
finish();
}
return super.onKeyDown(keyCode, event);
}
@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;
}
}
}
/**
* Created by LiJinWei on 2018/1/22.
* 调用方式为:KeyBoardListenerManager.newInstance(this).init();
* 出现华为虚拟按键冲突问题:
* 解决方案使用下面此类,此类主要作为封装处理特殊手机的工具类。
*/
public class PhoneSystemManager {
public static final String SYS_EMUI = "sys_emui";// 华为
public static final String SYS_MIUI = "sys_miui";// 小米
public static final String SYS_FLYME = "sys_flyme";// 魅族
public static final String SYS_NORMAL = "sys_normal";// 一般市面手机
private static final String KEY_MIUI_VERSION_CODE = "ro.miui.ui.version.code";
private static final String KEY_MIUI_VERSION_NAME = "ro.miui.ui.version.name";
private static final String KEY_MIUI_INTERNAL_STORAGE = "ro.miui.internal.storage";
private static final String KEY_EMUI_API_LEVEL = "ro.build.hw_emui_api_level";
private static final String KEY_EMUI_VERSION = "ro.build.version.emui";
private static final String KEY_EMUI_CONFIG_HW_SYS_VERSION = "ro.confg.hw_systemversion";
public static String getTelPhoneSystem() {
String SYS = SYS_NORMAL;
try {
Properties prop = new Properties();
prop.load(new FileInputStream(new File(Environment.getRootDirectory(), "build.prop")));
if (prop.getProperty(KEY_MIUI_VERSION_CODE, null) != null
|| prop.getProperty(KEY_MIUI_VERSION_NAME, null) != null
|| prop.getProperty(KEY_MIUI_INTERNAL_STORAGE, null) != null) {
SYS = SYS_MIUI;//小米
} else if (prop.getProperty(KEY_EMUI_API_LEVEL, null) != null
|| prop.getProperty(KEY_EMUI_VERSION, null) != null
|| prop.getProperty(KEY_EMUI_CONFIG_HW_SYS_VERSION, null) != null) {
SYS = SYS_EMUI;//华为
} else if (getMeizuFlymeOSFlag().toLowerCase().contains("flyme")) {
SYS = SYS_FLYME;//魅族
}
} catch (IOException e) {
e.printStackTrace();
return SYS;
}
return SYS;
}
public static String getMeizuFlymeOSFlag() {
return getSystemProperty("ro.build.display.id", "");
}
private static String getSystemProperty(String key, String defaultValue) {
try {
Class<?> clz = Class.forName("android.os.SystemProperties");
Method get = clz.getMethod("get", String.class, String.class);
return (String) get.invoke(clz, key, defaultValue);
} catch (Exception e) {
}
return defaultValue;
}
/**
* 设置透明状态栏目
* setMiuiStatusBarDarkMode(this, true);
* setMeizuStatusBarDarkIcon(this, true);
**/
public static boolean setMiuiStatusBarDarkMode(Activity activity, boolean darkmode) {
Class<? extends Window> clazz = activity.getWindow().getClass();
try {
int darkModeFlag = 0;
Class<?> layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
darkModeFlag = field.getInt(layoutParams);
Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
extraFlagField.invoke(activity.getWindow(), darkmode ? darkModeFlag : 0, darkModeFlag);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
public static boolean setMeizuStatusBarDarkIcon(Activity activity, boolean dark) {
boolean result = false;
if (activity != null) {
try {
WindowManager.LayoutParams lp = activity.getWindow().getAttributes();
Field darkFlag = WindowManager.LayoutParams.class.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");
Field meizuFlags = WindowManager.LayoutParams.class.getDeclaredField("meizuFlags");
darkFlag.setAccessible(true);
meizuFlags.setAccessible(true);
int bit = darkFlag.getInt(null);
int value = meizuFlags.getInt(lp);
if (dark) {
value |= bit;
} else {
value &= ~bit;
}
meizuFlags.setInt(lp, value);
activity.getWindow().setAttributes(lp);
result = true;
} catch (Exception e) {
}
}
return result;
}
/**
* 处理华为手机虚拟按键和底部导航遮挡问题
* 出现原因-
* getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
* getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
* 加:
* -android::fitsSystemWindows="true"
* 去除-FLAG_TRANSLUCENT_NAVIGATION
* -仍旧不行使用本类-在setContentView(……
* if (AndroidWorkaround.checkDeviceHasNavigationBar(this))
* {
* AndroidWorkaround.assistActivity(findViewById(android.R.id.content));
* }
*/
public static class AndroidWorkaround {
public static void assistActivity(View content) {
new AndroidWorkaround(content);
}
private View mChildOfContent;
private int usableHeightPrevious;
private ViewGroup.LayoutParams frameLayoutParams;
private AndroidWorkaround(View content) {
mChildOfContent = content;
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
possiblyResizeChildOfContent();
}
});
frameLayoutParams = mChildOfContent.getLayoutParams();
}
private void possiblyResizeChildOfContent() {
int usableHeightNow = computeUsableHeight();
if (usableHeightNow != usableHeightPrevious) {
frameLayoutParams.height = usableHeightNow;
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;
}
}
private int computeUsableHeight() {
Rect r = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(r);
return (r.bottom);
}
/**
* 获取虚拟功能键高度
*/
public static int getVirtualBarHeigh(Context context) {
int vh = 0;
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
DisplayMetrics dm = new DisplayMetrics();
try {
@SuppressWarnings("rawtypes")
Class c = Class.forName("android.view.Display");
@SuppressWarnings("unchecked")
Method method = c.getMethod("getRealMetrics", DisplayMetrics.class);
method.invoke(display, dm);
vh = dm.heightPixels - windowManager.getDefaultDisplay().getHeight();
} catch (Exception e) {
e.printStackTrace();
}
return vh;
}
//是否存在导航栏(虚拟功能键)
public static boolean checkDeviceHasNavigationBar(Context context) {
boolean hasNavigationBar = false;
Resources rs = context.getResources();
int id = rs.getIdentifier("config_showNavigationBar", "bool", "android");
if (id > 0) {
hasNavigationBar = rs.getBoolean(id);
}
try {
Class systemPropertiesClass = Class.forName("android.os.SystemProperties");
Method m = systemPropertiesClass.getMethod("get", String.class);
String navBarOverride = (String) m.invoke(systemPropertiesClass, "qemu.hw.mainkeys");
if ("1".equals(navBarOverride)) {
hasNavigationBar = false;
} else if ("0".equals(navBarOverride)) {
hasNavigationBar = true;
}
} catch (Exception e) {
}
return hasNavigationBar;
}
//NavigationBar状态是否是显示
public static boolean isNavigationBarShow(Context context) {
Activity mContext = (Activity) context;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Display display = mContext.getWindowManager().getDefaultDisplay();
Point size = new Point();
Point realSize = new Point();
display.getSize(size);
display.getRealSize(realSize);
return realSize.y != size.y;
} else {
boolean menu = ViewConfiguration.get(context).hasPermanentMenuKey();
boolean back = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
if (menu || back) {
return false;
} else {
return true;
}
}
}
}
}
/**
* Created by LiJinWei on 2018/1/22.
* webview-软键盘-冲突
* 由于项目中WebView输入数据,软键盘不会顶上布局,遮盖住页面,
* 使用下边此类解决冲突后出现底部导航栏被遮挡的问题。
*/
public class KeyBoardListenerManager {
private Activity activity;
private View mChildOfContent;
private int usableHeightPrevious;
private FrameLayout.LayoutParams frameLayoutParams;
private static KeyBoardListenerManager keyBoardListener;
public static KeyBoardListenerManager newInstance(Activity activity) {
keyBoardListener = new KeyBoardListenerManager(activity);
return keyBoardListener;
}
public KeyBoardListenerManager(Activity activity) {
super();
// TODO Auto-generated constructor stub
this.activity = activity;
}
public void init() {
FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
mChildOfContent = content.getChildAt(0);
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
possiblyResizeChildOfContent();
}
});
frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
}
private void possiblyResizeChildOfContent() {
int usableHeightNow = computeUsableHeight();
if (usableHeightNow != usableHeightPrevious) {
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
if (heightDifference > (usableHeightSansKeyboard / 4)) {
// keyboard probably just became visible
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
} else {
// keyboard probably just became hidden
frameLayoutParams.height = usableHeightSansKeyboard;
}
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;
}
}
private int computeUsableHeight() {
Rect r = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(r);
return (r.bottom - r.top);
}
}
这里面有个photoUtils,已经封装好了各种调用,简单好用
/**
* Created by LiJinWei on 2018/1/31.
*/
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.KITKAT) {
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.KITKAT) {
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 = 1280f;//这里设置高度为800f
float ww = 720f;//这里设置宽度为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 > 512) { //循环判断如果压缩后图片是否大于100kb,大于继续压缩
baos.reset();//重置baos即清空baos
//第一个参数 :图片格式 ,第二个参数: 图片质量,100为最高,0为最差 ,第三个参数:保存压缩后的数据的流
image.compress(Bitmap.CompressFormat.JPEG, options, baos);//这里压缩options%,把压缩后的数据存放到baos中
options -= 10;//每次都减少10
}
byte[] bytes = baos.toByteArray();
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把压缩后的数据baos存放到ByteArrayInputStream中
Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);//把ByteArrayInputStream数据生成图片
return bitmap;
}
/**
* bitmap 转 uri
* @param c
* @param b
* @return
*/
public static Uri bitmap2uri(Context c, Bitmap b) {
File path = new File(c.getCacheDir() + File.separator + "IMG_" + String.valueOf(System.currentTimeMillis()) + ".jpg");
try {
OutputStream os = new FileOutputStream(path);
b.compress(Bitmap.CompressFormat.JPEG, 100, os);
os.close();
return Uri.fromFile(path);
} catch (Exception ignored) {
}
return null;
}
/**
* @param context 上下文对象
* @param uri 当前相册照片的Uri
* @return 解析后的Uri对应的String
* 专为Android4.4设计的从Uri获取文件绝对路径,以前的方法已不好使
*/
@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());
}
}
OK ! ! ! 到这里 相信大家对WebView及所遇到的一些坑都有一定的了解,希望对你有帮助。给我点赞

