Android 自定义Gradle插件(二):修改AndroidManifest文件

2,998 阅读3分钟

之前有提到在项目中需要实现在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)
    }
}