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 必須不同,否則不會顯示。大家可以自己去嘗試下~

標簽屬性含義如下:

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

關於 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM