1、Android Webview onPageFinished()加载两次解决办法
在onPageFinished()中进行了一些操作,个别地址会出现加载多次的问题,先解决方法如下
private boolean isLoading;
webView.setWebViewClient(new WebViewClient() {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
isLoading = true;
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (isLoading) {
isLoading = false;
//做些处理
return;
}
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
return false;
}
});
2、webview长截图
public static Bitmap capture(WebView webView) {
Picture picture = webView.capturePicture();
int width = picture.getWidth();
int height = picture.getHeight();
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
picture.draw(canvas);
return bitmap;
}
public void goScreenShotAction() {
Bitmap longImage = capture(myWebView);
try {
File file = new File(Environment.getExternalStorageDirectory().toString() + Constants.DOWNIMAGES);
if (!file.exists()) {
file.mkdirs();
}
String fileName = file.getAbsolutePath() + "/screenshot" + System.currentTimeMillis() + ".png";
FileOutputStream fos = new FileOutputStream(fileName);
//压缩bitmap到输出流中
longImage.compress(Bitmap.CompressFormat.JPEG, 70, fos);
fos.close();
Toast.makeText(CommonSearchResultActivity.this, "截屏已保存至SD卡XXX文件夹下", Toast.LENGTH_LONG).show();
} catch (Exception e) {
Log.e("", e.getMessage());
} finally {
if (longImage != null) {
longImage.recycle();
}
}
}
3、webview内存泄漏问题
(1)、尽量不在xml布局中 定义Webview,而是在文件中new出来
//传ApplicationContext可以防止webview对activity的引用而造成的内存泄漏;
mWebView=new WebView(getApplicationContext());
LinearLayout linearLayout = findViewById(R.id.xxx);
linearLayout.addView(mWebView);
(2)、 从源码的角度解决内存泄漏
@Override
protected void onDestroy() {
if( mWebView!=null) {
ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);
}
mWebView.stopLoading();
// 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.clearView();
mWebView.removeAllViews();
mWebView.destroy();
}
super.on Destroy();
}
4、404页面拦截处理
Android6.0以下判断404或者500:
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
// android 6.0 以下通过title获取
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
if (title.contains("404") || title.contains("500") || title.contains("Error")) {
view.loadUrl("about:blank");// 避免出现默认的错误界面
view.loadUrl(mErrorUrl);
}
}
}
Android6.0以上判断404或者500:
@TargetApi(android.os.Build.VERSION_CODES.M)
@Override
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
super.onReceivedHttpError(view, request, errorResponse);
// 这个方法在6.0才出现
int statusCode = errorResponse.getStatusCode();
System.out.println("onReceivedHttpError code = " + statusCode);
if (404 == statusCode || 500 == statusCode) {
view.loadUrl("about:blank");// 避免出现默认的错误界面
view.loadUrl(mErrorUrl);
}
}
判断断网和链接超时
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
// 断网或者网络连接超时
if (errorCode == ERROR_HOST_LOOKUP || errorCode == ERROR_CONNECT || errorCode == ERROR_TIMEOUT) {
view.loadUrl("about:blank"); // 避免出现默认的错误界面
view.loadUrl(mErrorUrl);
}
}
监听页面返回按钮
webView.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) {
runOnUiThread(new Runnable() {
@Override
public void run() {
webView.goBack();
}
});
return true;
}
return false;
}
});
webview拨电电话和转到邮件
if (url.contains("tel:")) {
RxPermissions rxPermissions = new RxPermissions(activity);
rxPermissions.request(Manifest.permission.CALL_PHONE).subscribe(accept -> {
if (accept) {
String mobile = getPhoneNumber(url);
if (!TextUtils.isEmpty(mobile)) {//判断是否为空
Uri uri = Uri.parse("tel:" + mobile);
Intent intent = new Intent(Intent.ACTION_DIAL, uri);
activity.startActivity(intent);
}
} else {
new ToastUtil(mContext).showToast(mContext.getString(R.string.permission_deny_phone));
}
});
return true;
} else if (url.contains("mailto:")) {
Intent data = new Intent(Intent.ACTION_SENDTO);
data.setData(Uri.parse(url));
data.putExtra(Intent.EXTRA_SUBJECT, "业务联系");
// data.putExtra(Intent.EXTRA_TEXT, "这是内容");
activity.startActivity(data);
}
webview安装处理某个scheme开头的url的APP
try {
if (!url.startsWith("http://") && !url.startsWith("https://")) {
return true;
}
} catch (Exception e) {//防止crash (如果手机上没有安装处理某个scheme开头的url的APP, 会导致crash)
return true;//没有安装该app时,返回true,表示拦截自定义链接,但不跳转,避免弹出上面的错误页面
}
我的webview
/**
* @author yanxu
* @date:2019/2/21
* @description:
*/
public class ProgressWebView extends WebView {
private WebViewProgressBar progressBar;//进度条的矩形(进度线)
private Handler handler;
private WebView mWebView;
private JSBridge mJsBridge;
private Context mContext;
private Activity activity;
/**
* 视频全屏参数
*/
protected static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS =
new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
private View customView;
private FrameLayout fullscreenContainer;
private WebChromeClient.CustomViewCallback customViewCallback;
public ProgressWebView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
activity = CommonUtils.scanForActivity(mContext);
//实例化进度条
progressBar = new WebViewProgressBar(context);
//设置进度条的size
progressBar.setLayoutParams(new ViewGroup.LayoutParams
(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
//刚开始时候进度条不可见
progressBar.setVisibility(GONE);
//把进度条添加到webView里面
addView(progressBar);
//初始化handle
handler = new Handler();
mWebView = this;
initSettings();
}
private void initSettings() {
WebSettings webSettings = mWebView.getSettings();
mJsBridge = new JSBridge(mContext, mWebView);
mWebView.addJavascriptInterface(mJsBridge, "webViewApp");
String ua = mWebView.getSettings().getUserAgentString();//原来获取的UA
webSettings.setUserAgentString(ua + CommonUtils.getAgent());
//自动播放
webSettings.setMediaPlaybackRequiresUserGesture(false);
webSettings.setJavaScriptEnabled(true);
// 设置可以访问文件
webSettings.setAllowFileAccess(true);
// 设置可以支持缩放
webSettings.setSupportZoom(true);
// 支持保存数据
webSettings.setSaveFormData(false);
// 设置默认缩放方式尺寸是far
webSettings.setDefaultZoom(WebSettings.ZoomDensity.MEDIUM);
// 设置出现缩放工具
webSettings.setBuiltInZoomControls(false);
//设置 缓存模式
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
// 开启 DOM storage API 功能
webSettings.setDomStorageEnabled(true);
//http https混合
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
webSettings.setPluginState(WebSettings.PluginState.ON);
webSettings.setRenderPriority(WebSettings.RenderPriority.HIGH);
// 清除缓存
mWebView.clearCache(true);
// 清除历史记录
mWebView.clearHistory();
mWebView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
setWebViewClient(new MyWebClient());
setWebChromeClient(new MyWebChromeClient());
SensorsDataAPIUtil.showUpWebView(this);
setOnLongClickListener(new MyOnLongClickListener());
}
/**
* 长按下载图片
*/
private class MyOnLongClickListener implements View.OnLongClickListener {
@Override
public boolean onLongClick(View v) {
if (v instanceof WebView) {
WebView.HitTestResult result = ((WebView) v).getHitTestResult();
if (result != null) {
int type = result.getType();
if (type == WebView.HitTestResult.IMAGE_TYPE
|| type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
downloadImage(mContext, result.getExtra());
}
}
}
return false;
}
}
public static void downloadImage(final Context context, final String url) {
new RxPermissions(CommonUtils.scanForActivity(context))
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.subscribe(aBoolean -> {
if (aBoolean) {
AlertDialog alertDialog = new AlertDialog.Builder(context)
.setTitle("下载")
.setMessage("保存图片到手机?")
.setNegativeButton("是", (dialog, which) ->
new SaveImage(context, url).execute()).setPositiveButton("否", null).show();
alertDialog.show();
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(Color.BLACK);
alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setTextColor(Color.BLACK);
}
});
}
/**
* 自定义WebChromeClient
*/
private class MyWebChromeClient extends WebChromeClient {
/**
* 进度改变的回掉
*
* @param view WebView
* @param newProgress 新进度
*/
@Override
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress == 100) {
progressBar.setProgress(100);
handler.postDelayed(runnable, 200);//0.2秒后隐藏进度条
} else if (progressBar.getVisibility() == GONE) {
progressBar.setVisibility(VISIBLE);
}
//设置初始进度10,这样会显得效果真一点,总不能从1开始吧
if (newProgress < 10) {
newProgress = 10;
}
//不断更新进度
progressBar.setProgress(newProgress);
super.onProgressChanged(view, newProgress);
}
/*** 视频播放相关的方法 **/
@Override
public View getVideoLoadingProgressView() {
FrameLayout frameLayout = new FrameLayout(mContext);
frameLayout.setLayoutParams(new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
return frameLayout;
}
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
showCustomView(view, callback);
}
@Override
public void onHideCustomView() {
hideCustomView();
}
}
/**
* 视频播放全屏
**/
private void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {
// if a view already exists then immediately terminate the new one
if (customView != null) {
callback.onCustomViewHidden();
return;
}
CommonUtils.scanForActivity(mContext).getWindow().getDecorView();
FrameLayout decor = (FrameLayout) CommonUtils.scanForActivity(mContext).getWindow().getDecorView();
fullscreenContainer = new FullscreenHolder(mContext);
fullscreenContainer.addView(view, COVER_SCREEN_PARAMS);
decor.addView(fullscreenContainer, COVER_SCREEN_PARAMS);
customView = view;
setStatusBarVisibility(false);
customViewCallback = callback;
}
/**
* 隐藏视频全屏
*/
private void hideCustomView() {
if (customView == null) {
return;
}
setStatusBarVisibility(true);
FrameLayout decor = (FrameLayout) CommonUtils.scanForActivity(mContext).getWindow().getDecorView();
decor.removeView(fullscreenContainer);
fullscreenContainer = null;
customView = null;
customViewCallback.onCustomViewHidden();
mWebView.setVisibility(View.VISIBLE);
}
/**
* 全屏容器界面
*/
static class FullscreenHolder extends FrameLayout {
public FullscreenHolder(Context ctx) {
super(ctx);
setBackgroundColor(ctx.getResources().getColor(android.R.color.black));
}
@Override
public boolean onTouchEvent(MotionEvent evt) {
return true;
}
}
private void setStatusBarVisibility(boolean visible) {
int flag = visible ? 0 : WindowManager.LayoutParams.FLAG_FULLSCREEN;
CommonUtils.scanForActivity(mContext).getWindow().setFlags(flag, WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
private class MyWebClient extends WebViewClient {
/**
* 加载过程中 拦截加载的地址url
*
* @param view
* @param url 被拦截的url
* @return
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url == null) return false;
if (url.contains("tel:")) {
RxPermissions rxPermissions = new RxPermissions(activity);
rxPermissions.request(Manifest.permission.CALL_PHONE).subscribe(accept -> {
if (accept) {
String mobile = getPhoneNumber(url);
if (!TextUtils.isEmpty(mobile)) {//判断是否为空
Uri uri = Uri.parse("tel:" + mobile);
Intent intent = new Intent(Intent.ACTION_DIAL, uri);
activity.startActivity(intent);
}
} else {
new ToastUtil(mContext).showToast(mContext.getString(R.string.permission_deny_phone));
}
});
return true;
} else if (url.contains("mailto:")) {
Intent data = new Intent(Intent.ACTION_SENDTO);
data.setData(Uri.parse(url));
data.putExtra(Intent.EXTRA_SUBJECT, "业务联系");
// data.putExtra(Intent.EXTRA_TEXT, "这是内容");
activity.startActivity(data);
}
try {
if (!url.startsWith("http://") && !url.startsWith("https://")) {
return true;
}
} catch (Exception e) {//防止crash (如果手机上没有安装处理某个scheme开头的url的APP, 会导致crash)
return true;//没有安装该app时,返回true,表示拦截自定义链接,但不跳转,避免弹出上面的错误页面
}
// TODO Auto-generated method stub
//返回值是true的时候控制去WebView打开,为false调用系统浏览器或第三方浏览器
view.loadUrl(url);
return true;
}
/**
* 页面加载过程中,加载资源回调的方法
*
* @param view
* @param url
*/
@Override
public void onLoadResource(WebView view, String url) {
super.onLoadResource(view, url);
}
/**
* 页面加载完成回调的方法
*
* @param view
* @param url
*/
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
// 关闭图片加载阻塞
view.getSettings().setBlockNetworkImage(false);
}
/**
* 页面开始加载调用的方法
*
* @param view
* @param url
* @param favicon
*/
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
@Override
public void onReceivedError(WebView webView, int errorCode, String description, String failingUrl) {
super.onReceivedError(webView, errorCode, description, failingUrl);
StatusUtils.create(webView).fail(view -> {
StatusUtils.create(webView).showLoading();
new Handler().postDelayed(() -> {
StatusUtils.create(webView).hint();
webView.reload();
}, 1000);
});
}
@TargetApi(android.os.Build.VERSION_CODES.M) // 处理404错误
@Override
public void onReceivedHttpError(WebView webView, WebResourceRequest request,
WebResourceResponse errorResponse) {
super.onReceivedHttpError(webView, request, errorResponse);
// 这个方法在6.0才出现
int statusCode = errorResponse.getStatusCode();
System.out.println("onReceivedHttpError code = " + statusCode);
// if (404 == statusCode || 500 == statusCode) {
// StatusUtils.create(webView).fail(view -> {
// StatusUtils.create(webView).showLoading();
// new Handler().postDelayed(() -> {
// StatusUtils.create(webView).hint();
// webView.reload();
// }, 1000);
// });
// }
}
@Override
public void onScaleChanged(WebView view, float oldScale, float newScale) {
super.onScaleChanged(view, oldScale, newScale);
ProgressWebView.this.requestFocus();
ProgressWebView.this.requestFocusFromTouch();
}
}
/**
* 从url中提取电话号码
*
* @param url
* @return
*/
private String getPhoneNumber(String url) {
try {
//对url字符进行转义
String transferUrl = URLDecoder.decode(url, "utf-8");
//tel://(+8610) 87869999
String mobile = transferUrl.substring(transferUrl.lastIndexOf("/") + 1);
return RegexUtils.extractNumberFromStr(mobile);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return "";
}
/**
* 刷新界面(此处为加载完成后进度消失)
*/
private Runnable runnable = new Runnable() {
@Override
public void run() {
progressBar.setVisibility(View.GONE);
}
};
/**
* destroy
*/
public void dodestroy() {
if (mWebView != null) {
ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);
}
mWebView.stopLoading();
// 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.removeAllViews();
mWebView.destroy();
}
}
public JSBridge getJsEngine() {
return mJsBridge;
}
}
/**
* @author yanxu
* @date:2019/2/21
* @description:
*/
public class WebViewProgressBar extends View {
private int progress = 1;//进度默认为1
private final static int HEIGHT = 5;//进度条高度为5
private Paint paint;//进度条的画笔
// 渐变颜色数组
private final static int colors[] = new int[]{0xFF7AD237, 0xFF8AC14A, 0x35B056 }; //int类型颜色值格式:0x+透明值+颜色的rgb值
public WebViewProgressBar(Context context) {
this (context,null);
}
public WebViewProgressBar(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public WebViewProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaint(context);
}
private void initPaint(Context context) {
//颜色渐变从colors[0]到colors[2],透明度从0到1
// LinearGradient shader = new LinearGradient(
// 0, 0,
// 100, HEIGHT,
// colors,
// new float[]{0 , 0.5f, 1.0f},
// Shader.TileMode.MIRROR);
paint=new Paint(Paint.DITHER_FLAG);
paint.setStyle(Paint.Style.STROKE);// 填充方式为描边
paint.setStrokeWidth(HEIGHT);//设置画笔的宽度
paint.setAntiAlias(true);// 抗锯齿
paint.setDither(true);// 使用抖动效果
paint.setColor(context.getResources().getColor(R.color.main_text));//画笔设置颜色
}
/**
* 设置进度
* @param progress 进度值
*/
public void setProgress(int progress){
this.progress = progress;
invalidate();//刷新画笔
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawRect(0, 0, getWidth() * progress / 100, HEIGHT, paint);//画矩形从(0.0)开始到(progress,height)的区域
}
}