android实现WebView通用版

 

目录

WebView常用方法介绍

WebSettings常用方法介绍

WebViewClient常用函数介绍

WebViewClient错误码大全

WebChromeClient常用函数介绍

 

实现CacheWebView依赖

BaseApplication.java的实现:

JSONAnalyze.java的实现

CommonBrowserActivity.java的实现

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

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值