转载请注明出处:https://blog.csdn.net/u011038298/article/details/88973705
目录
activity_common_browser.xml的实现
WebView常用方法介绍
方法返回值 |
方法名称介绍 |
void | addJavascriptInterface(Object object, String name) 将提供的Java对象注入此WebView。 |
boolean | canGoBack() 获取此WebView是否具有后退历史记录项。 |
void | clearCache(boolean includeDiskFiles) 清除资源缓存。 |
void | clearHistory() 告诉此WebView清除其内部后退/前进列表。 |
void | destroy() 破坏此WebView的内部状态。 |
int | getContentHeight() 获取HTML内容的高度。 |
String | getOriginalUrl() 获取当前页面的原始URL。 |
int | getProgress() 获取当前页面的进度。 |
WebSettings | getSettings() 获取用于控制此WebView设置的WebSettings对象。 |
String | getTitle() 获取当前页面的标题。 |
String | getUrl() 获取当前页面的URL。 |
void | goBack() 回到这个WebView的历史。 |
void | loadData(String data, String mimeType, String encoding) 使用“数据”方案URL将给定数据加载到此WebView中。 |
void | loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl) 使用baseUrl作为内容的基本URL,将给定数据加载到此WebView中。 |
void | loadUrl(String url) 加载给定的URL。 |
void | loadUrl(String url, Map<String, String> additionalHttpHeaders) 使用指定的其他HTTP标头加载给定的URL。 |
boolean | onCheckIsTextEditor() 检查被调用的视图是否是文本编辑器,在这种情况下,为它自动显示软输入窗口是有意义的。 |
void | onPause() 尽最大努力尝试暂停任何可以安全暂停的处理,例如动画和地理位置。 |
void | onResume() 在之前的呼叫之后恢复WebView onPause()。 |
boolean | pageDown(boolean bottom) 将此WebView的内容向下滚动页面大小的一半。 |
boolean | pageUp(boolean top) 将此WebView的内容向上滚动视图大小的一半。 |
void | resumeTimers() 恢复所有WebView的所有布局,解析和JavaScript计时器。 |
void | pauseTimers() 暂停所有WebView的所有布局,解析和JavaScript计时器。 |
void | postUrl(String url, byte[] postData) 使用“POST”方法将带有postData的URL加载到此WebView中。 |
void | reload() 重新加载当前URL。 |
void | removeJavascriptInterface(String name) 从此WebView中删除以前注入的Java对象。 |
void | setInitialScale(int scaleInPercent) 设置此WebView的初始比例。 |
void | setLayerType(int layerType, Paint paint) 指定支持此视图的图层类型。 |
void | setLayoutParams(ViewGroup.LayoutParams params) 设置与此视图关联的布局参数。 |
void | setNetworkAvailable(boolean networkUp) 通知WebView网络状态。 |
void | setOverScrollMode(int mode) 为此视图设置过滚动模式。 |
void | setScrollBarStyle(int style) 指定滚动条的样式。 |
void | setWebChromeClient(WebChromeClient client) 设置chrome处理程序。 |
static void | setWebContentsDebuggingEnabled(boolean enabled) 允许调试加载到此应用程序的任何WebView中的Web内容(HTML / CSS / JavaScript)。 |
void | setWebViewClient(WebViewClient client) 设置将接收各种通知和请求的WebViewClient。 |
void | setWebViewRendererClient(WebViewRendererClient webViewRendererClient) 设置与此WebView关联的呈现器客户端对象。 |
void | setWebViewRendererClient(Executor executor, WebViewRendererClient webViewRendererClient) 设置与此WebView关联的呈现器客户端对象。 |
static void | startSafeBrowsing(Context context, ValueCallback<Boolean> callback) 启动安全浏览初始化。 |
void | stopLoading() 停止当前负载。 |
WebSettings常用方法介绍
方法返回值 |
方法名称介绍 |
abstract String | getUserAgentString() 获取WebView的用户代理字符串。 |
abstract void | setUserAgentString(String ua) 设置WebView的用户代理字符串。 请注意,从{@link android.os.Build.VERSION_CODES#KITKAT}开始,在加载网页时更改用户代理会导致WebView再次启动加载。 |
abstract void | setAllowContentAccess(boolean allow) 在WebView中启用或禁用内容URL访问。 |
abstract void | setAllowFileAccess(boolean allow) 在WebView中启用或禁用文件访问。 |
abstract void | setAllowFileAccessFromFileURLs(boolean flag) 设置是否应允许在文件方案URL上下文中运行的JavaScript访问其他文件方案URL中的内容 |
abstract void | setAllowUniversalAccessFromFileURLs(boolean flag) 设置是否应允许在文件方案URL的上下文中运行的JavaScript访问来自任何源的内容。 |
abstract void | setAppCacheEnabled(boolean flag) 设置是否应启用Application Caches API。 |
abstract void | setAppCacheMaxSize(long appCacheMaxSize) 此方法在API级别18中已弃用。将来将自动管理配额。 |
abstract void | setAppCachePath(String appCachePath) 设置Application Caches文件的路径。 |
abstract void | setBlockNetworkImage(boolean flag) 设置WebView是否不应从网络加载图像资源(通过http和https URI方案访问的资源)。 |
abstract void | setBlockNetworkLoads(boolean flag) 设置WebView是否不应从网络加载资源。 |
abstract void | setBuiltInZoomControls(boolean enabled) 设置WebView是否应使用其内置缩放机制。 |
abstract void | setCacheMode(int mode) 覆盖缓存的使用方式。 |
abstract void | setCursiveFontFamily(String font) 设置草书字体系列名称。 |
abstract void | setDatabaseEnabled(boolean flag) 设置是否启用数据库存储API。 |
abstract void | setDatabasePath(String databasePath) 此方法在API级别19中已弃用。数据库路径由实现管理,并且调用此方法将不起作用。 |
abstract void | setDefaultFixedFontSize(int size) 设置默认的固定字体大小。 |
abstract void | setDefaultFontSize(int size) 设置默认字体大小。 |
abstract void | setDefaultTextEncodingName(String encoding) 设置解码html页面时使用的默认文本编码名称。 |
abstract void | setDefaultZoom(WebSettings.ZoomDensity zoom) 此方法在API级别19中已弃用。不再支持此方法,请参阅功能文档以获取建议的替代方法。 |
abstract void | setDisabledActionModeMenuItems(int menuItems) 根据menuItems标志禁用动作模式菜单项。 |
abstract void | setDisplayZoomControls(boolean enabled) 设置使用内置缩放机制时WebView是否应显示屏幕缩放控件。 |
abstract void | setDomStorageEnabled(boolean flag) 设置是否启用DOM存储API。 |
abstract void | setUseWideViewPort(boolean use) 设置WebView是否应启用对“viewport”HTML元标记的支持,或者应使用宽视口。 |
abstract void | setTextZoom(int textZoom) 以百分比设置页面的文本缩放。 |
abstract void | setForceDarkMode(int forceDarkMode) 为此WebView设置强制黑暗模式。 |
abstract void | setGeolocationEnabled(boolean flag) 设置是否启用地理位置。 |
abstract void | setJavaScriptCanOpenWindowsAutomatically(boolean flag) 告诉JavaScript自动打开窗口。 |
abstract void | setJavaScriptEnabled(boolean flag) 告诉WebView启用JavaScript执行。 |
abstract void | setLayoutAlgorithm(WebSettings.LayoutAlgorithm l) 设置基础布局算法。 |
abstract void | setLoadWithOverviewMode(boolean overview) 设置WebView是否以概览模式加载页面,即缩小内容以适应屏幕宽度。 |
abstract void | setLoadsImagesAutomatically(boolean flag) 设置WebView是否应加载图像资源。 |
abstract void | setMediaPlaybackRequiresUserGesture(boolean require) 设置WebView是否需要用户手势来播放媒体。 |
abstract void | setSupportZoom(boolean support) 设置WebView是否应支持使用其屏幕缩放控件和手势进行缩放。 |
abstract void | setSupportMultipleWindows(boolean support) 设置WebView是否支持多个窗口。 |
abstract void | setMixedContentMode(int mode) 当安全源尝试从不安全的源加载资源时,配置WebView的行为。 |
abstract void | setSafeBrowsingEnabled(boolean enabled) 设置是否启用安全浏览。 |
abstract boolean | supportMultipleWindows() 获取WebView是否支持多个窗口。 |
abstract boolean | supportZoom() 获取WebView是否支持缩放。 |
WebViewClient常用函数介绍
(WebViewClient,处理各种通知和请求事件)
WebViewClient webViewClient = new WebViewClient() {
/**
* URL重定向会执行此方法以及点击页面某些链接也会执行此方法。
* 当URL即将加载到当前WebView中时,为主机应用程序提供控制的机会。
* 此方法在API级别24中已弃用。请shouldOverrideUrlLoading(WebView, WebResourceRequest)改用。
* @param view
* @param url
* @return true:表示当前url已经加载完成,即使url还会重定向都不会再进行加载 false 表示此url默认由系统处理,该重定向还是重定向,直到加载完成
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return super.shouldOverrideUrlLoading(view, url);
}
/**
* URL重定向会执行此方法以及点击页面某些链接也会执行此方法。
* 当URL即将加载到当前WebView中时,为主机应用程序提供控制的机会。
* @param view
* @param request
* @return true:表示当前url已经加载完成,即使url还会重定向都不会再进行加载 false 表示此url默认由系统处理,该重定向还是重定向,直到加载完成
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
return super.shouldOverrideUrlLoading(view, request);
}
/**
* 通知主机应用程序资源请求并允许应用程序返回数据。
* 在请求资源时执行此方法,可以通过此方法进行资源拦截及替换。
* 此方法在API级别21中已弃用。请shouldInterceptRequest(WebView, WebResourceRequest)改用。
* @param view
* @param url
* @return
*/
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
return super.shouldInterceptRequest(view, url);
}
/**
* 通知主机应用程序资源请求并允许应用程序返回数据。
* 在请求资源时执行此方法,可以通过此方法进行资源拦截及替换。
* @param view
* @param request
* @return
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
return super.shouldInterceptRequest(view, request);
}
/**
* 在加载页面资源时候会调用,每个资源加载一次都会调用一次。
* 通知主机应用程序WebView将加载由给定URL指定的资源。
* @param view 正在启动回调的WebView。
* @param url WebView将加载的资源的URL。
*/
@Override
public void onLoadResource(WebView view, String url) {
super.onLoadResource(view, url);
}
/**
* 开始加载页面时调用。
* 通知主机应用程序页面已开始加载。
* @param view
* @param url
* @param favicon
*/
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
/**
* 在页面加载结束时调用。
* 通知主机应用程序页面已完成加载。
* @param view
* @param url
*/
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
}
/**
* 网页产生错误时调用。
* 此方法在API级别23中已弃用。请onReceivedError(WebView, WebResourceRequest, WebResourceError)改用。
* @param view
* @param errorCode
* @param description
* @param failingUrl
*/
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
}
/**
* 网页产生错误时调用。
* @param view
* @param request
* @param error
*/
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
super.onReceivedError(view, request, error);
}
/**
* 通知主机应用程序应用于WebView的比例已更改。
* @param view 正在启动回调的WebView。
* @param oldScale 旧的比例因子
* @param newScale 新的比例因子
*/
@Override
public void onScaleChanged(WebView view, float oldScale, float newScale) {
super.onScaleChanged(view, oldScale, newScale);
}
/**
* 为主机应用程序提供同步处理键事件的机会。
* @param view 正在启动回调的WebView。
* @param event 关键事件。
* @return
*/
@Override
public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
return super.shouldOverrideKeyEvent(view, event);
}
};
WebViewClient错误码大全
webView.setWebViewClient(new WebViewClient() {
// 网页产生错误时调用
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
switch (errorCode) {
case WebViewClient.ERROR_UNKNOWN:
// 一般错误(-1)
break;
case WebViewClient.ERROR_HOST_LOOKUP:
//服务器或代理主机名查找失败(-2)
break;
case WebViewClient.ERROR_UNSUPPORTED_AUTH_SCHEME:
// 不支持的身份验证方案(不是基本或摘要)(-3)
break;
case WebViewClient.ERROR_AUTHENTICATION:
// 服务器上的用户验证失败(-4)
break;
case WebViewClient.ERROR_PROXY_AUTHENTICATION:
// 代理上的用户身份验证失败(-5)
break;
case WebViewClient.ERROR_CONNECT:
// 无法连接到服务器(-6)
break;
case WebViewClient.ERROR_IO:
// 无法读取或写入服务器(-7)
break;
case WebViewClient.ERROR_TIMEOUT:
// 连接超时(-8)
break;
case WebViewClient.ERROR_REDIRECT_LOOP:
// 重定向太多(-9)
break;
case WebViewClient.ERROR_UNSUPPORTED_SCHEME:
// 不支持的URI方案(-10)
break;
case WebViewClient.ERROR_FAILED_SSL_HANDSHAKE:
// 无法执行SSL握手(-11)
break;
case WebViewClient.ERROR_BAD_URL:
// 格式错误的网址(-12)
break;
case WebViewClient.ERROR_FILE:
// 通用文件错误(-13)
break;
case WebViewClient.ERROR_FILE_NOT_FOUND:
// 文件未找到(-14)
break;
case WebViewClient.ERROR_TOO_MANY_REQUESTS:
// 此负载期间请求太多(-15)
break;
case WebViewClient.ERROR_UNSAFE_RESOURCE:
// 安全浏览取消了资源加载(-16)
break;
case WebViewClient.SAFE_BROWSING_THREAT_UNKNOWN:
// 资源因未知原因被阻止(0)
break;
case WebViewClient.SAFE_BROWSING_THREAT_MALWARE:
// 资源被阻止,因为它包含恶意软件(1)
break;
case WebViewClient.SAFE_BROWSING_THREAT_PHISHING:
// 资源被阻止,因为它包含欺骗性内容(2)
break;
case WebViewClient.SAFE_BROWSING_THREAT_UNWANTED_SOFTWARE:
// 资源被阻止,因为它包含不需要的软件(3)
break;
}
}
});
WebChromeClient常用函数介绍
(WebChromeClient,辅助 WebView 处理 Javascript 的对话框,网站图标,网站标题等等)
WebChromeClient webChromeClient = new WebChromeClient() {
/**
* 获取网页的加载进度。
* 告诉主机应用程序加载页面的当前进度。
* @param view 启动回调的WebView。
* @param newProgress 当前页面加载进度,由0到100之间的整数表示。
*/
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
}
/**
* 获取网页中的标题。
* 通知主机应用程序文档标题的更改。
* @param view
* @param title
*/
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
}
/**
* 通知主机应用程序当前页面已进入全屏模式。
* 此方法在API级别18中已弃用。此方法支持过时的插件机制,并且将来不会调用
* @param view
* @param requestedOrientation
* @param callback
*/
@Override
public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) {
super.onShowCustomView(view, requestedOrientation, callback);
}
/**
* 通知主机应用程序当前页面已进入全屏模式。
* @param view
* @param callback
*/
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
super.onShowCustomView(view, callback);
}
/**
* 通知主机应用程序当前页面已退出全屏模式。
*/
@Override
public void onHideCustomView() {
super.onHideCustomView();
}
/**
* 告诉客户端显示文件选择器。
* @param webView 正在启动请求的WebView实例。
* @param filePathCallback 调用此回调以提供要上载或null取消的文件的路径列表。只有在实现返回时才能调用 。
* @param fileChooserParams 描述要打开的文件选择器的模式,以及与其一起使用的选项。
* @return
*/
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
return super.onShowFileChooser(webView, filePathCallback, fileChooserParams);
}
};
本文结合CacheWebView框架实现通用版WebView,自定义实现Android WebView缓存,离线网站,让cahe配置更加简单灵活。
实现CacheWebView依赖
// 网页加载SDK
implementation 'ren.yale.android:cachewebviewlib:2.1.8'
BaseApplication.java的实现:
import android.app.ActivityManager;
import android.app.Application;
import android.content.Context;
import android.os.Process;
import android.support.multidex.MultiDex;
import java.io.File;
import ren.yale.android.cachewebviewlib.CacheType;
import ren.yale.android.cachewebviewlib.WebViewCacheInterceptor;
import ren.yale.android.cachewebviewlib.WebViewCacheInterceptorInst;
import ren.yale.android.cachewebviewlib.config.CacheExtensionConfig;
public class BaseApplication extends Application {
private static BaseApplication instance;
public static BaseApplication getInstance() {
return instance;
}
@Override
public void onCreate() {
super.onCreate();
// 判断当前进程是否为主进程
if (getCurrentProcessName().equals(getPackageName())) {
instance = this;
// 在主进程中开启一个子线程去执行异步任务
new Thread(new Runnable() {
@Override
public void run() {
// 设置线程的优先级,不与主线程抢资源
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 初始化WebViewCacheInterceptor
CacheExtensionConfig extension = new CacheExtensionConfig();
extension.addExtension("json"); //添加删除缓存后缀
WebViewCacheInterceptor.Builder builder = new WebViewCacheInterceptor.Builder(instance);
/**
* setCachePath:设置缓存路径,默认getCacheDir,名称CacheWebViewCache
* setDebug:设置Debug模式,默认开启debug log , TAG="CacheWebView"
* setCacheSize:设置缓存大小,默认100M
* setConnectTimeoutSecond:设置http请求链接超时,默认20秒
* setReadTimeoutSecond:设置http请求链接读取超时,默认20秒
* setCacheType:设置缓存模式
* 1.普通缓存和 http 缓存模式一样;
* 2.强制缓存,这样对于静态资源直接走缓存,不需要和服务器沟通走 304 缓存,这样会更快;如果静态资源要更新,请让 web 前端同学修改静态资源链接
* setCacheExtensionConfig:通过后缀判断来缓存静态文件,可以添加和删除
*/
builder.setCachePath(new File(instance.getCacheDir(), "WebCache"))
.setDebug(false)
.setCacheSize(1024 * 1024 * 100)
.setConnectTimeoutSecond(30)
.setReadTimeoutSecond(30)
.setCacheType(CacheType.FORCE)
.setCacheExtensionConfig(extension);
WebViewCacheInterceptorInst.getInstance().init(builder);
// 强制缓存失效后,由WebView正常加载资源
WebViewCacheInterceptorInst.getInstance().enableForce(false);
}
}).start();
}
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
/**
* 获取当前进程名
*/
private String getCurrentProcessName() {
String processName = "";
int pid = android.os.Process.myPid();
ActivityManager manager = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningAppProcessInfo process : manager.getRunningAppProcesses()) {
if (process.pid == pid) {
processName = process.processName;
}
}
return processName;
}
}
JSONAnalyze.java的实现
(在JS与java互调的时候,实现点击跳转到原生任意界面,并且携带参数,这时候需要通过对传递的数据进行json解析)
import org.json.JSONArray;
import org.json.JSONObject;
import android.text.TextUtils;
/**
* 对json进行解析
*/
public class JSONAnalyze {
public static JSONObject getJSONObject(String json) {
try {
if (!TextUtils.isEmpty(json)) {
return new JSONObject(json);
} else {
return null;
}
} catch (Exception e) {
return null;
}
}
public static JSONArray getJSONArray(String json) {
try {
if (!TextUtils.isEmpty(json)) {
return new JSONArray(json);
} else {
return null;
}
} catch (Exception e) {
return null;
}
}
public static String getJSONValue(JSONObject json, String key) {
String text = "";
try {
if (json != null && !TextUtils.isEmpty(key)) {
text = json.isNull(key) ? "" : json.getString(key);
}
} catch (Exception e) {
e.printStackTrace();
}
return text;
}
public static String getJSONValue(JSONArray array, int index) {
String text = "";
try {
if (array != null && index >= 0 && index < array.length()) {
text = array.isNull(index) ? "" : array.getString(index);
}
} catch (Exception e) {
e.printStackTrace();
}
return text;
}
public static JSONArray getJSONArray(JSONArray array, int index) {
JSONArray jsonArray = null;
try {
if (array != null && index >= 0 && index < array.length()) {
jsonArray = array.isNull(index) ? null : array.getJSONArray(index);
}
} catch (Exception e) {
e.printStackTrace();
}
return jsonArray;
}
public static JSONObject getJSONObject(JSONObject json, String key) {
try {
if (json != null && !TextUtils.isEmpty(key)) {
return json.isNull(key) ? null : json.getJSONObject(key);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static JSONObject getJSONObject(JSONArray array, int index) {
JSONObject json = null;
try {
if (array != null && index >= 0 && index < array.length()) {
json = array.isNull(index) ? null : array.getJSONObject(index);
}
} catch (Exception e) {
e.printStackTrace();
}
return json;
}
public static JSONArray getJSONArray(JSONObject json, String key) {
JSONArray array = null;
try {
if (json != null && !TextUtils.isEmpty(key)) {
array = json.isNull(key) ? null : json.getJSONArray(key);
}
} catch (Exception e) {
e.printStackTrace();
}
return array;
}
}
CommonBrowserActivity.java的实现
(内嵌页通用,可通过动态传参进行功能扩展)
import android.Manifest;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;
import org.json.JSONArray;
import org.json.JSONObject;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.HashMap;
import ren.yale.android.cachewebviewlib.WebViewCacheInterceptorInst;
/**
* 内嵌页(通用版)
* WebView内存泄露的情况还是很常见的,尤其是当你加载的页面比较庞大的时候,
* 因此我们是可以在加载WebView页面的activity中单独开启一个新的进程,这样就能和我们app的主进程分开了,即使WebView产生了崩溃等问题也不会影响到主程序;
* android:process=".web"
* 但是,如果单独开启一个新的进程,每次进入内嵌页都比较慢,所以我们这里选择另外一种方式,通过java反射机制去解决WebView内存泄露的问题。
*/
public class CommonBrowserActivity extends AppCompatActivity {
// 上下文
private Context mContext;
// 内嵌页链接
private String mUrl = "";
// 加载网页的控件
private WebView mWebView;
// 网页控件的布局容器
private LinearLayout mLayoutBody;
// 缺省頁
private LinearLayout llDefault;
// 返回
private Button btnDefaultBack;
// 重試
private Button btnDefaultRefresh;
// 声明handler
private CommonHandler mHandler;
// 页面是否加载错误
private boolean isLoadError = false;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 初始化数据
initData();
// 隐藏标题
requestWindowFeature(Window.FEATURE_NO_TITLE);
// 沉浸模式(头部由触屏端实现)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
}
// 设置界面
setContentView(R.layout.activity_common_browser);
// 初始化组件
initView();
// 加载链接
loadUrl(mUrl);
}
/**
* 初始化数据
*/
private void initData() {
mContext = this;
mHandler = new CommonHandler(this);
Intent intent = getIntent();
Bundle bundle = intent.getExtras();
if (bundle != null && bundle.containsKey("url")) {
mUrl = bundle.getString("url");
}
}
/**
* 初始化组件
*/
private void initView() {
/**
* 动态添加WebView,解决内存占回收用无效的问题,
* WebView构建时如果传入Activity的Context的话,对内存的引用会一直被保持着;
* WebView构建时如果传入Activity的ApplicationContext的话,可以防止内存溢出,但是有个问题:
* 如果你需要在WebView中打开链接或者你打开的页面带有flash,或者你的WebView想弹出一个dialog,
* 都会导致从ApplicationContext到ActivityContext的强制类型转换错误,从而导致你应用崩溃,
* 这是因为在加载flash的时候,系统会首先把你的WebView作为父控件,然后在该控件上绘制flash,
* 它想找一个Activity的Context来绘制他,但是你传入的是ApplicationContext。
*/
mWebView = new WebView(this);
WebSettings webSettings = mWebView.getSettings();
// 默认是false 设置true允许和js交互
webSettings.setJavaScriptEnabled(true);
// 设置WebView是否使用viewport
webSettings.setUseWideViewPort(true);
// 设置WebView是否使用预览模式加载界面
webSettings.setLoadWithOverviewMode(true);
// 设置在WebView内部是否允许访问文件,默认允许访问
webSettings.setAllowFileAccess(true);
// 是否允许在WebView中访问内容URL(Content Url),默认允许
webSettings.setAllowContentAccess(true);
// 设置脚本是否允许自动打开弹窗,默认(false)不允许,适用于JavaScript方法window.open()
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
// 设置WebView是否不应从网络加载图像资源(通过http和https URI方案访问的资源),解决图像不显示问题
webSettings.setBlockNetworkImage(false);
// 设置字体百分比,适配内嵌页布局
webSettings.setTextZoom(100);
// 设置WebView是否应支持使用其屏幕缩放控件和手势进行缩放,默认值true
webSettings.setSupportZoom(true);
// 设置WebView是否应使用其内置缩放机制,默认值true
webSettings.setBuiltInZoomControls(false);
// 设置使用内置缩放机制时WebView是否应显示屏幕缩放控件,默认值为false
webSettings.setDisplayZoomControls(false);
// 设置默认的字符编码集,默认"UTF-8"
webSettings.setDefaultTextEncodingName("UTF-8");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
// 设置是否应允许在文件方案URL上下文中运行的JavaScript访问其他文件方案URL中的内容
webSettings.setAllowFileAccessFromFileURLs(true);
// 设置是否应允许在文件方案URL的上下文中运行的JavaScript访问来自任何源的内容
webSettings.setAllowUniversalAccessFromFileURLs(true);
}
/**
* 解决5.0 以后的WebView加载的链接为Https开头,但是链接里面的内容,比如图片链接为Http就会加载不出来
* MIXED_CONTENT_ALWAYS_ALLOW:允许从任何来源加载内容,即使起源是不安全的;
* MIXED_CONTENT_NEVER_ALLOW:不允许Https加载Http的内容,即不允许从安全的起源去加载一个不安全的资源;
* MIXED_CONTENT_COMPATIBILITY_MODE:当涉及到混合式内容时,WebView 会尝试去兼容最新Web浏览器的风格。
**/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
/**
* Android WebView自带的缓存机制:
* 浏览器缓存机制
* Application Cache 缓存机制
* Dom Storage 缓存机制
* Web SQL Database 缓存机制(不再推荐使用,不再维护,取而代之的是Indexed Database 缓存机制)
* Android 在4.4开始加入对 IndexedDB 的支持,只需打开允许 JS 执行的开关就好了
*/
// 开启 DOM storage API 功能 较大存储空间(5MB),Dom Storage 机制类似于 Android 的 SharedPreference机制
webSettings.setDomStorageEnabled(true);
// 开启 Application Caches 功能 方便构建离线APP
webSettings.setAppCacheEnabled(true);
/**
* 设置缓存模式
* LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
* LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
* LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。
* LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
*/
if (isNetworkConnected(this)) {
/**
* 如果有网络,则走浏览器缓存机制。
* 根据 HTTP 协议头里的 Cache-Control(或 Expires)和 Last-Modified(或 ETag)等字段来控制文件缓存的机制
* Cache-Control:用于控制文件在本地缓存有效时长,如服务器回包:Cache-Control:max-age=600,则表示文件在本地应该缓存,且有效时长是600秒(从发出请求算起);
* 在接下来600秒内,如果有请求这个资源,浏览器不会发出 HTTP 请求,而是直接使用本地缓存的文件。
* Expires:与Cache-Control功能相同,即控制缓存的有效时间,Expires是 HTTP1.0 标准中的字段,
* Cache-Control 是 HTTP1.1 标准中新加的字段,当这两个字段同时出现时,Cache-Control 优先级较高。
* Last-Modified:标识文件在服务器上的最新更新时间,下次请求时,如果文件缓存过期,浏览器通过 If-Modified-Since 字段带上这个时间,发送给服务器,
* 由服务器比较时间戳来判断文件是否有修改。如果没有修改,服务器返回304告诉浏览器继续使用缓存;如果有修改,则返回200,同时返回最新的文件。
* ETag:功能同Last-Modified ,即标识文件在服务器上的最新更新时间,不同的是,ETag 的取值是一个对文件进行标识的特征字串。
* ETag 和 Last-Modified 可根据需求使用一个或两个同时使用。两个同时使用时,只要满足基中一个条件,就认为文件没有更新。
* 常见用法是:Cache-Control与 Last-Modified 一起使用;Expires与 ETag一起使用。
* 浏览器缓存机制 是 浏览器内核的机制,一般都是标准的实现。
*/
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
} else {
/**
* 如果没有网络,则走Application Cache 缓存机制
* 以文件为单位进行缓存,且文件有一定更新机制(类似于浏览器缓存机制)
* 存储静态文件(如JS、CSS、字体文件)
* AppCache 是对 浏览器缓存机制 的补充,不是替代
*/
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
}
/**
* 1. 接口互调引起远程代码执行漏洞
* 漏洞产生原因是:当JS拿到Android这个对象后,就可以调用这个Android对象中所有的方法,包括系统类(java.lang.Runtime 类),从而进行任意代码执行。
* 在Android 4.2版本之前,采用拦截prompt()进行漏洞修复
* 在Android 4.2版本之后Google规定对被调用的函数以 @JavascriptInterface进行注解从而避免漏洞攻击;
*
* 2.searchBoxJavaBridge_接口引起远程代码执行漏洞
* 漏洞产生原因:在Android 3.0以下,Android系统会默认通过searchBoxJavaBridge_的Js接口给 WebView 添加一个JS映射对象:searchBoxJavaBridge_对象,
* 该接口可能被利用,实现远程任意代码,删除searchBoxJavaBridge_接口即可。
* 当系统辅助功能服务被开启时,在 Android 4.4 以下的系统中,由系统提供的 WebView 组件都默认导出 accessibility 和 accessibilityTraversal 这两个接口,
* 它们同样存在远程任意代码执行的威胁,同样的需要通过 removeJavascriptInterface 方法将这两个对象删除
*/
try {
mWebView.removeJavascriptInterface("searchBoxJavaBridge_");
mWebView.removeJavascriptInterface("accessibility");
mWebView.removeJavascriptInterface("accessibilityTraversal");
} catch (Exception ex) {
}
mWebView.addJavascriptInterface(new JavaScriptInterfaceClass(), "JavaScriptInterface");
// 设置WebViewClient,处理各种通知和请求事件
mWebView.setWebViewClient(new WebViewClient() {
/**
* url重定向会执行此方法以及点击页面某些链接也会执行此方法
* @param view
* @param url
* @return true:表示当前url已经加载完成,即使url还会重定向都不会再进行加载 false 表示此url默认由系统处理,该重定向还是重定向,直到加载完成
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return isShouldOverrideUrlLoading(url + "");
}
/**
* url重定向会执行此方法以及点击页面某些链接也会执行此方法
* @param view
* @param request
* @return true:表示当前url已经加载完成,即使url还会重定向都不会再进行加载 false 表示此url默认由系统处理,该重定向还是重定向,直到加载完成
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString() + "";
return isShouldOverrideUrlLoading(url);
}
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
return WebViewCacheInterceptorInst.getInstance().interceptRequest(url);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
return WebViewCacheInterceptorInst.getInstance().interceptRequest(request);
}
// 开始载入页面调用的
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
setBlockNetworkImage(true);
}
// 在页面加载结束时调用
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
setBlockNetworkImage(false);
/**
* 判断条件:
* 1.页面加载错误
* 2.当前没有网络
* 对于是否显示缺省页,客户端只针对没有网络的情况下做处理,如果有网络的话缺省页则由触屏端显示。
*/
if (mHandler != null && isLoadError && !isNetworkConnected(mContext)) {
// 先移除消息
mHandler.removeMessages(WHAT_LOAD_ERROR);
// 再发送消息
mHandler.sendEmptyMessageDelayed(WHAT_LOAD_ERROR, 100);
}
// 还原变量
isLoadError = false;
}
// 网页产生错误时调用
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
/**
* 有网络的时候,如果加载失败的话,那么缺省页由触屏端提供,客户端不做处理。
* 当没有网络但是有缓存的时候,虽然调用了onReceivedError函数,但是用户还是可以看到界面的,
* 所以基于没有网络并且又回调了onReceivedError函数的情况下,根据以下两种情况去判定显示客户端的缺省页面,
* 1.网页标题为:Cannot read property 'data' of undefined
* 2.加载的网页内容高度小于父布局的高度
* 如果不满足以上任意一个条件,那么即便是显示的非正常页面,客户端也不会去显示缺省页。
*/
if (!isNetworkConnected(mContext)) {
if (!TextUtils.isEmpty(view.getTitle()) && view.getTitle().equals("Cannot read property 'data' of undefined")) {
// 这个时候说明页面展示一定是错误的,并且他的内容高度不一定会小于父布局的高度
setLayoutIsVisible(false);
} else {
// 这个时候不一定说明页面展示是错误的,也有可能因为缓存而正常显示页面,所以等页面加载完成之后,再根据页面内容的高度是否小于它的父布局判定
isLoadError = true;
}
}
}
// 网页产生错误时调用
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
super.onReceivedError(view, request, error);
/**
* 有网络的时候,如果加载失败的话,那么缺省页由触屏端提供,客户端不做处理。
* 当没有网络但是有缓存的时候,虽然调用了onReceivedError函数,但是用户还是可以看到界面的,
* 所以基于没有网络并且又回调了onReceivedError函数的情况下,根据以下两种情况去判定显示客户端的缺省页面,
* 1.网页标题为:Cannot read property 'data' of undefined
* 2.加载的网页内容高度小于父布局的高度
* 如果不满足以上任意一个条件,那么即便是显示的非正常页面,客户端也不会去显示缺省页。
*/
if (!isNetworkConnected(mContext)) {
if (!TextUtils.isEmpty(view.getTitle()) && view.getTitle().equals("Cannot read property 'data' of undefined")) {
setLayoutIsVisible(false);
} else {
isLoadError = true;
}
}
}
});
// 设置WebChromeClient,辅助 WebView 处理 Javascript 的对话框,网站图标,网站标题等等
mWebView.setWebChromeClient(new WebChromeClient() {
// 获取网页的加载进度
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
if (newProgress >= 100) {
setBlockNetworkImage(false);
}
}
});
// 找到布局,并且添加WebView
mLayoutBody = findViewById(R.id.llBody);
// 添加WebView
mLayoutBody.addView(mWebView);
// 缺省頁面
llDefault = findViewById(R.id.llDefault);
btnDefaultBack = findViewById(R.id.btnDefaultBack);
btnDefaultBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBack();
}
});
btnDefaultRefresh = findViewById(R.id.btnDefaultRefresh);
btnDefaultRefresh.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isNetworkConnected(mContext)) {
if (mWebView != null) {
setLayoutIsVisible(true);
mWebView.reload();
}
} else {
Toast.makeText(mContext, "网络异常,请检查网络!", Toast.LENGTH_LONG).show();
}
}
});
}
/**
* 加载URL
*/
private void loadUrl(String url) {
if (mWebView != null) {
// 最后拼接完成的URL
final String lastUrl = getBuildLinkUrl(url);
try {
// 加载URL
WebViewCacheInterceptorInst.getInstance().loadUrl(mWebView, lastUrl);
} catch (Exception ex) {
}
}
}
/**
* 是否应该覆盖URL加载及逻辑处理
*
* @param url
* @return
*/
private boolean isShouldOverrideUrlLoading(String url) {
final String whatsAppMark = "whatsapp://send?";
final String fbAppMark = "https://www.facebook.com/sharer/sharer.php?u=";
if (url.contains(whatsAppMark)) {
try {
if (isAppAvailable(mContext, "com.whatsapp")) {
// whatsApp分享
Intent intent = new Intent();
intent.setAction("android.intent.action.VIEW");
Uri content_url = Uri.parse(url);
intent.setData(content_url);
startActivity(intent);
return true;
}
} catch (Exception ex) {
}
} else if (url.contains(fbAppMark)) {
try {
if (isAppAvailable(mContext, "com.facebook.katana")) {
// Facebook分享
String fbText = url.substring(url.indexOf(fbAppMark) + fbAppMark.length());
if (!TextUtils.isEmpty(fbText)) {
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setComponent(new ComponentName("com.facebook.katana",
"com.facebook.composer.shareintent.ImplicitShareIntentHandlerDefaultAlias"));
//这里就是组织内容了
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_SUBJECT, "分享資訊");
shareIntent.putExtra(Intent.EXTRA_TEXT, fbText);
shareIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(shareIntent);
return true;
}
}
} catch (Exception ex) {
}
} else if (url.startsWith("tel:")) {
try {
// 拨打电话
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
} catch (Exception ex) {
}
return true;
} else if (url.startsWith("mailto:")) {
try {
// 发送短信
Intent i = new Intent(Intent.ACTION_SENDTO, Uri.parse(url));
startActivity(i);
} catch (Exception ex) {
}
return true;
}
loadUrl(url);
return true;
}
/**
* 设置图像加载是否受阻
*
* @param isBlock
*/
private void setBlockNetworkImage(boolean isBlock) {
if (mWebView != null) {
if (isBlock) {
// 阻止图像加载
mWebView.getSettings().setBlockNetworkImage(true);
} else {
// 允许图像加载
mWebView.getSettings().setBlockNetworkImage(false);
if (!mWebView.getSettings().getLoadsImagesAutomatically()) {
// 设置wenView加载图片资源
mWebView.getSettings().setLoadsImagesAutomatically(true);
}
}
}
}
/**
* 设置布局是否可见
*
* @param flag
*/
private void setLayoutIsVisible(boolean flag) {
if (mLayoutBody != null) {
mLayoutBody.setVisibility(flag ? View.VISIBLE : View.GONE);
}
if (llDefault != null) {
llDefault.setVisibility(flag ? View.GONE : View.VISIBLE);
}
}
// 加载错误
private final static int WHAT_LOAD_ERROR = 0;
// app返回
private final static int WHAT_GO_BACK = 1;
// app分享
private final static int WHAT_APP_SHARE = 2;
// 调用第三方浏览器
private final static int WHAT_OPEN_BROWSER = 3;
// 跳转指定界面
private final static int WHAT_JUMP_ACTIVITY = 4;
/**
* 在Java 中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用,静态的内部类不会持有外部类的引用
*/
private static class CommonHandler extends Handler {
private WeakReference<CommonBrowserActivity> mWeakReference;
public CommonHandler(CommonBrowserActivity activity) {
mWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
final CommonBrowserActivity activity = mWeakReference.get();
if (activity != null && !activity.isFinishing()) {
switch (msg.what) {
case WHAT_LOAD_ERROR:
// 父布局和网页控件不能为空
if (activity.mLayoutBody != null && activity.mWebView != null) {
/**
* 对于是否显示缺省页,客户端只针对没有网络的情况下做处理,如果有网络的话缺省页则由触屏端显示。
* 当没有网络但是有缓存的时候,虽然系统执行了onReceivedError回调函数,但是用户还是可以看到正常界面的。
* 判断依据:
* 条件1:没有网络的时候
* 条件2:加载网页失败,系统执行了onReceivedError回调函数的时候
* 条件3:网页的内容高度小于网页父布局高度的时候
* 同时满足以上三个条件时,才会显示加载失败的缺省页面,否则就正常显示网页。
*/
if (activity.mWebView.getContentHeight() < activity.mLayoutBody.getHeight()) {
activity.setLayoutIsVisible(false);
} else {
activity.setLayoutIsVisible(true);
}
}
break;
case WHAT_GO_BACK:
activity.onBack();
break;
case WHAT_APP_SHARE:
Object objShare = msg.obj;
if (objShare != null && objShare instanceof String) {
activity.onShare((String) objShare);
}
break;
case WHAT_OPEN_BROWSER:
Object objBrowser = msg.obj;
if (objBrowser != null && objBrowser instanceof String) {
activity.onBrowser((String) objBrowser);
}
break;
case WHAT_JUMP_ACTIVITY:
try {
Object objJump = msg.obj;
if (objJump != null && objJump instanceof HashMap) {
HashMap<String, String> map = (HashMap<String, String>) objJump;
if (map != null) {
// 获取跳转的页面
String className = map.containsKey("class_name") ? map.get("class_name") : "";
// 获取传值的参数
String jsonExtras = map.containsKey("json_extras") ? map.get("json_extras") : "";
if (!TextUtils.isEmpty(className) && !className.equals("undefined")) {
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Intent.ACTION_VIEW);
if (!TextUtils.isEmpty(jsonExtras) && !jsonExtras.equals("undefined")) {
Bundle extras = new Bundle();
JSONArray items = JSONAnalyze.getJSONArray(jsonExtras);
if (items != null && items.length() > 0) {
for (int i = 0; i < items.length(); i++) {
JSONObject itemsObject = JSONAnalyze.getJSONObject(items, i);
if (itemsObject != null) {
String key = JSONAnalyze.getJSONValue(itemsObject, "key");
String value = JSONAnalyze.getJSONValue(itemsObject, "value");
if (!TextUtils.isEmpty(key)) {
extras.putString(key, value);
}
}
}
}
intent.putExtras(extras);
}
// 类反射机制
Class<?> clazz = Class.forName(className);
intent.setClass(activity, clazz);
activity.startActivity(intent);
}
}
}
} catch (Exception e) {
}
break;
}
}
}
}
private class JavaScriptInterfaceClass {
public JavaScriptInterfaceClass() {
}
/**
* app回退
*/
@JavascriptInterface
public void backPage() {
if (mHandler != null) {
mHandler.sendEmptyMessage(WHAT_GO_BACK);
}
}
/**
* app分享
*
* @param text
*/
@JavascriptInterface
public void appShare(final String text) {
if (TextUtils.isEmpty(text) || text.equals("undefined")) {
return;
}
if (mHandler != null) {
// 避免重复创建Message对象
Message msg = Message.obtain();
msg.obj = text;
msg.what = WHAT_APP_SHARE;
mHandler.sendMessage(msg);
}
}
/**
* 调用第三方浏览器
*
* @param url
*/
@JavascriptInterface
public void openBrowser(final String url) {
if (TextUtils.isEmpty(url) || url.equals("undefined")) {
return;
}
if (mHandler != null) {
// 避免重复创建Message对象
Message msg = Message.obtain();
msg.obj = url;
msg.what = WHAT_OPEN_BROWSER;
mHandler.sendMessage(msg);
}
}
/**
* 跳转指定界面
*
* @param className
* @param jsonExtras
*/
@JavascriptInterface
public void jumpActivity(String className, String jsonExtras, String supportMinVersion) {
if (TextUtils.isEmpty(className) || className.equals("undefined")) {
return;
}
try {
// 最低支持的版本,(默认可支持任意版本)
int minVersion = 0;
if (!TextUtils.isEmpty(supportMinVersion) && !supportMinVersion.equals("undefined")) {
minVersion = Integer.parseInt(supportMinVersion);
}
PackageInfo packageInfo = getPackageManager().getPackageInfo(mContext.getPackageName(), 0);
final long curVersionCode = packageInfo.versionCode;
if (curVersionCode < minVersion) {
return;
}
} catch (Exception ex) {
}
if (mHandler != null) {
HashMap<String, String> map = new HashMap<>();
map.put("class_name", "" + className);
map.put("json_extras", "" + jsonExtras);
// 避免重复创建Message对象
Message msg = Message.obtain();
msg.obj = map;
msg.what = WHAT_JUMP_ACTIVITY;
mHandler.sendMessage(msg);
}
}
}
/**
* 链接拼接(根据自己的需要进行参数拼接)
*
* @param url
* @return
*/
private String getBuildLinkUrl(String url) {
if (!TextUtils.isEmpty(url) && url.startsWith("http")) {
StringBuffer buffer = new StringBuffer();
if (!url.contains("device")) {
buffer.append("&device=android");
}
if (!url.contains("app_id")) {
String appId;
if (Build.VERSION.SDK_INT >= 23 &&
(ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED)) {
// 没有权限,自行获取UUID
appId = "uuid_self_acquisition";
} else {
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
appId = telephonyManager.getDeviceId();
}
buffer.append("&app_id=" + appId);
}
if (!url.contains("version")) {
String version = "";
try {
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
version = packageInfo.versionCode + "";
} catch (Exception ex) {
}
buffer.append("&version=" + version);
}
if (!url.contains("status_bar_height") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
buffer.append("&status_bar_height=" + pxToDip(this, getStatusBarHeight(this)));
}
if (!TextUtils.isEmpty(buffer.toString())) {
if (url.contains("?")) {
if (url.endsWith("?") || url.endsWith("&")) {
String sub = buffer.toString();
try {
sub = sub.substring(sub.indexOf("&") + 1);
} catch (Exception ex) {
}
return url + sub;
} else {
return url + buffer.toString();
}
} else {
String sub = buffer.toString();
try {
sub = sub.substring(sub.indexOf("&") + 1);
} catch (Exception ex) {
}
return url + "?" + sub;
}
} else {
return url;
}
} else {
return url;
}
}
/**
* 调用系统浏览器
*/
private void onBrowser(String url) {
try {
if (url.startsWith("http")) {
Uri uri = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
} catch (Exception ex) {
}
}
/**
* 分享
*
* @param text
*/
private void onShare(String text) {
try {
JSONObject jsonObject = JSONAnalyze.getJSONObject(text);
String title = JSONAnalyze.getJSONValue(jsonObject, "title");
String url = JSONAnalyze.getJSONValue(jsonObject, "url");
String pageType = JSONAnalyze.getJSONValue(jsonObject, "pageType");
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_SUBJECT, pageType);
intent.putExtra(Intent.EXTRA_TEXT, "" + title + "\n" + url);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(Intent.createChooser(intent, "分享給好友"));
} catch (Exception ex) {
}
}
/**
* 返回
*/
private void onBack() {
if (mWebView != null && mWebView.canGoBack()) {
setLayoutIsVisible(true);
mWebView.goBack();
} else {
finish();
/**
* 注意:要在当前activity执行finish之后才调用
* com.sec.android.app.launcher.activities.LauncherActivity
* 跳转首页(比如推送进来的,返回的时候不至于直接退出app)
*/
try {
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
ComponentName cn = am.getRunningTasks(1).get(0).topActivity;
if (cn.getPackageName() != getPackageName()) {
startActivity(new Intent(this, MainActivity.class));
}
} catch (Exception ex) {
}
}
}
/**
* 获取状态栏高度
*
* @param context
* @return
*/
private int getStatusBarHeight(Context context) {
int height = 0;
try {
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
height = context.getResources().getDimensionPixelSize(resourceId);
}
} catch (Exception ex) {
}
return height;
}
/**
* 判断app是否有效
*
* @param ctx
* @param pkgName
* @return
*/
private boolean isAppAvailable(Context ctx, String pkgName) {
PackageInfo packageInfo;
try {
packageInfo = ctx.getPackageManager().getPackageInfo(pkgName, 0);
} catch (PackageManager.NameNotFoundException e) {
packageInfo = null;
e.printStackTrace();
}
if (packageInfo != null) {
return true;
}
return false;
}
/**
* px转dp
*
* @param context
* @param pxValue
* @return
*/
private int pxToDip(Context context, float pxValue) {
float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
/**
* 判断是否有网络
*
* @param context
* @return
*/
private boolean isNetworkConnected(Context context) {
if (context != null) {
ConnectivityManager cm = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo ni = cm.getActiveNetworkInfo();
return (ni != null) && (ni.isConnectedOrConnecting());
}
return true;
}
@Override
protected void onResume() {
super.onResume();
if (mWebView != null) {
/**
* 执行自己的生命周期
*/
mWebView.onResume();
// 恢复与JS交互
mWebView.getSettings().setJavaScriptEnabled(true);
}
}
@Override
protected void onPause() {
super.onPause();
if (mWebView != null) {
/**
* 执行自己的生命周期
*/
mWebView.onPause();
// 禁用与JS交互,防止后台无法释放js 导致耗电
mWebView.getSettings().setJavaScriptEnabled(false);
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.getRepeatCount() == 0) {
onBack();
}
return true;
}
return super.dispatchKeyEvent(event);
}
/**
* 释放WebView,避免内存泄漏
*/
private void releaseAllWebViewCallback() {
if (android.os.Build.VERSION.SDK_INT < 16) {
try {
Field field = WebView.class.getDeclaredField("mWebViewCore");
field = field.getType().getDeclaredField("mBrowserFrame");
field = field.getType().getDeclaredField("sConfigCallback");
field.setAccessible(true);
field.set(null, null);
} catch (Exception e) {
}
} else {
try {
Field sConfigCallback = Class.forName("android.webkit.BrowserFrame").getDeclaredField("sConfigCallback");
if (sConfigCallback != null) {
sConfigCallback.setAccessible(true);
sConfigCallback.set(null, null);
}
} catch (Exception e) {
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// 停止浏览器缓存
if (mWebView != null) {
/**
* 因为WebView构建时传入了该Activity的context对象,
* 所以需要先从父容器中移除WebView,然后再销毁WebView
*/
if (mLayoutBody != null) {
mLayoutBody.removeView(mWebView);
}
// 停止加载
mWebView.stopLoading();
// 解决getSettings().setBuiltInZoomControls(true) 引发的crash的问题
mWebView.setVisibility(View.GONE);
// 移除所有组件
mWebView.removeAllViews();
// 销毁WebView,避免OOM异常
mWebView.destroy();
// WebView对象置空
mWebView = null;
// LayoutBody对象置空
mLayoutBody = null;
// 释放WebView
releaseAllWebViewCallback();
}
if (mHandler != null) {
// 删除handler所有的消息和回调函数,避免内存泄漏
mHandler.removeCallbacksAndMessages(null);
mHandler = null;
}
}
}
activity_common_browser.xml的实现
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff">
<LinearLayout
android:id="@+id/llBody"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:background="#ffffff"
android:orientation="vertical" />
<LinearLayout
android:id="@+id/llDefault"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/bg_default_web" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/width128px"
android:gravity="center"
android:singleLine="true"
android:text="加载失败,请稍后重试!"
android:textColor="#bbbdbf"
android:textSize="@dimen/width40px" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/width240px"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/btnDefaultBack"
android:layout_width="@dimen/width288px"
android:layout_height="@dimen/width88px"
android:layout_marginRight="@dimen/width30px"
android:background="@drawable/share_btn_browser_default"
android:text="返回"
android:textColor="#737373"
android:textSize="@dimen/width43px" />
<Button
android:id="@+id/btnDefaultRefresh"
android:layout_width="@dimen/width288px"
android:layout_height="@dimen/width88px"
android:layout_marginLeft="@dimen/width30px"
android:background="@drawable/share_btn_browser_default"
android:text="重試"
android:textColor="#737373"
android:textSize="@dimen/width43px" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
布局中的分辨率请参考:https://blog.csdn.net/u011038298/article/details/83269208