Android 7.1 新特性 - App Shortcuts 簡介

Android 7.1 - App Shortcuts

版權聲明:本文為博主原創文章,未經博主允許不得轉載。
微博:厲圣杰
源碼:AndroidDemo/Shortcuts
文中如有紕漏,歡迎大家留言指出。

Android 7.1 新功能之一就是 App Shortcuts(應用快捷方式) ,該功能與 iPhone 上的 3D Touch 功能相似,通過長按應用圖標,可彈出應用快捷方式,點擊可以直接跳轉到相應的界面。目前最多支持 5 個快捷方式,可以 getMaxShortcutCountPerActivity() 查看 Launcher 最多支持幾個快捷方式,不同的是 Android 支持通過拖拽將快捷方式固定到桌面。

看似美好,其實應用快捷方式還是有很多缺陷的:

  1. 只能在 Google 的 Nexus 及 Pixel 設備上使用

  2. 系統必須是 Android 7.1 及以上(API Level >= 25)

  3. 已經被用戶固定到桌面的快捷方式必須得到兼容性處理,因為你基本上失去了對其控制,除了升級時禁用

    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 實現動態添加、刪除、禁用等操作。

下面分別來講述如何創建靜態快捷方式和動態快捷方式。

創建靜態快捷方式

  1. 在 /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>
    
  2. 打開 AndroidManifest.xml 文件,找到其中 <intent-filter> 被設置為 android.intent.action.MAINandroid.intent.category.LAUNCHER 的 Activity

  3. 給這個 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 ...
}

![屏幕快照 2016-11-01 上午8.46.31](http://odsdowehg.bkt.clouddn.com/屏幕快照 2016-11-01 上午8.46.31.png)

關于 compileSdkVersion 、 minSdkVersion 以及 targetSdkVersion 的區別可參考這篇文章

創建動態快捷方式

創建動態快捷方式主要依靠 ShortManagerShortcutInfoShortcutInfo.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()disableDynamicShortcutsprintDynamicShortcuts() 測試,發現被禁用的快捷方式時不算已經創建的快捷方式的。

總結

簡單的總結了一下 Android 7.1 中應用快捷方式的創建及注意點,但某些不太常用的沒怎么去研究,有興趣的可以參考 Android 官方文檔

參考:

  1. ShortcutManager
  2. App Shortcuts
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379

推薦閱讀更多精彩內容