我們通常可以在通知欄上看到“飛行模式”、“移動(dòng)數(shù)據(jù)”趟薄、“屏幕錄制”等開關(guān)按鈕,這些按鈕都屬于通知欄上的快捷開關(guān)典徊,點(diǎn)擊快捷開關(guān)可以輕易調(diào)用某種系統(tǒng)能力或打開某個(gè)應(yīng)用程序的特定頁(yè)面杭煎。那是否可以在通知欄上自定義一個(gè)快捷開關(guān)呢?答案是可以的卒落,具體是通過(guò)TileService的方案實(shí)現(xiàn)羡铲。
TileService繼承自Service,所以它也是Android的四大組件之一儡毕,不過(guò)它是一個(gè)特殊的組件也切,開發(fā)者不需要手動(dòng)開啟調(diào)用,系統(tǒng)可以自動(dòng)識(shí)別并完成調(diào)用腰湾,系統(tǒng)會(huì)通過(guò)綁定服務(wù)(bindService)的方式調(diào)用雷恃。
創(chuàng)建使用:
快捷開關(guān)是Android 7(target 24)的新能力,因此在使用該能力前必須先判斷版本大蟹逊弧(大于等于target 24)倒槐。
1、自定義一個(gè)TileService類附井。
class MyQSTileService: TileService() {
override fun onTileAdded() {
super.onTileAdded()
}
override fun onStartListening() {
super.onStartListening()
}
override fun onStopListening() {
super.onStopListening()
}
override fun onClick() {
super.onClick()
}
override fun onTileRemoved() {
super.onTileRemoved()
}
}
TileService是通過(guò)綁定服務(wù)(bindService)的方式被調(diào)用的讨越,因此两残,綁定服務(wù)生命周期包含的四種典型的回調(diào)方法(onCreate()、onBind()把跨、onUnbind()和 onDestroy())都會(huì)被調(diào)用磕昼。但是,TileService也包含了以下特殊的生命周期回調(diào)方法:
- onTileAdded():當(dāng)用戶從編輯欄添加快捷開關(guān)到通知欄的快速設(shè)置中會(huì)調(diào)用节猿。
- onTileRemoved():當(dāng)用戶從通知欄的快速設(shè)置移除快捷開關(guān)時(shí)調(diào)用。
- onClick():當(dāng)用戶點(diǎn)擊快捷開關(guān)時(shí)調(diào)用漫雕。
- onStartListening():當(dāng)用戶打開通知欄的快速設(shè)置時(shí)調(diào)用滨嘱。當(dāng)快捷開關(guān)并沒(méi)有從編輯欄拖到設(shè)置欄中不會(huì)調(diào)用。在TileAdded添加之后會(huì)調(diào)用一次浸间。
- onStopListening():當(dāng)用戶打開通知欄的快速設(shè)置時(shí)調(diào)用太雨。當(dāng)快捷開關(guān)并沒(méi)有從編輯欄拖到設(shè)置欄中不會(huì)調(diào)用。在TileRemoved移除之前會(huì)調(diào)用一次魁蒜。
2囊扳、在應(yīng)用程序的清單文件中聲明TileService
。
<service
android:name=".MyQSTileService"
android:label="@string/my_default_tile_label"
android:icon="@drawable/my_default_icon_label"
android:exported="true"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
- name:自定義的
TileService
的類名兜看。 - label:快捷開關(guān)在通知欄上顯示的名稱锥咸。
- icon:快捷開關(guān)在通知欄上顯示的圖標(biāo)。
- exported:該服務(wù)能否被外部應(yīng)用調(diào)用细移。該屬性必須為true搏予。如果為false,那么快捷開關(guān)的功能將失效弧轧,原因是exported="false"時(shí)雪侥,
TileService
將不支持外部應(yīng)用調(diào)起,手機(jī)系統(tǒng)自然不能再和該快捷開關(guān)交互精绎。必須配置速缨。 - permission:需要給service配置的權(quán)限,BIND_QUICK_SETTINGS_TILE即允許應(yīng)用程序綁定到第三方快速設(shè)置代乃。必須配置旬牲。
- intent-filter:意圖過(guò)濾器,只有匹配內(nèi)部的action搁吓,才能調(diào)起該service引谜。必須配置员咽。
監(jiān)聽模式
TileService的監(jiān)聽模式(或理解為啟動(dòng)模式)有兩種滑频,一種是主動(dòng)模式银伟,另一種是標(biāo)準(zhǔn)模式。
- 主動(dòng)模式
在主動(dòng)模式下,TileService被請(qǐng)求時(shí)該服務(wù)會(huì)被綁定,并且TileService的onStartListening也會(huì)被調(diào)用。該模式需要在AndroidManifeast清單文件中聲明:
<service ...>
<meta-data android:name="android.service.quicksettings.ACTIVE_TILE"
android:value="true" />
...
</service>
通過(guò)TileService.requestListeningState()這一靜態(tài)方法桂肌,就可以實(shí)現(xiàn)對(duì)TileService的請(qǐng)求佩耳,示例如下:
TileService.requestListeningState(
applicationContext, ComponentName(
BuildConfig.APPLICATION_ID,
MyQSTileService::class.java.name
)
)
主動(dòng)模式下值得注意的是:
- 用戶在通知欄快速設(shè)置的地方點(diǎn)擊快捷開關(guān)時(shí)蛮瞄,TileService會(huì)自動(dòng)完成綁定闲先、TileService的onStartListening會(huì)被調(diào)用蒙谓。
- TileService無(wú)論是通過(guò)點(diǎn)擊被綁定還是通過(guò)requestListeningState請(qǐng)求被綁定,TileService所在的進(jìn)程都會(huì)被調(diào)起训桶。
標(biāo)準(zhǔn)模式
在標(biāo)準(zhǔn)模式下累驮,TileService可見時(shí)(即用戶下拉通知欄看見快捷開關(guān))該服務(wù)會(huì)被綁定,并且TileService的onStartListening也會(huì)被調(diào)用舵揭。標(biāo)準(zhǔn)模式不需要在AndroidManifeast清單文件中進(jìn)行額外的聲明谤专,默認(rèn)就是標(biāo)準(zhǔn)模式。
標(biāo)準(zhǔn)模式下值得注意的是:
- 和主動(dòng)模式相同琉朽,TileService被綁定時(shí),TileService所在的進(jìn)程就會(huì)被調(diào)起稚铣。
- 而和主動(dòng)模式不同的是箱叁,標(biāo)準(zhǔn)模式綁定TileService是通過(guò)用戶下拉通知欄實(shí)現(xiàn)的,這意味著TileService所在的進(jìn)程會(huì)被多次調(diào)起惕医。因此為了避免主進(jìn)程被頻繁調(diào)起耕漱、避免DAU等數(shù)據(jù)統(tǒng)計(jì)受到影響,我們還需要為TileService指定一個(gè)特定的子進(jìn)程抬伺,在Androidmanifest清單文件中設(shè)置:
<service
......
android:process="自定義子進(jìn)程的名稱">
......
</service>
更新快捷開關(guān)
如果需要對(duì)快捷開關(guān)的數(shù)據(jù)進(jìn)行更新螟够,可以通過(guò)getQsTile()獲取快捷開關(guān)的對(duì)象,然后通過(guò)setIcon(更新icon)峡钓、setLable(更新名稱)妓笙、setState(更新狀態(tài),包括STATE_ACTIVE——表示開啟或啟用狀態(tài)能岩、STATE_INACTIVE——表示關(guān)閉或暫停狀態(tài)寞宫、STATE_UNAVAILABLE:表示暫時(shí)不可用狀態(tài),在此狀態(tài)下拉鹃,用戶無(wú)法與您的磁貼交互)等方法設(shè)置快捷開關(guān)新的數(shù)據(jù)辈赋,最后調(diào)用updateTile()方法實(shí)現(xiàn)。
override fun onStartListening() {
super.onStartListening()
if (qsTile.state === Tile.STATE_ACTIVE) {
qsTile.label = "inactive"
qsTile.icon = Icon.createWithResource(context, R.drawable.inactive)
qsTile.state = Tile.STATE_INACTIVE
} else {
qsTile.label = "active"
qsTile.icon = Icon.createWithResource(context, R.drawable.active)
qsTile.state = Tile.STATE_ACTIVE
}
qsTile.updateTile()
}
操作快捷開關(guān)
- 如果想要實(shí)現(xiàn)點(diǎn)擊快捷開關(guān)時(shí)膏燕、關(guān)閉通知欄并跳轉(zhuǎn)到某個(gè)頁(yè)面钥屈,可以調(diào)用以下方法:
startActivityAndCollapse(Intent intent)
- 如果想要在點(diǎn)擊快捷開關(guān)時(shí)彈出對(duì)話框進(jìn)行交互,可以調(diào)用以下方法:
override fun onClick() {
super.onClick()
if(!isLocked()) {
showDialog()
}
}
因?yàn)榭旖蓍_關(guān)有可能在用戶鎖屏?xí)r出現(xiàn)坝辫,所以必須加上isLocked()的判斷篷就。只有非鎖屏的情況下,對(duì)話框才會(huì)出現(xiàn)近忙。
- 如果快捷開關(guān)含有敏感信息腻脏,需要使用isSecure()進(jìn)行設(shè)備安全性判斷鸦泳,當(dāng)設(shè)備安全時(shí),才能執(zhí)行快捷開關(guān)相關(guān)的邏輯(如點(diǎn)擊的邏輯)永品。當(dāng)設(shè)備不安全時(shí)(手機(jī)處于鎖屏狀態(tài)時(shí))做鹰,可調(diào)用unlockAndRun(Runnable runnable),提示用戶解鎖屏幕并執(zhí)行自定義的runnable操作鼎姐。