1.引子
因为系统原生的webview存在不同手机性能多差异化,android版本不同webview运行H5也有体验也存在差别。经过对比分析,目前tbs x5 内核 和corsswalk对webview的性能有优化。最终选择x5 内核进行替换系统webview,从而封装成自有webview库Fcwebview。
2.需要达到的目标
- 封装合理
- 简易使用
- 易于扩展
- 方便切换
3.目标拆解和解决思路
3.1 怎么算封装合理?
符合自己的需求才是合理的。
- 迪米特法则(高内聚,低耦合)
- 单一职责原则(Fcwebview与具体的x5内核模块隔离)
- 把常用webview操作的方法提取,并且封装
- 对app而言只需要操作Fcwebview,不直接操作x5内核相关的具体方法
3.2 怎么算简易使用?
- 使用Fcwebview 和使用正常的标准webview 的方法,以及属性保持一致,不增加额外的学习成本
- 在常用的webSetting中通常需要进行一系列的设置操作,结合我们自身的app,基本上设置保持一致。可以简化操作,甚至集成到Fcwebview中
- webview js的操作进行简化
- CookieManager 同步操作进行简化
3.3 怎么算易于扩展?
- 依赖倒转原则(Fcwebview的方法都面向接口,而不是面向具体x5内核的实现。)
- 采用适配器模式对于标准webview的接口可以有多套实现方式,而不能固定某种具体方案。
3.4 怎么算方便切换?
- 采用代理模式可以切换不同webview内核去具体实现。
- 切换内核的过程让用户之前调用Fcwebview 的方法或者引入的包尽可能少的改动。
4. FcWebview的架构图
由上面的思路,先进行FcWebview的整体模块划分。
4.1 划分成3个模块:
image.png-
抽象一个webview 相关的通用接口层,也是webviewFramework模块。包括webview,webSetting,CookieManager,Webviewchrome,webviewClient 等的公开接口。保证所有的内核都能够实现,对于具体应用层都是相同的调用。
-
x5webviewcore模块。实现了抽象webviewFramework接口,并且适配成x5内核的具体实现。
-
Fcwebview模块。对外暴露的通用自定义webview,内部默认设置x5webviewcore进行代理,可以动态支持替换其他的webview内核。Fcwebview进行对一些常用的方法进行进一步封装,对外提供一个简易的webview调用。对app而言只需要和Fcwebview进行通信,包括布局文件,WebSetting等等。
-
白色区域表示后续可以进行扩展其他webview 内核。
4.2 架构UML图
image.png5.各个模块的代码分析
5.1 webviewFramework
image.png这个模块包括了webview的通用接口,最主要的目的就是抽象出一套具有自己特色的webview的接口/方法,使得这些与具体的实现类分离.
- IWebview 这是核心接口.进行对webview的裁剪,把相对常用的方法提取出来了.
package com.fcbox.anglib.fcwebview.base;
import android.content.Context;
import android.graphics.Bitmap;
import android.view.View;
import java.util.Map;
/**
* @author: 朱丽君
* @create: 19-5-22 上午11:54
* @description:webview的通用接口
*/
public interface IWebview {
public View getWebview();
public View getView();
public void loadUrl(String url, Map<String, String> additionalHttpHeaders);
public void loadUrl(String url);
public void postUrl(String url, byte[] postData);
public void loadData(String data, String mimeType, String encoding);
public void loadDataWithBaseURL(String baseUrl, String data,
String mimeType, String encoding, String historyUrl);
public void stopLoading();
public void reload();
public boolean canGoBack();
public void goBack();
public boolean canGoForward();
public void goForward();
public boolean canGoBackOrForward(int steps);
public void goBackOrForward(int steps);
public String getUrl();
public String getOriginalUrl();
public String getTitle();
public Bitmap getFavicon();
public int getProgress();
public int getContentHeight();
public int getContentWidth();
public void onPause();
public void onResume();
public void freeMemory();
public void clearCache(boolean includeDiskFiles);
public void clearFormData();
public void clearHistory();
public void clearSslPreferences();
public void setWebViewClient(WebViewClient client);
public void setDownloadListener(DownloadListener listener);
public void setWebChromeClient(WebChromeClient client);
public void addJavascriptInterface(Object obj, String interfaceName);
public void removeJavascriptInterface(String interfaceName);
public IWebSettings getFcWebviewSettings();
public boolean canZoomIn();
public boolean canZoomOut();
public boolean zoomIn();
public boolean zoomOut();
public void destory();
}
其中在webview中包含 WebSetting,DownloadListener,WebViewClient,WebChromeClient 等系统的类,为了隔离性更强,把他们都与系统api分离,单独做成我们基础库中.
- 所以把WebSetting 也进行接口化,IWebSettings
public interface IWebSettings {
/**
* Used with {@link #setMixedContentMode}
* <p>
* In this mode, the WebView will allow a secure origin to load content from any other origin,
* even if that origin is insecure. This is the least secure mode of operation for the WebView,
* and where possible apps should not set this mode.
*/
public static final int MIXED_CONTENT_ALWAYS_ALLOW = 0;
/**
* Used with {@link #setMixedContentMode}
* <p>
* In this mode, the WebView will not allow a secure origin to load content from an insecure
* origin. This is the preferred and most secure mode of operation for the WebView and apps are
* strongly advised to use this mode.
*/
public static final int MIXED_CONTENT_NEVER_ALLOW = 1;
/**
* Used with {@link #setMixedContentMode}
* <p>
* In this mode, the WebView will attempt to be compatible with the approach of a modern web
* browser with regard to mixed content. Some insecure content may be allowed to be loaded by
* a secure origin and other types of content will be blocked. The types of content are allowed
* or blocked may change release to release and are not explicitly defined.
* <p>
* This mode is intended to be used by apps that are not in control of the content that they
* render but desire to operate in a reasonably secure environment. For highest security, apps
* are recommended to use {@link #MIXED_CONTENT_NEVER_ALLOW}.
*/
public static final int MIXED_CONTENT_COMPATIBILITY_MODE = 2;
@TargetApi(5)
public void setDatabaseEnabled(boolean var1);
@TargetApi(5)
public void setGeolocationEnabled(boolean var1);
@TargetApi(5)
public void setGeolocationDatabasePath(String var1);
public void setJavaScriptEnabled(boolean var1);
public void setBlockNetworkImage(boolean var1);
@TargetApi(7)
public void setDomStorageEnabled(boolean var1);
public void setSupportZoom(boolean var1);
@TargetApi(3)
public void setBuiltInZoomControls(boolean var1);
public void setUseWideViewPort(boolean var1);
@TargetApi(7)
public void setAppCacheEnabled(boolean var1);
@TargetApi(7)
public void setLoadWithOverviewMode(boolean var1);
public void setJavaScriptCanOpenWindowsAutomatically(boolean var1);
@TargetApi(3)
public void setAllowFileAccess(boolean var1);
@TargetApi(21)
public void setMixedContentMode(int mode);
}
3.对于WebviewClient ,进行了结合我们自己的app的一些常用设置进行裁剪,把一些平时用的少的方法舍弃了.至保留了两个用的多重要的方法.
package com.fcbox.anglib.fcwebview.base;
import com.fcbox.anglib.fcwebview.base.IWebview;
/**
* @author: 朱丽君
* @create: 19-5-22 上午11:57
* @description:这里的WebviewClient进行了相应的裁剪.保留了2个重要的方法
*/
public class WebViewClient {
public boolean shouldOverrideUrlLoading(IWebview var1, String var2) {
return false;
}
public void onReceivedError(IWebview view, int errorCode, String description, String failingUrl) {
}
}
- 对于WebChromeClient 类也进行了裁剪.保留了以下的方法
package com.fcbox.anglib.fcwebview.base;
import android.graphics.Bitmap;
import android.net.Uri;
import com.fcbox.anglib.fcwebview.base.GeolocationPermissionsCallback;
/**
* @author: 朱丽君
* @create: 19-5-22 下午3:23
* @description:
*/
public class WebChromeClient {
public void onGeolocationPermissionsShowPrompt(String var1, GeolocationPermissionsCallback callback) {
}
public void onProgressChanged(IWebview webview, int var2) {
}
public void onReceivedIcon(IWebview webview, Bitmap var2) {
}
public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType, String capture) {
}
public boolean onShowFileChooser(IWebview webView, ValueCallback<Uri[]> valueCallback) {
return false;
}
}
- webview中的CookieManager ,对于cookie的同步通常使用个CookieSyncManager 的sync方法.为了方便管理,我把这两个类合并到一起. 即ICookieManager
package com.fcbox.anglib.fcwebview.base;
/**
* @author: 朱丽君
* @create: 19-5-23 下午6:10
* @description:
*/
public interface ICookieManager {
/**
* Sets whether the application's {@link IWebview} instances should send and
* accept cookies.
* By default this is set to true and the WebView accepts cookies.
* <p>
* When this is true
* {@link CookieManager#setAcceptThirdPartyCookies setAcceptThirdPartyCookies} and
* {@link CookieManager#setAcceptFileSchemeCookies setAcceptFileSchemeCookies}
* can be used to control the policy for those specific types of cookie.
*
* @param accept whether {@link WebView} instances should send and accept
* cookies
*/
public abstract void setAcceptCookie(boolean accept);
/**
* Gets whether the application's {@link WebView} instances send and accept
* cookies.
*
* @return true if {@link WebView} instances send and accept cookies
*/
public abstract boolean acceptCookie();
/**
* Sets whether the {@link WebView} should allow third party cookies to be set.
* Allowing third party cookies is a per WebView policy and can be set
* differently on different WebView instances.
* <p>
* Apps that target {@link android.os.Build.VERSION_CODES#KITKAT} or below
* default to allowing third party cookies. Apps targeting
* {@link android.os.Build.VERSION_CODES#LOLLIPOP} or later default to disallowing
* third party cookies.
*
* @param webview the {@link WebView} instance to set the cookie policy on
* @param accept whether the {@link WebView} instance should accept
* third party cookies
*/
public abstract void setAcceptThirdPartyCookies(IWebview webview, boolean accept);
/**
* Gets whether the {@link WebView} should allow third party cookies to be set.
*
* @param webview the {@link WebView} instance to get the cookie policy for
* @return true if the {@link WebView} accepts third party cookies
*/
public abstract boolean acceptThirdPartyCookies(IWebview webview);
/**
* Sets a cookie for the given URL. Any existing cookie with the same host,
* path and name will be replaced with the new cookie. The cookie being set
* will be ignored if it is expired.
*
* @param url the URL for which the cookie is to be set
* @param value the cookie as a string, using the format of the 'Set-Cookie'
* HTTP response header
*/
public abstract void setCookie(String url, String value);
/**
* Sets a cookie for the given URL. Any existing cookie with the same host,
* path and name will be replaced with the new cookie. The cookie being set
* will be ignored if it is expired.
* <p>
* This method is asynchronous.
* If a {@link ValueCallback} is provided,
* {@link ValueCallback#onReceiveValue(T) onReceiveValue()} will be called on the current
* thread's {@link android.os.Looper} once the operation is complete.
* The value provided to the callback indicates whether the cookie was set successfully.
* You can pass {@code null} as the callback if you don't need to know when the operation
* completes or whether it succeeded, and in this case it is safe to call the method from a
* thread without a Looper.
*
* @param url the URL for which the cookie is to be set
* @param value the cookie as a string, using the format of the 'Set-Cookie'
* HTTP response header
* @param callback a callback to be executed when the cookie has been set
*/
public abstract void setCookie(String url, String value, ValueCallback<Boolean> callback);
/**
* Gets the cookies for the given URL.
*
* @param url the URL for which the cookies are requested
* @return value the cookies as a string, using the format of the 'Cookie'
* HTTP request header
*/
public abstract String getCookie(String url);
/**
* Removes all session cookies, which are cookies without an expiration
* date.
* @deprecated use {@link #removeSessionCookies(ValueCallback)} instead.
*/
@Deprecated
public abstract void removeSessionCookie();
/**
* Removes all session cookies, which are cookies without an expiration
* date.
* <p>
* This method is asynchronous.
* If a {@link ValueCallback} is provided,
* {@link ValueCallback#onReceiveValue(T) onReceiveValue()} will be called on the current
* thread's {@link android.os.Looper} once the operation is complete.
* The value provided to the callback indicates whether any cookies were removed.
* You can pass {@code null} as the callback if you don't need to know when the operation
* completes or whether any cookie were removed, and in this case it is safe to call the
* method from a thread without a Looper.
* @param callback a callback which is executed when the session cookies have been removed
*/
public abstract void removeSessionCookies(ValueCallback<Boolean> callback);
/**
* Removes all cookies.
* @deprecated Use {@link #removeAllCookies(ValueCallback)} instead.
*/
@Deprecated
public abstract void removeAllCookie();
/**
* Removes all cookies.
* <p>
* This method is asynchronous.
* If a {@link ValueCallback} is provided,
* {@link ValueCallback#onReceiveValue(Object)} onReceiveValue()} will be called on the current
* thread's {@link android.os.Looper} once the operation is complete.
* The value provided to the callback indicates whether any cookies were removed.
* You can pass {@code null} as the callback if you don't need to know when the operation
* completes or whether any cookies were removed, and in this case it is safe to call the
* method from a thread without a Looper.
* @param callback a callback which is executed when the cookies have been removed
*/
public abstract void removeAllCookies(ValueCallback<Boolean> callback);
/**
* Gets whether there are stored cookies.
*
* @return true if there are stored cookies
*/
public abstract boolean hasCookies();
/**
* Removes all expired cookies.
* @deprecated The WebView handles removing expired cookies automatically.
*/
@Deprecated
public abstract void removeExpiredCookie();
/**
* Ensures all cookies currently accessible through the getCookie API are
* written to persistent storage.
* This call will block the caller until it is done and may perform I/O.
*/
public abstract void flush();
/**
* 增加一个同步的方法
*/
public void sync();
}
以上就是webviewFramework的核心内容.
5.2 x5core 模块
image.png这个模块是具体的第三方sdk适配层.从这个模块可以衍生出其他的模块,例如增加crosswalk模块,以及未来其他的webview内核模块.这些模块的作用是 用自己的特性来适配成我们自己约定的webview的接口(即webviewFramework模块).里面采用的当然是适配器模式.
5.1 简单介绍下适配器模式:
-
1.类适配器模式
image.png
- 2.对象适配器模式
两者的区别是,类适配器模式是继承,而对象适配器是引用对象.考虑到我们这边的x5内核提供了一个webview,包括视图,刚好我们也需要,所以选择类适配器是比较合适的.
3. X5WebviewAdapter
package com.fcbox.anglib.x5core.x5Adapter;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import com.fcbox.anglib.fcwebview.base.DownloadListener;
import com.fcbox.anglib.fcwebview.base.IWebSettings;
import com.fcbox.anglib.fcwebview.base.IWebview;
import com.fcbox.anglib.fcwebview.base.WebChromeClient;
import com.fcbox.anglib.fcwebview.base.WebViewClient;
import com.tencent.smtt.export.external.interfaces.GeolocationPermissionsCallback;
import com.tencent.smtt.export.external.interfaces.SslError;
import com.tencent.smtt.export.external.interfaces.SslErrorHandler;
import com.tencent.smtt.sdk.ValueCallback;
import com.tencent.smtt.sdk.WebView;
import java.util.Map;
/**
* @author: 朱丽君
* @create: 19-5-22 上午11:49
* @description: X5webview 作为 Iwebview的一个Adapter
*/
public class X5WebviewAdapter extends WebView implements IWebview {
private IWebSettings fcWebSetting;
public X5WebviewAdapter(@NonNull Context context) {
this(context, null);
}
public X5WebviewAdapter(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public X5WebviewAdapter(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public X5WebviewAdapter(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
super(context, attrs, defStyleAttr, javaScriptInterfaces, privateBrowsing);
}
@Override
public View getView() {
return super.getView();
}
@Override
public View getWebview() {
return this;
}
@Override
public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
super.loadUrl(url, additionalHttpHeaders);
}
@Override
public void loadUrl(String url) {
super.loadUrl(url);
}
@Override
public void postUrl(String url, byte[] postData) {
super.postUrl(url, postData);
}
@Override
public void loadData(String data, String mimeType, String encoding) {
super.loadData(data, mimeType, encoding);
}
@Override
public void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl) {
super.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
}
@Override
public void stopLoading() {
super.stopLoading();
}
@Override
public void reload() {
super.reload();
}
@Override
public boolean canGoBack() {
return super.canGoBack();
}
@Override
public void goBack() {
super.goBack();
}
@Override
public boolean canGoForward() {
return super.canGoForward();
}
@Override
public void goForward() {
super.goForward();
}
@Override
public boolean canGoBackOrForward(int steps) {
return super.canGoBackOrForward(steps);
}
@Override
public void goBackOrForward(int steps) {
super.goBackOrForward(steps);
}
@Override
public String getUrl() {
return super.getUrl();
}
@Override
public String getOriginalUrl() {
return super.getOriginalUrl();
}
@Override
public String getTitle() {
return super.getTitle();
}
@Override
public Bitmap getFavicon() {
return super.getFavicon();
}
@Override
public int getProgress() {
return super.getProgress();
}
@Override
public int getContentHeight() {
return super.getContentHeight();
}
@Override
public int getContentWidth() {
return super.getContentWidth();
}
@Override
public void onPause() {
super.onPause();
}
@Override
public void onResume() {
super.onResume();
}
@Override
public void freeMemory() {
super.freeMemory();
}
@Override
public void clearCache(boolean includeDiskFiles) {
super.clearCache(includeDiskFiles);
}
@Override
public void clearFormData() {
super.clearFormData();
}
@Override
public void clearHistory() {
super.clearHistory();
}
@Override
public void clearSslPreferences() {
super.clearSslPreferences();
}
@Override
public void setWebViewClient(final WebViewClient client) {
super.setWebViewClient(new com.tencent.smtt.sdk.WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView webView, String s) {
return client.shouldOverrideUrlLoading(X5WebviewAdapter.this, s);
}
@Override
public void onReceivedSslError(WebView webView, SslErrorHandler sslErrorHandler, SslError sslError) {
sslErrorHandler.proceed();
}
});
}
@Override
public void setDownloadListener(final DownloadListener listener) {
super.setDownloadListener(new com.tencent.smtt.sdk.DownloadListener() {
@Override
public void onDownloadStart(String url, String userAgent,
String contentDisposition, String mimetype, long contentLength) {
listener.onDownloadStart(url, userAgent, contentDisposition, mimetype, contentLength);
}
});
}
@Override
public void setWebChromeClient(final WebChromeClient client) {
super.setWebChromeClient(new com.tencent.smtt.sdk.WebChromeClient() {
@Override
public void onGeolocationPermissionsShowPrompt(String s, final GeolocationPermissionsCallback geolocationPermissionsCallback) {
client.onGeolocationPermissionsShowPrompt(s, new com.fcbox.anglib.fcwebview.base.GeolocationPermissionsCallback() {
@Override
public void invoke(String origin, boolean allow, boolean retain) {
geolocationPermissionsCallback.invoke(origin, allow, retain);
}
});
}
@Override
public void onProgressChanged(WebView webView, int i) {
client.onProgressChanged(X5WebviewAdapter.this, i);
}
@Override
public void onReceivedIcon(WebView webView, Bitmap bitmap) {
client.onReceivedIcon(X5WebviewAdapter.this, bitmap);
}
@Override
public void openFileChooser(final ValueCallback<Uri> uploadFile, String acceptType, String capture) {
client.openFileChooser(new com.fcbox.anglib.fcwebview.base.ValueCallback<Uri>() {
@Override
public void onReceiveValue(Uri var1) {
uploadFile.onReceiveValue(var1);
}
}, acceptType, capture);
}
@Override
public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> valueCallback, FileChooserParams fileChooserParams) {
return client.onShowFileChooser(X5WebviewAdapter.this, new com.fcbox.anglib.fcwebview.base.ValueCallback<Uri[]>() {
@Override
public void onReceiveValue(Uri[] var1) {
valueCallback.onReceiveValue(var1);
}
});
}
});
}
@Override
public void addJavascriptInterface(Object obj, String interfaceName) {
super.addJavascriptInterface(obj, interfaceName);
}
@Override
public void removeJavascriptInterface(String interfaceName) {
super.removeJavascriptInterface(interfaceName);
}
@Override
public IWebSettings getFcWebviewSettings() {
if (fcWebSetting != null) {
return fcWebSetting;
} else {
fcWebSetting = new X5WebSettingsAdapter(super.getSettings());
}
return fcWebSetting;
}
@Override
public boolean canZoomIn() {
return super.canZoomIn();
}
@Override
public boolean canZoomOut() {
return super.canZoomOut();
}
@Override
public boolean zoomIn() {
return super.zoomIn();
}
@Override
public boolean zoomOut() {
return super.zoomOut();
}
@Override
public void destory() {
super.destroy();
}
}
5.对于X5WebSettingsAdapter 采用了对象适配器模式
public class X5WebSettingsAdapter implements IWebSettings {
private WebSettings x5WebSetting;
public X5WebSettingsAdapter(WebSettings x5WebSetting) {
this.x5WebSetting = x5WebSetting;
}
@Override
public void setDatabaseEnabled(boolean flag) {
x5WebSetting.setDatabaseEnabled(flag);
}
@Override
public void setGeolocationEnabled(boolean flag) {
x5WebSetting.setGeolocationEnabled(flag);
}
@Override
public void setGeolocationDatabasePath(String databasePath) {
x5WebSetting.setGeolocationDatabasePath(databasePath);
}
@Override
public void setJavaScriptEnabled(boolean flag) {
x5WebSetting.setJavaScriptEnabled(flag);
}
@Override
public void setBlockNetworkImage(boolean flag) {
x5WebSetting.setBlockNetworkImage(flag);
}
@Override
public void setDomStorageEnabled(boolean flag) {
x5WebSetting.setDomStorageEnabled(flag);
}
@Override
public void setSupportZoom(boolean flag) {
x5WebSetting.setSupportZoom(flag);
}
@Override
public void setBuiltInZoomControls(boolean flag) {
x5WebSetting.setBuiltInZoomControls(flag);
}
@Override
public void setUseWideViewPort(boolean flag) {
x5WebSetting.setUseWideViewPort(flag);
}
@Override
public void setAppCacheEnabled(boolean flag) {
x5WebSetting.setAppCacheEnabled(flag);
}
@Override
public void setLoadWithOverviewMode(boolean flag) {
x5WebSetting.setLoadWithOverviewMode(flag);
}
@Override
public void setJavaScriptCanOpenWindowsAutomatically(boolean flag) {
x5WebSetting.setJavaScriptCanOpenWindowsAutomatically(flag);
}
@Override
public void setAllowFileAccess(boolean flag) {
x5WebSetting.setAllowFileAccess(flag);
}
@Override
public void setMixedContentMode(int mode) {
x5WebSetting.setMixedContentMode(mode);
}
}
5.3 fcwebview模块
image.png这个模块是一个与具体用户打交道的模块,而且用户也只需要和这个模块进行交互.并且在未来的不管接入其他的浏览器内核.用户调用这个模块的代码都不需要改动.这就是最终我们需要Fcwebview达到的效果.实际上要达到这种效果,只需要提供给用户一个动态设置不同内核的权利即可.
从而我们很容易想到两种设计模式:
1.策略模式
image.png 2.代理模式 683744-20160929112107375-1656629935.png
两种模式是有区别的:
策略模式实际上是对一系列的算法的具体实现进行封装,在不同的场景中使用不同的算法.强调的是一个动作,或者行为.
而代理模式,则强调的是一个对象.这里面可以有多个代理对象,代理类与被代理类实现的是同一个接口,因此代理类与被代理类的结构是相同的.
显然在我们这里用代理模式
会比较合理,我可以设置不同的浏览器内核作为代理,从而实现Fcwebview的接口.
package com.fcbox.anglib.fcwebview;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.FrameLayout;
import com.fcbox.anglib.fcwebview.base.DownloadListener;
import com.fcbox.anglib.fcwebview.base.IWebSettings;
import com.fcbox.anglib.fcwebview.base.IWebview;
import com.fcbox.anglib.fcwebview.base.WebChromeClient;
import com.fcbox.anglib.fcwebview.base.WebViewClient;
import java.util.Map;
/**
* @author: 朱丽君
* @create: 19-5-29 上午10:42
* @description: 丰巢webview ,这个类是一个代理,可以设置其他类型的webview作为实际类
*/
public class FcWebview extends FrameLayout implements IWebview {
private IWebview webviewProxy;
public FcWebview(@NonNull Context context) {
this(context, null);
}
public FcWebview(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public FcWebview(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(FcWebviewInit.webCoreType);
}
public void setWebviewCore(IWebview webview) {
webviewProxy = webview;
init();
}
private void init(int type) {
webviewProxy = FcWebviewFactory.getWebview(getContext(), type);
init();
}
private void init() {
if (webviewProxy == null) {
return;
}
removeAllViews();
addView(webviewProxy.getWebview());
}
@Override
public View getWebview() {
if (checkNull()) {
return null;
}
return webviewProxy.getWebview();
}
@Override
public View getView() {
return this;
}
@Override
public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
if (checkNull()) {
return;
}
webviewProxy.loadUrl(url, additionalHttpHeaders);
}
@Override
public void loadUrl(String url) {
if (checkNull()) {
return;
}
webviewProxy.loadUrl(url);
}
@Override
public void postUrl(String url, byte[] postData) {
if (checkNull()) {
return;
}
webviewProxy.postUrl(url, postData);
}
@Override
public void loadData(String data, String mimeType, String encoding) {
if (checkNull()) {
return;
}
webviewProxy.loadData(data, mimeType, encoding);
}
@Override
public void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl) {
if (checkNull()) {
return;
}
webviewProxy.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
}
@Override
public void stopLoading() {
if (checkNull()) {
return;
}
webviewProxy.stopLoading();
}
@Override
public void reload() {
if (checkNull()) {
return;
}
webviewProxy.reload();
}
@Override
public boolean canGoBack() {
if (checkNull()) {
return false;
}
return webviewProxy.canGoBack();
}
@Override
public void goBack() {
if (checkNull()) {
return;
}
webviewProxy.goBack();
}
@Override
public boolean canGoForward() {
if (checkNull()) {
return false;
}
return webviewProxy.canGoForward();
}
@Override
public void goForward() {
if (checkNull()) {
return;
}
webviewProxy.goForward();
}
@Override
public boolean canGoBackOrForward(int steps) {
if (checkNull()) {
return false;
}
return webviewProxy.canGoBackOrForward(steps);
}
@Override
public void goBackOrForward(int steps) {
if (checkNull()) {
return;
}
webviewProxy.goBackOrForward(steps);
}
@Override
public String getUrl() {
if (checkNull()) {
return "";
}
return webviewProxy.getUrl();
}
@Override
public String getOriginalUrl() {
if (checkNull()) {
return "";
}
return webviewProxy.getOriginalUrl();
}
@Override
public String getTitle() {
if (checkNull()) {
return "";
}
return webviewProxy.getTitle();
}
@Override
public Bitmap getFavicon() {
if (checkNull()) {
return null;
}
return webviewProxy.getFavicon();
}
@Override
public int getProgress() {
if (checkNull()) {
return 0;
}
return webviewProxy.getProgress();
}
@Override
public int getContentHeight() {
if (checkNull()) {
return 0;
}
return webviewProxy.getContentHeight();
}
@Override
public int getContentWidth() {
if (checkNull()) {
return 0;
}
return webviewProxy.getContentWidth();
}
@Override
public void onPause() {
if (checkNull()) {
return;
}
webviewProxy.onPause();
}
@Override
public void onResume() {
if (checkNull()) {
return;
}
webviewProxy.onResume();
}
@Override
public void freeMemory() {
if (checkNull()) {
return;
}
webviewProxy.freeMemory();
}
@Override
public void clearCache(boolean includeDiskFiles) {
if (checkNull()) {
return;
}
webviewProxy.clearCache(includeDiskFiles);
}
@Override
public void clearFormData() {
if (checkNull()) {
return;
}
webviewProxy.clearFormData();
}
@Override
public void clearHistory() {
if (checkNull()) {
return;
}
webviewProxy.clearHistory();
}
@Override
public void clearSslPreferences() {
if (checkNull()) {
return;
}
webviewProxy.clearSslPreferences();
}
@Override
public void setWebViewClient(WebViewClient client) {
if (checkNull()) {
return;
}
webviewProxy.setWebViewClient(client);
}
@Override
public void setDownloadListener(DownloadListener listener) {
if (checkNull()) {
return;
}
webviewProxy.setDownloadListener(listener);
}
@Override
public void setWebChromeClient(WebChromeClient client) {
if (checkNull()) {
return;
}
webviewProxy.setWebChromeClient(client);
}
@Override
public void addJavascriptInterface(Object obj, String interfaceName) {
if (checkNull()) {
return;
}
webviewProxy.addJavascriptInterface(obj, interfaceName);
}
@Override
public void removeJavascriptInterface(String interfaceName) {
if (checkNull()) {
return;
}
webviewProxy.removeJavascriptInterface(interfaceName);
}
@Override
public IWebSettings getFcWebviewSettings() {
if (checkNull()) {
return null;
}
return webviewProxy.getFcWebviewSettings();
}
@Override
public boolean canZoomIn() {
if (checkNull()) {
return false;
}
return webviewProxy.canZoomIn();
}
@Override
public boolean canZoomOut() {
if (checkNull()) {
return false;
}
return webviewProxy.canZoomOut();
}
@Override
public boolean zoomIn() {
if (checkNull()) {
return false;
}
return webviewProxy.zoomIn();
}
@Override
public boolean zoomOut() {
if (checkNull()) {
return false;
}
return webviewProxy.zoomOut();
}
@Override
public void destory() {
if (checkNull()) {
return;
}
webviewProxy.destory();
}
private boolean checkNull() {
return (webviewProxy == null);
}
public void setDefaultSetting() {
IWebSettings ws = getFcWebviewSettings();
//启用数据库
ws.setDatabaseEnabled(true);
String dir = getContext().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
//启用地理定位
ws.setGeolocationEnabled(true);
//设置定位的数据库路径
ws.setGeolocationDatabasePath(dir);
// 设置javascript是可以执行的
ws.setJavaScriptEnabled(true);
ws.setBlockNetworkImage(false);//解决图片不显示
// 开启DOM缓存。
ws.setDomStorageEnabled(true);
ws.setSupportZoom(false);
ws.setBuiltInZoomControls(false);
ws.setUseWideViewPort(false);
ws.setAppCacheEnabled(false);
ws.setLoadWithOverviewMode(true);
ws.setUseWideViewPort(true);
ws.setGeolocationEnabled(true);
ws.setJavaScriptCanOpenWindowsAutomatically(true);
ws.setAllowFileAccess(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ws.setMixedContentMode(IWebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
}
/**
* 执行js方法,可以传多个参数
*
* @param funcName
* @param params 1个或者多个参数
*/
public void callJsFunc(String funcName, String... params) {
if (TextUtils.isEmpty(funcName)) {
return;
}
if (params == null || params.length == 0) {
return;
}
StringBuffer param = new StringBuffer();
for (String eachParam : params) {
param.append(eachParam);
param.append(",");
}
//去掉最后一个逗号
param.deleteCharAt(param.length() - 1);
String callBackUrl = "javascript:" + funcName + "(" +"\'"+ param.toString() +"\'"+ ")";
loadUrl(callBackUrl);
if (FcWebviewInit.isDebug) {
Log.d("Fcwebview", "callJavascript:" + callBackUrl);
}
}
/**
* 设置fc默认js回调,这里进行了通用数据格式的解析
*
* @param callBack
*/
public void setFcDefaultJavascript(FcJsActionBridge.OnDataCallBack callBack) {
addJavascriptInterface(new FcJsActionBridge(this, callBack), "npc4fc");
}
}
FcWebviewFactory
一个简单工厂类,返回不同的浏览器内核.
package com.fcbox.anglib.fcwebview;
import android.content.Context;
import com.fcbox.anglib.fcwebview.base.IWebview;
import com.fcbox.anglib.x5core.x5Adapter.X5WebviewAdapter;
/**
* @author: 朱丽君
* @create: 19-6-4 下午4:22
* @description:简单工厂,选择具体的webview适配器
*/
public class FcWebviewFactory {
public static IWebview getWebview(Context context, int type) {
if (type == FcWebviewInit.X5) {
return new X5WebviewAdapter(context);
}
return null;
}
}
6. 测试
布局文件activity_fc_webview_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.fcbox.anglib.fcwebview.FcWebview
android:id="@+id/fcwebview"
android:layout_width="match_parent"
android:layout_height="match_parent"></com.fcbox.anglib.fcwebview.FcWebview>
</android.support.constraint.ConstraintLayout>
测试代码java
application初始化
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
initX5();
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
/**
* 初始化X5
*/
private void initX5() {
FcWebviewInit.setIsDebug(true);
FcWebviewInit.init(this, new FcWebviewInit.Result() {
@Override
public void initResult(boolean isSucc) {
}
});
}
}
TestFcwebview.java
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import android.webkit.JavascriptInterface;
import com.fcbox.anglib.fcwebview.FcCookieManager;
import com.fcbox.anglib.fcwebview.FcJsActionBridge;
import com.fcbox.anglib.fcwebview.FcWebview;
import com.fcbox.anglib.fcwebview.base.IWebview;
import com.fcbox.anglib.fcwebview.base.ValueCallback;
import com.fcbox.anglib.fcwebview.base.WebChromeClient;
import com.fcbox.anglib.fcwebview.base.WebViewClient;
import org.json.JSONObject;
import top.wintp.mytbsdemo.R;
/**
* @author: 朱丽君
* @create: 19-5-22 下午3:05
* @description:Fcwebview的 测试类
*/
public class TestFcwebview extends Activity {
private FcWebview mWebView;
private ValueCallback<Uri> valueCallback;
private ValueCallback<Uri[]> valueCallBackAboveL;
private static String TAG = TestFcwebview.class.getSimpleName();
private String url = "https://mall.fcbox.com";
// private String url = "file:///android_asset/testH5.html";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fc_webview_main);
mWebView = findViewById(R.id.fcwebview);
//设置浏览器内核
mWebView.setDefaultSetting();
setCookie(url);
mWebView.loadUrl(url);
mWebView.setWebViewClient(new WebViewClient() {
});
mWebView.setWebChromeClient(new WebChromeClient() {
//For Android >= 4.1
@Override
public void openFileChooser(ValueCallback<Uri> fileCallback, String acceptType,
String capture) {
valueCallback = fileCallback;
album(TestFcwebview.this, 2001);
}
// For Android >= 5.0
@Override
public boolean onShowFileChooser(IWebview webView, ValueCallback<Uri[]> filePathCallback) {
valueCallBackAboveL = filePathCallback;
album(TestFcwebview.this, 2001);
return true;
}
});
mWebView.addJavascriptInterface(new Object() {
@JavascriptInterface
public void callAction(String jsonStr) {
testJs(jsonStr);
}
}, "npc4fc");
//设置fc默认的js
// mWebView.setFcDefaultJavascript(new FcJsActionBridge.OnDataCallBack() {
// @Override
// public void onCallBack(int actionType, JSONObject actionParam, String functionName) {
//
// }
// });
}
private void testJs(String jsonStr) {
Log.d(TAG, "JsCall" + jsonStr);
if (jsonStr.equals("callBack")) {
TestFcwebview.this.runOnUiThread(() -> {
mWebView.callJsFunc("fccallback", "test");
});
}
}
private void setCookie(String url) {
FcCookieManager fcCookieManager = FcCookieManager.getInstance(getApplicationContext());
FcCookieManager.Cookie cookie = new FcCookieManager.Cookie(url)
.addCookieMap("key", "test")
.addCookieMap("key2", "----2");
fcCookieManager.addCookie(cookie);
fcCookieManager.setCookieToUrl(mWebView, url);
Log.d(TAG, "cookie" + fcCookieManager.getCookie(url));
}
public static void album(Activity context, int requestCode) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT).setType("image/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {//如果大于等于7.0使用FileProvider
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivityForResult(intent, requestCode);
} else {
context.startActivityForResult(intent, requestCode);
}
} catch (ActivityNotFoundException e) {
Log.d(TAG, "not found");
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
}
@Override
protected void onDestroy() {
super.onDestroy();
mWebView.destory();
}
}
6. 总结:
说一千,道一万.软件的设计以及第三方库的集成.最终还是要符合面向对象的六大原则:
- 单一职责原则
- 开闭原则
- 里氏替换原则
- 依赖倒置原则
- 接口隔离原则
- 迪米特原则(最少知识原则)
网友评论