深入Weex系列(七)Adapter组件源码解析

1、前言

在上一篇文章《深入Weex系列(六)Weex渲染流程分析》中我们分析了Weex的渲染流程,但是实际上串起来的还是Module以及Component组件,加上Weex特有的渲染流程。

Module组件和Component组件对Weex Android SDK的意义非常大,属于没有这俩寸步难行的关系,包括今天我们要分析的Adapter也依赖于Module、Component组件,尤其是Js引擎与Module、Component交互的部分,Adapter表面上并不涉及,但是实际上息息相关。如果还有疑惑的话非常建议大家回过头再去看看之前的源码分析文章。

本篇文章我们就开始分析Weex中另一个组件Adapter,对Weex的设计理解更深一步。

2、初识Adapter

2.1 Adapter的定位

在《Android 扩展》中我们可以看到Adapter的定位:

Adapter 扩展 Weex 对一些基础功能实现了统一的接口,可实现这些接口来定制自己的业务。例如:图片下载等。

此处可以看到:Weex对Adapter的定位是基础功能的定义,可以实现这些接口自己进行实现。

2.2 Adapter的使用

实际上在我的WeexList项目中已经有了关于Adapter的使用,因为Weex并没有实现默认的图片加载功能。

Adapter的注册:

    InitConfig config = new InitConfig.Builder().setImgAdapter(new WeexImageAdapter()).build();
    WXSDKEngine.initialize(this, config);

Adapter的实现:

public class WeexImageAdapter implements IWXImgLoaderAdapter {

    @Override
    public void setImage(String url, ImageView view, WXImageQuality quality, WXImageStrategy strategy) {
        Glide.with(view.getContext())
                .load(url)
                .error(R.mipmap.me_image_man)
                .into(view);
    }
}

可以看到我们实现了Weex的IWXImgLoaderAdapter接口,自己实现了图片加载的能力。需要注意的是Adapter的注册和Module、Component的注册方式是不一样的。

2.3 Adapter与Module的区别

我们知道Module的定位是非UI性质的功能组件,那和Adapter是不是有点冲突?为什么还多了一个Adapter组件呢?

不要Adapter组件可不可以呢?其实是可以的,要做的事情都通过Module来做。但是一些基础功能例如图片加载、网络请求等不同的应用有自己的实现方式、依赖库,如果Weex自己再加一套,就显得冗余而且这些基础能力Weex定义好接口让接入的应用来提供就好了。

这就是Adapter存在的意义,你看Weex团队是多么的贴心啊!

备注: 那么我们此时可以由Adapter和Module的战略意义不一样,猜到两个组件的地位也是不一样的。通过翻阅源码,在InitConfig中Weex使用建造者模式来提供各种各样的Adapter的自定义,但是却不支持自己新增Adapter的类型。此处也提现了Adapter的定位:基础功能实现了统一的接口。

3、Adapter源码分析

3.1 Adapter注册

可以看到:Adapter的注册实际上非常简单,只是通过WXSDKEngine初始化了一些配置信息而已,根本不需要像Module或者Component一样需要通过JsBridge也让Js引擎知道自己的存在。

再次看出Adapter的定位:基础功能实现了统一的接口,具体的交互交给Module或Component来做,然后Module或Component来调用我们实现的Adapter。

3.2 Adapter调用

关于Adapter的调用就不再画图分析,因为实在不需要怎么分析。实际上对于Adapter的调用本质就是类的调用,因为刚才在注册的时候设置了各种各样的Adapter,然后直接调用接口即可。

4、特定Adapter分析

