**html
** 近期google play發佈了新的政策,其中一部分是限制權限使用,只容許知足條件的使用場景才能申請權限,小編所在的項目被檢測出使用了RECEIVE_SMS權限,可是從app下的Androidmanifest文件中並未發現有該權限的註冊,因此該權限是哪裏來的呢?java
首先使用android studio查看了打包出來的apk中的Androidmanifest文件,發現其中確實存在RECEIVE_SMS權限,也就是說打包到apk中的Androidmanifest文件並非app下的該文件,從android開發者官網中合併多個manifest文件的文檔來看,實際上打包到apk中的manifest文件是由多個menifest文件合併而來的,其合併順序以下: node
優先級由低到高分別是: 第三方庫中 < app模塊 < app模塊的源集 當合併發生衝突的時候,能夠使用合併規則標記來處理衝突,因此我這裏使用了tools:node="remove"來處理移除RECEIVE_SMS權限,從新打包查看結果發現,仍然存在該權限,也就是說該標記失效了嗎?再思考一下,是否有記錄合併過程的文件呢?答案是有的,以下: 合併規則結束生成的android manifest文件:app/build/intermediates/manifests/full/googleplay/debug/AndroidManifest.xml 合併操做記錄文件: app/build/outputs/logs/manifest-merger-googleplay-debug-report.txt 上述的googleplay是自定義的productFlavors,若是未定義就是app。 查看AndroidManifest.xml發現確實存在RECEIVE_SMS權限,可是查看manifest-merger-googleplay-debug-report.txt卻找不到該權限的合併記錄;也就是說,在正常的合併流程中,並無RECEIVE_SMS權限的寫入,會不會有人破壞了正常的合併流程呢?沒辦法,須要追蹤到具體是哪個第三方庫引入了該權限,仔細對比了下apk中的Androidmenifest文件和app下的該文件,發現每次都是在該文件末尾多出了RECEIVE_SMS和其餘一些東西,仔細一看發現是mobsdk相關的,因而剔除了該庫,再編譯發現仍是存在該權限。。仔細想下,有點不對,由於要破壞manifest文件的合併,那麼普通的第三方庫是不行的,至少須要第三方gradle插件,因而移除了「com.mob.sdk」插件,再打包發現確實沒有RECEIVE_SMS了到此已經找到了問題的製造者,接下來就是看下他是怎麼實現的,面向google編程,搜索com.mob.sdk source code 找到maven倉庫,能夠找到其實現核心以下:android
project.afterEvaluate {
def android = project.extensions.getByName("android")
if (globalVariants.autoConfig == null) {
globalVariants.autoConfig = true
}
if (globalVariants.autoConfig) {
if (android != null) {
configShareSDKXML(android)
def variants = null
boolean appModel = false
try {
variants = android.applicationVariants
appModel = true
} catch (Throwable t) {
try {
variants = android.libraryVariants
} catch (Throwable tt) {}
}
if (variants != null) {
variants.all { variant ->
variant.outputs.each { output ->
output.processManifest.doLast {
configManifest(output, appModel, variant)
}
}
}
}
}
}
}
複製代碼
private void configManifest(def output, boolean appModel, def variant) {
...
manifestFiles.add(new File(output.processManifest.manifestOutputDirectory, "AndroidManifest.xml"))
manifestFiles.each { manifestFile->
if (manifestFile != null && manifestFile.exists()) {
...
shouldAdd.each { per ->
String lastPermission = "<uses-permission ${ns} android:name=\"${per}\" />"
if (packageName != null && lastPermission.contains('${applicationId}')) {
lastPermission = lastPermission.replace('${applicationId}', packageName)
}
def permission = parser.parseText(lastPermission)
manifest.appendNode(permission)
}
...
def nsCustom = 'xmlns:android="http://schemas.android.com/apk/res/android"'
def level = 'android:protectionLevel="signature"'
shouldAddCoustom.each { per ->
String lastPermission = "<permission ${nsCustom} android:name=\"${per}\" ${level}/>"
if (packageName != null && lastPermission.contains('${applicationId}')) {
lastPermission = lastPermission.replace('${applicationId}', packageName)
}
def permission = parser.parseText(lastPermission)
manifest.appendNode(permission)
}
...
manifestFile.setText(XmlUtil.serialize(manifest), "utf-8")
}
複製代碼
能夠看出其hook了gradle的解析了配置以後注入了gradle任務(任務相關能夠參考官網),詳細的gradle的構建周期函數能夠參考這個文章。在processManifest任務執行以後執行了他本身的動做,也就是更改androidmanifest文件的內容編程
瞭解了其實現原理以後,開始整理其修復方案,主要須要解決的是,在合適的時間點去移除權限,也就是須要在其修改完Androidmanifest文件以後,和Androidmanifest文件被打包到apk中以前這段時間,這裏涉及到gradle打包中的各個函數調用順序,詳細的打包流程參考這裏,詳細的任務在這裏,我這裏選擇的切入點在processResources的任務執行以前,詳細代碼以下:api
project.afterEvaluate {
project.android.applicationVariants.all { variant ->
variant.outputs.each { output ->
output.processResources.doFirst { pm->
String manifestPath = output.processResources.manifestFile;
def manifestContent = file(manifestPath).getText()
manifestContent = manifestContent.replace('<uses-permission android:name="android.permission.RECEIVE_SMS"/>', '')
file(manifestPath).write(manifestContent)
}
}
}
}
複製代碼
主要的處理就是剔除其中的RECEIVE_SMS權限相關。固然,前提是項目中確實沒有使用該權限,因此移除不會致使相關問題。bash
解決該問題主要涉及到 Androidmanifest.xml的合併,gradle構建生命週期,android打包流程和相關的gradle知識,當前對gradle的瞭解不夠,致使閱讀和理解比較耗時,接下來須要多關注,此外還有一個問題沒解決,就是採用.processManifest.finalizedBy這種方式時,發現androidmanifest文件經歷了以下狀況: 沒有注入權限->注入權限->刪除權限->又注入了權限 不知道是在哪一步又被注入了權限,仍是其餘狀況?併發