利用annotationProcessor实现基于注解的DeepLink分发

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 目录下看到生成的 DeepLinkDispatchDeepLinkLoader 代码

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

猜你喜欢

转载自www.cnblogs.com/wenhui92/p/9264245.html