AdvancedHybrid-Framework-Android

Introduction: An advanced android hybrid framework, works for H5 and native interactions via webview. This framework also includes some basic UI frameworks for webview.
More: Author   ReportBugs   
Tags:

该项目适合那些 H5 和原生控件混合的 Hybride 应用,这类应用有一个特点,大部分页面通过前端 H5 页面实现,难免会遇到 B 页面和 Native 交互的场景,通常安卓端和 IOS 端页面和 Native 层间的交互使用的两套不同架构,前端开发同时需要维护两套 JS 交互框架,增加了代码的维护成本和可扩展性,故提出一个安卓和 IOS 统一的 JS 交互框架显得尤为必要,需要另辟蹊径,去寻找既安全,又能实现兼容 Android 和 IOS 各个版本的方案,JSBridge 交互框架应运而生。

同时,该项目也对 webview 本身进行了重构和优化,包括代码层级的划分、webview 生命周期的控制、性能和安全方面的提升以及对 webview 缓存策略的梳理。为了方便三方集成使用,项目中把 webview、下拉刷新( https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh )、错误页面、超时处理、加载样式、页面地址过滤等通用逻辑封装在一个抽象 Fragment 里面,集成后只需继承对应 Fragment,并实现对应接口回调即可。

价值评估

(1)实现对扩展开放,对修改封闭,方便 webview 界面相关的功能扩展和集中管理,实现高内聚低耦合

(2)提升 Webview 的性能和安全

(3)梳理 Webview 缓存策略,实现基于 HTTP 标准协议的缓存策略

(4)为后续 H5 功能开发提供支持

(5)提供安全、可靠的 H5 和 Native 交互框架 JSBridge,并支持带回传数据等四种交互类型

什么是 JsBridge

全新的 JSBridge 框架替代了 WebView 的自带的 JavascriptInterface 接口注入,作为客户端本地 java 层和前端 js 层互通的框架,使得我们的开发更加灵活和安全。IOS 端和 Android 按照约定好的 URL 格式使用同一套规则的 B+C 交互框架,极大减少维护成本,提高开发效率,同时安卓端避免了使用系统自带 JavascriptInterface 接口,根本上解决了 4.0.x 系统上 WebView 存在的接口隐患与手机挂马利用的安全漏洞。

JSBridge 实践

(一)URL 定义:

cmread://call/xxx?jsonData=xxx

cmread://fetch/xxx?retMethod=xxx&jsonData=xxx

cmread://listen/channel?key=xxx&jsonData=xxx&cleanType=1

cmread://broadcast/channel?key=xxx&retMethod=xxx&cleanType=1

(二)Fetch 类型 URL:前端调用客户端方法,客户端本地处理完成后把结果值返回给前端,返回值方法由前端指定,URL 中包含返回方法和 json 数据,两者分开。 cmread://fetch/xxxx?retMethod=xxx&jsonData=xxxxxxxx

(三)对于 Fetch 类型 URL 的返回值数据,格式为 JSON 字符串,定义为: { “code”:0, "message":null, "result":object } a. 客户端定义 code 的类型和意义 b. 如果 执行失败,result 为 null c. result 永远为对象,如果结果是 bool,number 等,也需转成对象

(四)Listen 类型的 URL,前端调用客户端方法,请求客户端存储数据,用于跨页面调用,需配合 Broadcast 类型的 URL 使用 示例:cmread://listen/channel?key=xxx&jsonData=xxx&cleanType=1 channel 为常量字符串 cleanType 定义 a. 0 或者不传,追加 【按照加入顺序加入】 b. 1 清空原有的数据后,加入 c. 2 只清空,不加入

(五)Broadcast 类型的 URL。前端调用客户端方法,获取前端通过 Listen 类型方法存储在客户端本地的数据 示例:cmread://broadcast/channel?key=xxx&retMethod=xxx&cleanType=1 channel 为常量字符串 cleanType 的定义 a. 0 或者不传,什么都不做 b. 1,发送 broadcast 后,清空数据

(六)Call 类型的 URL,前端调用客户端的方法,使用客户端提供的 Native 层能力

(七)编码 jsonData 采用 URLSAFE 的 Base64 编码,对传输的数据进行编码加密,防止信息泄露。客户端提供一个样例字符串,并且给出编码后的字符串,用于算法比对

使用说明

