Android Gradle 实战之自动生成DeepLink配置信息

1.引言

android的deeplink 在项目中运用十分广泛,之前的一个项目大量使用deeplink。每完成一个新的功能,都得在AndroidManifest.xml写上deeplink的配置,同时还得在java文件中 写上deeplink的url静态变量。那个时候我就在想,能不能通过注解方式,代替手动抒写deeplink的配置?经过一番学习,终于有了实现的思路。

  • 目标:实现deeplink host path 等信息 自动注入到AndroidManifest.xml
    • 自定义注解,通过apt技术,得到每个activity的包名,host,scheme,path等信息
    • 得到上述信息之后,生成deepLink.xml 用于保存,配置deepLink相关的信息
    • 通过gradle去修改AndroidManifest.xml文件。自动生成 等相关标签

用到的技术:apt,注解,gradle自定义插件以及插件的发布,XmlParser 操作xml,

2.正题

项目的文件目录:


1_image-20211121153429726.png

2.1 deeplink-annotation

首先new 一个 java 版的module,自定义一个DeeplinkAnnotation注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface DeeplinkAnnotation {
    String scheme();
    String host();
    String pathPrefix();
}

再自定义一个javaBean 用来保存scheme,host,pathPrefix三者信息:

DeepLinkBean.java

public class DeepLinkBean {

    private String host;
    private String scheme;
    private String pathPrefix;
}

之后在app的 build.gradle 中引入当前的注解:implementation project(path: ':deeplink-annotation')

2.2 deeplink-compiler2

再new 一个 java的 deeplink-compiler module 。主要对注解进行预处理操作。本身也是一个java的 module, 创建完毕引入相关的依赖:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    //自动生成 META-INF
    api 'com.google.auto.service:auto-service:1.0-rc6'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'    //动态生成Java 代码
    api 'com.squareup:javapoet:1.9.0'
    implementation project(':deeplink-annotation')
}

之后编译注解处理器。

注意:最好使用java 自定义注解处理器,博主用kotlin 以及build:gradle:7.0.3会报一些错,想来是最新的build:gradle有一些bug,这里建议最好用java 去弄

@AutoService(Processor.class)
@SupportedAnnotationTypes("com.heytap.tv.deeplink_annotation.DeeplinkAnnotation")
public class DeeplinkAnnotationProcess extends AbstractProcessor {

    private Messager messager;
    private Elements elements;
    private HashMap maps = new HashMap<>();

    /**
     * 注释:可以得到ProcessingEnviroment,
     * ProcessingEnviroment提供很多有用的工具类Elements, Types 和 Filer
     * @param processingEnv
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        messager = processingEnv.getMessager();
        elements = processingEnv.getElementUtils();

    }


    /**
     * 注释:指定这个注解处理器是注册给哪个注解的
     * 时间:2021/2/18 0018 15:39
     * @return
     */
    @Override
    public Set getSupportedAnnotationTypes() {
        HashSet supportTypes = new LinkedHashSet<>();
        supportTypes.add(DeeplinkAnnotation.class.getCanonicalName());
        return supportTypes;
    }

    @Override
    public boolean process(Set annotations, RoundEnvironment roundEnv) {
        Set elements = roundEnv.getElementsAnnotatedWith(DeeplinkAnnotation.class);
        for (Element element : elements) {
            TypeMirror mTypeMirror = element.asType()  //获取当前Activity的全类名

            DeeplinkAnnotation bindAnnotation = element.getAnnotation(DeeplinkAnnotation.class);
            String host = bindAnnotation.host();
            String pathPrefix = bindAnnotation.pathPrefix();
            String scheme = bindAnnotation.scheme();
            DeepLinkBean deepLinkBean = new DeepLinkBean(host, scheme, pathPrefix);
            maps.put(mTypeMirror.toString(), deepLinkBean);
        }
  
      //SaxParser操作xml,生成一个xml
        
        String xmlString = SaxParser.saxCreateXML2(maps);
        messager.printMessage(Diagnostic.Kind.NOTE, xmlString);

        //获取app module下  build.gradle的位置,将xml保存到  app的build.gradle
        try {
            final FileObject fo = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", "test");
            String temFilePath = fo.toUri().getPath();
            String rootPath = temFilePath.split("/build")[0];
            rootPath = rootPath.substring(1, rootPath.length());
            SaxParser.saveXml(rootPath, xmlString, "deepLink.xml");//将xml,保存到 app build.gradle下面
        } catch (Exception e) {
            e.printStackTrace();
            messager.printMessage(Diagnostic.Kind.NOTE, "error  :  " + e.getMessage());
        }

        return false;
    }
}

