使用cordova的加强webview

来源:互联网 发布:express.js w3cschool 编辑:程序博客网 时间:2024/05/16 19:16

原文地址:http://blog.csdn.net/pur_e/article/details/46807529

demo已经上传git


这种有两种利用方式:

一、集成一个webview来展示工程中的网页,这种稍微简单;

二、集成一个webview来展示服务端的网页,这个稍微有点麻烦,会了这一种前面那种就很容易了。这个其实也有两种方式,一种是将cordova lib编译为arr,在工程中添加依赖库,一种是直接将源码集成进来。因为我有修改源码的需要,所以就直接导入源码了。

 

这里只说我集成第二种的过程,因为走了不少弯路,记录一下,也帮助下后来人,我集成的cordova版本是4.0.2

1、使用Android studio新建一个空工程

2、将前面集成的android工程下的CordovaLib导入进来:

1)file->new->import module,选择对应的目录:


2)导入后,android studio 自动在settings.gradle中添加module:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. include ':app'':CordovaLib'  

之后gradle自动sync工程,会报错:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. Error:(480) Could not find property 'cdvCompileSdkVersion' on com.android.build.gradle.LibraryExtension_Decorated@5eae5aaa.  

这个是因为cordova集成自己的工程添加的属性,直接修改CordovaLib/build.gradle文件:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. apply plugin: 'android-library'  
  2. android {  
  3.     compileSdkVersion 22  
  4.     buildToolsVersion "22.0.1"  
  5.     sourceSets {  
  6.         main {  
  7.             manifest.srcFile 'AndroidManifest.xml'  
  8.             java.srcDirs = ['src']  
  9.             resources.srcDirs = ['src']  
  10.             aidl.srcDirs = ['src']  
  11.             renderscript.srcDirs = ['src']  
  12.             res.srcDirs = ['res']  
  13.             assets.srcDirs = ['assets']  
  14.         }  
  15.     }  
  16. }  

 

这时,工程就已经可以编译了,Lib已经添加成功,下面就是用起来;

 

3、在主工程中添加cordova lib的依赖:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. dependencies {  
  2.     compile fileTree(dir: 'libs', include: ['*.jar'])  
  3.     compile 'com.android.support:appcompat-v7:22.1.1'  
  4.     compile project(':CordovaLib')  
  5. }  

 

4、修改layout文件,添加SystemWebview,这个就是cordova修改过的webview。

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <org.apache.cordova.engine.SystemWebView  
  2.     android:id="@+id/cordova_webview"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent">  
  5. </org.apache.cordova.engine.SystemWebView>  

 

5、在MainActivity中尝试打开外部url:

1)添加权限:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <uses-permission android:name="android.permission.INTERNET" />  
  2. <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />  
  3. <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />  

 

2)添加cordova的配置文件res/xml/config.xml

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <?xml version='1.0' encoding='utf-8'?>  
  2. <widget id="com.example.hello" version="0.0.1">  
  3.         <preference name="loglevel" value="DEBUG" />  
  4.         <feature name="myplugin">  
  5.             <param name="android-package" value="com.example.wangfeng.mycordovawebview.MyCordovaPlugin"/>  
  6.             <param name="onload" value="true"/>  
  7.         </feature>  
  8. </widget>  

 

2)打开url:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. LOG.setLogLevel(LOG.DEBUG);//设置日志级别  
  2. systemWebView = (SystemWebView)findViewById(R.id.cordova_webview);  
  3. ConfigXmlParser parser = new ConfigXmlParser();  
  4. parser.parse(this);//这里会解析res/xml/config.xml配置文件  
  5. CordovaWebView cordovaWebView = new CordovaWebViewImpl(new SystemWebViewEngine(systemWebView));//创建一个cordovawebview  
  6. cordovaWebView.init(new CordovaInterfaceImpl(this), parser.getPluginEntries(), parser.getPreferences());//初始化  
  7. systemWebView.loadUrl("http://www.baidu.com");  

 