1.可按需直接使用 JSBridgeWebView 或 AdvancedWebView 类,JSBridgeWebView 继承了 AdvancedWebView,AdvancedWebView 继承自系统 webview 控件。JSBridgeWebView 封装了 B+C 交互的处理,而 AdvancedWebView 则对 webview 系统控件做了优化和扩展,提升安全和性能。

2.errorview 和 loadingview 可以自定义

3.Fragment 的使用

public class CommonWebFragment extends WebFragment {
      @Override
      public void onPageStarted(String url, Bitmap favicon) {

      }

      @Override
      public void onPageFinished(String url) {

      }

      @Override
      public void onPageError(int errorCode, String description, String failingUrl) {
          showErrorView();
      }

      @Override
      public void onExternalPageRequest(String url, boolean isHostNameForbiddon) {
          showErrorView();
      }

      @Override
      protected AdvancedWebView initWebView() {
          Activity act = getActivity();
          if (act != null) {
              return new CommonWebView(act);
          }
          return null;
      }

      @Override
      protected void configureWebView(AdvancedWebView webView) {
          if (null == webView) {
              return;
          }

          webView.addPermittedHostname("");   // 设置禁止的 host 地址
          webView.setVerticalScrollBarEnabled(false);
          setPullRefreshEnable(false, true);  // 是否禁止下拉刷新功能
      }

      @Override
      protected boolean overrideUrlLoading(WebView view, String url) {
          if (url.startsWith("http") || url.startsWith("https")) {
              loadUrl(url, false);
          } else {
              Activity activity = getActivity();
              if (activity != null && !activity.isFinishing()) {
                  try {
                      Uri uri = Uri.parse(url);
                      Intent intent = new Intent(Intent.ACTION_VIEW, uri);
                      activity.startActivity(intent);
                  } catch (RuntimeException e) {
                      e.printStackTrace();
                  }
              }
          }
          return true;
      }
}

4.对应回调的 Action 添加到 com.example.neilyi.advancedhybridframework.hybride.Actions 类里面

  public enum Actions {
   notifyResultToast, notifyPopup, notifyAlert, notifyConfirm,
  }

5.实现 Call 类型的 Handler

public class CallTypeJSHandler implements HybridHandler {

      private WeakReference<AdvancedWebView> mWebView;
      private WeakReference<WebBaseActivity> mActivity;

      public CallTypeJSHandler(WebBaseActivity activity) {
          if (activity != null && activity instanceof WebBaseActivity) {
              this.mActivity = new WeakReference<>(activity);
          }
      }

      @Override
      public String getHandlerType() {
          return HybridConstans.CALL_TYPE_TASK;
      }

      @Override
      public AdvancedWebView getWebView() {
          if(mWebView != null && mWebView.get() != null) {
              return mWebView.get();
          }
          return null;
      }

      @Override
      public boolean handerCallTask(WebBaseActivity activity, String actionStr, JSONObject jsonData, AdvancedWebView webview) {
          if (TextUtils.isEmpty(actionStr)) {
              return false;
          }

          if (activity != null) {
              this.mActivity = new WeakReference<>(activity);
          }

          if (webview != null) {
              this.mWebView = new WeakReference<>(webview);
          }

          Actions action = null;
          try {
              action = Actions.values()[Actions.valueOf(actionStr).ordinal()];
          } catch (RuntimeException e) {
              e.printStackTrace();
          }

          if (action != null) {
              switch (action) {
                  case notifyResultToast:
                      notifyResultToast(jsonData);
                      break;
                  default:
                      ToastUtil.showLong("App 没有处理事件的 Action, action=" + actionStr);
                      break;
              }
              return true;
          }

          return false;
      }

      private void notifyResultToast(JSONObject jsonData) {
          String info = "notifyResultToast!";
          if (jsonData != null) {
              try {
                  info = jsonData.getString("responseInfo");
              } catch (JSONException e) {
                  e.printStackTrace();
              }
          }
          ToastUtil.showLong(info);
      }

      private WebBaseActivity getActivityContext() {
          if (mActivity != null && mActivity.get() != null) {
              return mActivity.get();
          }
          return null;
      }

      @Override
      public boolean handerFetchTask(WebBaseActivity activity, String actionStr, JSONObject jsonObject, BridgeCallback callback) {
          return false;
      }

      @Override
      public boolean handerUrlTask(WebBaseActivity activity, String string) {
          return false;
      }
}
Apps
About Me
GitHub: Trinea
Facebook: Dev Tools