之后通过Rebbuild Project 重新构建下项目。生成的xml如下所示:

deepLink.xml



    
    

有了这些数据,接下来,我们得将这些数据写入到AndroidManifest.xml

2.3 deeplink-gradle

创建一个java的 deeplink-gradle module 。自定义一个名为DeepLinkPlugin的插件,这个插件完成以下的事情:

  • 在target.afterEvaluate中 找到 android的拓展,

    • 找到各个变体(variant)的 processDebugManifest任务,为这个任务增加doLast操作(hook操作)
    • 修改AndroidManifest.xml 找到activity 向里面插入 deepLink的
    • 发布插件

DeepLinkPlugin是整个deeplink-gradle的核心,主要是解析deepLink.xml得到对应的data数据,然后修改AndroidManifest.xml信息。

class DeepLinkPlugin implements Plugin {

    @Override
    void apply(Project target) {
        target.afterEvaluate {
            //获取 android 拓展 
            def mAndroid = target.getExtensions().getByName("android")
                
            //mAndroid.applicationVariants 得到是一个集合
            mAndroid.applicationVariants.all { variant ->   
               
                //variant:ApplicationVariant 代表每一种构建版本,如debug,release ,根据ApplicationVariant 我们可以得知 /签名信息
                def variantName = variant.name.capitalize()   
                def processManifestTask = target.tasks.getByName("process${variantName}Manifest")
                
                //找到一个名如processDebugManifest的task,通过给他添加last闭包完成hook操作
                
                processManifestTask.doLast {
                
                    //最终AndroidManifest.xml生成的地方
                    String manifestPath = target.getBuildDir().getAbsolutePath() + "/intermediates/merged_manifests/${variant.name}/AndroidManifest.xml"
                        
                    // 通过XmlParser deepLink.xml 并解析activity对应的data信息
                    HashMap deepLinkMaps = new HashMap();
                    String deepLinkXml = target.projectDir.getAbsolutePath() + "/deepLink.xml"
                    File file = new File(deepLinkXml);
                    if (!file.exists()) return
                    XmlParser mXmlParser = new XmlParser()
                    groovy.util.Node rootNode = mXmlParser.parseText(file.getText())
                    List list = rootNode.children()
                    for (i in 0..信息
                
                    File manifestFile = new File(manifestPath);
                    String manifest = manifestFile.getText()    
                    groovy.util.Node applicationNode = new XmlParser().parseText(manifest)
                    groovy.util.Node applicationNode2 = applicationNode.get("application").get(0);
                    NodeList activityNodeList = applicationNode2.get("activity")
                    List hasExit = new ArrayList();

                    for (i in 0.. entrys = iterator.next();
                            String activityPath = entrys.value;
                            if (deepLinkMaps.containsKey(activityPath)) {
                                NodeList intentFilterList = mNode.get("intent-filter");
                                if (intentFilterList.size() > 0) {
                                    groovy.util.Node mIntentFilter = intentFilterList.get(0)
                                    DeepLinkBean mDeepLinkBean = deepLinkMaps.get(activityPath);
                                    HashMap maps = new HashMap<>();
                                    maps.put("android:host", mDeepLinkBean.getHost())
                                    maps.put("android:scheme", mDeepLinkBean.getScheme())
                                    maps.put("android:path", mDeepLinkBean.getPathPrefix())
                                    mIntentFilter.appendNode("data", maps)
                                    //假如当前没有activity view 则需要添加
//                                   

                                    NodeList mNodeList=mIntentFilter.get("action")
                                    if (mNodeList.size()==0){
                                        HashMapactionMap=new HashMap<>();
                                        actionMap.put("android:name","android.intent.action.VIEW")
                                        mIntentFilter.appendNode("action",actionMap)
                                    }
                                    NodeList categoryNodeList=mIntentFilter.get("category")
                                    if (categoryNodeList.size()<2){
                                        HashMapcategoryMap=new HashMap<>();
                                        categoryMap.put("android:name","android.intent.category.DEFAULT")
                                        mIntentFilter.appendNode("category",categoryMap)
                                        HashMapcategoryMap2=new HashMap<>();
                                        categoryMap2.put("android:name","android.intent.category.BROWSABLE")
                                        mIntentFilter.appendNode("category",categoryMap2)
                                    }

                                    def stringConfig = XmlUtil.serialize(applicationNode)
                                    manifestFile.write(stringConfig)
                                    hasExit.add(activityPath)
                                } else {
                                    applicationNode2.remove(mNode)
                                }
                            }
                        }
                    }

                    for (m in deepLinkMaps) {
                        if (!hasExit.contains(m.key)) {
                            HashMap maps = new HashMap<>();
                            maps.put("android:name", m.key);
                            groovy.util.Node mActivityNode = applicationNode2.appendNode("activity", maps)
                            groovy.util.Node filterNode = mActivityNode.appendNode("intent-filter")

                            NodeList mNodeList=filterNode.get("action")
                            if (mNodeList.size()==0){
                                HashMapactionMap=new HashMap<>();
                                actionMap.put("android:name","android.intent.action.VIEW")
                                filterNode.appendNode("action",actionMap)
                            }
                            NodeList categoryNodeList=filterNode.get("category")
                            if (categoryNodeList.size()<2){
                                HashMapcategoryMap=new HashMap<>();
                                categoryMap.put("android:name","android.intent.category.DEFAULT")
                                filterNode.appendNode("category",categoryMap)
                                HashMapcategoryMap2=new HashMap<>();
                                categoryMap2.put("android:name","android.intent.category.BROWSABLE")
                                filterNode.appendNode("category",categoryMap2)
                            }

                            HashMap dataMaps = new HashMap<>();
                            dataMaps.put("android:host", m.value.getHost())
                            dataMaps.put("android:scheme", m.value.getScheme())
                            dataMaps.put("android:path", m.value.getPathPrefix())
                            filterNode.appendNode("data", dataMaps)
                            def stringConfig = XmlUtil.serialize(applicationNode)
                            manifestFile.write(stringConfig)
                        }
                    }
                    boolean bl = file.delete()
                    println "delete success  :" + bl
                }
            }
        }
    }
}

