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

一、前言

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

Module组件和Component组件对Weex Android SDK的意义很是大,属于没有这俩步履维艰的关系,包括今天咱们要分析的Adapter也依赖于Module、Component组件,尤为是Js引擎与Module、Component交互的部分,Adapter表面上并不涉及,可是实际上息息相关。若是还有疑惑的话很是建议你们回过头再去看看以前的源码分析文章。android

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

二、初识Adapter

2.1 Adapter的定位

《Android 扩展》中咱们能够看到Adapter的定位:github

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

此处能够看到:Weex对Adapter的定位是基础功能的定义,能够实现这些接口本身进行实现。bash

2.2 Adapter的使用

实际上在个人WeexList项目中已经有了关于Adapter的使用,由于Weex并无实现默认的图片加载功能。微信

Adapter的注册:weex

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的注册方式是不同的。ide

2.3 Adapter与Module的区别

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

不要Adapter组件可不能够呢?实际上是能够的,要作的事情都经过Module来作。可是一些基础功能例如图片加载、网络请求等不一样的应用有本身的实现方式、依赖库,若是Weex本身再加一套,就显得冗余并且这些基础能力Weex定义好接口让接入的应用来提供就行了。

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

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

三、Adapter源码分析

3.1 Adapter注册

Adapter注册时序图

能够看到:Adapter的注册实际上很是简单,只是经过WXSDKEngine初始化了一些配置信息而已,根本不须要像Module或者Component同样须要经过JsBridge也让Js引擎知道本身的存在。

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

3.2 Adapter调用

关于Adapter的调用就再也不画图分析,由于实在不须要怎么分析。实际上对于Adapter的调用本质就是类的调用,由于刚才在注册的时候设置了各类各样的Adapter,而后直接调用接口便可。

四、特定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);
        }
    }
}
复制代码

IWXImgLoaderAdapter调用时序图

总结:

  • 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来讲实际上只是提供默认的简单实现,也没错,须要本身去从新定义。

接下来看图总结下:

Module调用Adapter的时序图

总结:

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

五、问题

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也支持别的类型的图片调用方式,老司机们能够本身探索实现下。

六、Adapter总结

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

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

欢迎关注微信公众号:按期分享Java、Android干货!

欢迎关注
相关文章
相关标签/搜索