閱讀26 返回首頁    go 阿裏雲 go 技術社區[雲棲]


ToyBricks簡介以及原理分析

ToyBricks背景

我始終認為,在高內聚,低耦合的原則下,進行組件化,模塊化,插件化都是移動應用開發的趨勢。

為什麼這麼說呢?下麵我們舉個栗子:
大家都知道,以前Android應用開發中,可以使用HttpClient或者HttpUrlConnection來進行http訪問。這裏假設有一個耦合嚴重,但代碼量巨大的項目,使用了基於HttpClient封裝的loopj/android-async-http來進行http訪問。但是,後來,Google明確支持使用HttpUrlConnection。此時,經過調研,你們覺得square/okhttp基於HttpUrlConnection,符合你們的要求。

現在,不管是否將HttpClient替換成okhttp,你們都可能麵臨以下困境:
1. 需求都做不完,根本沒有排期做這個替換。於是,你們麵臨離google的支持越來越遠,離風險越來越近的困境;
1. 辛辛苦苦耗費人力將HttpClient替換成okhttp。但由於兩者變化很大,需要投入很多測試資源,來重新確認這些接口是否正常訪問。一旦出現問題,還需要安排研發資源,去一一排查。
1. 替換的工作量太大。替換一部分之後,發現沒有足夠人力去繼續完成。於是,替換終止。整個工程又變得混亂和臃腫,同時包含了兩種http的封裝庫和調用。

模塊化可以有效解決這些問題。通用的做法,是按照業務,功能等將整個項目分成不同的模塊,由不同的研發測試小組負責。
每個模塊又分為接口和實現兩個部分。接口部分提供給模塊外部調用,而實現部分則禁止來自外部的調用。

apt.png

那麼,如何將模塊的接口和實現部分關聯起來呢?通過APT工具,可以輕鬆地將接口部分和實現部分關聯起來。

APT,即Annotation Processing Tool,可以理解為“編譯時注解處理器工具”。

官方說明:
“The apt tool is a command-line utility for annotation processing. It includes a set of reflective APIs and supporting infrastructure to process program annotations (JSR 175). These reflective APIs provide a build-time, source-based, read-only view of program structure. They are designed to cleanly model the Java programming language's type system after the addition of generics (JSR 14).”

簡單理解如下:
apt工具是javac工具的一部分。在編譯時,apt工具首先會掃描工程下Java源碼中的編譯時注解,再根據預先定義的編譯時注解處理工具,生成指定的Java源碼文件。緊接著,生成的Java源碼文件和之前項目下的Java源碼一起,由javac工具來編譯成class。

但是,APT有一個局限性,就是隻會掃描Java源碼,不會掃描jar ,aar 和class 。也就是說,所有模塊需要以源碼形式存在。而現在通用的做法是,將模塊打包成jar或者aar,發布到Maven庫,再由其他模塊自行引用。

有沒有辦法將APT的這種功能和特性延伸到jar和aar呢?
於是,ToyBricks應運而生。

ToyBricks簡介

ToyBricks簡介

ToyBricks是一個Android項目模塊化的解決方案,主要包括四個部分,APT注解,APT注解處理器,ToyBricks插件(Gradle Plugin),ToyBricks庫。

ToyBricks.png

其中:
1. APT注解,主要定義了兩個注解:Interface(接口,例如:IText),Implementation (實現,例如:TextImpl)
1. APT注解處理器,在javac編譯java源碼之前。APT注解處理器會掃描Java源碼中帶有上麵兩個注解的接口和類,並且生成一個json文件, ToyBricks.json.
1. ToyBricks插件(Gradle Plugin),負責ToyBricks.json的打包,合並,生成Java源文件等工作
1. ToyBricks,提供對外調用方法。通過參數傳入接口,返回相應的實現。

ToyBricks原理分析

下麵以接口IText和實現TextImpl為例,簡單介紹下ToyBricks原理。

主要分為兩個部分:

Android Library(最終可能打包成jar,aar,並發布到maven庫)

如果工程是Android庫模塊,則主要流程如下:
1.在javac編譯java源碼之前,由APT注解處理器掃描Java源碼中帶有上麵兩個注解的接口和類,生成ToyBricks.json。

