上一章在CordovaWebViewImpl
的createEngine
方法中根据config.xml
的配置来创建不同的驱动类,默认的为SystemWebViewEngine
,这里再贴一下代码:
public static CordovaWebViewEngine createEngine(Context context, CordovaPreferences preferences) {
// 这里默认是SystemWebViewEngine类
String className = preferences.getString("webview", SystemWebViewEngine.class.getCanonicalName());
try {
Class<?> webViewClass = Class.forName(className);
Constructor<?> constructor = webViewClass.getConstructor(Context.class, CordovaPreferences.class);
return (CordovaWebViewEngine) constructor.newInstance(context, preferences);
} catch (Exception e) {
throw new RuntimeException("Failed to create webview. ", e);
}
}
所以这一篇就针对SystemWebViewEngine
类的设计来做一下分析。
CordovaWebViewEngine
接口
SystemWebViewEngine
类实现了CordovaWebViewEngine
接口,这个接口主要暴露处理webview
的接口。先贴一下CordovaWebViewEngine
接口类,加了中文注释,方便理解。
/**
* Interface for all Cordova engines.
* No methods will be added to this class (in order to be compatible with existing engines).
* Instead, we will create a new interface: e.g. CordovaWebViewEngineV2
*
* 这段英文基本意思是:
* 为了保证自定义的引擎与系统引擎一致,所以不建议修改这个接口,
* 不过可以自己建个新的接口标准。
*
*/
public interface CordovaWebViewEngine {
// 初始化方法
void init(CordovaWebView parentWebView, CordovaInterface cordova, Client client,
CordovaResourceApi resourceApi, PluginManager pluginManager,
NativeToJsMessageQueue nativeToJsMessageQueue);
CordovaWebView getCordovaWebView();
// 处理webview的cookie
ICordovaCookieManager getCookieManager();
View getView();
// 加载url
void loadUrl(String url, boolean clearNavigationStack);
void stopLoading();
/** Return the currently loaded URL */
// 返回当前url
String getUrl();
// 清除缓存
void clearCache();
/** After calling clearHistory(), canGoBack() should be false. */
// 清除访问历史
void clearHistory();
// 判断webview是否可以返回
boolean canGoBack();
/** Returns whether a navigation occurred */
// 返回上一个url
boolean goBack();
/** Pauses / resumes the WebView's event loop. */
void setPaused(boolean value);
/** Clean up all resources associated with the WebView. */
void destroy();
/** Add the evaulate Javascript method **/
// 执行js语法
void evaluateJavascript(String js, ValueCallback<String> callback);
/**
* Used to retrieve the associated CordovaWebView given a View without knowing the type of Engine.
* E.g. ((CordovaWebView.EngineView)activity.findViewById(android.R.id.webView)).getCordovaWebView();
*/
public interface EngineView {
CordovaWebView getCordovaWebView();
}
/**
* Contains methods that an engine uses to communicate with the parent CordovaWebView.
* Methods may be added in future cordova versions, but never removed.
* 处理webview的基本事件方法
*/
public interface Client {
Boolean onDispatchKeyEvent(KeyEvent event);
void clearLoadTimeoutTimer();
void onPageStarted(String newUrl);// 开始加载
void onReceivedError(int errorCode, String description, String failingUrl);
void onPageFinishedLoading(String url);
boolean onNavigationAttempt(String url);
}
}
大体理解了CordovaWebViewEngine
接口方法,再回过头来看SystemWebViewEngine
类是如何实现的。
SystemWebViewEngine
类的构造方法
构造方法没什么可说的,根据参数的不同来初始化,其中第三个构造方法初始化了SystemCookieManager
类。这个类以后再做分析。
/** Used when created via reflection. */
public SystemWebViewEngine(Context context, CordovaPreferences preferences) {
this(new SystemWebView(context), preferences);
}
public SystemWebViewEngine(SystemWebView webView) {
this(webView, null);
}
public SystemWebViewEngine(SystemWebView webView, CordovaPreferences preferences) {
this.preferences = preferences;
this.webView = webView;
cookieManager = new SystemCookieManager(webView);
}
SystemWebViewEngine
类的init方法
@Override
public void init(CordovaWebView parentWebView, CordovaInterface cordova, CordovaWebViewEngine.Client client,
CordovaResourceApi resourceApi, PluginManager pluginManager,
NativeToJsMessageQueue nativeToJsMessageQueue) {
if (this.cordova != null) {
throw new IllegalStateException();
}
// Needed when prefs are not passed by the constructor
if (preferences == null) {
preferences = parentWebView.getPreferences();
}
// 1.传递相关参数
this.parentWebView = parentWebView;
this.cordova = cordova;
this.client = client;
this.resourceApi = resourceApi;
this.pluginManager = pluginManager;
this.nativeToJsMessageQueue = nativeToJsMessageQueue;
// 2.初始化webview
webView.init(this, cordova);
// 3.设置webview
initWebViewSettings();
// 4.设置nativeToJsMessageQueue模式
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode.OnlineEventsBridgeModeDelegate() {
@Override
public void setNetworkAvailable(boolean value) {
//sometimes this can be called after calling webview.destroy() on destroy()
//thus resulting in a NullPointerException
if(webView!=null) {
webView.setNetworkAvailable(value);
}
}
@Override
public void runOnUiThread(Runnable r) {
SystemWebViewEngine.this.cordova.getActivity().runOnUiThread(r);
}
}));
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.EvalBridgeMode(this, cordova));
// 5.创建CordovaBridge实例
bridge = new CordovaBridge(pluginManager, nativeToJsMessageQueue);
// 6.调用exposeJsInterface方法
exposeJsInterface(webView, bridge);
}
init方法中主要是做了6件事:
一、传递相关参数
if (preferences == null) {
preferences = parentWebView.getPreferences();
}
this.parentWebView = parentWebView;
this.cordova = cordova;
this.client = client;
this.resourceApi = resourceApi;
this.pluginManager = pluginManager;
this.nativeToJsMessageQueue = nativeToJsMessageQueue;
二、初始化webview
初始化webview调用的是webView.init(this, cordova);
方法,webview是SystemWebView
类的实例,回头再分析此类。
三、设置webview
设置webview调用的是initWebViewSettings
方法。详细api介绍可以参考:
https://blog.csdn.net/carson_ho/article/details/52693322
https://blog.csdn.net/zhanwubus/article/details/80340025
https://www.jianshu.com/p/fea5e829b30a
private void initWebViewSettings() {
/*设置webview缩放等级,0表示默认,webview会根据html里设置的缩放来配置,
其他数值50表示缩小50%,100表示100%不缩放,200表示200%缩大2倍。*/
webView.setInitialScale(0);
// 是否设置垂直滚动条
webView.setVerticalScrollBarEnabled(false);
// Enable JavaScript
final WebSettings settings = webView.getSettings();
// 是否支持js交互
settings.setJavaScriptEnabled(true);
// 指示JavaScript自动打开窗口。默认false。
settings.setJavaScriptCanOpenWindowsAutomatically(true);
// 设置基础布局算法。默认NARROW_COLUMNS。
settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
String manufacturer = android.os.Build.MANUFACTURER;
LOG.d(TAG, "CordovaWebView is running on device made by: " + manufacturer);
//We don't save any form data in the application
// 设置是否保留表单数据
settings.setSaveFormData(false);
settings.setSavePassword(false);
// Jellybean rightfully tried to lock this down. Too bad they didn't give us a whitelist
/*
解决跨域的问题,访问其他网站接口。
android端可通过设置setAllowUniversalAccessFromFileURLs来解决跨域访问的目的。
这个方法目前需要在api level 16以上使用,即对应android系统为4.1版本。也就是说4.1版本以上才可以。
setAllowFileAccess(boolean allow)
启用或禁用WebView中的文件访问,注意,这里仅启用或禁用文件系统访问。Assets 和 resources 文件使用file:///android_asset和file:///android_res仍是可访问的。
setAllowFileAccessFromFileURLs主要用于设置是否允许通过file url加载的Javascript读取其他的本地文件
setAllowUniversalAccessFromFileURLs可以设置是否允许通过file url加载的Javascript可以访问其他任何的源,也就是说,它包括其他的文件和http,https等其他的源
如果设置了 setAllowUniversalAccessFromFileURLs为true,则setAllowFileAccessFromFileURLs就不用设置了。
*/
settings.setAllowUniversalAccessFromFileURLs(true);
// 设置WebView是否需要用户手势才能播放媒体。默认true。
settings.setMediaPlaybackRequiresUserGesture(false);
// Enable database
// We keep this disabled because we use or shim to get around DOM_EXCEPTION_ERROR_16
/* 设置是否支持存储和设置存储路径,设置为Context的私有模式,此模式下,只能应用本身才可以访问,
在该模式下,写入的内容会覆盖原文件的内容,如果想把新写入的内容追加到原文件中。可以使用Context.MODE_APPEND */
/* 普及学习一下这几种模式
Context.MODE_APPEND:模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件。
Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE用来控制其他应用是否有权限读写该文件。
MODE_WORLD_READABLE:表示当前文件可以被其他应用读取;
MODE_WORLD_WRITEABLE:表示当前文件可以被其他应用写入。*/
String databasePath = webView.getContext().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
settings.setDatabaseEnabled(true);
settings.setDatabasePath(databasePath);
//Determine whether we're in debug or release mode, and turn on Debugging!
ApplicationInfo appInfo = webView.getContext().getApplicationContext().getApplicationInfo();
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
//设置是否支持调试
enableRemoteDebugging();
}
// 设置定位的数据库路径
settings.setGeolocationDatabasePath(databasePath);
// Enable DOM storage
// 设置是否支持Storage存储,DOM Storage 分为 sessionStorage 和 localStorage
settings.setDomStorageEnabled(true);
// Enable built-in geolocation
// 设置是否支持定位
settings.setGeolocationEnabled(true);
// Enable AppCache
// Fix for CB-2282
// 设置缓存大小5M、路径、是否支持
settings.setAppCacheMaxSize(5 * 1048576);
settings.setAppCachePath(databasePath);
settings.setAppCacheEnabled(true);
// Fix for CB-1405
// Google issue 4641
// 获取代理
String defaultUserAgent = settings.getUserAgentString();
// Fix for CB-3360
// 设置自定义代理
String overrideUserAgent = preferences.getString("OverrideUserAgent", null);
if (overrideUserAgent != null) {
settings.setUserAgentString(overrideUserAgent);
} else {
String appendUserAgent = preferences.getString("AppendUserAgent", null);
if (appendUserAgent != null) {
settings.setUserAgentString(defaultUserAgent + " " + appendUserAgent);
}
}
// End CB-3360
// todo 这个广播没看懂有什么作用
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
if (this.receiver == null) {
this.receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
settings.getUserAgentString();
}
};
webView.getContext().registerReceiver(this.receiver, intentFilter);
}
// end CB-1405
}
四、设置nativeToJsMessageQueue模式
NativeToJsMessageQueue
本地到js层消息队列,后续文章再分析。
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode(new NativeToJsMessageQueue.OnlineEventsBridgeMode.OnlineEventsBridgeModeDelegate() {
@Override
public void setNetworkAvailable(boolean value) {
//sometimes this can be called after calling webview.destroy() on destroy()
//thus resulting in a NullPointerException
if (webView != null) {
webView.setNetworkAvailable(value);
}
}
@Override
public void runOnUiThread(Runnable r) {
SystemWebViewEngine.this.cordova.getActivity().runOnUiThread(r);
}
}));
nativeToJsMessageQueue.addBridgeMode(new NativeToJsMessageQueue.EvalBridgeMode(this, cordova));
五、创建CordovaBridge实例
CordovaBridge
通信桥,后续文章分析。
bridge = new CordovaBridge(pluginManager, nativeToJsMessageQueue);
六、调用exposeJsInterface方法
从下面代码中我们可以看出,先创建了类SystemExposedJsApi
,然后调用了webview的addJavascriptInterface
方法。addJavascriptInterface
方法是Android API 17之前WebKit的原生API,属于WebView对象的公共方法,用于暴露一个java对象给js,使得js可以直接调用方法。因为该方法存在安全隐患问题,现在基本不会用了。详情请参考:https://blog.csdn.net/u012721519/article/details/79173377
@SuppressLint("AddJavascriptInterface")
private static void exposeJsInterface(WebView webView, CordovaBridge bridge) {
SystemExposedJsApi exposedJsApi = new SystemExposedJsApi(bridge);
webView.addJavascriptInterface(exposedJsApi, "_cordovaNative");
}