Android 7.1 - App Shortcuts
版權聲明:本文為博主原創文章,未經博主允許不得轉載。
微博:厲圣杰
源碼:AndroidDemo/Shortcuts
文中如有紕漏,歡迎大家留言指出。
Android 7.1 新功能之一就是 App Shortcuts(應用快捷方式) ,該功能與 iPhone 上的 3D Touch 功能相似,通過長按應用圖標,可彈出應用快捷方式,點擊可以直接跳轉到相應的界面。目前最多支持 5 個快捷方式,可以 getMaxShortcutCountPerActivity() 查看 Launcher 最多支持幾個快捷方式,不同的是 Android 支持通過拖拽將快捷方式固定到桌面。
看似美好,其實應用快捷方式還是有很多缺陷的:
只能在 Google 的 Nexus 及 Pixel 設備上使用
系統必須是 Android 7.1 及以上(API Level >= 25)
-
已經被用戶固定到桌面的快捷方式必須得到兼容性處理,因為你基本上失去了對其控制,除了升級時禁用
Launcher applications allow users to "pin" shortcuts so they're easier to access. Both manifest and dynamic shortcuts can be pinned. Pinned shortcuts cannot be removed by publisher applications; they're removed only when the user removes them, when the publisher application is uninstalled, or when the user performs the "clear data" action on the publisher application from the device's Settings application.
However, the publisher application can disable pinned shortcuts so they cannot be started. See the following sections for details.
應用快捷方式可分為 Static Shortcuts(靜態快捷方式) 和 Dynamic Shortcuts(動態快捷方式) 兩種。
- 靜態快捷方式:又名 Manifest Shortcuts,在應用安裝時創建,不能實現動態修改,只能通過應用更新相應的 XML 資源文件才能實現更新。
- 動態快捷方式:應用運行時通過 ShortcutManager 實現動態添加、刪除、禁用等操作。
下面分別來講述如何創建靜態快捷方式和動態快捷方式。
創建靜態快捷方式
-
在 /res/xml 目錄下創建 shortcuts.xml ,添加根元素 <shortcuts> ,其包含一組 <shortcut> 標簽。每個 <shortcut> 標簽為一個靜態快捷方式,它包含相應的圖標、描述以及對應的 intent
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android"> <shortcut android:shortcutId="compose" android:enabled="true" android:icon="@drawable/compose_icon" android:shortcutShortLabel="@string/compose_shortcut_short_label1" android:shortcutLongLabel="@string/compose_shortcut_long_label1" android:shortcutDisabledMessage="@string/compose_disabled_message1"> <intent android:action="android.intent.action.VIEW" android:targetPackage="com.example.myapplication" android:targetClass="com.example.myapplication.ComposeActivity" /> <!-- If your shortcut is associated with multiple intents, include them here. The last intent in the list is what the user sees when they launch this shortcut. --> <categories android:name="android.shortcut.conversation" /> </shortcut> <!-- Specify more shortcuts here. --> </shortcuts>
打開 AndroidManifest.xml 文件,找到其中 <intent-filter> 被設置為
android.intent.action.MAIN
和android.intent.category.LAUNCHER
的 Activity-
給這個 Activity 添加 <meta-data> ,引用資源 shortcuts.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapplication"> <application ... > <activity android:name="Main"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <!-- 在 meta-data 中設置 shortcuts --> <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts" /> </activity> </application> </manifest>
補充:注意第 2 點的描述,也就是說如果 Manifest 中存在多個滿足條件的 Activity ,那么就可以存在多組應用快捷方式,但資源文件必須不同,主要是 shortcutId 必須不同,否則不會顯示。大家可以自己去嘗試下~
<shortcut> 標簽屬性含義如下:
shortcutId:快捷方式的唯一標識。
當用戶拖拽快捷方式到桌面,只要 shortcutId 不變,修改 <shortcut> 其余屬性值,重新打包,修改之后的變化會體現到已拖拽到桌面的快捷方式上。
若存在多個 <shortcut> 但 shortcutId 相同,則只會顯示一個
icon:快捷方式圖標
shortcutShortLabel:快捷方式的 Label,當用戶拖拽快捷方式到桌面時,顯示的也是該字符串
shortcutLongLabel:長按 app 圖標出現快捷方式時顯示的圖標
shortcutDisabledMessage:當快捷方式被禁用時顯示的提示語
enabled:標識快捷方式是否可用,可用時,快捷方式圖標正常顯示,點擊跳轉到對應的 intent ;不可用時,快捷方式圖標變灰,點擊彈出 shortcutDisabledMessage 對應字符串的 Toast
intent:快捷方式關聯的 intent,當用戶點擊快捷方式時,列表中所有 intent 都會被打開,但用戶看見的是列表中最后一個 intent 。
categories:用于指定 shortcut 的 category,目前只有 SHORTCUT_CATEGORY_CONVERSATION 一個值
補充:如果 <shortcuts> 中只有一個 <shortcut> 且 enabled = false ,那么長按 app 圖標是不會彈出任意快捷方式
gradle 配置:
apply plugin: 'com.android.application'
android {
//如果只是創建靜態快捷方式,那么版本號任意
//即使 compileSdkVersion 、targetSdkVersion 為 23 ,在 Android 7.1 的 Nexus 和 Pixel 設備上也能使用。
//但是如果是創建動態快捷方式,因為則必須使 compileSdkVersion 為 25
compileSdkVersion 25
buildToolsVersion "25.0.0"
defaultConfig {
applicationId "com.littlejie.shortcuts"
minSdkVersion 23
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
// something else ...
}

關于 compileSdkVersion 、 minSdkVersion 以及 targetSdkVersion 的區別可參考這篇文章
創建動態快捷方式
創建動態快捷方式主要依靠 ShortManager 、 ShortcutInfo 和 ShortcutInfo.Builder 這幾個類來實現。ShortcutInfo 和 ShortcutInfo.Builder 主要用來構造快捷方式對象, ShortManager 是一個系統服務,用于管理應用快捷方式,ShortManager 可以通過以下方式獲取:
ShortManager shortManager = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE);
ShortManager 主要有以下幾個功能:
- 發布:通過調用
setDynamicShortcuts(List)
替換整個快捷方式列表或者通過addDynamicShortcuts(List)
往已存在的快捷方式列表中添加快捷方式。 - 更新:調用
updateShortcuts(List)
來更新已存在的快捷方式列表 - 移除:調用
removeDynamicShortcuts(List)
移除列表中指定快捷方式,調用removeAllDynamicShortcuts()
移除列表中所以快捷方式。 - 禁用:因為用戶可能將您任意的快捷方式拖拽到桌面,而這些快捷方式會將用戶引導至應用中不存在或過期的操作,所以可以通過調用
disableShortcuts(List)
來禁用任何已存在的快捷方式。調用disableShortcuts(List, Charsquence)
會給出錯誤提示。
下面代碼主要演示了使用 ShortManager 實現動態發布、更新、移除以及禁用快捷方式。
動態創建快捷方式核心代碼:
public class MainActivity extends Activity implements View.OnClickListener {
private static final String TAG = MainActivity.class.getSimpleName();
private ShortcutManager mShortcutManager;
private ShortcutInfo[] mShortcutInfos;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//這里常量Context.SHORTCUT_SERVICE會報錯,不用管,可正常編譯。看著煩的話把minSdkVersion改為25即可
mShortcutManager = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE);
mShortcutInfos = new ShortcutInfo[]{getShoppingShortcut(), getDateShortcut()};
findViewById(R.id.btn_set).setOnClickListener(this);
findViewById(R.id.btn_add).setOnClickListener(this);
findViewById(R.id.btn_update).setOnClickListener(this);
findViewById(R.id.btn_disabled).setOnClickListener(this);
findViewById(R.id.btn_remove).setOnClickListener(this);
findViewById(R.id.btn_removeAll).setOnClickListener(this);
findViewById(R.id.btn_print_max_shortcut_per_activity).setOnClickListener(this);
findViewById(R.id.btn_print_dynamic_shortcut).setOnClickListener(this);
findViewById(R.id.btn_print_static_shortcut).setOnClickListener(this);
}
private ShortcutInfo getAlarmShortcut(String shortLabel) {
if (TextUtils.isEmpty(shortLabel)) {
shortLabel = "Python";
}
ShortcutInfo alarm = new ShortcutInfo.Builder(this, "alarm")
.setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.baidu.org/")))
.setShortLabel(shortLabel)
.setLongLabel("不正經的鬧鐘")
.setIcon(Icon.createWithResource(this, R.mipmap.ic_alarm))
.build();
return alarm;
}
private ShortcutInfo getShoppingShortcut() {
ShortcutInfo shopping = new ShortcutInfo.Builder(this, "shopping")
.setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.taobao.com")))
.setShortLabel("雙十一剁手")
.setLongLabel("一本正經的購物")
.setIcon(Icon.createWithResource(this, R.mipmap.ic_shopping))
.build();
return shopping;
}
private ShortcutInfo getDateShortcut() {
ShortcutInfo date = new ShortcutInfo.Builder(this, "date")
.setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com")))
.setShortLabel("被強了")
.setLongLabel("日程")
.setIcon(Icon.createWithResource(this, R.mipmap.ic_today))
.build();
return date;
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_set:
setDynamicShortcuts();
break;
case R.id.btn_add:
addDynamicShortcuts();
break;
case R.id.btn_update:
updateDynamicShortcuts();
break;
case R.id.btn_disabled:
disableDynamicShortcuts();
break;
case R.id.btn_remove:
removeDynamicShortcuts();
break;
case R.id.btn_removeAll:
removeAllDynamicShortcuts();
break;
case R.id.btn_print_max_shortcut_per_activity:
printMaxShortcutsPerActivity();
break;
case R.id.btn_print_dynamic_shortcut:
printDynamicShortcuts();
break;
case R.id.btn_print_static_shortcut:
printStaticShortcuts();
break;
}
}
/**
* 動態設置快捷方式
*/
private void setDynamicShortcuts() {
mShortcutManager.setDynamicShortcuts(Arrays.asList(mShortcutInfos));
}
/**
* 添加快捷方式
*/
private void addDynamicShortcuts() {
mShortcutManager.addDynamicShortcuts(Arrays.asList(getAlarmShortcut(null)));
}
/**
* 更新快捷方式,類似于Notification,根據id進行更新
*/
private void updateDynamicShortcuts() {
mShortcutManager.updateShortcuts(Arrays.asList(getAlarmShortcut("Java")));
}
/**
* 禁用動態快捷方式
*/
private void disableDynamicShortcuts() {
mShortcutManager.disableShortcuts(Arrays.asList("alarm"));
//因為shortcutId=compose2的快捷方式為靜態,所以不能實現動態修改
//mShortcutManager.disableShortcuts(Arrays.asList("compose2"));
}
/**
* 移除快捷方式
*
* 已被用戶拖拽到桌面的快捷方式還是回繼續存在,如果不在支持的話,最好將其置為disable
*/
private void removeDynamicShortcuts() {
mShortcutManager.removeDynamicShortcuts(Arrays.asList("alarm"));
}
/**
* 移除所有動態快捷方式
*/
private void removeAllDynamicShortcuts() {
mShortcutManager.removeAllDynamicShortcuts();
}
/**
* 只要 intent-filter 的 action = android.intent.action.MAIN
* 和 category = android.intent.category.LAUNCHER 的 activity 都可以設置 Shortcuts
*/
private void printMaxShortcutsPerActivity() {
Log.d(TAG, "每個 Launcher Activity 能顯示的最多快捷方式個數:" + mShortcutManager.getMaxShortcutCountPerActivity());
}
/**
* 打印動態快捷方式信息
*/
private void printDynamicShortcuts() {
Log.d(TAG, "動態快捷方式列表數量:" + mShortcutManager.getDynamicShortcuts().size() + "信息:" + mShortcutManager.getDynamicShortcuts());
}
/**
* 打印靜態快捷方式信息
*/
private void printStaticShortcuts() {
Log.d(TAG, "靜態快捷方式列表數量:" + mShortcutManager.getManifestShortcuts().size() + "信息:" + mShortcutManager.getManifestShortcuts());
}
}
代碼基本涵蓋了動態創建快捷方式的所有情況,組合測試一下就可以了。
注意代碼中的 addDynamicShortcuts()
方法,該方法調用 getAlarmShortcut(String shortcutLabel)
方法生成 ShortcutInfo ,該方法生成的 ShortcutInfo 的 id 是在變化的,如果多次點擊超過 mShortcutManager.getMaxShortcutCountPerActivity()
的值,就會拋出如下異常:
java.lang.IllegalArgumentException: Max number of dynamic shortcuts exceeded
at android.os.Parcel.readException(Parcel.java:1688)
at android.os.Parcel.readException(Parcel.java:1637)
at android.content.pm.IShortcutService$Stub$Proxy.addDynamicShortcuts(IShortcutService.java:430)
at android.content.pm.ShortcutManager.addDynamicShortcuts(ShortcutManager.java:535)
所以在動態添加快捷方式之前最好先檢查一下是否超過最大值。
還有,disableDynamicShortcuts()
注釋了使用 ShortManager 動態修改靜態快捷方式的代碼,因為靜態快捷方式時不允許在運行時進行修改的,如果執行了修改會拋出如下異常:
java.lang.IllegalArgumentException: Manifest shortcut ID=compose2 may not be manipulated via APIs
at android.os.Parcel.readException(Parcel.java:1688)
at android.os.Parcel.readException(Parcel.java:1637)
at android.content.pm.IShortcutService$Stub$Proxy.disableShortcuts(IShortcutService.java:540)
at android.content.pm.ShortcutManager.disableShortcuts(ShortcutManager.java:615)
at com.littlejie.shortcuts.MainActivity.disableDynamicShortcuts(MainActivity.java:135)
值得注意的地方
前面講了創建靜態快捷方式和動態快捷方式,可能某些要點還沒講到,這里做下總結。
被禁用的快捷方式還計入已經創建快捷方式里嘛
用 addDynamicShortcuts()
、 disableDynamicShortcuts
和 printDynamicShortcuts()
測試,發現被禁用的快捷方式時不算已經創建的快捷方式的。
總結
簡單的總結了一下 Android 7.1 中應用快捷方式的創建及注意點,但某些不太常用的沒怎么去研究,有興趣的可以參考 Android 官方文檔
參考: