最近发现偶尔还是会踩到以前踩过的坑,所以开始了我的博客之旅,自己备忘,也可以将这些坑分享出来,帮助大家快速解决问题:
1. ## webview内存泄露 -异常加日志截图 ##
Activity com.dejia.demo.webview.WebViewActivity has leaked ServiceConnection
com.android.org.chromium.com.googlecode.eyesfree.braille.selfbraille.SelfBrailleClient$Connection@426aef18 that was originally bound here
经过一顿排查,当加上
mWebView.getSettings().setJavaScriptEnabled(true);
词句代码时,就会报以上错误,去掉则不会泄露。从LeakCanary日志中看到是由TextToSpeech.mContext,引用导致内存泄露的;通过google发现TextToSpeech.connecttoengine()
会采用上下文调用bindservice()
从日志中也可以发现相关问题;
详细请转本地址了解
http://www.haowuyun.com/view/145
解决方案:
1.方案一:采用微信和QQ的做法,就是 当你要用webview的时候,记得最好 另外单独开一个进程 去使用webview 并且当这个 进程结束时,请手动调用System.exit(0),目前本人感觉是最好的解决办法。
2.方案二 :在代码中动态引用Application的上下文创建webview,
mWebView = new WebView(getApplicationContext());
注意:采用方案二 ,加载个别页面可能会出现功能不能用或者导致应用崩溃,如果影响功能,那就放弃此方法;
如果点击返回,我们可以看到正在加载支付宝web支付页面,并且又迅速进入上面的截图页面,这是因为在调支付宝付款的时候支付宝会通过Uri调本地支付宝app, 而如果我们在代码中像如下这样配置就会找不到网页:
public boolean shouldOverrideUrlLoading(WebView view, String url) {
super.shouldOverrideUrlLoading(view,url);
mWebView.loadUrl(url);
return true;
}
通过打印log发现访问不到的url为
alipays://platformapi/startApp?+支付信息
所以会找不地址,我们应该创建隐式意图去启动支付宝客户端;
解决方案如下:
if (url.contains("alipays://platformapi")){
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
List resolveInfos = getPackageManager().queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
if(resolveInfos.size() > 0){
startActivity( intent );
}
}else{
mWebView.loadUrl(url);
}
如果本地存在支付宝app则优先唤起app进行支付,如果本地没有安装支付宝app则在web进行支付宝支付。
通过上边处理方法,如果本地没有安装app的话不会导致抛异常,如果直接startActivity(new Intent(…))会找不到该指向并抛异常;
3.## webview清理Cookie ##
在项目中,前端同志在获取获取token或cookie的时候优先从缓存的Cookie中获取,如果退出webview或是项目的时候未进行清理本地Cookie缓存,则有可能会在携带过期token或Cookie访问服务器的时候不能正常访问,所以在退出webview或是注销登录的时候清楚缓存Cookie;
处理办法:
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.removeAllCookie();
4.## webview退出时候,释放内存占用,防止内存泄漏 ##
直接上代码:
@Override
protected void onDestroy() {
super.onDestroy();
if (mWebView != null) {
mWebView.removeAllViews();
((ViewGroup) mWebView.getParent()).removeView(mWebView);
mWebView.setTag(null);
mWebView.clearHistory();
mWebView.destroy();
mWebView = null;
}
}
5.## web页面调本地相机拍照或相册时无反应 ##
如果遇到此问题,则需要在改WebViewActivity中进行一些配置,代码如下:
private ValueCallback mUploadMessage;// 表单的数据信息
private ValueCallback mUploadCallbackAboveL;
private final static int FILECHOOSER_RESULTCODE = 1;// 表单的结果回调
private Uri imageUri;
WebChromeClient webChromeClient = new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
}
//=========h5调相机相册==========================================================
@Override
public boolean onShowFileChooser(WebView webView,
ValueCallback filePathCallback,
FileChooserParams fileChooserParams) {
mUploadCallbackAboveL = filePathCallback;
take();
return true;
}
public void openFileChooser(ValueCallback uploadMsg) {
mUploadMessage = uploadMsg;
take();
}
public void openFileChooser(ValueCallback uploadMsg, String acceptType) {
mUploadMessage = uploadMsg;
take();
}
public void openFileChooser(ValueCallback uploadMsg, String acceptType, String capture) {
mUploadMessage = uploadMsg;
take();
}
//=========h5调相机相册==========================================================
};
//=========h5调相机相册==========================================================
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");
imageUri = Uri.fromFile(file);
final List cameraIntents = new ArrayList();
final Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
final PackageManager packageManager = getPackageManager();
final List listCam = packageManager.queryIntentActivities(captureIntent, 0);
for (ResolveInfo res : listCam) {
final String packageName = res.activityInfo.packageName;
final Intent i = new Intent(captureIntent);
i.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name));
i.setPackage(packageName);
i.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
cameraIntents.add(i);
}
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
Intent chooserIntent = Intent.createChooser(i, "Image Chooser");
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toArray(new Parcelable[]{}));
WebViewActivity.this.startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE);
}
//=========h5调相机相册==========================================================
@SuppressWarnings("null")
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
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)};
}
}
if (results != null) {
mUploadCallbackAboveL.onReceiveValue(results);
mUploadCallbackAboveL = null;
} else {
results = new Uri[]{imageUri};
mUploadCallbackAboveL.onReceiveValue(results);
mUploadCallbackAboveL = null;
}
return;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
//=========h5调相机相册==========================================================
if (requestCode == FILECHOOSER_RESULTCODE) {
if (null == mUploadMessage && null == mUploadCallbackAboveL) return;
Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
if (mUploadCallbackAboveL != null) {
onActivityResultAboveL(requestCode, resultCode, data);
} else if (mUploadMessage != null) {
if (result != null) {
String path = getPath(getApplicationContext(),
result);
Uri uri = Uri.fromFile(new File(path));
mUploadMessage
.onReceiveValue(uri);
} else {
mUploadMessage.onReceiveValue(imageUri);
}
mUploadMessage = null;
}
}
}
//=========h5调相机相册==========================================================
@SuppressLint("NewApi")
@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;
}
//=========h5调相机相册==========================================================
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;
}
//=========h5调相机相册==========================================================
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
//=========h5调相机相册==========================================================
/**
* @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());
}
//=========h5调相机相册=======
直接copy到你的WebViewActivity中即可,亲测有效;
6. 访问https资源无法正常显示
在webview中加载https资源无法正常显示,原来是因为没有没有识别的证书所以无法正常显示资源,那么我们应该设置WebView接受所有网站的证书,具体代码如下:
webView.setWebViewClient(new WebViewClient() {
@Override
public void onReceivedSslError(WebView view,
SslErrorHandler handler, SslError error) {
// handler.cancel();// Android默认的处理方式
handler.proceed();// 接受所有网站的证书
// handleMessage(Message msg);// 其他处理
}
});
7. 在WebView中无法通过Scheme唤起QQ或其它应用
在WebView的使用中遇到在三星手机上无法通过Scheme唤起QQ,经过排查,是因为下面这句代码导致:
mWebSettings.setSupportMultipleWindows(true);
那为什么这句会导致无法打开呢,这其实和html的代码有关,html页面中设置点击按钮打开QQ,它设置了这个属性
target="_blank"
也就是在新窗口打开,但是如果设置支持多窗口,但是又没有重写下面onCreateWindow(…)就导致出问题;所以默认为不支持多窗口,如果设置多窗口,就必须重写下面的onCreateWindow(…)方法:
WebChromeClient webChromeClient = new WebChromeClient() {
@Override
public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
WebView newWebView = new WebView(view.getContext());
view.addView(newWebView);
newWebView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (!TextUtils.isEmpty(url) && url.contains("mqq://im/chat")){
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
List resolveInfos = getPackageManager().queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
if(resolveInfos.size() > 0){
startActivity( intent );
}else if(url.contains("mqq://im/chat")){ //本地未安装QQ后的操作
Toast.makeText(getApplicationContext(),"请安装QQ后使用此功能",Toast.LENGTH_LONG).show();
}
}else{
mWebView.loadUrl(url);
}
return true;
}
});
newWebView.setWebChromeClient(this);
WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
transport.setWebView(newWebView);
resultMsg.sendToTarget();
return true;
}
}
但其实最简单的办法就是不支持多窗口:直接像如下这样设置或者不设置也可以解决问题:
mWebSettings.setSupportMultipleWindows(false);
如果我们仅仅是将WebView嵌入我们自己的应用然后加载网页,很少有必要去设置支持多窗口。只在当前窗口加载新的网页就可。
希望本博客,可以帮到你 ——勤劳的码农们!