老司機帶你吃牛軋糖--適配Android 7.1 Nougat新特性

1337896903 9年前發布 | 9K 次閱讀 安卓開發 Android開發 移動開發

What's new in Android 7.1 Nougat?

Android 7.1 Nougat 已經推出有一段時間,相信大多數人和我一樣,并沒有用上最新的系統,但是,總有一群走在時代的前列線上的Geek們,勇于嘗鮮,艱苦奮斗,為刷新版本號貢獻自己的力量。好吧,實際上就是我還沒有用上7.1,有些眼饞了。那么,和開發者息息相關的有哪些新特性呢?

Android 7.1 Nougat

本次主要介紹3個新特性: App Shortcuts , Round Icon ResourceImage Keyboard Support

App Shortcuts

作為一個密切關注Android發展的偽Geek,在7.1正式版未發布之前,通過網上的一些爆料文章,我就了解到了這一新功能。實際上,這個功能剛開始出現時,我還以為Google Pixel要上壓感屏了呢,事實證明,的確是我想多了。

App Shortcuts允許用戶直接在啟動器中顯示一些操作,讓用戶立即執行應用的深層次的功能。觸發這一功能的操作就是「長按」。這一功能類似于iOS中的「3D Touch」。

下面通過一張GIF,直觀的感受一下App Shortcuts是怎樣的。(由于我的一加3并沒有升級到最新的7.1,還只是7.0,所以我安裝了Nova Launcher來體驗。)

App Shortcuts

長按圖標,收到震動后松手,如果能夠看到圖標上彈出了支持的跳轉操作,說明成功的呼出了Shortcuts功能,如果不支持這一功能,在Nova Launcher上彈出的就是卸載或者移除操作,在Pixel Launcher上不會出現彈出菜單,顯示的是常見的長按操作。長按彈出的操作,可以將這個操作已快捷方式圖標的形式直接放置在主屏上。如果長按主圖標不松手,就可以調整位置了。

目前,一個應用最多可以支持 5 個Shortcut,可以通過 getMaxShortcutCountPerActivity 查看Launcher最多支持Shortcut的數量。每一個Shortcut都對應著一個或者多個intent,當用戶選擇某一個Shortcut時,應該做出特定的動作。下面是一些將一些特定的動作作為Shortcuts的例子:

  • 在地圖APP中,指引用戶至最常用的位置

  • 在聊天APP中,發送信息至某個好友

  • 在多媒體APP中,播放下一個電視節目

  • 在游戲APP中,加載至上次保存的地方

App Shortcut可以分為兩種不同的類型: Static Shortcuts(靜態快捷方式) 和 Dynamic Shortcuts(動態快捷方式)。

  • Static Shortcuts:在打包到apk的資源文件中定義,所以,直到下一次更新版本時才能改變靜態快捷方式的詳細說明。
  • Dynamic Shortcuts:通過ShortcutManager API在運行時發布,在運行時,應用可以發布,升級和移除快捷方式。

Using Static Shortcuts

創建Static Shortcuts分為以下幾步:

1.在工程的manifest文件 (AndroidManifest.xml)下,找到 intent filter設置為 android.intent.action.MAINandroid.intent.category.LAUNCHER 的Activity。

2.在次Activity下添加<meta-data>標簽,引用定義shortcuts的資源文件。

<activity
        android:name=".homepage.MainActivity"
        android:configChanges="orientation|keyboardHidden|screenSize|screenLayout"
        android:label="@string/app_name"
        android:theme="@style/AppTheme.NoActionBar">
        <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></code></pre> 

3.創建新的資源文件 res/xml/shortcuts.xml

<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">

    <shortcut
        android:enabled="true"
        android:icon="@drawable/ic_search_circle"
        android:shortcutId="search_bookmarks"
        android:shortcutShortLabel="@string/search_bookmarks"
        android:shortcutLongLabel="@string/search_bookmarks">

        <intent
            android:action="android.intent.action.VIEW"
            android:targetPackage="com.marktony.zhihudaily"
            android:targetClass="com.marktony.zhihudaily.search.SearchActivity" />

        <!--如果你的一個shortcut關聯著多個intent,你可以在這里繼續添
            加。最后一個intent決定著用戶在加載這個shortcut時會看到什么-->

        <categories android:name="android.shortcut.conversation" />

    </shortcut>

    <!--在這里添加更多的shortcut-->

