Android中hybrid开发的基础知识

前言

现在很多App里都内置了Web网页(Hybrid App),比如说很多电商平台,淘宝、京东、聚划算等等。那么这种该如何实现呢?其实这是Android里一个叫WebView组件实现。下面将围绕着这点进行详细的整理说明。

简介

android控件中的WebView,一个基于webkit引擎、展现web页面的控件

  1. Android 4.4前:Android Webview在低版本 & 高版本采用了不同的webkit版本的内核
  2. Android 4.4后:直接使用了Chrome内核

作用

  1. 在 Android 客户端上加载h5页面
  2. 在本地 与 h5页面实现交互 & 调用
  3. 其他:对 url 请求、页面加载、渲染、对话框 进行额外处理。

具体使用

  1. 涉及到的类:
    • Webview类:
      • 创建对象,加载URL,生命周期管理,状态管理
      • loadUrl(),goBack()等
    • WebSettings类
      • 配置 & 管理WebView
      • 缓存:setCacheMode()
      • 与js交互:setJavascriptEnable()
    • WebViewClient类
      • 处理各种通知 & 请求事件
      • shouldOverrideUrlLoading():打开网页在webview显示
      • onPageStarted():载入页面时
      • onPageFinished():页面加载结束时
      • onLoadResource():在加载页面资源时会调用,每一个资源(比如图片)的加载都会调用一次。
      • onReceivedError():加载页面的服务器出现错误时(如404)调用。
      • onReceivedSslError(): 处理https请求,webView默认是不处理https请求的,页面显示空白,需要进行如下设置:
          webView.setWebViewClient(new WebViewClient() {    
                  @Override    
                  public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {    
                      handler.proceed();    //表示等待证书响应
                  // handler.cancel();      //表示挂起连接,为默认方式
                  // handler.handleMessage(null);    //可做其他处理
                  }    
              });  
      
          // 特别注意:5.1以上默认禁止了https和http混用,以下方式是开启
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
          mWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
          }
      
    • WebChromeClient类
      • 辅助WebView处理js对话框,网站title,网站icon等
      • onProgressChanged()
      • onReceivedTitle()
      • onJsAlert() 无返回值 知道了
      • onJsConfirm() 返回值 true:确认,false:取消
      • onJsPrompt():输入框,确认:返回输入框的内容,取消:返回null

Native与JS交互

  1. 交互方式:桥梁WebView
    • android调用js代码
      • 方式一 通过webview的loadUrl()

        • 该方式会引起页面刷新
        • 指定url:http:www.baidu.com
        • 指定资源文件: file:///android_assets/filename.file
        • loadUrl(“javascripr:functionname()”)
      • 方式二:通过webView的evaluateJavaScript()

        • 该方式不会引起页面刷新,并且在android4.4之后才可使用
        • 向下兼容差
    • js去调用android的代码
      • 方式一:通过webView的addJavaScriptInterface()进行对象映射
        • 这种方式存在严重的漏洞问题
      • 方式二:通过WebViewClient的shouldOverrideUrlLoading()回调拦截url
        • 具体原理:第一步,webview通过webviewclient的回调方法shouldOverrideUrlLoading()拦截url,第二步,解析该url的协议;第三步,如果检测到内部约定好的协议,则执行业务流程代码(即js调用android native代码)
        • 优点: 不存在方式1的漏洞
        • 缺点:js获取android方法的返回值复杂。如果js想要得到android方法的返回值,只能通过webview的loadUrl()去执行js方法把返回值传递回去
            // Android:MainActivity.java
            mWebView.loadUrl("javascript:returnResult(" + result + ")");
        
            // JS:javascript.html
            function returnResult(result){
                alert("result is" + result);
            }
        
      • 方式三:通过WebChromeClient的onJsAlert()onJsConfirm()onJsPrompt()方法拦截js对话框信息
        • 常用的拦截是:onJsPrompt()方法,
        • 因为该方法可以返回任意类型的值;操作简便;不存在漏洞;