结果发现url不能打开,检查网络也没有问题,后来在日志中找到线索:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. 07-08 08:45:51.998  11874-11894/? W/SystemWebViewClient﹕ URL blocked by whitelist:http://www.baidu.com/  

 

翻了下源码,cordova中对外部网站控制的比较严,用的是白名单的方式,只要不是被允许的,全部拦截。

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. //SystemWebViewClient.java中,重载了shouldInterceptRequest函数,会进行处理:  
  2. // Check the against the whitelist and lock out access to the WebView directory  
  3. // Changing this will cause problems for your application  
  4. if (!parentEngine.pluginManager.shouldAllowRequest(url)) {  
  5.     LOG.w(TAG,"URL blocked by whitelist: " + url);  
  6.     // Results in a 404.  
  7.     return new WebResourceResponse("text/plain""UTF-8"null);  
  8. }  
  9.    
  10.    
  11. /** 
  12.  * Called when the webview is going to request an external resource. 
  13.  * 
  14.  * This delegates to the installed plugins, and returns true/false for the 
  15.  * first plugin to provide a non-null result.  If no plugins respond, then 
  16.  * the default policy is applied. 
  17.  * 
  18.  * @param url      The URL that is being requested. 
  19.  * @return          Returns true to allow the resource to load, 
  20.  *                  false to block the resource. 
  21.  */  
  22. public boolean shouldAllowRequest(String url) {  
  23.     for (PluginEntry entry : this.entryMap.values()) {  
  24.         CordovaPlugin plugin = pluginMap.get(entry.service);  
  25.         if (plugin != null) {  
  26.             Boolean result = plugin.shouldAllowRequest(url);  
  27.             if (result != null) {  
  28.                 return result;  
  29.             }  
  30.         }  
  31.     }  
  32.    
  33.     // Default policy:  
  34.     if (url.startsWith("blob:") || url.startsWith("data:") || url.startsWith("about:blank")) {  
  35.         return true;  
  36.     }  
  37.     // TalkBack requires this, so allow it by default.  
  38.     if (url.startsWith("https://ssl.gstatic.com/accessibility/javascript/android/")) {  
  39.         return true;  
  40.     }  
  41.     if (url.startsWith("file://")) {  
  42.         //This directory on WebKit/Blink based webviews contains SQLite databases!  
  43.         //DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING!  
  44.         return !url.contains("/app_webview/");  
  45.     }  
  46.     return false;  
  47. }  

这时,我在上面config.xml中添加的插件MyCordovaPlugin就有用了,看上面源码,只要任何一个plugin允许外部连接,就可以访问,所以我们可以添加自己的规则,我这里只是演示用,全部允许:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class MyCordovaPlugin extends CordovaPlugin {  
  2.     @Override  
  3.     public Boolean shouldAllowRequest(String url) {  
  4.         return true;  
  5.     }  
  6. }  

此时,外部连接已经可以打开:



但是二级连接还是不能访问:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. 07-08 08:58:10.406  22771-22771/com.example.wangfeng.mycordovawebview W/CordovaWebViewImpl﹕ Blocked (possibly sub-frame) navigation to non-allowed URL:http://map.baidu.com/mobile/webapp/place/hotelzt/ldata=%7B%22src_from%22%3A%22webapp_wise_home%22%7D  

还是去查看源码:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. //SystemWebViewClient.java重载了shouldOverrideUrlLoading  
  2. @Override  
  3.    public boolean shouldOverrideUrlLoading(WebView view, String url) {  
  4.        return parentEngine.client.onNavigationAttempt(url);  
  5.    }  
  6.    
  7. //CordovaWebViewImpl.java  
  8. @Override  
  9. public boolean onNavigationAttempt(String url) {  
  10.     // Give plugins the chance to handle the url  
  11.     if (pluginManager.onOverrideUrlLoading(url)) {  
  12.         return true;  
  13.     } else if (pluginManager.shouldAllowNavigation(url)) {  
  14.         return false;  
  15.     } else if (pluginManager.shouldOpenExternalUrl(url)) {  
  16.         showWebPage(url, true,falsenull);  
  17.         return true;  
  18.     }  
  19.     LOG.w(TAG,"Blocked (possibly sub-frame) navigation to non-allowed URL: " + url);  
  20.     return true;  
  21. }  

