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.正题
项目的文件目录:
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 extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set extends Element> 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的名称。
之后在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删除,