It was mentioned before that the function of opening H5 page in WebView to open other applications should be realized in the project, and it has been realized. However, the current project is SDK, and it is expected that the access party can access SDK simply. However, this function must be modified on AndroidManifest, which requires additional operations. Therefore, we decided to optimize this function by using customized Gradle plug-in.
The idea is this
- 1. Request the configuration (host and path) of Android Deep Links from the backend.
- 2. Automatically modify the AndroidManifest and insert the configuration during packaging.
1. Request configuration from the back-end
To make network requests in a custom plug-in, you need to add dependency packages:
Dependencies {implementation(gradleApi()) Implementation (" com. Squareup. Okhttp3: okhttp: 4.9.2 ") / / used to parse the background the returned json data, also can use groovy's own json parsing, but is more complex. Implementation (" com. Google. Code. Gson: gson: 2.8.9 ")}Copy the code
You can then make a simple network request:
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() } }Copy the code
2. Automatically modify the AndroidManifest and insert the configuration during packaging
2.1 the filter assembleTask
Our goal is to process AndroidManifest at package time and not sync time, so we filter out assembleTask as follows:
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) { isAssembleTask = true break } } if (isAssembleTask) { return true } } return false }Copy the code
2.2 Customizing Manifest Tasks
After filtering out assembleTask, we need to customize tasks to handle the AndroidManifest. Android :exported Activities, Receive, and Services that contain inter-filter but do not contain Android: Exported Activities
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)}}}} / * * * * / void Settings need to merge the Manifest file setData(FileCollection collection, ManifestCollection = collection PluginConfig = gradlePlugin} /** * Process the Manifest file of a single variant */ void handlerVariantManifestFile(File manifestFile) { if (! manifestFile.exists()) { System.out.println("manifestFile do not exists") return } ReadManifestFromPackageManifest (manifestFile)} / * * * whether contains < intent - filter > * / Boolean hasIntentFilter children (List) { boolean isIntent = false children.find { if ("intent-filter" == it.name()) { isIntent = true } return true } return IsIntent} /** * Whether the SDK OpenActivity */ Boolean isOpenActivity(Map Attributes) {Boolean isFindOpenActivity = false Attributes. Find {isFindOpenActivity = "packageName.OpenActivity" == it IsFindOpenActivity} return isFindOpenActivity} / * * * read the manifest content * / void readManifestFromPackageManifest (the File manifestFile) { try { XmlParser xmlParser = new XmlParser() def node = xmlParser.parse(manifestFile) / / processing not exported but have intent - filter handleMissingExportedComponents four components (manifestFile, Attributes ().find {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 ()) // Only 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() } } /** * Saved to the original AndroidManifest File * / void writeManifestForPackageManifest (File manifestFile, Node node) { String result = XmlUtil.serialize(node) System.out.println(result) manifestFile.write(result, "Intent-filter ")} /** * Add data to intent-filter * / void addDataToIntentFilterNode containing AppLinks related parameters (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 ()) {// Record the existing if for data tags containing host and path (android.get("host").matches(entry.key)) { hostAndPathDataIndexList.add(child) } } } if (! HostAndPathDataIndexList. IsEmpty ()) {/ / remove the existing original data label 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"), Path) intentFilterNode.appEnd (linkNode)}}} /** * to handle four exported components with 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, /** * Add Exproted to component */ void addExportedToComponents(node node) { node.attributes().put(android.get("exported"), "false") } }Copy the code
2.3 Adding a customized Task to a Task that processes the Manifest
After completing the MyProcessManifestTask, we need to execute our Task before the system processes AndroidMainfest, as follows:
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)) { AfterEvaluate {variantnames.each {// Add the task that inserts AppLinks Data capitalize (project, it.capitalize(), GradlePlugin)}}}}} /** * Add the task to insert AppLinks data */ Private static void addProcessManifestTask(Project Project, String name, PluginConfig gradlePlugin) {/ / find processing Task of the Manifest 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) } }Copy the code