annotationProcessor 练手
创建一个 java 库 (deeplink),并新建一个注解类
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface DeepLink {
String value();
}
创建一个 java 库 (deeplink-compiler),添加下列依赖库
//google 提供的自动注册 processor 的库
implementation 'com.google.auto.service:auto-service:1.0-rc2'
//强大的 java 文件生成库
implementation 'com.squareup:javapoet:1.8.0'
//引用注解库
implementation project(':deeplink')
新建一个类,继承自 AbstractProcessor
,并给类加上 @AutoService(Processor.class)
注解,实现或者重写以下方法
@AutoService(Processor.class)
public class DeepLinkProcessor extends AbstractProcessor {
private static final String DEEPLINK_PACKAGE = "com.wenkiwu.deeplink";
private static final String DEEPLINK_LOADER = "DeepLinkLoader";
private static final String DEEPLINK_DISPATCH = "DeepLinkDispatch";
private static final ClassName ANDROID_INTENT = ClassName.get("android.content", "Intent");
private static final ClassName ANDROID_ACTIVITY = ClassName.get("android.app", "Activity");
private static final ClassName ANDROID_URI = ClassName.get("android.net", "Uri");
private static final ClassName CLASS_DEEPLINKENTRY = ClassName.get(DeepLinkEntry.class);
private static final ClassName CLASS_DEEPLINKLOADER = ClassName.get(DEEPLINK_PACKAGE, DEEPLINK_LOADER);
private static final ClassName CLASS_LIST = ClassName.get(Collections.class);
private static final ClassName CLASS_ARRAY = ClassName.get(Arrays.class);
private static final Class<DeepLink> DEEP_LINK_CLASS = DeepLink.class;
private Elements elementUtils;
private Filer filer;
private Messager messager;
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> stringSet = new HashSet<>();
stringSet.addAll(super.getSupportedAnnotationTypes());
stringSet.add(DEEP_LINK_CLASS.getName());
return stringSet;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementUtils = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(DeepLink.class);
List<Pair<String, TypeElement>> entryList = new ArrayList<>();
for (Element element : elements) {
TypeElement typeElement = (TypeElement) element;
DeepLink deepLink = element.getAnnotation(DeepLink.class);
String path = deepLink.value();
Pair<String, TypeElement> pair = new Pair<>(path, typeElement);
entryList.add(pair);
}
try {
generateLoaderClass(entryList);
generateDispatchClass();
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
private void generateDispatchClass() throws IOException {
MethodSpec constructor = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addStatement("this.loader = new DeepLinkLoader()")
.build();
MethodSpec dispatch = MethodSpec.methodBuilder("dispatchFrom")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(ANDROID_ACTIVITY, "activity")
.beginControlFlow("if(activity == null)")
.addStatement("throw new NullPointerException($S)", "activity is null")
.endControlFlow()
.addStatement("$T sourceIntent = activity.getIntent()", ANDROID_INTENT)
.addStatement("$T uri = sourceIntent.getData()", ANDROID_URI)
.beginControlFlow("if(uri != null)")
.addStatement("$T uriString = uri.toString()", String.class)
.addStatement("$T deepLinkEntry = loader.parseUri(uriString)", CLASS_DEEPLINKENTRY)
.beginControlFlow("if (deepLinkEntry != null)")
.addStatement("Intent newIntent = new Intent(activity, deepLinkEntry.getActivityClass())")
.beginControlFlow("if(sourceIntent.getExtras() != null)")
.addStatement("newIntent.putExtras(sourceIntent.getExtras())")
.endControlFlow()
.addStatement("newIntent.setData(sourceIntent.getData())")
.addComment("add some custom data")
.addStatement("activity.startActivity(newIntent)")
.endControlFlow()
.endControlFlow()
.build();
FieldSpec loader = FieldSpec.builder(CLASS_DEEPLINKLOADER, "loader", Modifier.PRIVATE)
.build();
TypeSpec deepLinkDispatch = TypeSpec.classBuilder(DEEPLINK_DISPATCH)
.addModifiers(Modifier.PUBLIC)
.addField(loader)
.addMethod(constructor)
.addMethod(dispatch)
.build();
JavaFile.builder(DEEPLINK_PACKAGE, deepLinkDispatch).build().writeTo(filer);
}
private void generateLoaderClass(List<Pair<String, TypeElement>> entryList) throws IOException {
CodeBlock.Builder initializer = CodeBlock.builder()
.add("$T.unmodifiableList($T.asList(\n", CLASS_LIST, CLASS_ARRAY)
.indent();
int totalEntrys = entryList.size();
for (int i = 0; i < totalEntrys; i++) {
Pair<String, TypeElement> pair = entryList.get(i);
ClassName activity = ClassName.get(pair.second);
String path = pair.first;
initializer.add("new DeepLinkEntry($S, $T.class)$L\n",
path, activity, (i < totalEntrys - 1) ? "," : "");
}
FieldSpec registry = FieldSpec.builder(
ParameterizedTypeName.get(List.class, DeepLinkEntry.class),
"REGISTRY", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.initializer(initializer.unindent().add("))").build())
.build();
MethodSpec parseMethod = MethodSpec.methodBuilder("parseUri")
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, "uri")
.returns(DeepLinkEntry.class)
.beginControlFlow("for (DeepLinkEntry entry : REGISTRY)")
.beginControlFlow("if (uri.startsWith(entry.getPath()))")
.addStatement("return entry")
.endControlFlow()
.endControlFlow()
.addStatement("return null")
.build();
TypeSpec deepLinkLoader = TypeSpec.classBuilder(DEEPLINK_LOADER)
.addModifiers(Modifier.PUBLIC)
.addField(registry)
.addMethod(parseMethod)
.build();
JavaFile.builder(DEEPLINK_PACKAGE, deepLinkLoader)
.build()
.writeTo(filer);
}
}
在android项目中引入 deeplink
库和 deeplink-compiler
注解处理器
implementation project(':deeplink')
annotationProcessor project(':deeplink-compiler')
在各个需要跳转的 Activity
上添加 DeepLink
注解,将不同的 uri
导向不同的 Activity
@DeepLink("acdef://wenkiwu.com/a/")
public class ActivityA extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_a);
}
}
@DeepLink("acdef://wenkiwu.com/b/")
public class ActivityB extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_b);
}
}
@DeepLink("acdef://wenkiwu.com/c/")
public class ActivityC extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_c);
}
}
做完以上工作后,选择 ReBuild Project
,即可在项目的 app/build/generated/source/apt
目录下看到生成的 DeepLinkDispatch
和 DeepLinkLoader
代码
public class DeepLinkLoader {
private static final List<DeepLinkEntry> REGISTRY = Collections.unmodifiableList(Arrays.asList(
new DeepLinkEntry("acdef://wenkiwu.com/a/", ActivityA.class),
new DeepLinkEntry("acdef://wenkiwu.com/b/", ActivityB.class),
new DeepLinkEntry("acdef://wenkiwu.com/c/", ActivityC.class)
));
public DeepLinkEntry parseUri(String uri) {
for (DeepLinkEntry entry : REGISTRY) {
if (uri.startsWith(entry.getPath())) {
return entry;
}
}
return null;
}
}
public class DeepLinkDispatch {
private DeepLinkLoader loader;
public DeepLinkDispatch() {
this.loader = new DeepLinkLoader();
}
public void dispatchFrom(Activity activity) {
if(activity == null) {
throw new NullPointerException("activity is null");
}
Intent sourceIntent = activity.getIntent();
Uri uri = sourceIntent.getData();
if(uri != null) {
String uriString = uri.toString();
DeepLinkEntry deepLinkEntry = loader.parseUri(uriString);
if (deepLinkEntry != null) {
Intent newIntent = new Intent(activity, deepLinkEntry.getActivityClass());
if(sourceIntent.getExtras() != null) {
newIntent.putExtras(sourceIntent.getExtras());
}
newIntent.setData(sourceIntent.getData());
// add some custom data
activity.startActivity(newIntent);
}
}
}
}
最后写一个 Activity
来处理 deeplink
的分发
public class DispatchActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DeepLinkDispatch deepLinkDispatch = new DeepLinkDispatch();
deepLinkDispatch.dispatchFrom(this);
finish();
}
}
注意配置 AndroidMainfest.xml
,使用 DispatchActivity
来接收 scheme
的拉起
<activity android:name=".DispatchActivity"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="acdef"/>
</intent-filter>
</activity>
运行项目,在 adb shell
中测试一下
am start -W -a android.intent.action.VIEW -d "acdef://wenkiwu.com/b/test?k=123"
发现会直接跳转到 ActivityB