Android Apt之Activity Route

weixin_34019929發表於2018-01-29

前言

  • 什麼是Apt
    APT從原理上講是一個編譯期的註解處理工具(Annotation Processing Tool)。一些主流的三方庫(ButterKnife,Glide)都用到了這個技術來生成程式碼。

  • Apt有什麼好處

    1. 自動生成模板程式碼,提高了開發效率
    2. 編譯期對註解的處理,相對於執行期對註解的處理,效能上要好的多。
  • Gradle指令碼中的apt和annotationProcessor
    這兩個從廣義上說都是編譯期的註解處理工具。只不過android-apt(其實是一個gradle外掛,apt是外掛命令)是早期的github的一個開源專案,annotationProcessor是gradle build tools 2.2之後自帶的編譯期註解工具(官方支援的,可替代開源的gradle外掛android-apt)。android-apt的作者已經發表宣告表示Android Studio外掛已經支援annotationProcessor,並且會警告和阻止使用android-apt。總的來說,看你的gradle build tools的版本,低版本用android-apt(需要引入外掛),高版本用annotationProcessor(無需引入外掛)

程式碼設計

  • 需求分析
    這裡將route模組分成三部分(一個android library,兩個java library)
  1. router-annotation(java library)
    這裡java工程裡面只放註解的宣告類。這裡只實現了兩個註解RouterActivityRouterField
  2. router-compiler (java library)
    這個工程是編譯期依賴的工程,作用是編譯期掃描程式碼,根據RouterActivityRouterField這兩個註解的使用,生成相關程式碼。這裡需要講下如何掃描程式碼並且生成程式碼的。這部分功能的實現主要依賴兩個庫:Google的auto-service(掃描程式碼),Squareup的javapoet(生成程式碼)
  3. router (android library)
    主要邏輯程式碼。在這個模組中會定義一些功能類和介面。router-compiler模組可以根據這些介面和功能類generate邏輯程式碼。需要注意的是router-compiler是不需要依賴router的,router-compiler是根據包名+類名的方式獲取類的。

程式碼實現

router-annotation

RouterActivity是一個註解,用此註解修飾的Activity根據指定的路由地址,會自動新增到路由表中,當系統掛載了路由表之後,就可根據指定的路由地址來訪問特定的Activity了。程式碼如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RouterActivity {
    String[] value();
}

這裡Activity可用多個路由地址修改。

RouterField是一個用於表示Activity跳轉時引數傳遞的註解,用這個註解修飾的成員變數,表示為接收Intent引數的變數。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RouterField {
    String[] value();
}

router-compiler

這個模組只包含一個類RouterProcessor,這個類的大致結構如下:

//此處用AutoService註解,就可實現編譯期自動掃描程式碼
@AutoService(Processor.class)
public class RouterProcessor extends AbstractProcessor{
    private Elements elementUtils;
    private String targetModuleName = "";

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        //支援的註解型別
        return Collections.singleton(RouterActivity.class.getCanonicalName());
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
      //處理程式碼掃描結果的關鍵函式
      ...
       return true;
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        //在掃描程式碼之前可從build.gradle中讀取一些配置項
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        //表示支援的Jdk版本
        return SourceVersion.RELEASE_7;
    }

}

下面分別講解一下函式的實現:

  1. init函式
    我們的專案大多都是多module的形式,這時候我們就需要為每個module建立一個Activity路由登錄檔,然後在Application初始化的時候將所有的路由登錄檔掛載上,達到Activity路由跳轉的目的。這裡我們在init函式中,配置每個模組路由表的字首名稱。
 @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elementUtils = processingEnvironment.getElementUtils();
        Map<String, String> map = processingEnvironment.getOptions();
        Set<String> keys = map.keySet();
        for (String key: keys) {
            if ("targetModuleName".equals(key)) {
                this.targetModuleName = map.get(key);
            }
            System.out.println(key + " + " + map.get(key));

並在module的build.gradle檔案下配置如下程式碼:

apt {
    arguments {
        targetModuleName 'moduleName'
    }
}
  1. process函式
    這個函式的大致流程如下:找到所有被RouterActivity修飾的Activity;實現router模組中的RouterInitializer介面,將每個Activity的路由地址加入路由表中;同時為每個Activity建立一個XXXActivityHelper(用於更友好的Activity調整),並將每個XXXAcitivyHelper放入RouterHelper中,提供get方法獲取。process函式的具體實現,可詳見專案原始碼(都是一些程式碼生成的語句,沒有多少邏輯)。

router

  • RouterInitializer介面,用於每個module登錄檔的實現
  • ActivityHelper,封裝了一些引數解析邏輯,更方便的Activity跳轉
  • SafeBundle, 對Activity的引數進行了封裝
  • Router, 路由核心類,支援url跳轉,解析url,並實現跳轉。
  • 'RouterCenterActivity', 可被外部瀏覽器喚起的中轉Activity(外面根據url scheme喚醒RouterCenterActivityRouterCenterActivity分發路由地址)

程式碼使用

  • 初始化Router
public class DemoApp extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        Router.init("demo");  //自定義scheme協議
    }
}
  • Activity跳轉
@RouterActivity({"main"})
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn_second).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                RouterHelper.getSecondActivityHelper().start(MainActivity.this);
            }
        });
    }
}

@RouterActivity({"second"})
public class SecondActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
    }
}

build目錄生成的程式碼如下:


1895495-445eff7790daff8f.png
build目錄生成程式碼

詳細程式碼可檢視:Github專案
現階段程式碼還不完善,後期會新增更多功能。

相關文章