頁面路由框架蹬挺,無論在android還是在iOS的開發(fā)中都是很常見的模塊與模塊之間的解耦工具瓣俯,特別是對中大型App而言闰非,基本上都會有自己的路由框架剩盒。
Processor的原理
在講原理之前呆瞻,先看看整個項目的結(jié)構(gòu)台夺。
- saf-router:是整個路由框架的核心,可以單獨使用痴脾。
- saf-router-annotation:是路由框架的注解模塊颤介,可以基于注解來聲明router跳轉(zhuǎn)的頁面。
- saf-router-compiler:由于我們的注解是編譯時注解,而非運行時注解滚朵。在程序編譯時會生成一個RouterManager的類冤灾,此類會管理App的router mapping信息。
然后辕近,我們來看看神奇的RouterProcessor
package com.safframework.router
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.TypeSpec
import java.util.*
import javax.annotation.processing.*
import javax.lang.model.SourceVersion
import javax.lang.model.element.Element
import javax.lang.model.element.Modifier
import javax.lang.model.element.TypeElement
import javax.lang.model.util.Elements
/**
* Created by Tony Shen on 2017/1/10.
*/
//@AutoService(Processor::class)
class RouterProcessor: AbstractProcessor() {
var mFiler: Filer?=null //文件相關(guān)的輔助類
var mElementUtils: Elements?=null //元素相關(guān)的輔助類
var mMessager: Messager?=null //日志相關(guān)的輔助類
@Synchronized override fun init(processingEnv: ProcessingEnvironment) {
super.init(processingEnv)
mFiler = processingEnv.filer
mElementUtils = processingEnv.elementUtils
mMessager = processingEnv.messager
}
/**
* @return 指定使用的 Java 版本韵吨。通常返回 SourceVersion.latestSupported()。
*/
override fun getSupportedSourceVersion(): SourceVersion {
return SourceVersion.latestSupported()
}
/**
* @return 指定哪些注解應(yīng)該被注解處理器注冊
*/
override fun getSupportedAnnotationTypes(): Set<String> {
val types = LinkedHashSet<String>()
types.add(RouterRule::class.java.canonicalName)
return types
}
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
val elements = roundEnv.getElementsAnnotatedWith(RouterRule::class.java)
try {
val type = getRouterTableInitializer(elements)
if (type != null) {
JavaFile.builder("com.safframework.router", type).build().writeTo(mFiler)
}
} catch (e: FilerException) {
e.printStackTrace()
} catch (e: Exception) {
Utils.error(mMessager, e.message)
}
return true
}
@Throws(ClassNotFoundException::class)
private fun getRouterTableInitializer(elements: Set<Element>?): TypeSpec? {
if (elements == null || elements.size == 0) {
return null
}
val routerInitBuilder = MethodSpec.methodBuilder("init")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addParameter(TypeUtils.CONTEXT, "context")
routerInitBuilder.addStatement("\$T.getInstance().setContext(context)", TypeUtils.ROUTER)
routerInitBuilder.addStatement("\$T options = null", TypeUtils.ROUTER_OPTIONS)
elements.map {
it as TypeElement
}.filter(fun(it: TypeElement): Boolean {
return Utils.isValidClass(mMessager, it, "@RouterRule")
}).forEach {
val routerRule = it.getAnnotation(RouterRule::class.java)
val routerUrls = routerRule.url
val enterAnim = routerRule.enterAnim
val exitAnim = routerRule.exitAnim
if (routerUrls != null) {
for (routerUrl in routerUrls!!) {
if (enterAnim > 0 && exitAnim > 0) {
routerInitBuilder.addStatement("options = new \$T()", TypeUtils.ROUTER_OPTIONS)
routerInitBuilder.addStatement("options.enterAnim = " + enterAnim)
routerInitBuilder.addStatement("options.exitAnim = " + exitAnim)
routerInitBuilder.addStatement("\$T.getInstance().map(\$S, \$T.class,options)", TypeUtils.ROUTER, routerUrl, ClassName.get(it))
} else {
routerInitBuilder.addStatement("\$T.getInstance().map(\$S, \$T.class)", TypeUtils.ROUTER, routerUrl, ClassName.get(it))
}
}
}
}
val routerInitMethod = routerInitBuilder.build()
return TypeSpec.classBuilder("RouterManager")
.addModifiers(Modifier.PUBLIC)
.addMethod(routerInitMethod)
.build()
}
}
kotlin用起來是很爽移宅,但是還是踩過很多的坑归粉。
- 坑1:
原先用java來寫時,用谷歌的Auto庫很順暢地生成RouterManager類漏峰。換了kotlin以后糠悼,好像不行了,于是我用了土方法浅乔。創(chuàng)建了META-INF/services/javax.annotation.processing.Processor倔喂,并加上
com.safframework.router.RouterProcessor
這樣才能生成RouterManager。
- 坑2:
原先getRouterTableInitializer()是長這樣的:
private TypeSpec getRouterTableInitializer(Set<? extends Element> elements) throws ClassNotFoundException {
if(elements == null || elements.size() == 0){
return null;
}
MethodSpec.Builder routerInitBuilder = MethodSpec.methodBuilder("init")
.addModifiers(Modifier.PUBLIC,Modifier.STATIC)
.addParameter(TypeUtils.CONTEXT,"context");
routerInitBuilder.addStatement("$T.getInstance().setContext(context)",TypeUtils.ROUTER);
routerInitBuilder.addStatement("$T options = null",TypeUtils.ROUTER_OPTIONS);
for(Element element : elements){
TypeElement classElement = (TypeElement) element;
// 檢測是否是支持的注解類型靖苇,如果不是里面會報錯
if (!Utils.isValidClass(mMessager,classElement,"@RouterRule")) {
continue;
}
RouterRule routerRule = element.getAnnotation(RouterRule.class);
String [] routerUrls = routerRule.url();
int enterAnim = routerRule.enterAnim();
int exitAnim = routerRule.exitAnim();
if(routerUrls != null){
for(String routerUrl : routerUrls){
if (enterAnim>0 && exitAnim>0) {
routerInitBuilder.addStatement("options = new $T()",TypeUtils.ROUTER_OPTIONS);
routerInitBuilder.addStatement("options.enterAnim = "+enterAnim);
routerInitBuilder.addStatement("options.exitAnim = "+exitAnim);
routerInitBuilder.addStatement("$T.getInstance().map($S, $T.class,options)",TypeUtils.ROUTER, routerUrl, ClassName.get((TypeElement) element));
} else {
routerInitBuilder.addStatement("$T.getInstance().map($S, $T.class)",TypeUtils.ROUTER, routerUrl, ClassName.get((TypeElement) element));
}
}
}
}
MethodSpec routerInitMethod = routerInitBuilder.build();
return TypeSpec.classBuilder("RouterManager")
.addModifiers(Modifier.PUBLIC)
.addMethod(routerInitMethod)
.build();
}
我用kotlin對for循環(huán)進行了優(yōu)化席噩,起初還可以用map、filter贤壁,但是遇到兩層for循環(huán)好像找不到更好的辦法班挖。本想用高階函數(shù),但是不想折騰了芯砸。如果您有更好的辦法,一定要告訴我给梅。
- 坑3:
Kotlin的類沒有靜態(tài)變量假丧。不過有同伴對象(Companion Object)的概念。如果在某個類中聲明一個同伴對象, 那么只需要使用類名作為限定符就可以調(diào)用同伴對象的成員了, 語法與Java中調(diào)用類的靜態(tài)方法动羽、靜態(tài)變量一樣包帚。
舉個栗子:
class TypeUtils {
companion object {
val CONTEXT = ClassName.get("android.content", "Context");
val ROUTER = ClassName.get("com.safframework.router", "Router")
val ROUTER_OPTIONS = ClassName.get("com.safframework.router.RouterParameter", "RouterOptions")
}
}
既然踩了很多坑,那還是放上github地址吧:
https://github.com/fengzhizi715/SAF-Kotlin-Router
下載安裝
在根目錄下的build.gradle中添加
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
在app 模塊目錄下的build.gradle中添加
apply plugin: 'com.neenbedankt.android-apt'
...
dependencies {
compile 'com.safframework.router:saf-router:1.0.0'
apt 'com.safframework.router:saf-router-compiler:1.0.0'
...
}
特性
它提供了類似于rails的router功能运吓,可以輕易地實現(xiàn)app的應(yīng)用內(nèi)跳轉(zhuǎn),包括Activity之間渴邦、Fragment之間實現(xiàn)相互跳轉(zhuǎn),并傳遞參數(shù)拘哨。
這個框架的saf-router-compiler模塊是用kotlin
編寫的谋梭。
使用方法
Activity跳轉(zhuǎn)
它支持Annotation方式和非Annotation的方式來進行Activity頁面跳轉(zhuǎn)。使用Activity跳轉(zhuǎn)時倦青,必須在App的Application中做好router的映射瓮床。
我們會做這樣的映射,表示從某個Activity跳轉(zhuǎn)到另一個Activity需要傳遞user、password這2個參數(shù)
Router.getInstance().setContext(getApplicationContext()); // 這一步是必須的隘庄,用于初始化Router
Router.getInstance().map("user/:user/password/:password", DetailActivity.class);
有時候踢步,activity跳轉(zhuǎn)還會有動畫效果,那么我們可以這么做
RouterOptions options = new RouterOptions();
options.enterAnim = R.anim.slide_right_in;
options.exitAnim = R.anim.slide_left_out;
Router.getInstance().map("user/:user/password/:password", DetailActivity.class, options);
Annotation方式
在任意要跳轉(zhuǎn)的目標Activity上丑掺,添加@RouterRule,它是編譯時的注解获印。
@RouterRule(url={"second/:second"})
public class SecondActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent i = getIntent();
if (i!=null) {
String second = i.getStringExtra("second");
Log.i("SecondActivity","second="+second);
}
}
}
而且,使用@RouterRule也支持跳轉(zhuǎn)的動畫效果街州。
如果要跳轉(zhuǎn)到SecondActivity兼丰,在App的任意地方只需:
Router.getInstance().open("second/1234"); // 1234表示傳遞的參數(shù),是String類型菇肃。
用Annotation方式來進行頁面跳轉(zhuǎn)時地粪,Application無需做router的映射。因為琐谤,saf-router-compiler模塊已經(jīng)在編譯時生成了一個類RouterManager蟆技。它長得形如:
package com.safframework.router;
import android.content.Context;
import com.safframework.activity.SecondActivity;
import com.safframework.router.RouterParameter.RouterOptions;
public class RouterManager {
public static void init(Context context) {
Router.getInstance().setContext(context);
RouterOptions options = null;
Router.getInstance().map("second/:second", SecondActivity.class);
}
}
Application只需做如下調(diào)用,就可在任何地方使用Router了斗忌。
RouterManager.init(this);// 這一步是必須的质礼,用于初始化Router
非Annotation方式
在Application中定義好router映射之后,activity之間跳轉(zhuǎn)只需在activity中寫下如下的代碼织阳,即可跳轉(zhuǎn)到相應(yīng)的Activity眶蕉,并傳遞參數(shù)
Router.getInstance().open("user/fengzhizi715/password/715");
如果在跳轉(zhuǎn)前需要先做判斷,看看是否滿足跳轉(zhuǎn)的條件,doCheck()返回false表示不跳轉(zhuǎn)唧躲,true表示進行跳轉(zhuǎn)到下一個activity
Router.getInstance().open("user/fengzhizi715/password/715",new RouterChecker(){
public boolean doCheck() {
return true;
}
);
Fragment跳轉(zhuǎn)
Fragment之間的跳轉(zhuǎn)也無須在Application中定義跳轉(zhuǎn)映射造挽。直接在某個Fragment寫下如下的代碼
Router.getInstance().openFragment(new FragmentOptions(getFragmentManager(),new Fragment2()), R.id.content_frame);
當然在Fragment之間跳轉(zhuǎn)可以傳遞參數(shù)
Router.getInstance().openFragment("user/fengzhizi715/password/715",new FragmentOptions(getFragmentManager(),new Fragment2()), R.id.content_frame);
其他跳轉(zhuǎn)
單獨跳轉(zhuǎn)到某個網(wǎng)頁,調(diào)用系統(tǒng)電話弄痹,調(diào)用手機上的地圖app打開地圖等無須在Application中定義跳轉(zhuǎn)映射饭入。
Router.getInstance().openURI("http://www.g.cn");
Router.getInstance().openURI("tel://18662430000");
Router.getInstance().openURI("geo:0,0?q=31,121");
總結(jié)
最后,使用這個框架是不需要先有的程序去配置Kotlin的環(huán)境的肛真。
未來谐丢,會考慮把這個項目的其余模塊也都用Kotlin來編寫,以及新功能的開發(fā)蚓让。