Android 7.1 新特性 - App Shortcuts 簡介

Android 7.1 - App Shortcuts

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

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

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

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

  2. 系統(tǒng)必須是 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(靜態(tài)快捷方式)Dynamic Shortcuts(動態(tài)快捷方式) 兩種。

  • 靜態(tài)快捷方式:又名 Manifest Shortcuts,在應用安裝時創(chuàng)建,不能實現動態(tài)修改,只能通過應用更新相應的 XML 資源文件才能實現更新。
  • 動態(tài)快捷方式:應用運行時通過 ShortcutManager 實現動態(tài)添加、刪除、禁用等操作。

下面分別來講述如何創(chuàng)建靜態(tài)快捷方式和動態(tài)快捷方式。

創(chuàng)建靜態(tài)快捷方式

  1. 在 /res/xml 目錄下創(chuàng)建 shortcuts.xml ,添加根元素 <shortcuts> ,其包含一組 <shortcut> 標簽。每個 <shortcut> 標簽為一個靜態(tài)快捷方式,它包含相應的圖標、描述以及對應的 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:快捷方式關聯(lián)的 intent,當用戶點擊快捷方式時,列表中所有 intent 都會被打開,但用戶看見的是列表中最后一個 intent 。

categories:用于指定 shortcut 的 category,目前只有 SHORTCUT_CATEGORY_CONVERSATION 一個值

補充:如果 <shortcuts> 中只有一個 <shortcut> 且 enabled = false ,那么長按 app 圖標是不會彈出任意快捷方式

gradle 配置

apply plugin: 'com.android.application'

android {
    //如果只是創(chuàng)建靜態(tài)快捷方式,那么版本號任意
    //即使 compileSdkVersion 、targetSdkVersion 為 23 ,在 Android 7.1 的 Nexus 和 Pixel 設備上也能使用。
    //但是如果是創(chuàng)建動態(tài)快捷方式,因為則必須使 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 的區(qū)別可參考這篇文章

創(chuàng)建動態(tài)快捷方式

創(chuàng)建動態(tài)快捷方式主要依靠 ShortManager 、 ShortcutInfoShortcutInfo.Builder 這幾個類來實現。ShortcutInfo 和 ShortcutInfo.Builder 主要用來構造快捷方式對象, ShortManager 是一個系統(tǒng)服務,用于管理應用快捷方式,ShortManager 可以通過以下方式獲?。?/p>

ShortManager shortManager = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE);

ShortManager 主要有以下幾個功能:

  • 發(fā)布:通過調用 setDynamicShortcuts(List) 替換整個快捷方式列表或者通過 addDynamicShortcuts(List) 往已存在的快捷方式列表中添加快捷方式。
  • 更新:調用 updateShortcuts(List) 來更新已存在的快捷方式列表
  • 移除:調用 removeDynamicShortcuts(List) 移除列表中指定快捷方式,調用 removeAllDynamicShortcuts() 移除列表中所以快捷方式。
  • 禁用:因為用戶可能將您任意的快捷方式拖拽到桌面,而這些快捷方式會將用戶引導至應用中不存在或過期的操作,所以可以通過調用 disableShortcuts(List) 來禁用任何已存在的快捷方式。調用 disableShortcuts(List, Charsquence) 會給出錯誤提示。

下面代碼主要演示了使用 ShortManager 實現動態(tài)發(fā)布、更新、移除以及禁用快捷方式。

動態(tài)創(chuàng)建快捷方式核心代碼:

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;
        }
    }

    /**
     * 動態(tài)設置快捷方式
     */
    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")));
    }

    /**
     * 禁用動態(tài)快捷方式
     */
    private void disableDynamicShortcuts() {
        mShortcutManager.disableShortcuts(Arrays.asList("alarm"));
        //因為shortcutId=compose2的快捷方式為靜態(tài),所以不能實現動態(tài)修改
        //mShortcutManager.disableShortcuts(Arrays.asList("compose2"));
    }

    /**
     * 移除快捷方式
     *
     * 已被用戶拖拽到桌面的快捷方式還是回繼續(xù)存在,如果不在支持的話,最好將其置為disable
     */
    private void removeDynamicShortcuts() {
        mShortcutManager.removeDynamicShortcuts(Arrays.asList("alarm"));
    }

    /**
     * 移除所有動態(tài)快捷方式
     */
    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());
    }

    /**
     * 打印動態(tài)快捷方式信息
     */
    private void printDynamicShortcuts() {
        Log.d(TAG, "動態(tài)快捷方式列表數量:" + mShortcutManager.getDynamicShortcuts().size() + "信息:" + mShortcutManager.getDynamicShortcuts());
    }

    /**
     * 打印靜態(tài)快捷方式信息
     */
    private void printStaticShortcuts() {
        Log.d(TAG, "靜態(tài)快捷方式列表數量:" + mShortcutManager.getManifestShortcuts().size() + "信息:" + mShortcutManager.getManifestShortcuts());
    }

}

代碼基本涵蓋了動態(tài)創(chuàng)建快捷方式的所有情況,組合測試一下就可以了。

注意代碼中的 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)

所以在動態(tài)添加快捷方式之前最好先檢查一下是否超過最大值。

還有,disableDynamicShortcuts() 注釋了使用 ShortManager 動態(tài)修改靜態(tài)快捷方式的代碼,因為靜態(tài)快捷方式時不允許在運行時進行修改的,如果執(zhí)行了修改會拋出如下異常:

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)

值得注意的地方

前面講了創(chuàng)建靜態(tài)快捷方式和動態(tài)快捷方式,可能某些要點還沒講到,這里做下總結。

被禁用的快捷方式還計入已經創(chuàng)建快捷方式里嘛

addDynamicShortcuts()disableDynamicShortcutsprintDynamicShortcuts() 測試,發(fā)現被禁用的快捷方式時不算已經創(chuàng)建的快捷方式的。

總結

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

參考:

  1. ShortcutManager
  2. App Shortcuts
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容