Android 7.1 Nougat 已經推出有一段時間,相信大多數人和我同樣,並無用上最新的系統,可是,總有一羣走在時代的前列線上的Geek們,敢於嚐鮮,艱苦奮鬥,爲刷新版本號貢獻本身的力量。好吧,實際上就是我尚未用上7.1,有些眼饞了。那麼,和開發者息息相關的有哪些新特性呢?html
本次主要介紹3個新特性:App Shortcuts, Round Icon Resource 和 Image Keyboard Support。全部的新特性能夠訪問谷歌開發者中文博客的文章歡迎使用Android 7.1.1 Nougat。java
做爲一個密切關注Android發展的僞Geek,在7.1正式版未發佈以前,經過網上的一些爆料文章,我就瞭解到了這一新功能。實際上,這個功能剛開始出現時,我還覺得Google Pixel要上壓感屏了呢,事實證實,的確是我想多了。android
App Shortcuts容許用戶直接在啓動器中顯示一些操做,讓用戶當即執行應用的深層次的功能。觸發這一功能的操做就是「長按」。這一功能相似於iOS中的「3D Touch」。git
下面經過一張GIF,直觀的感覺一下App Shortcuts是怎樣的。(因爲個人一加3並無升級到最新的7.1,還只是7.0,因此我安裝了Nova Launcher來體驗。)github
長按圖標,收到震動後鬆手,若是可以看到圖標上彈出了支持的跳轉操做,說明成功的呼出了Shortcuts功能,若是不支持這一功能,在Nova Launcher上彈出的就是卸載或者移除操做,在Pixel Launcher上不會出現彈出菜單,顯示的是常見的長按操做。長按彈出的操做,能夠將這個操做已快捷方式圖標的形式直接放置在主屏上。若是長按主圖標不鬆手,就能夠調整位置了。web
目前,一個應用最多能夠支持 5 個Shortcut,能夠經過getMaxShortcutCountPerActivity)查看Launcher最多支持Shortcut的數量。每個Shortcut都對應着一個或者多個intent,當用戶選擇某一個Shortcut時,應該作出特定的動做。下面是一些將一些特定的動做做爲Shortcuts的例子:app
在地圖APP中,指引用戶至最經常使用的位置框架
在聊天APP中,發送信息至某個好友異步
在多媒體APP中,播放下一個電視節目async
在遊戲APP中,加載至上次保存的地方
App Shortcut能夠分爲兩種不一樣的類型: Static Shortcuts(靜態快捷方式) 和 Dynamic Shortcuts(動態快捷方式)。
Static Shortcuts:在打包到apk的資源文件中定義,因此,直到下一次更新版本時才能改變靜態快捷方式的詳細說明。
Dynamic Shortcuts:經過ShortcutManager API在運行時發佈,在運行時,應用能夠發佈,升級和移除快捷方式。
建立Static Shortcuts分爲如下幾步:
1.在工程的manifest文件 (AndroidManifest.xml)下,找到 intent filter設置爲 android.intent.action.MAIN 和 android.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>
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>
動態快捷方式應該和應用內的特定的、上下文敏感的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就會被加載。
須要注意的是,在動態建立快捷方式以前,最好是檢查一下是否超過了所容許的最大值。不然會拋出相應的異常。
當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();
當設計和建立應用的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部分的代碼片斷。
在Android 7.1上,Google推出了一個部分用戶可能不太喜歡的特性--圓形圖標。圓形圖標長什麼樣,能夠看看下面的圖。
同時,圓形圖標規範也做爲一部份內容加入到了更新說明和開發文檔中。
應用程序如今能夠定義圓形啓動器圖標以用於特定的移動設備之上。當啓動器請求應用程序圖標時,程序框架應返回 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 便可設計圖標。
更多關於設計應用圖標的信息,能夠參考Material Design guidelines。
在較早版本的Android系統中,軟鍵盤(例如咱們所熟知的Input Method Editors,或者說IME),只可以給應用發送unicode編碼的emoji,對於rich content,應用只能經過使用自建的私有的API實現發送圖片的功能。而在Android 7.1中,SDK包含了一個全新的Commit Content API,輸入法應用不只能夠調用此 API 實現發送圖片和其餘rich content,一些通信應用(好比 Google Messenger)也能夠經過此 API 來更好地處理這些來自輸入法的圖片、網頁信息和 GIF 內容。
當用戶點擊EditText時, editor會發送一個它所能接受的 EditorInfo.contentMimeTypes MIME 內容類型的列表。
IME讀取這個在軟鍵盤中支持類型和展現內容的列表。
當用戶選擇一張圖片後,IME調用 commitContent() 並向editor發送一個InputContentInfo。 commitContent() 方法是一個相似於 commitText() 的方法,可是是rich content的。 InputContentInfo 包含着一個表示content provider中內容的URI。而後咱們的應用就能夠請求相應的權限並讀取URI中的內容。
爲了接收來自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 並複寫了它改變 InputConnection 的 onCreateInputConnection(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不對訪問進行約束。更多的信息能夠訪問Adding Image Support to IMEs
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獲取到完整的示例代碼。
想要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的更多信息,能夠參見 CommitContent IME sample, [Content Provider] (https://developer.android.com...文檔, File 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。
Google在刷新版本號的路上簡直是在策馬奔騰了,嘚兒駕。咱們也可以看到Google的努力,Android也在變的愈來愈好,加油吧,小機器人。
本次Shortcuts部分的代碼能夠在個人GitHub倉庫ZhiHuDaily中看到。歡迎star喲。