{
  "interfaceList" : [ "com.github.snowdream.toybricks.app.IText" ],
  "globalImplementation" : {
    "com.github.snowdream.toybricks.app.IText" : "com.github.snowdream.toybricks.app.impl.NewTextGobalImpl"
  },
  "defaultImplementation" : {
    "com.github.snowdream.toybricks.app.IText" : "com.github.snowdream.toybricks.app.impl.TextImpl"
  },
  "singletonImplementation" : [ "com.github.snowdream.toybricks.app.impl.NewTextGobalImpl" ]
}

2.在打包jar,aar的時候,由ToyBricks插件(Gradle Plugin)提前處理,保證ToyBricks.json能被拷貝進去jar包或者aar包的根目錄下,並隨同一起分布到maven倉庫。

Android Application

如果工程是Android應用模塊,則主要流程如下:
1. 第一步,和Android Library第一步一致。
2. Javac編譯Java源代碼
3. 掃描所有依賴的庫文件,過濾出所有包含ToyBricks.json文件的jar包或者aar包,並且提取出來。提取完畢後,合並所有的ToyBricks.json文件,成為一個ToyBricks.json。
4. 將最終的ToyBricks.json按照預定規則生成一個Java源碼文件.文件名為: InterfaceLoaderImpl.java

package com.github.snowdream.toybricks.annotation.impl;

import com.github.snowdream.toybricks.annotation.InterfaceLoader;
import java.lang.Class;
import java.lang.Override;
import java.util.HashMap;

/**
 *
 * Created by snowdream
 *
 * This file is automatically generated by apt(Annotation Processing Tool)
 * Do not modify this file -- YOUR CHANGES WILL BE ERASED!
 *
 * This file should *NOT* be checked into Version Control Systems,
 * as it contains information specific to your local configuration.
 */
final class InterfaceLoaderImpl implements InterfaceLoader {
  private static HashMap<Class, Object> sSingletonMap = new HashMap<Class, Object>();

  private static HashMap<Class, Class> sGlobalMap = new HashMap<Class, Class>();

  private static HashMap<Class, Class> sDefaultMap = new HashMap<Class, Class>();

  public InterfaceLoaderImpl() {
    addGlobalMap();
    addDefaultMap();
    addSingletonMap();
  }

  private void addGlobalMap() {
    sGlobalMap.put(com.github.snowdream.toybricks.app.IText.class,com.github.snowdream.toybricks.app.impl.NewTextGobalImpl.class);
  }

  private void addDefaultMap() {
    sDefaultMap.put(com.github.snowdream.toybricks.app.IText.class,com.github.snowdream.toybricks.app.impl.TextImpl.class);
  }

  private void addSingletonMap() {
    sSingletonMap.put(com.github.snowdream.toybricks.app.impl.NewTextGobalImpl.class,null);
  }

  @Override
  public <T> T getImplementation(Class<T> clazz) {
    T implementation = null;

    boolean isSingleton = false;

    Class implClass;

    implClass = sGlobalMap.get(clazz);

    if (implClass == null) {
      implClass = sDefaultMap.get(clazz);
    }

    if (implClass != null) {
      isSingleton = sSingletonMap.containsKey(implClass);

      if (isSingleton) {
        implementation = (T) sSingletonMap.get(implClass);
        if (implementation != null) {
          return implementation;
        }
      }

      try {
        implementation = (T) implClass.newInstance();
      } catch (InstantiationException e) {
        e.printStackTrace();
      } catch (IllegalAccessException e) {
        e.printStackTrace();
      }

      if (isSingleton && implementation != null) {
        sSingletonMap.put(implClass, implementation);
      }
    }

    return implementation;
  }
}

5.再次使用Javac工具編譯InterfaceLoaderImpl.java文件。
6.這個文件就類似字典索引,通過這個文件,就可以通過傳入接口,來查找對應的實現類。

總結

與APT工具相比,ToyBricks能夠將接口和實現之間的關係進行持久化,存儲在jar和aar中,並隨之發布到Maven倉庫,實現接口和實現的徹底分離。

如果您對ToyBricks有什麼問題或者建議,歡迎通過後麵的聯係方式聯係我。

參考資料:

  1. SnowdreamFramework/ToyBricks
  2. Annotation Processing Tool (apt)
  3. ANNOTATION PROCESSING 101
  4. Annotation-Processing-Tool詳解
  5. Java注解處理器
  6. 什麼是高內聚、低耦合?

聯係方式

sn0wdr1am

最後更新:2017-05-04 22:01:50

  上一篇:go 像逛淘寶一樣“辦政事”:阿裏雲在政務領域的實踐
  下一篇:go NLP技術的應用及思考