前言
記錄一下今天同事給我分享的比較有意思的Bug,在已有的已經(jīng)在AndroidManifest.xml
中注冊(cè)的廣播在部分手機(jī)上無(wú)法通過(guò)Action
隱式啟動(dòng)。上網(wǎng)搜搜資料自己寫(xiě)了個(gè)Demo,Mark一下!!
Android官網(wǎng):Oreo后臺(tái)執(zhí)行限制
我們這里主要看對(duì)于廣播的影響,摘抄一段官網(wǎng)上的介紹:
廣播限制
如果應(yīng)用注冊(cè)為接收廣播,則在每次發(fā)送廣播時(shí),應(yīng)用的接收器都會(huì)消耗資源。 如果多個(gè)應(yīng)用注冊(cè)為接收基于系統(tǒng)事件的廣播,則會(huì)引發(fā)問(wèn)題:觸發(fā)廣播的系統(tǒng)事件會(huì)導(dǎo)致所有應(yīng)用快速地連續(xù)消耗資源,從而降低用戶體驗(yàn)。 為了緩解這一問(wèn)題,Android 7.0
(API 級(jí)別 24)對(duì)廣播施加了一些限制,如后臺(tái)優(yōu)化中所述。 Android 8.0
(API 級(jí)別 26)讓這些限制更為嚴(yán)格。
- 適配
Android 8.0
或更高版本的應(yīng)用無(wú)法繼續(xù)在其清單中為隱式廣播注冊(cè)廣播接收器。 隱式廣播是一種不專門(mén)針對(duì)該應(yīng)用的廣播。 例如,ACTION_PACKAGE_REPLACED
就是一種隱式廣播,因?yàn)樵搹V播將被發(fā)送給所有已注冊(cè)偵聽(tīng)器,讓后者知道設(shè)備上的某些軟件包已被替換。 不過(guò),ACTION_MY_PACKAGE_REPLACED
不是隱式廣播,因?yàn)椴还芤褳樵搹V播注冊(cè)偵聽(tīng)器的其他應(yīng)用有多少,它都會(huì)只被發(fā)送給軟件包已被替換的應(yīng)用。 - 應(yīng)用可以繼續(xù)在它們的清單中注冊(cè)顯式廣播。
- 應(yīng)用可以在運(yùn)行時(shí)使用
Context.registerReceiver()
為任意廣播(不管是隱式還是顯式)注冊(cè)接收器。 - 需要簽名權(quán)限的廣播不受此限制所限,因?yàn)檫@些廣播只會(huì)發(fā)送到使用相同證書(shū)簽名的應(yīng)用,而不是發(fā)送到設(shè)備上的所有應(yīng)用。
在許多情況下,之前注冊(cè)隱式廣播的應(yīng)用使用 JobScheduler
作業(yè)可以獲得類似的功能。 例如,一款社交照片應(yīng)用可能需要不時(shí)地執(zhí)行數(shù)據(jù)清理,并且傾向于在設(shè)備連接到充電器時(shí)執(zhí)行此操作。 之前,應(yīng)用已經(jīng)在清單中為 ACTION_POWER_CONNECTED
注冊(cè)了一個(gè)接收器;當(dāng)應(yīng)用接收到該廣播時(shí),它會(huì)檢查清理是否必要。 為了遷移到 Android 8.0
或更高版本,應(yīng)用將該接收器從其清單中移除。 應(yīng)用將清理作業(yè)安排在設(shè)備處于空閑狀態(tài)和充電時(shí)運(yùn)行。
請(qǐng)注意:很多隱式廣播當(dāng)前已不受此限制所限。 應(yīng)用可以繼續(xù)在其清單中為這些廣播注冊(cè)接收器,不管應(yīng)用適配哪個(gè) API 級(jí)別。 有關(guān)已豁免廣播的列表,請(qǐng)參閱隱式廣播例外。
更具上面的描述,我們可以得到一下幾點(diǎn):
- 適配
Android 8.0
或更高版本的應(yīng)用無(wú)法繼續(xù)在其清單中為隱式廣播注冊(cè)廣播接收器; - 應(yīng)用可以繼續(xù)在它們的清單中注冊(cè)顯式廣播;
- 推薦運(yùn)行時(shí)使用
Context.registerReceiver()
注冊(cè)廣播; - 需要簽名權(quán)限的廣播不受此約束;
自定義權(quán)限
<permission android:description="string resource"
android:icon="drawable resource"
android:label="string resource"
android:name="string"
android:permissionGroup="string"
android:protectionLevel=["normal" | "dangerous" |"signature" | ...] />
android:protectionLevel
說(shuō)明權(quán)限中隱含的潛在風(fēng)險(xiǎn),并指示系統(tǒng)在確定是否將權(quán)限授予請(qǐng)求授權(quán)的應(yīng)用時(shí)應(yīng)遵循的流程。
每個(gè)保護(hù)級(jí)別都包含基本權(quán)限類型以及零個(gè)或多個(gè)標(biāo)記。例如,dangerous
保護(hù)級(jí)別沒(méi)有標(biāo)記。相反,保護(hù)級(jí)別 signature|privileged
是signature
基本權(quán)限類型和privileged
標(biāo)記的組合。
下表列出了所有基本權(quán)限類型。如需查看標(biāo)記列表,請(qǐng)參閱 protectionLevel。
值 | 含義 |
---|---|
normal |
默認(rèn)值。具有較低風(fēng)險(xiǎn)的權(quán)限,此類權(quán)限允許請(qǐng)求授權(quán)的應(yīng)用訪問(wèn)隔離的應(yīng)用級(jí)功能,對(duì)其他應(yīng)用、系統(tǒng)或用戶的風(fēng)險(xiǎn)非常小。系統(tǒng)會(huì)自動(dòng)向在安裝時(shí)請(qǐng)求授權(quán)的應(yīng)用授予此類權(quán)限,無(wú)需征得用戶的明確許可(但用戶始終可以選擇在安裝之前查看這些權(quán)限)。 |
dangerous |
具有較高風(fēng)險(xiǎn)的權(quán)限,此類權(quán)限允許請(qǐng)求授權(quán)的應(yīng)用訪問(wèn)用戶私人數(shù)據(jù)或獲取可對(duì)用戶造成不利影響的設(shè)備控制權(quán)。由于此類權(quán)限會(huì)帶來(lái)潛在風(fēng)險(xiǎn),因此系統(tǒng)可能不會(huì)自動(dòng)向請(qǐng)求授權(quán)的應(yīng)用授予此類權(quán)限。例如,應(yīng)用請(qǐng)求的任何危險(xiǎn)權(quán)限都可能會(huì)向用戶顯示并且獲得確認(rèn)才會(huì)繼續(xù)執(zhí)行操作,或者系統(tǒng)會(huì)采取一些其他方法來(lái)避免用戶自動(dòng)允許使用此類功能。 |
signature |
只有在請(qǐng)求授權(quán)的應(yīng)用使用與聲明權(quán)限的應(yīng)用相同的證書(shū)進(jìn)行簽名時(shí)系統(tǒng)才會(huì)授予的權(quán)限。如果證書(shū)匹配,則系統(tǒng)會(huì)在不通知用戶或征得用戶明確許可的情況下自動(dòng)授予權(quán)限。 |
signatureOrSystem |
signature\privileged 的舊同義詞。在API級(jí)別23中已棄用。系統(tǒng)僅向位于Android 系統(tǒng)映像的專用文件夾中的應(yīng)用或使用與聲明權(quán)限的應(yīng)用相同的證書(shū)進(jìn)行簽名的應(yīng)用授予的權(quán)限。不要使用此選項(xiàng),因?yàn)?signature 保護(hù)級(jí)別應(yīng)足以滿足大多數(shù)需求,無(wú)論應(yīng)用安裝在何處,該保護(hù)級(jí)別都能正常發(fā)揮作用。signatureOrSystem 權(quán)限適用于以下特殊情況:多個(gè)供應(yīng)商將應(yīng)用內(nèi)置到一個(gè)系統(tǒng)映像中,并且需要明確共享特定功能,因?yàn)檫@些功能是一起構(gòu)建的。 |
自定義簽名權(quán)限并使用
<permission
android:protectionLevel="signature"
android:name="com.xx.xx.receiver" />
<uses-permission android:name="com.xx.xx.receiver"/>
測(cè)試
我寫(xiě)個(gè)Demo測(cè)試一下,測(cè)試機(jī)MI 8
,系統(tǒng)為Android 10
。
聲明兩個(gè)Broadcast
,一個(gè)帶權(quán)限,一個(gè)不帶權(quán)限。
public open class CustomReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if(intent != null) {
var param = intent.getStringExtra("message")
var result:String?
result = String.format("get the receiver for %s", param)
XToast.show(result)
}
}
}
public class CustomReceiver2 : CustomReceiver() {}
<!--帶簽名權(quán)限的廣播-->
<receiver android:name=".main.receiver.CustomReceiver"
android:permission="com.xx.xx.receiver">
<intent-filter>
<action android:name="com.xx.xx.message"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<!--普通的廣播-->
<receiver android:name=".main.receiver.CustomReceiver2">
<intent-filter>
<action android:name="com.xx.xx.message2"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
發(fā)送權(quán)限隱式廣播-不加權(quán)限
var intent = Intent("com.xx.xx.message" + num++)
intent.putExtra("message","custom test" + num++)
sendBroadcast(intent)
結(jié)果:吐司無(wú)法展現(xiàn)。
15:59:23.420#1635#1758#W#BroadcastQueue #Background execution not allowed: receiving Intent { act=com.xx.xx.message flg=0x10 (has extras) } to com.xx.xx.demo/.main.receiver.CustomReceiver
發(fā)送權(quán)限隱式廣播-加權(quán)限
var intent = Intent("com.xx.xx.message")
intent.putExtra("message","custom test")
sendOrderedBroadcast(intent, "com.xx.xx.receiver")
結(jié)果:吐司展現(xiàn)。
發(fā)送權(quán)限隱式廣播-加權(quán)限-錯(cuò)誤
var intent = Intent("com.xx.xx.message")
intent.putExtra("message","custom test")
sendOrderedBroadcast(intent, "com.xx.xx.receiver.error")
結(jié)果:吐司無(wú)法展現(xiàn)。
15:44:54.514#1696#1774#W#BroadcastQueue #Permission Denial: receiving Intent { act=com.xx.xx.message flg=0x1000010 (has extras) } to com.xx.xx.ztemplate/.main.receiver.CustomReceiver requires com.xx.xx.receiver.error due to sender com.xx.xx.demo (uid 10547)
發(fā)送隱式廣播
var intent = Intent("com.xx.xx.message2")
intent.putExtra("message","custom test")
sendBroadcast(intent)
結(jié)果:吐司無(wú)法展現(xiàn)。
15:59:23.420#1635#1758#W#BroadcastQueue #Background execution not allowed: receiving Intent { act=com.xx.xx.message flg=0x10 (has extras) } to com.xx.xx.demo/.main.receiver.CustomReceiver
發(fā)送隱式廣播-添加package
var intent = Intent("com.xx.xx.message2")
intent.`package` = "com.xx.xx.demo"
intent.putExtra("message","custom test")
sendBroadcast(intent)
結(jié)果:吐司展現(xiàn)。
發(fā)送隱式廣播-setClass(等同于添加component)
var intent = Intent("com.xx.xx.message2")
intent.setClass(this, CustomReceiver2::class.java)
intent.putExtra("message","custom test")
sendBroadcast(intent)
結(jié)果:吐司展現(xiàn)。
其實(shí)第5
和第6
個(gè)case已經(jīng)不算隱式廣播了,他們都為Intent
設(shè)置了package
指明了當(dāng)前的環(huán)境。
錯(cuò)誤分析
BroadcastQueue #Permission Denial:
這里提示權(quán)限有問(wèn)題,需要添加或修改權(quán)限。
BroadcastQueue #Background execution not allowed:
//android.content.Intent.java
public static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 0x01000000;
public static final int FLAG_RECEIVER_EXCLUDE_BACKGROUND = 0x00800000;
這個(gè)代碼塊有一個(gè)||
操作,我們不想讓其進(jìn)入到改邏輯需要使前面判斷為false
,后面判斷為false
。
判斷r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND
中攜帶了FLAG_RECEIVER_EXCLUDE_BACKGROUND
標(biāo)志位。我們一般都不會(huì)攜帶,所以前面邏輯為false
。
后面邏輯有三個(gè)&&
操作,那么只需要讓其中一個(gè)為false
即可。
r.intent.getComponent() == null
, 會(huì)進(jìn)入此邏輯(設(shè)置component)。r.intent.getPackage() == null
,會(huì)進(jìn)入此邏輯(設(shè)置component)。r.intent.getFlags() & Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0
不能帶有FLAG_RECEIVER_INCLUDE_BACKGROUND
這個(gè)標(biāo)志位,否則會(huì)進(jìn)入此邏輯(設(shè)置FLAG_RECEIVER_INCLUDE_BACKGROUND)。如果啟動(dòng)廣播的時(shí)候攜帶了權(quán)限,那么如果不是簽名權(quán)限會(huì)進(jìn)入此邏輯(設(shè)置簽名權(quán)限)。
其實(shí)1
和2
我們上面已經(jīng)測(cè)試過(guò)了(第5
個(gè)和第6
個(gè)case);
3
設(shè)置Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
,但改常量是hide
無(wú)法通過(guò)Intent
訪問(wèn)。我們只能寫(xiě)0x01000000
,但不建議這么做;
4
其實(shí)就是文檔中說(shuō)明的簽名權(quán)限不受Android 8.0
后臺(tái)執(zhí)行優(yōu)化的控制;
文章到這里就全部講述完啦,若有其他需要交流的可以留言哦!!