Android中hybrid开发的基础知识

发布于:2021-05-15 02:35:05

前言

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


简介

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


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

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

具体使用
    涉及到的类:
    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交互
    交互方式:桥梁WebView
    android调用js代码

    方式一 通过webview的loadUrl()


    该方式会引起页面刷新指定url:http:www.baidu.com指定资源文件: file:///android_assets/filename.fileloadUrl(“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内存泄漏
    不在xml中定义webview,而是在需要的时候再activity中创建,并且使用application context;在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存在较为明显的性能问题,特别突出的是:加载速度慢,流量消耗大。那么有么有解决的方案呢?如下:


    存在哪些性能问题?


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

    针对问题,有哪些解决方案:


      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,有更新回包200Etag 和 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的那些漏洞
类型
    任意代码执行漏洞
    addJavascriptInterface()webview内置到处的searchBoxJavaBridge 对象webview内置导出的accesssibility和accessibilityTraversalObject对象 密码明文存储漏洞域控制不严格漏洞

具体分析
    任意代码执行漏洞
    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_对象 密码明文存储漏洞
    WebView默认开启密码保存功能 :mWebView.setSavePassword(true)`开启后,在用户输入密码时,会弹出提示框:询问用户是否保存密码;如果选择”是”,密码会被明文保到 /data/data/com.package.name/databases/webview.db 中,这样就有被盗取密码的危险解决方案:关闭密码保存提醒,WebSettings.setSavePassword(false) 域控制不严格漏洞
    即 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代码能够使用进行同源策略跨域访问,从而导致隐私信息泄露

      同源策略跨域访问:对私有目录文件进行访问针对IM类产品,泄漏的是聊天信息,以及联系人等针对浏览器类软件,泄漏的是cookie信息解决该问题方案:则需要对不使用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,不区别对待是非常危险的。

相关推荐

最新更新

猜你喜欢