一次Android許可權刪除經歷

十二月發表於2019-04-15

**

1.事發經過

** 近期google play釋出了新的政策,其中一部分是限制許可權使用,只允許滿足條件的使用場景才能申請許可權,小編所在的專案被檢測出使用了RECEIVE_SMS許可權,但是從app下的Androidmanifest檔案中並未發現有該許可權的註冊,所以該許可權是哪裡來的呢?

2.初步定位

首先使用android studio檢視了打包出來的apk中的Androidmanifest檔案,發現其中確實存在RECEIVE_SMS許可權,也就是說打包到apk中的Androidmanifest檔案並不是app下的該檔案,從android開發者官網中合併多個manifest檔案的文件來看,實際上打包到apk中的manifest檔案是由多個menifest檔案合併而來的,其合併順序如下:

manifest檔案合併規則
優先順序由低到高分別是: 第三方庫中 < 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了

3.原理解析

到此已經找到了問題的製造者,接下來就是看下他是怎麼實現的,面向google程式設計,搜尋com.mob.sdk source code 找到maven倉庫,可以找到其實現核心如下:

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檔案的內容

4.修復方案

瞭解了其實現原理之後,開始整理其修復方案,主要需要解決的是,在合適的時間點去移除許可權,也就是需要在其修改完Androidmanifest檔案之後,和Androidmanifest檔案被打包到apk中之前這段時間,這裡涉及到gradle打包中的各個函式呼叫順序,詳細的打包流程參考這裡,詳細的任務在這裡,我這裡選擇的切入點在processResources的任務執行之前,詳細程式碼如下:

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許可權相關。當然,前提是專案中確實沒有使用該許可權,所以移除不會導致相關問題。

5.覆盤

解決該問題主要涉及到 Androidmanifest.xml的合併,gradle構建生命週期,android打包流程和相關的gradle知識,當前對gradle的瞭解不夠,導致閱讀和理解比較耗時,接下來需要多關注,此外還有一個問題沒解決,就是採用.processManifest.finalizedBy這種方式時,發現androidmanifest檔案經歷瞭如下情況: 沒有注入許可權->注入許可權->刪除許可權->又注入了許可權 不知道是在哪一步又被注入了許可權,還是其他情況?

相關文章