以上代码主要是使用java写的,实现难度不是很复杂。在这里我主要想说说,

ApplicationVariant

ApplicationVariant 中文是应用变体,什么叫变体,初学者懵逼。通过下面打印名称:

   mAndroid.applicationVariants.all { variant ->   
                //variant:ApplicationVariant 代表每一种构建版本,如debug,release ,根据ApplicationVariant 我们可以得知 /签名信息
                def variantName = variant.name.capitalize()   
                pritfln  variantName;
  }

发现:variantName为debug, release 。假如在build.gradle中自定义了,产品如:productA,productB,productC。那么也会打印上述的名称。

通过查看api,可以看到,这个变体。能知道applicationId,签名信息,输出的outputs等重要信息。实际上build.gradle中经常使用ApplicationVariant ,来改变最终生成的apk的名称。

1_image-20211121163002968.png

之后在deeplink-gradle的gradle 中,加入发布到本地仓库的代码:

uploadArchives {
    def group='com.heytap.demo.deeplink' //组
    def version='1.0.0' //版本
    def artifactId='deeplinkPlugin' //唯一标示
    repositories {
        mavenDeployer {
            pom.groupId = group
            pom.artifactId = artifactId
            pom.version = version
            //指定本地maven的路径,在项目根目录下
            repository(url: uri('../repos'))
        }
    }
}

这样我们的demo 就算完成。最后在app module中引用一番


dependencies {
    //添加
    annotationProcessor project(path: ':deeplink-compiler2')
    implementation project(path: ':deeplink-annotation')
}

在新创建的Activity上面使用:

@DeeplinkAnnotation(host = "demo", scheme = "test", pathPrefix = "/mainActivity")

点击Rebuild project 就能生成deepLink.xml。 安装apk的时候会将此deeplink.xml删除,

你可能感兴趣的:(Android Gradle 实战之自动生成DeepLink配置信息)