Search code examples
androidandroid-studioandroid-gradle-pluginandroid-manifestandroid-studio-2.0

Editing AndroidManifest.xml in Gradle task processManifest.doLast has no effect when running app from Android Studio


I use the following Gradle script to make some modifications to the AndroidManifest.xml at compile time. In this example I want to inject a <meta-data> element. The code is based on this answer.

android {
    // ...
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            output.processManifest.doLast {
                def manifestOutFile = output.processManifest.manifestOutputFile
                def newFileContents = manifestOutFile.getText('UTF-8').replace("</application>", "<meta-data ... /></application>")
                manifestOutFile.write(newFileContents, 'UTF-8')
            }
        }
    }
}

This works as expected when I do a Gradle sync in Android Studio or make a clean build from command line: The meta-data is accessible from within the app.

But when I run ▶ the application from Android Studio the modified manifest seems to be ignored, since the inserted meta-data is not part of the compiled manifest in the APK, and the app itself cannot find it at runtime either, the meta-data is simply not there.

In all cases the merged intermediate AndroidManifest.xml (in /build/intermediates/manifests/) does contain the changes, but for some reason it looks like it gets ignored if I run the app.

To make it even more obvious, I tried to insert some invalid XML: In this case, the Gradle sync and the clean build failed as expected because of a syntax error in the manifest. But I was still able to run the app from Android Studio, thus the modification effectively gets ignored..

The easiest way to reproduce this is to clean the project first (in Android Studio), which causes the manifest to be reprocessed (in case of the syntax error I get a failure as expected), and then run the app, which works even with an invalid manifest.

Note that the task in doLast gets executed everytime: A println() in the task is printed and the intermediate manifest contains the changes.

It's as if the manifest gets compiled into the APK before my task is executed.

Where is the issue here?

I'm using Android Studio 2.0 with the Android Gradle Plugin 2.0.0.


Solution

  • I figured out that it's related to the Instant Run feature introduced in Android Studio 2.0. If I turn it off, everything works as expected. But since I want to use Instant Run, I digged a little further.

    The thing is, with Instant Run enabled the intermediate AndroidManifest.xml file will be at another location, namely /build/intermediates/bundles/myflavor/instant-run/. That means I was effectively editing the wrong file. That other manifest file is accessible with the property instantRunManifestOutputFile, which can be used instead of manifestOutputFile.

    To make it work in all use-cases I check both temporary manifest files whether they exist and modify them if so:

    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            output.processManifest.doLast {
                [output.processManifest.manifestOutputFile,
                 output.processManifest.instantRunManifestOutputFile
                ].forEach({ File manifestOutFile ->
                    if (manifestOutFile.exists()) {
                        def newFileContents = manifestOutFile.getText('UTF-8').replace("</application>", "<meta-data ... /></application>")
                        manifestOutFile.write(newFileContents, 'UTF-8')
                    }
                })
            }
        }
    }
    

    There is literally no documentation of instantRunManifestOutputFile. The only Google search result I got was the Android Gradle Plugin source code. But then I also found a third potential manifest file property aaptFriendlyManifestOutputFile, which I don't know what it's about either...