如何避免webview内存泄漏

  1. 不在xml中定义webview,而是在需要的时候再activity中创建,并且使用application context;
  2. 在activity销毁的时候,先让webview加载null内容,然后移除webview,再销毁webview,最后置空
    @Override
        protected void onDestroy() {
            if (mWebView != null) {
                mWebView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
                mWebView.clearHistory();

                ((ViewGroup) mWebView.getParent()).removeView(mWebView);
                mWebView.destroy();
                mWebView = null;
            }
            super.onDestroy();
        }

构建全面的webview缓存机制 & 资源加载方案

因为在移动端webview存在较为明显的性能问题,特别突出的是:加载速度慢,流量消耗大。那么有么有解决的方案呢?如下:

  1. 存在哪些性能问题?

    • H5页面加载速度慢
      • 渲染速度
        • js解析效率:js本身解析过程复杂,解析速度不快。并且页面内涉及较多的js文件,所以叠加起来使得总的js解析效率低
        • 手机硬件性能:android机型碎片化,导致手机性能参差不齐。
      • 资源加载慢:H5页面需要从服务端下载,并存储在手机内存中
        • h5页面资源多:
        • 网络请求数量多:每加载一个html页面,都有url自身的请求,以及页面中对外部资源的引用也是一个个独立的http请求;每个请求都是串行
    • 耗费流量:
      • 每次使用h5页面时, 用户都需要重新加载页面
      • 每次加载一个h5页面,都会产生较多的网络请求
      • 每个请求都是串行的,这么多串行导致流量消耗大
  2. 针对问题,有哪些解决方案:

    1. h5自带的缓存方案(也是WebView自带缓存机制):
      • h5页面加载后会存储在缓存区域 /data/data/包名/cache 以及 database 其中请求的url记录保存在WebViewCache.db 而URL的内容是保存在WebViewcache文件夹下
      • android webview 目前除了新的File System缓存机制还不支持,其他都支持
      • 作用:
        • 离线浏览
        • 提高页面加载速度,以及减少流量消耗
      • 应用:此处主要讲解前端H5的缓存机制(或者缓存模式)
        • 缓存机制:如何将加载过的网页数据保存到本地(保存)

          • 浏览器 缓存机制
            • 特点:
              • 支持HTTP协议层
              • 缓存文件需要首次加载后才会产生;
              • 浏览器缓存的存储空间有限;
              • 缓存有被清除的可能;
              • 缓存的文件没有校验
            • 根据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的取值是一个对文件进行标识的特征字串。
              • 在向服务器查询文件是否有更新时,浏览器通过If-None-Match 字段把特征字串发送给服务器,由服务器和文件最新特征字串进行匹配,来判断文件是否有更新:没有更新回包304,有更新回包200
              • Etag 和 Last-Modified 可根据需求使用一个或两个同时使用。两个同时使用时,只要满足基中一个条件,就认为文件没有更新。
            • 常见用法是:
              • Cache-Control与 Last-Modified 一起使用;控制缓存有效时间
              • Expires与 Etag一起使用;缓存失效后,向服务器查询是否有更新
            • 应用场景:
              • 静态资源文件的存储,如js,css,字体,图片等,存放在/data/data/包名/…
            • 具体实现:
              • webview内置自动实现,即不需要设置
                • android 4.4后的webview浏览器内核chrome
                • 浏览器缓存机制是浏览器内核的机制,一般都是标准的实现
          • Application cache缓存机制
            • 原理:

              • 以文件为单位进行缓存,且文件有一定的更新机制
              • AppCache原理有两个关键点:manifest属性以及manifest文件
                    
                    
                    // HTML 在头中通过 manifest 属性引用 manifest 文件
                    // manifest 文件:就是上面以 appcache 结尾的文件,是一个普通文件文件,列出了需要缓存的文件
                    // 浏览器在首次加载 HTML 文件时,会解析 manifest 属性,并读取 manifest 文件,获取 Section:CACHE MANIFEST 下要缓存的文件列表,再对文件缓存
                    
                    ...
                    
                    
                
                    // 原理说明如下:
                    // AppCache 在首次加载生成后,也有更新机制。被缓存的文件如果要更新,需要更新 manifest 文件
                    // 因为浏览器在下次加载时,除了会默认使用缓存外,还会在后台检查 manifest 文件有没有修改(byte by byte)
                    发现有修改,就会重新获取 manifest 文件,对 Section:CACHE MANIFEST 下文件列表检查更新
                    // manifest 文件与缓存文件的检查更新也遵守浏览器缓存机制
                    // 如用户手动清了 AppCache 缓存,下次加载时,浏览器会重新生成缓存,也可算是一种缓存的更新
                    // AppCache 的缓存文件,与浏览器的缓存文件分开存储的,因为 AppCache 在本地有 5MB(分 HOST)的空间限制
                
                
                
              • 特点: 方便构建web app的缓存
              • 应用场景:存储静态文件(js,css,字体文件)
                • 应用场景同浏览器缓存机制
                • 但appcache是对浏览器缓存机制的补充,不是替代
              • 具体实现
              // 通过设置WebView的settings来实现
              WebSettings settings = getSettings();
              
              String cacheDirPath = context.getFilesDir().getAbsolutePath()+"cache/";
              settings.setAppCachePath(cacheDirPath);
              // 1. 设置缓存路径
              
              settings.setAppCacheMaxSize(20*1024*1024);
              // 2. 设置缓存大小
              
              settings.setAppCacheEnabled(true);
              // 3. 开启Application Cache存储机制
              
              // 特别注意
              // 每个 Application 只调用一次 
              WebSettings.setAppCachePath() 和WebSettings.setAppCacheMaxSize()
              
          • DOM Storage缓存机制
            • 原理:
              • 通过存储字符串的key-value来提供
              • Dom storage分为session storage和local storage
                • session storage:具备临时性,即存储与页面相关的数据,它在页面关闭后无法使用
                • local storage:具备持久性,即保存的数据在页面关闭后也可以使用
            • 特点
              • 存储空间大(5MB):存储空间对于不同浏览器不同,如cookie才4kb
              • 存储安全,便捷:DOM storage存储的数据在本地,不需要经常和服务器进行交互
              • 不像cookie每次请求一次页面,都会向服务器发送网络请求
            • 应用场景
              • 存储临时,简单的数据;
                • dom storage 机制类似于android的sharedPreference机制
            • 具体实现:

          // 通过设置 WebViewSettings类实现
          WebSettings settings = getSettings();

                settings.setDomStorageEnabled(true);
                // 开启DOM storage
            ```
          
          • Web SQL Database缓存机制
            • 原理:
              • 基于SQL的数据库存储机制
            • 特点:
              • 充分利用数据库的优势,可方便对数据进行增删改查
            • 应用场景:
              • 存储适合数据库的结构化数据
            • 具体实现
            // 通过设置WebView的settings实现
            WebSettings settings = getSettings();
            
            String cacheDirPath = context.getFilesDir().getAbsolutePath()+"cache/";
            settings.setDatabasePath(cacheDirPath);
            // 设置缓存路径
            
            settings.setDatabaseEnabled(true);
            // 开启 数据库存储机制
            
            • 特别说明:
              • Web SQL Database存储机制不再推荐使用
              • 取而代之的是IndexeDB缓存机制
          • Indexed Database缓存机制
            • 原理:
              • 类似NoSQL数据库,通过存储字符串的key-value对来提供
              • 类似Dom storage 存储机制的key-value存储方式
            • 特点:
              • 功能强大,使用简单
                • 通过数据库的事物transation机制进行数据操作
                • 可对对象的任意属性生成索引,方便查询
              • 存储空间大:
                • 较大存储空间,默认推荐250MB(以host区分),比Dom storage的5MB要大的多
              • 使用灵活:
                • 以key-value的方式存储对象,可以是任何类型值或对象,包括二进制
                • 异步api调用,避免造成等待而影响体验
            • 应用场景:
              • 存储复杂,数据量大的结构化数据
            • 具体实现:
            // 通过设置WebView的settings实现
            WebSettings settings = getSettings();
            
            settings.setJavaScriptEnabled(true);
            // 只需设置支持JS就自动打开IndexedDB存储机制
            // Android 在4.4开始加入对 IndexedDB 的支持,只需打开允许 JS 执行的开关就好了。
            
          • File System 缓存机制(H5页面新加入的缓存机制,虽然android webview暂时不支持,但会进行简单介绍)
            • 原理:
              • 为H5页面的数据,提供一个虚拟的文件系统
                • 可以进行文件的创建,读写,删除,遍历等,就想native app访问本地文件系统一样
                • 虚拟的文件系统是运行在沙盒中
                • 不同web app的虚拟文件系统是相互隔离的,虚拟文件系统与本地文件系统也是互相隔离的
              • 虚拟文件系统提供两种类型的存储空间:临时 & 持久
                • 临时:由浏览器自动分配,但可能被浏览器回收
                • 持久:需要显示申请;自己管理;
            • 特点:
              • 可存储数据体积较大的二进制数据
              • 可预加载资源文件
              • 可直接编辑文件
            • 应用场景
              • 通过文件系统,管理数据
            • 具体使用
              • 由于File System是由H5新加入的缓存机制,所以android webview暂不支持
        • 缓存模式:

          • 定义:加载网页时webview如何读取之前保存到本地的网页缓存(读取)。
          • webview自带缓存模式四种
            • LOAD_CACHE_ONLY : 不使用网络,只读取本地缓存数据
            • LOAD_NO_CACHE: 不使用缓存,只从网络获取数据
            • LOAD_DEFAULT:默认,根据Cache-Control决定是否从网络上获取数据
            • LOAD_CACHE_ELSE_NETWORK:只要本地有,无论是否过期,或者no-cache,都使用缓存数据
      • 资源预加载:
        • 定义:提前加载将需要使用的H5页面,即 提前构建缓存;使用时直接取过来,而不是在需要的时候才去加载
        • 具体实现:
          • 预加载webview对象 & 预加载H5资源
        • 预加载webview对象:
          • 首次使用 WebView对象
            • 原因:
              • 首次初始化webview会比第2次初始化慢很多
              • 初始化后,及时webview已释放,但是一些多个webview共用的全局服务/资源对象仍未释放
              • 第二次初始化时,不需要再生成,从而变快
            • 实现思路:
              • 应用启动时就初始化1个全局的webview对象
              • 当用户需加载H5页面时,则直接使用该WebView对象加载并显示
            • 具体实现:
              • 在android的BaseApplication中初始化一个webView对象,放入webview对象池
          • 后续使用Webview对象
            • 原因:
              • 多次创建webview对象会消耗很多时间以及资源
            • 实现思路:
              • 自身构建webview复用池
              • 当用户需使用webview加载H5页面时,直接从池中获取webview对象
            • 具体实现:
              • 采用2个/多个webview的复用,而不需要每次打开H5都需要重新构建
        • 预加载h5资源
          • 原理:
            • 在应用启动,初始化第一个webview对象的时候,直接开始网络请求加载h5页面
            • 后续需要打开这些H5页面时就直接从该本地对象中获取
              • 事前加载常用的H5页面资源(加载后就有缓存了)
              • 此方法虽然不能减少webview初始化时间,但是数据请求和webview初始化可以并行进行,总体的页面加载时间就缩短了;缩短总体的页面加载时间
          • 具体实现:
            • 在android的BaseApplication里初始化一个webview对象,(用于加载常用的H5页面资源);当需要使用这些页面时再取出来直接使用
          • 应用场景:
            • 对于android webview的首页建议使用这种方案,能有效提高首页加载的效率。
      • 自身构建缓存
        • 除了使用android webview自身的缓存机制外,还可以自己针对某一需求场景构建缓存机制。
        • 背景:H5页面有一些更新频率低,常用&固定的静态资源文件,如js,css文件,图片
        • 冲突:每次重新加载会浪费很多资源(时间&流量)
        • 解决方案:
          • 通过拦截H5页面的资源网络请求,若资源相同,则直接从本地读取资源,而不需要发送网络请求到服务器获取
        • 实现步骤:
          • 提前将更新频率低,常用&固定的H5静态资源文件存放到本地
          • 拦截H5页面的网络请求,过滤相关资源的网络请求
          • 如果检测到本地有相同资源,则读取本地的

webview的那些漏洞

类型

  1. 任意代码执行漏洞
    • addJavascriptInterface()
    • webview内置到处的searchBoxJavaBridge 对象
    • webview内置导出的accesssibility和accessibilityTraversalObject对象
  2. 密码明文存储漏洞
  3. 域控制不严格漏洞

具体分析

  1. 任意代码执行漏洞
    • addJavascriptInterface()接口
      • 漏洞产生原因:
      webView.addJavascriptInterface(new JSObject(), "myObj");
      // 参数1:Android的本地对象
      // 参数2:JS的对象
      // 通过对象映射将Android中的本地对象和JS中的对象进行关联,从而实现JS调用Android的对象和方法
      所以,漏洞产生的原因是:当js拿到android这个对象后,从而实现js调用android对象中所有的方法,包括系统类,从而进行任意代码执行。如可以执行命令获取本地设备的sd卡中的文件等信息从而造成信息泄露。
      
      • 具体获取系统类的描述(结合java反射机制)
        • android中的对象有一公共的方法:getClass();
        • 该方法可以获取到当前类 类型Class
        • 该类有一关键的方法:Class.forName()
        • 该方法可以加载一个类(java.lang.Runtime)
        • 而且该类是可以执行本地命令的
      • js攻击核心代码
            function execute(cmdArgs)  
            {  
                // 步骤1:遍历 window 对象
                // 目的是为了找到包含 getClass ()的对象
                // 因为Android映射的JS对象也在window中,所以肯定会遍历到
                for (var obj in window) {  
                    if ("getClass" in window[obj]) {  
        
                // 步骤2:利用反射调用forName()得到Runtime类对象
                        alert(obj);          
                        return  window[obj].getClass().forName("java.lang.Runtime")  
        
                // 步骤3:以后,就可以调用静态方法来执行一些命令,比如访问文件的命令
            getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);  
        
            // 从执行命令后返回的输入流中得到字符串,有很严重暴露隐私的危险。
            // 如执行完访问文件的命令之后,就可以得到文件名的信息了。
                    }  
                }  
            }   
        
      • 当一些 APP 通过扫描二维码打开一个外部网页时,攻击者就可以执行这段 js 代码进行漏洞攻击。在微信盛行、扫一扫行为普及的情况下,该漏洞的危险性非常大
      • 具体解决方案:
        • 方案一:在Android 4.2 版本中规定对被调用的函数以 @JavascriptInterface进行注解从而避免漏洞攻击
        • 方案二:在Android 4.2版本之前采用拦截prompt()进行漏洞修复。
          • 具体步骤:继承WebView,重写addJavascriptInterface()方法,然后在内部自己维护一个对象映射关系的Map;将需要添加的js接口放入该map中
          • 每次当WebView加载页面前,加载一段本地代码的js代码。原理是:
            • 让js调用一JavaScript方法:该方法是通过调用prompt()把js中的信息(含有特点标识,方法名称,参数等)传递到android端;
            • 在android的onJsPrompt()中,解析传递过来的信息,再通过反射机制调用java对象的方法,这样实现安全的js调用android代码。
            • 关于android返回给js的值:可以通过prompt()把java中方法的处理结果返回到js中
    • webview内置到处的searchBoxJavaBridge_对象
      • 原因:在Android 3.0以下,Android系统会默认通过searchBoxJavaBridge_的Js接口给 WebView 添加一个JS映射对象:searchBoxJavaBridge_对象。该接口可能被利用,实现远程任意代码。
      • 解决方案:删除searchBoxJavaBridge_的js接口
          // 通过调用该方法删除接口
          removeJavascriptInterface();
      
    • webview内置导出的accesssibility和accessibilityTraversalObject对象
      • 同searchBoxJavaBridge_对象
  2. 密码明文存储漏洞
    • WebView默认开启密码保存功能 :mWebView.setSavePassword(true)`
    • 开启后,在用户输入密码时,会弹出提示框:询问用户是否保存密码;如果选择”是”,密码会被明文保到 /data/data/com.package.name/databases/webview.db 中,这样就有被盗取密码的危险
    • 解决方案:关闭密码保存提醒,WebSettings.setSavePassword(false)
  3. 域控制不严格漏洞
    • 即 A 应用可以通过 B 应用导出的 Activity 让 B 应用加载一个恶意的 file 协议的 url,从而可以获取 B 应用的内部私有文件,从而带来数据泄露威胁。。具体:当其他应用启动此 Activity 时, intent 中的 data 直接被当作 url 来加载(假定传进来的 url 为 file:///data/local/tmp/attack.html ),其他 APP 通过使用显式 ComponentName 或者其他类似方式就可以很轻松的启动该 WebViewActivity 并加载恶意url。
    • 下面我们着重分析WebView中getSettings类的方法对 WebView 安全性的影响:
      • setAllowFileAccess()
          // 设置是否允许 WebView 使用 File 协议
          webView.getSettings().setAllowFileAccess(true);     
          // 默认设置为true,即允许在 File 域下执行任意 JavaScript 代码;
          // 如果是false,则不会存在上述的威胁,但同时也限制了 WebView 的功能,使其不能加载本地的 html 文件
      
      使用 file 域加载的 js代码能够使用进行同源策略跨域访问,从而导致隐私信息泄露
      1. 同源策略跨域访问:对私有目录文件进行访问
      2. 针对IM类产品,泄漏的是聊天信息,以及联系人等
      3. 针对浏览器类软件,泄漏的是cookie信息
      4. 解决该问题方案:则需要对不使用file协议的业务启用,对使用file协议的禁用
          // 禁止 file 协议加载 JavaScript
          if (url.startsWith("file://") {
              setJavaScriptEnabled(false);
          } else {
              setJavaScriptEnabled(true);
          }
      
      • setAllowFileAccessFromFileURLs():设置是否允许通过 file url 加载的 Js代码读取其他的本地文件
            当AllowFileAccessFromFileURLs()设置为 true 时,攻击者的JS代码为:
            
        
                // 通过该代码可成功读取 /etc/hosts 的内容数据
        
        • 解决方案:设置setAllowFileAccessFromFileURLs(false);当设置成为 false 时,上述JS的攻击代码执行会导致错误,表示浏览器禁止从 file url 中的 javascript 读取其它本地文件。
      • setAllowUniversalAccessFromFileURLs(): 设置是否允许通过 file url 加载的 Javascript 可以访问其他的源(包括http、https等源)。// 在Android 4.1前默认允许(setAllowFileAccessFromFileURLs()不起作用)// 在Android 4.1后默认禁止
        •   当AllowFileAccessFromFileURLs()被设置成true时,攻击者的JS代码是:
            // 通过该代码可成功读取 http://www.so.com 的内容
                
            解决方案:设置setAllowUniversalAccessFromFileURLs(false);
          
      • setJavaScriptEnabled():设置是否允许 WebView 使用 JavaScript(默认是不允许).但很多应用(包括移动浏览器)为了让 WebView 执行 http 协议中的 JavaScript,都会主动设置为true,不区别对待是非常危险的。

你可能感兴趣的:(android)