</shortcuts>

shortcut下標簽的含義:

  • enabled:見名知意,shortcut是否可用。如果你決定讓這個static shortcut不在可用的話,可直接將其設置為 false ,或者直接從 shortcuts 標簽中移除。

  • icon:顯示在左邊的圖標,可用使用 Vector drawable

  • shortcutDisabledMessage:當禁用此shortcut后,它仍然會出現在用戶長按應用圖標后的快捷方式列表里,也可以被拖動并固定到桌面上,但是它會呈現灰色并且用戶點擊時會彈出Toast這個標簽所定義的內容。

  • shortcutLongLabel:當啟動器有足夠多的空間時,會顯示這個標簽所定義的內容。

  • shortcutShortLabel:shortcut的簡要說明,是必需字段。當shortcut被添加到桌面上時,顯示的也是這個字段。

  • intent:shortcut關聯的一個或者多個intent,當用戶點擊shortcut時被打開。

  • shortcutId:shortcut的唯一標示id,若存在具有相同shortcutId的shortcut,則只顯示一個。

到這里,最簡單的shortcut就添加成功了。運行包含上面的文件的項目,點擊shortcut就可以直接進入 SearchActivity ,當按下back鍵時,直接就退出了應用。如果希望不退出應用,而是進入 MainActivity 時,應該怎么辦呢?不用著急,在shortcut繼續添加intent就可以了。

<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">

    <shortcut
        android:enabled="true"
        android:icon="@drawable/ic_search_circle"
        android:shortcutId="search_bookmarks"
        android:shortcutShortLabel="@string/search_bookmarks"
        android:shortcutLongLabel="@string/search_bookmarks">

        <intent
                android:action="android.intent.action.MAIN"
                android:targetClass="com.marktony.zhihudaily.homepage.MainActivity"
            android:targetPackage="com.marktony.zhihudaily" />

        <intent
            android:action="android.intent.action.VIEW"
            android:targetPackage="com.marktony.zhihudaily"
            android:targetClass="com.marktony.zhihudaily.search.SearchActivity" />

        <categories android:name="android.shortcut.conversation" />

    </shortcut>

      <!--在這里添加更多的shortcut-->

 </shortcuts>

Using Dynamic Shortcuts

動態快捷方式應該和應用內的特定的、上下文敏感的action鏈接。這些action應該可以在用戶的幾次使用之間、甚至是在應用運行過程中被改變。好的候選action包括打電話給特定的人、導航至特定的地方、或者展示當前游戲的分數。

ShortcutManager API允許我們在動態快捷方式上完成下面的操作:

  • 發布:使用setDynamicShortcuts()重新定義整個動態快捷方式列表,或者是使用addDynamicShortcuts()向已存在的動態快捷方式列表中添加快捷方式。

  • 更新:使用updateShortcuts()方法。

  • 移除:使用removeDynamicShortcuts()方法移除特定動態快捷方式或者使用removeAllDynamicShortcuts()移除所有動態快捷方式。

下面是在MainActivity的onCreate()中創建動態快捷方式的例子:

@Override
protected void onCreate(Bundle savedInstanceState) {

    ...

    ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);

    ShortcutInfo webShortcut = new ShortcutInfo.Builder(this, "shortcut_web")
            .setShortLabel("github")
            .setLongLabel("Open Tonny's github web site")
            .setIcon(Icon.createWithResource(this, R.drawable.ic_dynamic_shortcut))
            .setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse("https://marktony.github.io")))
            .build();

    shortcutManager.setDynamicShortcuts(Collections.singletonList(webShortcut));
}

也可以為動態快捷方式創建返回棧。

@Override
protected void onCreate(Bundle savedInstanceState) {

    ...

    ShortcutInfo dynamicShortcut = new ShortcutInfo.Builder(this, "shortcut_dynamic")
            .setShortLabel("Dynamic")
            .setLongLabel("Open dynamic shortcut")
            .setIcon(Icon.createWithResource(this, R.drawable.ic_dynamic_shortcut_2))
            .setIntents(
                    new Intent[]{
                            new Intent(Intent.ACTION_MAIN, Uri.EMPTY, this, MainActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK),
                            new Intent(DynamicShortcutActivity.ACTION)
                    })
            .build();

    shortcutManager.setDynamicShortcuts(Arrays.asList(webShortcut, dynamicShortcut));
}