我们有好几个方式可以允许操作:

onOverrideUrlLoading这个函数说明我们是否要自己处理这个url,返回true则webview不再处理

shouldAllowNavigation这个函数说明是否允许这种导航,302等跳转包含在内,返回true则webview直接跳转

shouldOpenExternalUrl这个函数说明是否调用外部浏览器打开

 

我重载了shouldAllowNavigation这个函数,同样的,我们可以在这里定义自己的规则,我这里是全部允许:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. public boolean shouldAllowNavigation(String url) {  
  3.     return true;  
  4. }  

当然,我们也可以重载SystemWebViewClient的shouldOverrideUrlLoading函数。

 

好了,现在我们的webview已经可以打开网页,也可以进入二级页面,甚至cordova帮我们重载了dispatchKeyEvent,在按返回键时,不会退出activity,而是返回历史页面。

 

6、现在要实现最后一步,执行服务器端的cordova js代码。cordova存在的意义,就在于将js和原生页面结合起来,进行交互。

写了一个test.html,来执行和cordova的plugin交互:cordova.js这个文件就在我们之前自动创建的cordova android工程中

[javascript] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <script type="text/javascript"src="cordova.js"></script>  
  2. <script>  
  3.     showToast = function(msg, callback) {  
  4.         cordova.exec(function(winParam) {  
  5.             callback && callback(null, winParam);  
  6.         }, function(error) {  
  7.             callback && callback(error);  
  8.         }, "myplugin""toast", [msg]);  
  9.     };  
  10.    
  11.     document.addEventListener('deviceready',function(){  
  12.         showToast("这是一个测试!")  
  13.     }, false);//放在deviceready事件中调用,要不cordova对象可能还没加载完  
  14. </script>  

myplugin就是我们在config.xml中给MyCordovaPlugin起得名字,在其中添加交互的代码:

结果给报了个错:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. 07-08 09:34:05.954  22669-22669/com.example.wangfeng.mycordovawebview D/CordovaBridge﹕ Ignoring exec() from previous page load  

还是翻源码,发现在初始化cordova时,会设定是否允许外部网站进行这种交互:

初始化发生在:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. cordova.js  
  2. androidExec.init = function() {  
  3.     bridgeSecret = +prompt('''gap_init:' + nativeToJsBridgeMode);  
  4.     channel.onNativeReady.fire();  
  5. };  
  6.    
  7. SystemWebChromeClient.java重载onJsPrompt  
  8. String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message, defaultValue);  
  9.    
  10. CordovaBridge.java:promptOnJsPrompt  
  11. 这里,会根据defaultValue来进行初始化操作:  
  12. else if (defaultValue != null && defaultValue.startsWith("gap_init:")) {  
  13.     // Protect against random iframes being able to talk through the bridge.  
  14.     // Trust only pages which the app would have been allowed to navigate to anyway.  
  15.     if (pluginManager.shouldAllowBridgeAccess(origin)) {  
  16.         // Enable the bridge  
  17.         int bridgeMode = Integer.parseInt(defaultValue.substring(9));  
  18.         jsMessageQueue.setBridgeMode(bridgeMode);  
  19.         // Tell JS the bridge secret.  
  20.         int secret = generateBridgeSecret();  
  21.         return ""+secret;  
  22.     } else {  
  23.         Log.e(LOG_TAG,"gap_init called from restricted origin: " + origin);  
  24.     }  
  25.     return "";  
  26. }  

这里会根据shouldAllowBridgeAccess的返回值,来设定bridge的模式,这个函数也会轮询plugin的同名函数,所以还是和上面一样,可以在plugin中进行重载的:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. public Boolean shouldAllowBridgeAccess(String url) {  
  3.     return true;  
  4. }  

 

最后终于测试成功,使用服务端的网页和app的原生页面进行了交互!!

 


0 0