对于Adapter的调用分为Component调用和Module调用(Adapter处于调用链的下端

4.1 IWXImgLoaderAdapter(Component调用)

我们来介绍下实现图片加载能力的Adapter,这个Adapter没有被Weex默认实现。之前总结过组件的交互是通过JsBridge然后来调用Component被注解修饰的方法。

对于ImageView它在Weex里对应了WXImage这个Component。我们在Vue代码里写的src属性既然是需要通过Adapter实现的,那在WXImage中必定有方法对应src属性,果然我们发现了它。

    @Component(lazyload = false)
    public class WXImage extends WXComponent<ImageView> {
        @WXComponentProp(name = Constants.Name.SRC)
        public void setSrc(String src) {
            if (src == null) {
                return;
            }
            this.mSrc = src;
            WXSDKInstance instance = getInstance();
            Uri rewrited = instance.rewriteUri(Uri.parse(src), URIAdapter.IMAGE);

            if (Constants.Scheme.LOCAL.equals(rewrited.getScheme())) {
                setLocalSrc(rewrited);
            } else {
                int blur = 0;
                if (getDomObject() != null) {
                    String blurStr = getDomObject().getStyles().getBlur();
                    blur = parseBlurRadius(blurStr);
                }
                setRemoteSrc(rewrited, blur);
            }
        }

    private void setRemoteSrc(Uri rewrited, int blurRadius) {

        ·······

        IWXImgLoaderAdapter imgLoaderAdapter = getInstance().getImgLoaderAdapter();
        if (imgLoaderAdapter != null) {
            imgLoaderAdapter.setImage(rewrited.toString(), getHostView(),
                    getDomObject().getAttrs().getImageQuality(), imageStrategy);
        }
    }
}

总结:

  • Js引擎通过JsBridge发送消息给客户端,最终调用到相关Component的具体方法;
  • 对于WXImage来说,被调用setSrc方法之后,会调用设置的IWXImgLoaderAdapter的setImage方法;

4.2 IWXHttpAdapter(Module调用)

对于网络请求比较常用,Weex就做了默认的实现,但是之前在《Weex系列(四)之Module组件源码解析》分析过,实现比较简陋,最好重新实现。

我们先看下平时写Vue代码的时候使用网络请求是怎么做的:

    var stream = weex.requireModule('stream')
    stream.fetch

既然require的是名叫”stream”的Module,那么我们就在WXSDKEngine中找一下,果然在register()方法中找到了:

    registerModule("stream", WXStreamModule.class);

接下来,我们不用猜测,坚信WXStreamModule类中必定存在一个fetch方法;

  @JSMethod(uiThread = false)
  public void fetch(String optionsStr, final JSCallback callback, JSCallback progressCallback){

    JSONObject optionsObj = null;
    try {
      optionsObj = JSON.parseObject(optionsStr);
    }catch (JSONException e){
      WXLogUtils.e("", e);
    }

    boolean invaildOption = optionsObj==null || optionsObj.getString("url")==null;
    if(invaildOption){
      if(callback != null) {
        Map<String, Object> resp = new HashMap<>();
        resp.put("ok", false);
        resp.put(STATUS_TEXT, Status.ERR_INVALID_REQUEST);
        callback.invoke(resp);
      }
      return;
    }

    //此处可以看到Http请求的那些参数最终是被怎么取的,这样即便是文档没写的一些设置我们也可以根据取参数的方法反推出来怎么设置。
    String method = optionsObj.getString("method");
    String url = optionsObj.getString("url");
    JSONObject headers = optionsObj.getJSONObject("headers");
    String body = optionsObj.getString("body");
    String type = optionsObj.getString("type");
    int timeout = optionsObj.getIntValue("timeout");

    if (method != null) method = method.toUpperCase();
    Options.Builder builder = new Options.Builder()
            .setMethod(!"GET".equals(method)
                    &&!"POST".equals(method)
                    &&!"PUT".equals(method)
                    &&!"DELETE".equals(method)
                    &&!"HEAD".equals(method)
                    &&!"PATCH".equals(method)?"GET":method)
            .setUrl(url)
            .setBody(body)
            .setType(type)
            .setTimeout(timeout);

    extractHeaders(headers,builder);
    final Options options = builder.createOptions();

    // 真正请求网络去了,里面会调用IWXHttpAdapter
    sendRequest(options, new ResponseCallback() {
      @Override
      public void onResponse(WXResponse response, Map<String, String> headers) {
        if(callback != null) {
          Map<String, Object> resp = new HashMap<>();
          if(response == null|| "-1".equals(response.statusCode)){
            resp.put(STATUS,-1);
            resp.put(STATUS_TEXT,Status.ERR_CONNECT_FAILED);
          }else {
            int code = Integer.parseInt(response.statusCode);
            resp.put(STATUS, code);
            resp.put("ok", (code >= 200 && code <= 299));
            if (response.originalData == null) {
              resp.put("data", null);
            } else {
              String respData = readAsString(response.originalData,
                      headers != null ? getHeader(headers, "Content-Type") : ""
              );
              try {
                resp.put("data", parseData(respData, options.getType()));
              } catch (JSONException exception) {
                WXLogUtils.e("", exception);
                resp.put("ok", false);
                resp.put("data","{'err':'Data parse failed!'}");
              }
            }
            resp.put(STATUS_TEXT, Status.getStatusText(response.statusCode));
          }
          resp.put("headers", headers);
          callback.invoke(resp);
        }
      }
    }, progressCallback);
  }


  private void sendRequest(Options options,ResponseCallback callback,JSCallback progressCallback){
    WXRequest wxRequest = new WXRequest();
    wxRequest.method = options.getMethod();
    wxRequest.url = mWXSDKInstance.rewriteUri(Uri.parse(options.getUrl()), URIAdapter.REQUEST).toString();
    wxRequest.body = options.getBody();
    wxRequest.timeoutMs = options.getTimeout();

    if(options.getHeaders()!=null)
    if (wxRequest.paramMap == null) {
      wxRequest.paramMap = options.getHeaders();
    }else{
      wxRequest.paramMap.putAll(options.getHeaders());
    }

    IWXHttpAdapter adapter = (mAdapter==null && mWXSDKInstance != null) ? mWXSDKInstance.getWXHttpAdapter() : mAdapter;
    if (adapter != null) {
    // 调用了IWXHttpAdapter的sendRequest方法
      adapter.sendRequest(wxRequest, new StreamHttpListener(callback,progressCallback));
    }else{
      WXLogUtils.e("WXStreamModule","No HttpAdapter found,request failed.");
    }
  }

然后我们看下DefaultWXHttpAdapter的默认网络请求实现;

public class DefaultWXHttpAdapter implements IWXHttpAdapter {

  private static final IEventReporterDelegate DEFAULT_DELEGATE = new NOPEventReportDelegate();
  private ExecutorService mExecutorService;

  private void execute(Runnable runnable){
    if(mExecutorService==null){
      mExecutorService = Executors.newFixedThreadPool(3);
    }
    mExecutorService.execute(runnable);
  }

  @Override
  public void sendRequest(final WXRequest request, final OnHttpListener listener) {
    if (listener != null) {
      listener.onHttpStart();
    }
    execute(new Runnable() {
      @Override
      public void run() {
        WXResponse response = new WXResponse();
        IEventReporterDelegate reporter = getEventReporterDelegate();
        try {
          HttpURLConnection connection = openConnection(request, listener);
          reporter.preConnect(connection, request.body);
          Map<String,List<String>> headers = connection.getHeaderFields();
          int responseCode = connection.getResponseCode();
          if(listener != null){
            listener.onHeadersReceived(responseCode,headers);
          }
          reporter.postConnect();

          response.statusCode = String.valueOf(responseCode);
          if (responseCode >= 200 && responseCode<=299) {
            InputStream rawStream = connection.getInputStream();
            rawStream = reporter.interpretResponseStream(rawStream);
            response.originalData = readInputStreamAsBytes(rawStream, listener);
          } else {
            response.errorMsg = readInputStream(connection.getErrorStream(), listener);
          }
          if (listener != null) {
            listener.onHttpFinish(response);
          }
        } catch (IOException|IllegalArgumentException e) {
          e.printStackTrace();
          response.statusCode = "-1";
          response.errorCode="-1";
          response.errorMsg=e.getMessage();
          if(listener!=null){
            listener.onHttpFinish(response);
          }
          if (e instanceof IOException) {
            reporter.httpExchangeFailed((IOException) e);
          }
        }
      }
    });
  }
}

可以看到Weex默认的网络请求是基于HttpURLConnection,一个核心池和最大池都是3的FixThreadPool。对于网络请求来说缺点显而易见:

  • 没有Https的实现;
  • 线程池使用可以更优;

但是对Weex来说实际上只是提供默认的简单实现,也没错,需要自己去重新定义。

接下来看图总结下:

总结:

  • Js引擎发消息来执行网络请求;
  • 调用到了WXStreamModule,调用其fetch方法;
  • WXStreamModule里会调用IWXHttpAdapter的sendRequest方法,实现真正的网络请求;

5、问题

Weex除了使用网络图片之外可以使用别的类型图片吗,例如直接使用drawable文件夹里的图片?

在刚开始接触到Weex的时候我内心的答案也是NO,毕竟drawable里的图片在常规的安卓开发中都是需要使用R文件来调用的。源码面前,了无秘密!我们就来看下WXImage的实现吧,在setSrc方法中有一个判断:

    if (Constants.Scheme.LOCAL.equals(rewrited.getScheme())) {
      setLocalSrc(rewrited);// 以local 开头的话则走到了这里
    } else {
      int blur = 0;
      if(getDomObject() != null) {
        String blurStr = getDomObject().getStyles().getBlur();
        blur = parseBlurRadius(blurStr);
      }
      setRemoteSrc(rewrited, blur);
    }

最终会走到这里:

  public static Drawable getDrawableFromLoaclSrc(Context context, Uri rewrited) {
    Resources resources = context.getResources();
    List<String> segments = rewrited.getPathSegments();
    if (segments.size() != 1) {
      WXLogUtils.e("Local src format is invalid.");
      return null;
    }
    int id = resources.getIdentifier(segments.get(0), "drawable", context.getPackageName());
    return id == 0 ? null : ResourcesCompat.getDrawable(resources, id, null);
  }

老司机们已经明白了吧:通过资源名生成uri,然后还是拿到了资源对应的id,获取的图片。

备注:

  • 如果不是仔细跟踪Weex源码的话,我们很容易给Weex贴上一个不能加载本地图片的标签。
  • 实际上,Weex也支持别的类型的图片调用方式,老司机们可以自己探索实现下。

6、Adapter总结

  • Module和Component类是理解Adapter的前提;与Js引擎交互的部分被Module和Component做了,但是对理解很重要;
  • Adapter的定位是扩展Weex对一些基础功能实现了统一的接口,可实现这些接口来定制自己的业务;
  • Module自身的源码其实很简单,复杂的是上面与Module、Component相关的调用链;
  • 通过细读源码,可以发现很多问题的答案;带着问题去读,更加事半功倍;

欢迎持续关注Weex源码分析项目:Weex-Analysis-Project

原文发布于微信公众号 - 双十二技术哥(gh_b0e7544783e2)

原文发表时间:2017-11-30

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏CSDN技术头条

内存泄漏的检测、解决、防止

引言 今天又是没什么事情,好,不多说,直接进入我们的主题吧。 今天说的是关于内存泄漏的检测与解决。这个问题想必对于初学者是个迷,也不知道从何出入手,那么今天这个...

343100
来自专栏Android知识点总结

O1-开源框架使用之EventBus

说明使用POSTING,发布与订阅在同一个线程,也就是子线程,更新UI会崩 说明使用MAIN,不管发布者在哪,订阅者都在main线程,可更新UI,但不能耗时操...

11320
来自专栏软件开发 -- 分享 互助 成长

WIFI环境下Android手机和电脑通信

前面已经写过一篇java实现最基础的socket网络通信,这篇和之前那篇大同小异,只是将客户端代码移植到手机中,然后获取本机IP的方法略有不同。 先讲一下本篇中...

40150
来自专栏知识分享

android 之TCP客户端编程

吸取教训!!!本来花了5个小时写完了,没想到,,,因为没点上面的自动保存查看一下,全没了,重新写呗 关于网络通信:每一台电脑都有自己的ip地址,每台电脑上的网络...

38880
来自专栏jianhuicode

如何使用融云地图,文件等插件--融云 Android SDK 2.8.0+ Extension 开发文档

转载自融云 Android SDK 2.8.0+ Extension 开发文档 融云 SDK 2.8.0 后对 会话界面输入区域、+号扩展区域、语音消息、Emo...

350100
来自专栏学海无涯

Android开发之Retrofit小试牛刀

感觉好久没有写Android的文章了,囧囧囧!因为Retrofit实在是太火了, 火得我一直跃跃欲试,但是由于种种原因吧,一直都没有用过。周末闲来无事,利用一个...

29390
来自专栏24K纯开源

Android开发中遇到的requestFeature() must be called before adding content异常

缘起     上一篇博文中讲到了几种实现全屏显示Activity内容的方法。然而实际在实现中发现了一些问题,在本篇博文中进行总结下。首先交代一下开发环境,本人使...

39570
来自专栏Android先生

Android官方MVP架构项目解析

前段时间Google在Github开源了一个项目Android Architecture Blueprints,在项目中展示了使用不同的实现架构和工具来实现相同...

11330
来自专栏刘望舒

lifecycle-mvp,像前端那样组合式写页面

31540
来自专栏Android机动车

Retrofit实现带进度下载

Retrofit是目前最主流的网络框架了,它对网络请求几近完美的封装,大大降低了我们开发者的研发难度,缩短了研发周期。最近项目中遇到了下载视频和图片文件的需求(...

12920

扫码关注云+社区

领取腾讯云代金券