Android 7.1 新特性:快捷方式 Shortcuts 詳解
一、Shortcuts 介紹
Android 7.1 允許 App 自定義 Shortcuts,類似 iOS 的 3D touch。通過在桌面長按 App 彈出 Shortcut 列表,點(diǎn)擊某個(gè) Shortcut 快速進(jìn)入某項(xiàng)操作,同時(shí) Shortcut 可以拖動(dòng)到桌面進(jìn)行固定,如下圖:
1. Shortcuts 作用及分類
Shortcuts 為 App 常用操作提供了快速訪問的方式,如上面日歷的新建提醒。
這個(gè)功能目前只能在 Android 7.1 系統(tǒng)桌面進(jìn)行使用,這個(gè)依然保留著“應(yīng)用抽屜”古老設(shè)計(jì)的產(chǎn)品國內(nèi)應(yīng)該沒多少用戶。三方桌面可以通過 API 接入這個(gè)功能。
目前支持 Shortcut 的應(yīng)用主要還是 Google 的 App,看到有即刻的朋友說他們在 7.1 系統(tǒng)發(fā)布時(shí)快速支持了這個(gè)功能并上線,速度很贊。
類似 BroadcastReceiver 可通過靜態(tài)和動(dòng)態(tài)方式注冊,Shortcuts 也可以通過靜態(tài)和動(dòng)態(tài)方式添加。
2. 靜態(tài) Shortcuts(Static Shortcuts)
靜態(tài) ShortcutsStatic Shortcuts通過在 Manifest 中聲明添加。缺點(diǎn)是不可以修改,只能通過應(yīng)用升級來添加新的靜態(tài) Shortcuts。添加主要分為兩步:
2.1 AndroidManifest.xml 的 Main Launcher 對應(yīng)的 Activity 內(nèi)添加 meta-data meta-data name
為android.app.shortcuts
,如下:
<application
……>
<activity android:name=".main.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts"/>
</activity>
</application>
必須在 Main Launcher 對應(yīng)的 Activity 內(nèi)設(shè)置,其中android:resource指向定義了 shortcuts 的資源文件。
2.2 資源文件中定義具體的 shortcuts
res 目錄下新建 xml 文件夾,并新建 shortcuts.xml 文件,內(nèi)容如下:
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:enabled="true"
android:icon="@drawable/search"
android:shortcutId="search"
android:shortcutDisabledMessage="@string/disabled"
android:shortcutLongLabel="@string/menu_label"
android:shortcutShortLabel="@string/launcher_label">
<intent
android:action="android.intent.action.VIEW"
android:targetClass="cn.trinea.android.demo.SearchActivity"
android:targetPackage="cn.trinea.android.demo"/>
<intent
……/>
</shortcut>
……
</shortcuts>
以shortcuts元素為根,可以包含多個(gè)shortcut元素,每個(gè)shortcut元素表示一個(gè) shortcut。其中屬性分別表示:
- shortcutId表示 shortcut 唯一標(biāo)識符,相同的 shortcutId 會(huì)被覆蓋。必須字段。
- shortcutShortLabel為將 shortcut 拖動(dòng)到桌面時(shí)顯示的名字,官方建議不超過 10 個(gè)字符,必須字段。
- shortcutLongLabel為 shortcut 列表中每個(gè) shortcut 的名字,不宜過長,如果過長或未設(shè)置默認(rèn)會(huì)顯示 ShortLabel,官方建議不超過 25 個(gè)字符。可選字段。
- icon為 shortcut 的 icon,在列表展示和拖動(dòng)到桌面時(shí)顯示需要,可選字段。
- enabled表示 shortcut 是否可用,false 表示禁用。xml 中這個(gè)屬性幾乎沒有被設(shè)置為 false 的實(shí)際場景,具體原因可見6.7 如何更好的刪除(廢棄)老的 Shortcut中介紹。
- shortcutDisabledMessage為已固定在桌面的 shortcut 被 Disabled 后點(diǎn)擊時(shí)的 Toast 提示內(nèi)容。可選字段。
- intent為點(diǎn)擊 shortcut 時(shí)響應(yīng)的 Intent,必須字段。
這里可以添加多個(gè) Intent,但點(diǎn)擊時(shí)不會(huì)啟動(dòng)所有 Intent,而是啟動(dòng)最后一個(gè) Intent,在這個(gè) Intent 回退時(shí)會(huì)啟動(dòng)它前面一個(gè) Intent,相當(dāng)于自動(dòng)將所有 Intent 添加到了堆棧。
對于先跳轉(zhuǎn)到某個(gè)頁面,Back 鍵希望退回主頁而不是結(jié)束 App 這類場景,多個(gè) Intents 挺實(shí)用的。
intent可設(shè)置屬性包括:
android:action、android:data、android:mimeType、android:targetClass、android:targetPackage
其中android:action為必須屬性。
3. 動(dòng)態(tài) Shortcuts(Dynamic Shortcuts)
動(dòng)態(tài) ShortcutsDynamic Shortcuts 通過 ShortcutManager API 進(jìn)行操作。可以動(dòng)態(tài)添加、修改、刪除。
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
return;
}
ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
ShortcutInfo shortcut = new ShortcutInfo.Builder(this, "id1")
.setShortLabel("trinea.cn")
.setLongLabel("Open trinea.cn")
.setDisabledMessage("Disabled")
.setIcon(Icon.createWithResource(context, R.drawable.trinea_cn))
.setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.trinea.cn/")))
.build();
shortcutManager.setDynamicShortcuts(Arrays.asList(shortcut));
通過ShortcutInfo.Builder新建 ShortcutInfo,再通過shortcutManager添加即可。其他:
- setDynamicShortcuts(List)可以替換并添加所有 shortcut 列表;
- addDynamicShortcuts(List)可以添加新的 shortcut 到列表,超過最大個(gè)數(shù)會(huì)報(bào)異常;
- updateShortcuts(List)可以更新一組 shortcuts;
- removeDynamicShortcuts(List)和removeAllDynamicShortcuts() 可以刪除部分或所有 shortcuts。
ShortcutInfo的屬性與 xml 中定義字段含義一致,shortcutId shortcutShortLabel intent 是必須設(shè)置的字段,并且intent必須設(shè)置Action。
4. 固定的 Shortcuts(Pinned Shortcuts)
指通過拖動(dòng)固定到桌面的 Shortcuts,App 不可以添加、修改、刪除這些 Shortcuts,只能禁用他們。即便 App 內(nèi)刪除了某個(gè) Shorcut,對應(yīng)的已固定到桌面的 Shortcuts 也不會(huì)被刪除。
可以通過:
- getPinnedShortcuts()得到所有固定的 Shortcuts 的信息。
- disableShortcuts(List)或disableShortcuts(List, CharSequence)禁用動(dòng)態(tài)的 Shortcuts。
對于靜態(tài)的 Shortcuts 需要在資源文件中設(shè)置android:enabled="false"
進(jìn)行禁用,不過沒有必要,靜態(tài) Shortcuts 可直接通過刪除達(dá)到禁用的效果,具體原因可見6.7 如何更好的刪除(廢棄)老的 Shortcut中介紹。
靜態(tài) Shortcuts 和動(dòng)態(tài) Shortcuts 是有最大個(gè)數(shù)限制的,默認(rèn)為 5,超過最大個(gè)數(shù)后添加會(huì)報(bào)異常。而固定的 Shortcuts 并沒有個(gè)數(shù)限制,并且固定的 Shortcut 對應(yīng)的 Shortcut 即便被動(dòng)態(tài)刪除了,依然可以通過 id 進(jìn)行 Update 操作。
5. 其他
5.1 動(dòng)態(tài) Shortcuts 與靜態(tài) Shortcuts 區(qū)別
- 靜態(tài) Shortcuts 只能通過升級應(yīng)用修改,動(dòng)態(tài) Shortcuts 隨時(shí)可以修改;
- 靜態(tài) Shortcuts 的 Intent 無法設(shè)置 Flag,默認(rèn)為
FLAG_ACTIVITY_NEW_TASK
和FLAG_ACTIVITY_CLEAR_TASK Flag
,即若應(yīng)用運(yùn)行中會(huì)清除所有已存在的 Activity。動(dòng)態(tài) Shortcuts 的 Intent 可以設(shè)置 Flag; - 靜態(tài) Shortcuts 的rank系統(tǒng)默認(rèn)根據(jù)聲明順序設(shè)置,動(dòng)態(tài) Shortcuts 的rank可以通過setRank(int rank)接口主動(dòng)設(shè)置,rank 不能小于 0,值越大表示在 shortcut 列表展示時(shí)離 App Icon 越遠(yuǎn)。靜態(tài) Shortcuts 默認(rèn)比動(dòng)態(tài) Shortcuts 離 App Icon 更近。
- 靜態(tài) Shortcuts 刪除可以直接刪除,動(dòng)態(tài) Shortcuts 建議通過禁用刪除;
5.2 動(dòng)態(tài) Shortcuts 操作的頻率問題
當(dāng)應(yīng)該完全退到后臺(無 Activity 或 Service 在前臺時(shí)),其操作 Shortcut(包括添加、刪除、修改) 的頻率是受限的。可通過isRateLimitingActive()查詢是否已受限,true表示已受限。
5.3 跟蹤 Shorcut 使用情況
在 Shortcut 被選擇或者其關(guān)聯(lián)的操作被操作時(shí)需調(diào)用reportShortcutUsed(String shortcutId)接口上報(bào)數(shù)據(jù),為了方便啟動(dòng)器收集應(yīng)用 Shortcuts 使用情況,以便未來進(jìn)行預(yù)測或者向開發(fā)者展示哪些操作適合作為 Shortcuts 以及其優(yōu)先級。
PS:這個(gè)接口其實(shí)挺尷尬的,一方面需要 App 主動(dòng)上報(bào),侵入性太強(qiáng)。另一方面這個(gè)預(yù)測功能未來也不好加到 Shortcuts 推薦里,更多是個(gè)開發(fā)工具相關(guān)功能。
最好是由啟動(dòng)器自己純粹收集 Shortcut 被選擇的使用情況數(shù)據(jù),而不需要統(tǒng)計(jì) Shortcut 被關(guān)聯(lián)操作通過其他方式調(diào)用的使用情況數(shù)據(jù)。至于哪些操作適合作為 Shortcuts,開發(fā)者大可通過其他監(jiān)控 SDK 去判斷。
5.4 應(yīng)用備份
如果應(yīng)用通過備份恢復(fù)到另外一臺機(jī)器上,固定的 Shortcuts 是可以直接恢復(fù)的,不過啟動(dòng)器不保存這些 Shortcut 的 icon,所以應(yīng)用內(nèi)需要存在這些 icon 對應(yīng)的資源以便啟動(dòng)器能找到。
靜態(tài) Shortcuts 需要應(yīng)用重新安裝、升級才能生效。
動(dòng)態(tài) Shortcuts 需要相應(yīng)代碼被執(zhí)行過才能生效。
二、Shortcuts 一些實(shí)踐&問題
6. 最佳實(shí)踐
這塊官網(wǎng)已經(jīng)給出了一部分建議,包括:
- 設(shè)計(jì)上和系統(tǒng) App 的 Shortcuts 保持一致。
- 最多添加 4 個(gè) Shortcuts 以保持在啟動(dòng)器中顯示的樣式最佳
目前雖然說是 5 個(gè),但實(shí)際最多只能添加 4 個(gè),可見7.2 Shortcut 添加或修改無效中介紹。 - 限制 Label 長度
其中shortcutShortLabel建議不超過 10 個(gè)字符,shortcutLongLabel 建議不超過 25 個(gè)字符。這塊可能有些問題,可見7.1 LongLabel 和 ShortLabel中介紹。 - 記錄 Shortcut 及其對應(yīng)操作使用記錄。
這個(gè)在5.3 跟蹤 Shorcut 使用情況中已經(jīng)介紹了。 - 只在 Shortcut 意義不變的情況下更新,否則新增。
- 動(dòng)態(tài) Shortcuts 在 BackUp 恢復(fù)后不可以直接恢復(fù),考慮適時(shí)新增或更新已有的 Shortcuts
除了以上這些外,個(gè)人覺得還有幾點(diǎn)需要遵守: - 如何更好的刪除(廢棄)老的 Shortcut
這里主要考慮到刪除老的 Shortcut,可能會(huì)影響已經(jīng)固定的 Shortcut。
對于靜態(tài) Shortcuts,直接刪除配置文件中對應(yīng)的 Shortcut 即可,系統(tǒng)桌面會(huì)將已固定的該 Shortcut 置灰,點(diǎn)擊會(huì)提示 shortcutDisabledMessage。
對于動(dòng)態(tài) Shortcuts 建議通過禁用的方式而不是直接刪除的方式,因?yàn)橐呀?jīng)刪除的動(dòng)態(tài) Shortcut 如果被固定了依然是可用的,所以希望該入口不可用最好的方式是禁用。 - 始終設(shè)置shortcutDisabledMessage
根據(jù)上面的介紹廢棄老的 Shortcut 較好的方式是禁用,通過自定義shortcutDisabledMessage去更友好的提示用戶。 - 動(dòng)態(tài)添加 Shortcut 前需要判斷 API 版本不小于 25
否則在低版本會(huì)報(bào) ClassNotFoundException 異常。
7. 一些問題
7.1 LongLabel 和 ShortLabel
LongLabel和ShortLabel的含義,官方 API 文檔解釋的并不是很清楚。
在 Nexus 6 上測試,當(dāng) LongLabel 長度大于 17 個(gè)小寫字符時(shí),會(huì)顯示 ShortLabel,而不是 LongLabel。這里的界限長度跟大小寫、空格都有關(guān),應(yīng)該是受限于桌面 Shortcuts 列表 Item 的寬度!
7.2 Shortcut 添加、修改、點(diǎn)擊無效
可能原因:
- shortcutId 被覆蓋
shortcutId 是唯一標(biāo)識,相同 shortcutId 會(huì)被覆蓋。 - intent 不對
intent 必須設(shè)置 android:action 屬性,同時(shí)目標(biāo) Activity 必須有效即已在 Manifest 中聲明。 - 后臺 App 有頻率限制
當(dāng)應(yīng)該完全退到后臺(無 Activity 或 Service 在前臺時(shí)),其操作 Shortcut(包括添加、刪除、修改) 的頻率是受限的。可通過isRateLimitingActive()查詢是否已受限,true表示已受限。
若已受限,可通過開發(fā)者選項(xiàng)中“重置 ShortcutManager 調(diào)用頻率限制”或命令行adb shell cmd shortcut reset-throttling [ --user USER-ID ]
解決。 - Shortcut 個(gè)數(shù)限制
雖然官方文檔介紹靜態(tài)和動(dòng)態(tài) Shortcut 總和不能超過 5 個(gè),通過getMaxShortcutCountPerActivity()得到的也是 5,但實(shí)際測試下來是不超過4個(gè)!即靜態(tài)和動(dòng)態(tài)shortcuts加起來總數(shù)最多是五個(gè).
當(dāng)我們嘗試添加第六個(gè)shortcut時(shí), 應(yīng)用會(huì)拋出異常:
java.lang.IllegalArgumentException: Max number of dynamic shortcuts exceeded.
雖然總數(shù)限制是5個(gè), 但是當(dāng)我正好有5個(gè)(2個(gè)靜態(tài) + 3個(gè)動(dòng)態(tài))的時(shí)候, 長按只顯示了4個(gè)shortcuts.
7.3 getIntents() 有 Bug
從 getIntents() 實(shí)現(xiàn) 中可以看出未做mIntents是否為 null 及 empty 的判斷,在 null 時(shí)會(huì)出現(xiàn):
java.lang.NullPointerException: Attempt to get length of null array
的異常。
8. 三方桌面支持 Shortcuts——LauncherApps
如果三方桌面希望支持這個(gè)特性,請參考 LauncherApps API 介紹,不過只有系統(tǒng)默認(rèn)桌面才有權(quán)限得到其他 App Shortcuts 信息。
可通過hasShortcutHostPermission()查看是否擁有權(quán)限,如果沒有權(quán)限,會(huì)報(bào)如下異常:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.xx/com.xx.XXActivity}:
java.lang.SecurityException: Caller can't access shortcut information
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2665)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726)
通過getShortcuts API 獲取到所有 Shortcuts 信息。
參考:
App Shortcuts的官方文檔: App Shortcuts
Exploring Android Nougat 7.1 App Shortcuts