創建一個新的空的Activity,名字叫做DynamicShortcutActivity,在manifest文件中注冊。

<activity  
      android:name=".DynamicShortcutActivity"
      android:label="Dynamic shortcut activity">
      <intent-filter>
        <action android:name="com.marktony.zhihudaily.OPEN_DYNAMIC_SHORTCUT" />
        <category android:name="android.intent.category.DEFAULT" />
      </intent-filter>
</activity>

通過清除array中的排序過的intents,當我們通過創建好的shortcut進入DynamicShortcutActivity之后,按下back鍵,MainActivity就會被加載。

需要注意的是,在動態創建快捷方式之前,最好是檢查一下是否超過了所允許的最大值。否則會拋出相應的異常。

Extra Bits

  • 當static shortcut 和 dynamic shortcut一起展示時,其出現的順序是怎樣定制呢?

    ShortcutInfo.Builder 中有一個專門的方法 setRank(int) ,通過設置不同的等級,我們就可以控制動態快捷方式的出現順序,等級越高,出現在快捷方式列表中的位置就越高。

  • 我們還可以設置動態快捷方式的shortLabel的字體顏色。

    ForegroundColorSpan colorSpan = new ForegroundColorSpan(getResources().getColor(android.R.color.holo_red_dark, getTheme()));
      String label = "github";
      SpannableStringBuilder colouredLabel = new SpannableStringBuilder(label);
      colouredLabel.setSpan(colorSpan, 0, label.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
    
      ShortcutInfo webShortcut = new ShortcutInfo.Builder(MainActivity.this, "shortcut_web")
              .setShortLabel(colouredLabel)
              .setRank(1)
              .build();

App Shortcuts Best Practices

當設計和創建應用的shortcuts時,應該遵守下面的指導建議:

  • 遵循設計規范:為了保持我們的應用和系統應用的快捷方式在視覺上一致性,應該遵守 App Shortcuts Design Guidelines 。

  • 發布4個不同的快捷方式:盡管現在的API支持靜態和動態總共5個快捷方式,但是為了提高shortcut的視覺效果,建議只添加4個不同的快捷方式。

  • 限制快捷方式描述的文本長度:在Launcher中,顯示快捷方式時,空間長度受到了限制。如果可能的話,應該將「short description」的文字長度控制在10個字母以內,將「long discription」的長度限制在25個字母以內。

  • 保存shortcut和action的歷史記錄:創建的每一個shortcut,應該考慮到用戶能夠通過不同的方式完成相同的任務。在這種情況下,記得調用 reportShortcutUsed() 方法,這樣,launcher就可以提高shortcut對應的actions的反應速度。

  • 只有在shortcuts的意義存在時更新:當改變動態快捷方式時,只有在shortcut仍然保持著它的含義時,調用 updateShortcuts() 方法改變它的信息。否則,應該使用 addDynamicShortcuts() 或者 setDynamicShortcuts() 創建一個具有新含義的ID的快捷方式。

    舉個例子,如果我們已經創建了導航到一個超市的快捷方式,如果超市的名稱改變了但是位置并沒有變化時,只更新信息是合適的。但是如果用戶開始在一個不同位置的超市購物時,最好是創建一個全新的快捷方式(而不僅僅是更新信息了)。

  • 在備份和恢復時,動態shortcuts不應該被保存:正是因為這個原因,推薦我們在需要APP啟動和重新發布動態快捷方式時,檢查 getDynamicShortcuts() 的對象的數量。可以參考 Backup and Restore 部分的代碼片段。

Round Icon Resources

在Android 7.1上,Google推出了一個部分用戶可能不太喜歡的特性--圓形圖標。圓形圖標長什么樣,可以看看下面的圖。

round icon

同時,圓形圖標規范也作為一部分內容加入到了更新說明和開發文檔中。

應用程序現在可以定義圓形啟動器圖標以用于特定的移動設備之上。當啟動器請求應用程序圖標時,程序框架應返回 android:icon 或 android:roundIcon,視設備具體要求而定。因此,應用程序在開發時應該確保同時定義 android:icon和 android:roundIcon 兩個變量。您可以使用 Image Asset Studio 來設計圓形圖標。

您應該確保在支持新的圓形圖標的設備上測試您的應用程序,以確保應用程序圖標的外觀無虞和實際效果。測試您的資源的一種方法是在 Google Pixel 設備上安裝您的應用。您還可以通過運行 Android 模擬器并使用 Google API 模擬器系統(目標 API 等級為 25)測試您的圖標。

我們可以通過 Android Studio 自帶的 Image Asset Studio 設計圖標。在項目的 res 目錄下點擊鼠標右鍵,選擇 new --> Image Asset 即可設計圖標。

Image Asset Studio

Image Keyboard Support

在較早版本的Android系統中,軟鍵盤(例如我們所熟知的Input Method Editors,或者說IME),只能夠給應用發送unicode編碼的emoji,對于rich content,應用只能通過使用自建的私有的API實現發送圖片的功能。而在Android 7.1中,SDK包含了一個全新的Commit Content API,輸入法應用不僅可以調用此 API 實現發送圖片和其他rich content,一些通訊應用(比如 Google Messenger)也可以通過此 API 來更好地處理這些來自輸入法的圖片、網頁信息和 GIF 內容。

image keyboard sample

How it works

  1. 當用戶點擊EditText時, editor會發送一個它所能接受的 EditorInfo.contentMimeTypes MIME 內容類型的列表。

  2. IME讀取這個在軟鍵盤中支持類型和展示內容的列表。

  3. 當用戶選擇一張圖片后,IME調用 commitContent() 并向editor發送一個InputContentInfo。 commitContent() 方法是一個類似于 commitText() 的方法,但是是rich content的。 InputContentInfo 包含著一個表示content provider中內容的URI。然后我們的應用就可以請求相應的權限并讀取URI中的內容。

image keyboard diagram

Adding Image Support to Apps

為了接收來自IME的rich content,應用必須告訴IME它所能接收的內容類型并之指定當接收到內容后的回調方法。下面是一個怎樣創建一個能夠接收PNG圖片的 EditText 的演示代碼。

EditText editText = new EditText(this) {
    @Override
    public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
        final InputConnection ic = super.onCreateInputConnection(editorInfo);
        EditorInfoCompat.setContentMimeTypes(editorInfo,
                new String [] {"image/png"});

        final InputConnectionCompat.OnCommitContentListener callback =
            new InputConnectionCompat.OnCommitContentListener() {
                @Override
                public boolean onCommitContent(InputContentInfoCompat inputContentInfo,
                        int flags, Bundle opts) {
                    // read and display inputContentInfo asynchronously
                    if (BuildCompat.isAtLeastNMR1() && (flags &
                        InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
                        try {
                            inputContentInfo.requestPermission();
                        }
                        catch (Exception e) {
                            return false; // return false if failed
                        }
                    }

                    // read and display inputContentInfo asynchronously.
                    // call inputContentInfo.releasePermission() as needed.

                    return true;  // return true if succeeded
                }
            };
        return InputConnectionCompat.createWrapper(ic, editorInfo, callback);
    }
};

代碼還是蠻多的,解釋一下。

  • 例子使用了support library,并且引用的是 android.support.v13.view.inputmethod 而不是 android.view.inputmethod

  • 例子創建了一個 EditText 并復寫了它改變 InputConnectiononCreateInputConnection(EditorInfo) 方法. InputConnection 是IME和正在接收輸入的溝通管道。

  • 調用 super.onCreateInputConnection() 保留了內建的行為(包括發送和接收文本),并提供給我們一個 InputConnection 的引用。

  • setContentMimeTypes()向 EditorInfo 添加了一個所支持的MIME類型的列表。 需要保證在 setContentMimeTypes() 之前調用 super.onCreateInputConnection()

  • 回調在IME提交內容是被執行。 onCommitContent() 方法有一個對包含了內容URI的 InputContentInfoCompat 的引用。

    • 當我們的應用運行在API Level 25或者更高并且IME設置了 INPUT_CONTENT_GRANT_READ_URI_PERMISSION flag時,我們應該請求并且釋放權限。否則,我們應該在此之前就擁有content URI的訪問權限,一是因為權限是由IME授權的,二是content provider不對訪問進行約束。
  • createWrapper()包裝了inputConnection和已修改的editorInfo,新的InputConnection的回調并且返回。

下面是一些實踐小技巧。

  • 不支持rich content的Editor不應該調用 setContentTypes() 并把 EditorInfo.contentMimeTypes 設置為null。

  • Editor應該忽略掉在 InputConnectionInfo 中指定的MIME類型和所接收類型不通的內容。

  • rich content不影響也不被文本指針的位置所影響。editor在進行內容處理是可以直接忽略掉光標的位置。

  • 在editor的 OnCommitContentListener.onCommitContent() 方法中,我們可以異步的返回true,甚至是在加載內容之前。

  • 不同于文本內容在被提交之前可以在IME中被編輯,rich content會被立即提交。需要注意特性,如果想要提供編輯或者刪除內容的能力,我們需要自己提供處理邏輯。

為了測試APP,需要確保你的設備或者虛擬機的鍵盤能夠發送rich content。你可以在Android 7.1或者更高的系統中使用Google Keyboard,或者是安裝 CommitContent IME sample .

你可以在 CommitContent App sample 獲取到完整的示例代碼。

Adding Image Support to IMEs

想要IME支持發送rich content,需要引入下面所展示的Commit Content API。

  • 復寫 onStartInput() 或者 onStartInputView() ,并讀取來自目標editor的支持內容類型列表。

    @Override
      public void onStartInputView(EditorInfo info, boolean restarting) {
          String[] mimeTypes = EditorInfoCompat.getContentMimeTypes(editorInfo);
    
          boolean gifSupported = false;
          for (String mimeType : mimeTypes) {
              if (ClipDescription.compareMimeTypes(mimeType, "image/gif")) {
                  gifSupported = true;
              }
          }
    
          if (gifSupported) {
              // the target editor supports GIFs. enable corresponding content
          } else {
              // the target editor does not support GIFs. disable corresponding content
          }
      }
  • 當用戶選擇了一張圖片時,將內容提交給APP。當IME有正在編輯的文本時,應該避免調用 commitContent() ,因為這樣可能導致editor失去焦點。下面的代碼片段展示了怎樣提交一張GIF圖片。

    /**
       * Commits a GIF image
       *
       * @param contentUri Content URI of the GIF image to be sent
       * @param imageDescription Description of the GIF image to be sent
       */
      public static void commitGifImage(Uri contentUri, String imageDescription) {
          InputContentInfoCompat inputContentInfo = new InputContentInfoCompat(
                  contentUri,
                  new ClipDescription(imageDescription, new String[]{"image/gif"}));
          InputConnection inputConnection = getCurrentInputConnection();
          EditorInfo editorInfo = getCurrentInputEditorInfo();
          Int flags = 0;
          If (android.os.Build.VERSION.SDK_INT >= 25) {
              flags |= InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION;
          }
          InputConnectionCompat.commitContent(
                  inputConnection, editorInfo, inputContentInfo, flags, opts);
      }
  • 作為一個IME開發者,有很大可能你需要引入你自己的content provider來響應content URI請求。如果你的IME支持來自像 MediaStore 這樣已經存在的content provider倒是可以例外。

  • 如果正在創建自己的content provider,建議不要export(將 android:export 設置為false)。通過設置 android:grandUriPermission 為true允許在provider內部進行權限授予替代。然后,你的IME在內容提交時可以授予訪問content URI的權限。有兩種實現的方法:

    • 在Android 7.1(API Level 25)或更高的系統中,當調用 commitContent 方法時,將flag參數設置為 INPUT_CONTENT_GRANT_READ_URI_PERMISSION 。然后,APP收到的 InputContentInfo 對象可以通過調用 requestPermission() 方法和 releasePermission() 請求和釋放臨時訪問權限。

    • 在Android 7.0(API Level 24)或者更低的系統中, INPUT_CONTENT_GRANT_READ_URI_PERMISSION 直接被忽略,所以我們需要手動的授予內容訪問權限。方法就是 grantUriPermission() ,但是我們也可以引入滿足自己要求的機制。

權限授予的例子,我們可以在 CommitContent IME sample 中的doCommitContent()方法。

為了測試IME,確保我們的設備或者模擬器擁有接收rich content的的應用。我們可以在Android 7.1或者更高的系統中使用Google Messenger應用或者安裝 CommitContent App Sample 。

獲取完整的示例代碼,可以訪問 CommitContent IME Sample

Summary

Google在刷新版本號的路上簡直是在策馬奔騰了,嘚兒駕。我們也能夠看到Google的努力,Android也在變的越來越好,加油吧,小機器人。

 

 

來自:http://www.jianshu.com/p/f56f2e709ad8

 

 本文由用戶 1337896903 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!