深入Weex系列(十)Weex SDK可優化細節思考

貌似許亞軍發表於2017-12-29

1、前言

在上一篇文章中我們介紹了Weex SDK原始碼中可借鑑的細節,那麼現在的Weex SDK已經是最優的嗎?作為技術RD,我們心中一定要有敬畏:藝無止境,學習的過程中逐漸反思,尋找最優解。那麼我們今天就來說說Weex SDK中有哪些可以優化的細節。

2、反射獲取方法

大家知道對於Weex來說,JS引擎與Native的互動本質上就是方法的呼叫,最終呼叫的時候必然是反射無疑,但是方法的獲取也是反射這一點存在很大優化空間。

先回憶下我們使用Module元件的方法:

  • 繼承WXModule;
  • 編寫函式體;
  • 給函式體打上註解@JSMethod;

而這些被打上註解的函式則可以拿來與Js進行互動,我們回憶下Module註冊的程式碼,分別有Native的註冊和Js的註冊,註冊有一步是獲取Module元件中打上註解的方法:

  @Override
  public String[] getMethods() {
    if (mMethodMap == null) {
      generateMethodMap();
    }
    Set<String> keys = mMethodMap.keySet();
    return keys.toArray(new String[keys.size()]);
  }
  
  private void generateMethodMap() {
    if(WXEnvironment.isApkDebugable()) {
      WXLogUtils.d(TAG, "extractMethodNames:" + mClazz.getSimpleName());
    }
    HashMap<String, Invoker> methodMap = new HashMap<>();
    try {
      for (Method method : mClazz.getMethods()) {// 拿到方法
        // iterates all the annotations available in the method
        for (Annotation anno : method.getDeclaredAnnotations()) {// 方法是否被打上註解
          if (anno != null) {
            if(anno instanceof JSMethod) {
              JSMethod methodAnnotation = (JSMethod) anno;
              String name = JSMethod.NOT_SET.equals(methodAnnotation.alias())? method.getName():methodAnnotation.alias();
              methodMap.put(name, new MethodInvoker(method, methodAnnotation.uiThread()));// 封裝成MethodInvoker物件
              break;
            }else if(anno instanceof WXModuleAnno) {
              WXModuleAnno methodAnnotation = (WXModuleAnno)anno;
              methodMap.put(method.getName(), new MethodInvoker(method,methodAnnotation.runOnUIThread()));
              break;
            }
          }
        }
      }
    } catch (Throwable e) {
      WXLogUtils.e("[WXModuleManager] extractMethodNames:", e);
    }
    mMethodMap = methodMap;
  }  
複製程式碼

可以看到,Module註冊的過程必定是相對耗時的,而Module越多時間也越長,應用啟動階段註冊的話尤為明顯。而對於另一個元件Component基本也是一樣的,只不過多了個註解@Component:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
  boolean lazyload() default true;
}
複製程式碼

區別在於:對於Native註冊,會有懶載入的判斷,不過效果一般,因為Js端註冊前已經生成一次了:

  @Override
  public void loadIfNonLazy() {
    Annotation[] annotations = mClz.getDeclaredAnnotations();
    for (Annotation annotation :
      annotations) {
      if (annotation instanceof Component){
        // 懶載入則暫時跨過這步
        if(!((Component) annotation).lazyload() && mMethodInvokers == null){
          generate();
        }
        return;
      }
    }
  }
複製程式碼

那麼反射消耗效能加上耗時的缺點應該如何解決呢?這裡提供兩條解決途徑供參考:

  • 對Weex進行非同步初始化,絕大多數應用使用Weex不會是整個App都由Weex完成,那麼只要能保證Weex的初始化在使用之前完成即可;這點很容易做到畢竟App都有閃屏的時間可以利用;
  • 參考EventBus不同版本的改進,我們也可以將Weex的執行時註解改為編譯時註解,這樣就將在執行時的反射工作挪換到編譯時,這種方式顯然更好,也不需要再進行非同步初始化;

3、適配的問題

對於Weex,它預設的將顯示的寬度設定為750px作為適配的標準。

  @Deprecated
  public static float getRealPxByWidth(float pxValue) {
     return getRealPxByWidth(pxValue,750);
  }
  
  public static float getRealPxByWidth(float pxValue,int customViewport) {
    if (Float.isNaN(pxValue)) {
      return pxValue;
    }
    if (mUseWebPx) {
      return (float) Math.rint(pxValue);
    } else {
      float realPx = (pxValue * getScreenWidth() / customViewport);
      return realPx > 0.005 && realPx < 1 ? 1 : (float) Math.rint(realPx);
    }
  }
複製程式碼

那提一個開發中的細節問題:我怎麼知道需要在Vue的佈局程式碼中寫多少px呢?如果UI同學給出的不是750標準就需要自己使用公尺去計算。

解決思路:

  • 規範使用統一的頁面適配保證比如出圖按照750或者720(修改Weex適配的程式碼);
  • 修改Weex的webpack-loader,還是使用類如Android原生dp一樣的佈局單位(需要前端同學配合寫個工具);

4、基礎能力不夠好

這個之前在Module的原始碼解析中其實提到過,比如網路請求的能力,不管是執行緒池的使用還是對快取、協議等的支援都很一般。類如別的一些基礎能力比如Stroage等模組也是一樣。

不過這條屬於苛責,之前也總結過對於Weex來說我們實際上只需要儲存其Js引擎與Native的互動能力即可,別的都屬於Weex為了吸引開發者而做的簡略能力。現實的要求是把這些程式碼去掉,自己橋接原生原有的能力,縮減Apk的方法數。

5、其它

  • CSS的支援度不夠完善,一些效果在Web上的顯示和Native上不一樣,這要求除錯儘量在Native;
  • 關於文件,不可否認Weex的思路和SDK程式碼都非常優秀,但是文件就相對一般了,有些屬於錯誤的;有些屬於過於簡單,比如對網路請求,沒有Post請求的示例,對Component的自定義,沒有ViewGroup的示例,有點避重就輕的嫌疑;

歡迎持續關注Weex原始碼分析專案:Weex-Analysis-Project

歡迎關注微信公眾號:定期分享Java、Android乾貨!

歡迎關注

相關文章