之前有提到在项目中需要实现在WebView中打开的H5页面可以打开其他应用的功能,并且已经实现。但是目前的项目是SDK,期望是让接入方可以简单的接入SDK,而这个功能必须对AndroidManifest进行修改,需要额外的操作,因此决定使用自定义Gradle插件来对该功能进行优化。
实现的思路是
- 1.从后端请求Android Deep Links的配置(host和path)。
- 2.在打包时自动对AndroidManifest进行修改,插入配置。
1. 从后端请求配置
自定义插件中进行网络请求需要添加依赖包:
plugins {
id 'groovy'
}
dependencies {
implementation(gradleApi())
//网络请求库
implementation("com.squareup.okhttp3:okhttp:4.9.2")
//用于解析后台返回的json数据,也可以用groovy自带的json解析,但比较复杂。
implementation("com.google.code.gson:gson:2.8.9")
}
然后就可以进行简单的网络请求:
void getAppLinksFromNet() {
def url = "config file download path"
OkHttpClient client = new OkHttpClient.Builder()
.build()
Request request = new Request.Builder()
.get()
.url(url)
.build()
Call call = client.newCall(request)
try {
Response response = call.execute()
if (response.isSuccessful()) {
ResponseBody body = response.body()
if (body != null) {
String bodyString = body.string()
try {
Config Config = new Gson().fromJson(bodyString, new TypeToken<SdkConfig>() {
}.getType())
} catch (JsonIOException e) {
e.printStackTrace()
}
}
}
} catch (IOException e) {
e.printStackTrace()
}
}
2. 在打包时自动对AndroidManifest进行修改,插入配置
2.1 过滤assembleTask
我们的目标是在打包时对AndroidManifest进行处理,sync时不做处理,所以我们要过滤出assembleTask,代码如下:
boolean isAssembleTask(Project project) {
def isAssembleTask = false
def requests = project.gradle.getStartParameter().getTaskRequests()
if (requests.size() != 0) {
def args = requests.get(0).getArgs()
for (argValue in args) {
//判断是打包Task
if (argValue.contains("assemble")) {
isAssembleTask = true
break
}
}
if (isAssembleTask) {
return true
}
}
return false
}
2.2 自定义处理Manifest的Task
过滤出assembleTask后,我们需要自定义Task来对AndroidManifest进行处理。这里顺便做了对Android 12的适配,即对包含inter-filter但缺少了android:exported的Activity、Receive、Service添加该属性,代码如下:
import groovy.xml.Namespace
import groovy.xml.XmlParser
import groovy.xml.XmlUtil
import org.gradle.api.DefaultTask
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.TaskAction
import org.xml.sax.SAXException
import javax.xml.parsers.ParserConfigurationException
class MyProcessManifestTask extends DefaultTask {
private def manifestCollection
private static ArrayList<Link> appLinks = null
private static def android = new Namespace("http://schemas.android.com/apk/res/android", "android")
private static PluginConfig pluginConfig
@TaskAction
void doTaskAction() {
if (pluginConfig != null) {
appLinks = pluginConfig.getApp_link()
if (appLinks != null) {
manifestCollection.each {
handlerVariantManifestFile(it)
}
}
}
}
/**
* 设置需要合并的Manifest文件
*/
void setData(FileCollection collection, PluginConfig gradlePlugin) {
manifestCollection = collection
pluginConfig = gradlePlugin
}
/**
* 处理单个变体的Manifest文件
*/
void handlerVariantManifestFile(File manifestFile) {
if (!manifestFile.exists()) {
System.out.println("manifestFile do not exists")
return
}
readManifestFromPackageManifest(manifestFile)
}
/**
* 是否含有<intent-filter>
*/
boolean hasIntentFilter(List children) {
boolean isIntent = false
children.find {
if ("intent-filter" == it.name()) {
isIntent = true
}
return true
}
return isIntent
}
/**
* 是否是SDK中的OpenActivity
*/
boolean isOpenActivity(Map attributes) {
boolean isFindOpenActivity = false
attributes.find {
isFindOpenActivity = "packageName.OpenActivity" == it.value
//当为true时可以提前结束循环
return isFindOpenActivity
}
return isFindOpenActivity
}
/**
* 读manifest内容
*/
void readManifestFromPackageManifest(File manifestFile) {
try {
XmlParser xmlParser = new XmlParser()
def node = xmlParser.parse(manifestFile)
//处理没有exported但是有intent-filter的四大组件
handleMissingExportedComponents(manifestFile, node)
//处理SDK的Manifest
node.attributes().find {
//判断manifest是自己的SDK的
if ("my sdk package name" == it.value) {
for (int i = 0; i < node.application.activity.size(); i++) {
def activityNode = node.application.activity.get(i)
def isOpenActivity = isOpenActivity(activityNode.attributes())
//只处理OpenActivity
if (isOpenActivity) {
if (appLinks != null) {
addDataToIntentFilterNode(activityNode)
}
writeManifestForPackageManifest(manifestFile, node)
break
}
}
return true
} else {
return false
}
}
} catch (ParserConfigurationException e) {
e.printStackTrace()
} catch (SAXException e) {
e.printStackTrace()
} catch (IOException e) {
e.printStackTrace()
}
}
/**
* 保存到原AndroidManifest文件中
*/
void writeManifestForPackageManifest(File manifestFile, Node node) {
String result = XmlUtil.serialize(node)
System.out.println(result)
manifestFile.write(result, "utf-8")
}
/**
* 向intent-filter标签添加data标签,内含 AppLinks 相关的参数
*/
void addDataToIntentFilterNode(Node activityNode) {
def children = activityNode.children()
def hasIntentFilter = hasIntentFilter(children)
if (hasIntentFilter) {
Node intentFilterNode = (Node) children.get(0)
def intentFilterChildren = intentFilterNode.children()
ArrayList hostAndPathDataIndexList = new ArrayList<Node>()
for (child in intentFilterChildren) {
for (entry in child.attributes().entrySet()) {
//记录原来已有的包含host和path的data标签的角标
if (android.get("host").matches(entry.key)) {
hostAndPathDataIndexList.add(child)
}
}
}
if (!hostAndPathDataIndexList.isEmpty()) {
//移除原来已有的data标签
hostAndPathDataIndexList.each {
intentFilterChildren.remove(it)
}
hostAndPathDataIndexList.clear()
}
appLinks.each {
Node linkNode = new Node(null, "data", new HashMap())
linkNode.attributes().remove(android.get("scheme"))
linkNode.attributes().put(android.get("host"), it.host)
linkNode.attributes().put(android.get("path"), it.path)
intentFilterNode.append(linkNode)
}
}
}
/**
* 处理没有exported但是有intent-filter的四大组件
*/
void handleMissingExportedComponents(File manifestFile, Node node) {
def isNeedHandle = false
node.application.activity.each {
def hasIntentFilter = hasIntentFilter(it.children())
def hasExport = it.attributes().containsKey(android.get("exported"))
if (hasIntentFilter && !hasExport) {
isNeedHandle = true
addExportedToComponents(it)
}
}
node.application.receiver.each {
def hasIntentFilter = hasIntentFilter(it.children())
def hasExport = it.attributes().containsKey(android.get("exported"))
if (hasIntentFilter && !hasExport) {
isNeedHandle = true
addExportedToComponents(it)
}
}
node.application.service.each {
def hasIntentFilter = hasIntentFilter(it.children())
def hasExport = it.attributes().containsKey(android.get("exported"))
if (hasIntentFilter && !hasExport) {
isNeedHandle = true
addExportedToComponents(it)
}
}
if (isNeedHandle) {
writeManifestForPackageManifest(manifestFile, node)
}
}
/**
* 添加Exproted到组件
*/
void addExportedToComponents(Node node) {
node.attributes().put(android.get("exported"), "false")
}
}
2.3 将自定义的Task添加到系统处理Manifest的Task之前
完成了MyProcessManifestTask之后,需要在系统处理AndroidMainfest之前先执行我们的Task,代码如下:
class MyPlugin implements Plugin<Project> {
List variantNames = new ArrayList()
@Override
void apply(Project project) {
def applicationId = ""
project.afterEvaluate {
def type = project.extensions.findByType(AppExtension.class)
type.getApplicationVariants().findAll {
if (applicationId != it.applicationId) {
applicationId = it.applicationId
}
variantNames.add(it.name)
}
if (isAssembleTask(project)) {
project.afterEvaluate {
variantNames.each {
//添加插入AppLinks data的任务 capitalize(首字母大写)
addProcessManifestTask(project, it.capitalize(), gradlePlugin)
}
}
}
}
}
/**
* 添加插入AppLinks data的任务
*/
private static void addProcessManifestTask(Project project, String name, PluginConfig gradlePlugin) {
//找到处理Manifest的Task
ProcessApplicationManifest processManifestTask = project.getTasks().getByName(String.format("process%sMainManifest", name))
MyProcessManifestTask myProcessManifestTask = project.getTasks().create("MyProcessManifest" + name, MyProcessManifestTask)
myProcessManifestTask.setData(processManifestTask.getManifests(), gradlePlugin)
processManifestTask.dependsOn(myProcessManifestTask)
}
}