深刻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乾貨!

歡迎關注
相關文章
相關標籤/搜索