老司机带你吃牛轧糖--适配Android 7.1 Nougat新特性

1337896903 7年前
   <h3>What's new in Android 7.1 Nougat?</h3>    <p>Android 7.1 Nougat 已经推出有一段时间,相信大多数人和我一样,并没有用上最新的系统,但是,总有一群走在时代的前列线上的Geek们,勇于尝鲜,艰苦奋斗,为刷新版本号贡献自己的力量。好吧,实际上就是我还没有用上7.1,有些眼馋了。那么,和开发者息息相关的有哪些新特性呢?</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/6b1b5ee08e2d33e7ec8ace3c91a94bfa.png"></p>    <p style="text-align:center">Android 7.1 Nougat</p>    <p>本次主要介绍3个新特性: <strong>App Shortcuts</strong> , <strong>Round Icon Resource</strong> 和 <strong>Image Keyboard Support</strong> 。</p>    <h3>App Shortcuts</h3>    <p>作为一个密切关注Android发展的伪Geek,在7.1正式版未发布之前,通过网上的一些爆料文章,我就了解到了这一新功能。实际上,这个功能刚开始出现时,我还以为Google Pixel要上压感屏了呢,事实证明,的确是我想多了。</p>    <p>App Shortcuts允许用户直接在启动器中显示一些操作,让用户立即执行应用的深层次的功能。触发这一功能的操作就是「长按」。这一功能类似于iOS中的「3D Touch」。</p>    <p>下面通过一张GIF,直观的感受一下App Shortcuts是怎样的。(由于我的一加3并没有升级到最新的7.1,还只是7.0,所以我安装了Nova Launcher来体验。)</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c0bdae3196429b7ad3c2bdc95296fba5.gif"></p>    <p style="text-align:center">App Shortcuts</p>    <p>长按图标,收到震动后松手,如果能够看到图标上弹出了支持的跳转操作,说明成功的呼出了Shortcuts功能,如果不支持这一功能,在Nova Launcher上弹出的就是卸载或者移除操作,在Pixel Launcher上不会出现弹出菜单,显示的是常见的长按操作。长按弹出的操作,可以将这个操作已快捷方式图标的形式直接放置在主屏上。如果长按主图标不松手,就可以调整位置了。</p>    <p>目前,一个应用最多可以支持 <strong>5</strong> 个Shortcut,可以通过 getMaxShortcutCountPerActivity 查看Launcher最多支持Shortcut的数量。每一个Shortcut都对应着一个或者多个intent,当用户选择某一个Shortcut时,应该做出特定的动作。下面是一些将一些特定的动作作为Shortcuts的例子:</p>    <ul>     <li> <p>在地图APP中,指引用户至最常用的位置</p> </li>     <li> <p>在聊天APP中,发送信息至某个好友</p> </li>     <li> <p>在多媒体APP中,播放下一个电视节目</p> </li>     <li> <p>在游戏APP中,加载至上次保存的地方</p> </li>    </ul>    <p>App Shortcut可以分为两种不同的类型: Static Shortcuts(静态快捷方式) 和 Dynamic Shortcuts(动态快捷方式)。</p>    <ul>     <li>Static Shortcuts:在打包到apk的资源文件中定义,所以,直到下一次更新版本时才能改变静态快捷方式的详细说明。</li>     <li>Dynamic Shortcuts:通过ShortcutManager API在运行时发布,在运行时,应用可以发布,升级和移除快捷方式。</li>    </ul>    <p>Using Static Shortcuts</p>    <p>创建Static Shortcuts分为以下几步:</p>    <p>1.在工程的manifest文件 (AndroidManifest.xml)下,找到 intent filter设置为 <strong>android.intent.action.MAIN</strong> 和 <strong>android.intent.category.LAUNCHER</strong> 的Activity。</p>    <p>2.在次Activity下添加<meta-data>标签,引用定义shortcuts的资源文件。</p>    <pre>  <code class="language-java"><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>    <p>3.创建新的资源文件 res/xml/shortcuts.xml</p>    <pre>  <code class="language-java"><?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></code></pre>    <p>shortcut下标签的含义:</p>    <ul>     <li> <p>enabled:见名知意,shortcut是否可用。如果你决定让这个static shortcut不在可用的话,可直接将其设置为 <strong>false</strong> ,或者直接从 <strong>shortcuts</strong> 标签中移除。</p> </li>     <li> <p>icon:显示在左边的图标,可用使用 <em>Vector drawable</em> 。</p> </li>     <li> <p>shortcutDisabledMessage:当禁用此shortcut后,它仍然会出现在用户长按应用图标后的快捷方式列表里,也可以被拖动并固定到桌面上,但是它会呈现灰色并且用户点击时会弹出Toast这个标签所定义的内容。</p> </li>     <li> <p>shortcutLongLabel:当启动器有足够多的空间时,会显示这个标签所定义的内容。</p> </li>     <li> <p>shortcutShortLabel:shortcut的简要说明,是必需字段。当shortcut被添加到桌面上时,显示的也是这个字段。</p> </li>     <li> <p>intent:shortcut关联的一个或者多个intent,当用户点击shortcut时被打开。</p> </li>     <li> <p>shortcutId:shortcut的唯一标示id,若存在具有相同shortcutId的shortcut,则只显示一个。</p> </li>    </ul>    <p>到这里,最简单的shortcut就添加成功了。运行包含上面的文件的项目,点击shortcut就可以直接进入 <strong>SearchActivity</strong> ,当按下back键时,直接就退出了应用。如果希望不退出应用,而是进入 <strong>MainActivity</strong> 时,应该怎么办呢?不用着急,在shortcut继续添加intent就可以了。</p>    <pre>  <code class="language-java"><?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></code></pre>    <p>Using Dynamic Shortcuts</p>    <p>动态快捷方式应该和应用内的特定的、上下文敏感的action链接。这些action应该可以在用户的几次使用之间、甚至是在应用运行过程中被改变。好的候选action包括打电话给特定的人、导航至特定的地方、或者展示当前游戏的分数。</p>    <p>ShortcutManager API允许我们在动态快捷方式上完成下面的操作:</p>    <ul>     <li> <p>发布:使用setDynamicShortcuts()重新定义整个动态快捷方式列表,或者是使用addDynamicShortcuts()向已存在的动态快捷方式列表中添加快捷方式。</p> </li>     <li> <p>更新:使用updateShortcuts()方法。</p> </li>     <li> <p>移除:使用removeDynamicShortcuts()方法移除特定动态快捷方式或者使用removeAllDynamicShortcuts()移除所有动态快捷方式。</p> </li>    </ul>    <p>下面是在MainActivity的onCreate()中创建动态快捷方式的例子:</p>    <pre>  <code class="language-java">@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));  }</code></pre>    <p>也可以为动态快捷方式创建返回栈。</p>    <pre>  <code class="language-java">@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));  }</code></pre>    <p>创建一个新的空的Activity,名字叫做DynamicShortcutActivity,在manifest文件中注册。</p>    <pre>  <code class="language-java"><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></code></pre>    <p>通过清除array中的排序过的intents,当我们通过创建好的shortcut进入DynamicShortcutActivity之后,按下back键,MainActivity就会被加载。</p>    <p>需要注意的是,在动态创建快捷方式之前,最好是检查一下是否超过了所允许的最大值。否则会抛出相应的异常。</p>    <p>Extra Bits</p>    <ul>     <li> <p>当static shortcut 和 dynamic shortcut一起展示时,其出现的顺序是怎样定制呢?</p> <p>在 <strong>ShortcutInfo.Builder</strong> 中有一个专门的方法 <strong>setRank(int)</strong> ,通过设置不同的等级,我们就可以控制动态快捷方式的出现顺序,等级越高,出现在快捷方式列表中的位置就越高。</p> </li>     <li> <p>我们还可以设置动态快捷方式的shortLabel的字体颜色。</p> <pre>  <code class="language-java">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();</code></pre> </li>    </ul>    <p>App Shortcuts Best Practices</p>    <p>当设计和创建应用的shortcuts时,应该遵守下面的指导建议:</p>    <ul>     <li> <p>遵循设计规范:为了保持我们的应用和系统应用的快捷方式在视觉上一致性,应该遵守 App Shortcuts Design Guidelines 。</p> </li>     <li> <p>发布4个不同的快捷方式:尽管现在的API支持静态和动态总共5个快捷方式,但是为了提高shortcut的视觉效果,建议只添加4个不同的快捷方式。</p> </li>     <li> <p>限制快捷方式描述的文本长度:在Launcher中,显示快捷方式时,空间长度受到了限制。如果可能的话,应该将「short description」的文字长度控制在10个字母以内,将「long discription」的长度限制在25个字母以内。</p> </li>     <li> <p>保存shortcut和action的历史记录:创建的每一个shortcut,应该考虑到用户能够通过不同的方式完成相同的任务。在这种情况下,记得调用 <strong>reportShortcutUsed()</strong> 方法,这样,launcher就可以提高shortcut对应的actions的反应速度。</p> </li>     <li> <p>只有在shortcuts的意义存在时更新:当改变动态快捷方式时,只有在shortcut仍然保持着它的含义时,调用 <strong>updateShortcuts()</strong> 方法改变它的信息。否则,应该使用 <strong>addDynamicShortcuts()</strong> 或者 <strong>setDynamicShortcuts()</strong> 创建一个具有新含义的ID的快捷方式。</p> <p>举个例子,如果我们已经创建了导航到一个超市的快捷方式,如果超市的名称改变了但是位置并没有变化时,只更新信息是合适的。但是如果用户开始在一个不同位置的超市购物时,最好是创建一个全新的快捷方式(而不仅仅是更新信息了)。</p> </li>     <li> <p>在备份和恢复时,动态shortcuts不应该被保存:正是因为这个原因,推荐我们在需要APP启动和重新发布动态快捷方式时,检查 <strong>getDynamicShortcuts()</strong> 的对象的数量。可以参考 <a href="/misc/goto?guid=4959732588106003929" rel="nofollow,noindex">Backup and Restore</a> 部分的代码片段。</p> </li>    </ul>    <h3>Round Icon Resources</h3>    <p>在Android 7.1上,Google推出了一个部分用户可能不太喜欢的特性--圆形图标。圆形图标长什么样,可以看看下面的图。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/08b193163b194df6aa7db5279d2fbd4e.png"></p>    <p style="text-align:center">round icon</p>    <p>同时,圆形图标规范也作为一部分内容加入到了更新说明和开发文档中。</p>    <p>应用程序现在可以定义圆形启动器图标以用于特定的移动设备之上。当启动器请求应用程序图标时,程序框架应返回 android:icon 或 android:roundIcon,视设备具体要求而定。因此,应用程序在开发时应该确保同时定义 android:icon和 android:roundIcon 两个变量。您可以使用 Image Asset Studio 来设计圆形图标。</p>    <p>您应该确保在支持新的圆形图标的设备上测试您的应用程序,以确保应用程序图标的外观无虞和实际效果。测试您的资源的一种方法是在 Google Pixel 设备上安装您的应用。您还可以通过运行 Android 模拟器并使用 Google API 模拟器系统(目标 API 等级为 25)测试您的图标。</p>    <p>我们可以通过 <strong>Android Studio</strong> 自带的 <strong>Image Asset Studio</strong> 设计图标。在项目的 <strong>res</strong> 目录下点击鼠标右键,选择 <strong>new</strong> --> <strong>Image Asset</strong> 即可设计图标。</p>    <p><img src="https://simg.open-open.com/show/0e06be2179573da95909e92eccd0a08c.png"></p>    <p style="text-align:center">Image Asset Studio</p>    <h3>Image Keyboard Support</h3>    <p>在较早版本的Android系统中,软键盘(例如我们所熟知的Input Method Editors,或者说IME),只能够给应用发送unicode编码的emoji,对于rich content,应用只能通过使用自建的私有的API实现发送图片的功能。而在Android 7.1中,SDK包含了一个全新的Commit Content API,输入法应用不仅可以调用此 API 实现发送图片和其他rich content,一些通讯应用(比如 Google Messenger)也可以通过此 API 来更好地处理这些来自输入法的图片、网页信息和 GIF 内容。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/2a86feb5935ad03754938ad33d89baa6.png"></p>    <p style="text-align:center">image keyboard sample</p>    <p>How it works</p>    <ol>     <li> <p>当用户点击EditText时, editor会发送一个它所能接受的 <strong>EditorInfo.contentMimeTypes</strong> MIME 内容类型的列表。</p> </li>     <li> <p>IME读取这个在软键盘中支持类型和展示内容的列表。</p> </li>     <li> <p>当用户选择一张图片后,IME调用 <strong>commitContent()</strong> 并向editor发送一个InputContentInfo。 <strong>commitContent()</strong> 方法是一个类似于 <strong>commitText()</strong> 的方法,但是是rich content的。 <strong>InputContentInfo</strong> 包含着一个表示content provider中内容的URI。然后我们的应用就可以请求相应的权限并读取URI中的内容。</p> </li>    </ol>    <p style="text-align:center"><img src="https://simg.open-open.com/show/763e5b00b8ae42d0c8e0d616de39a9fc.png"></p>    <p style="text-align:center">image keyboard diagram</p>    <p>Adding Image Support to Apps</p>    <p>为了接收来自IME的rich content,应用必须告诉IME它所能接收的内容类型并之指定当接收到内容后的回调方法。下面是一个怎样创建一个能够接收PNG图片的 <strong>EditText</strong> 的演示代码。</p>    <pre>  <code class="language-java">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);      }  };</code></pre>    <p>代码还是蛮多的,解释一下。</p>    <ul>     <li> <p>例子使用了support library,并且引用的是 <strong>android.support.v13.view.inputmethod</strong> 而不是 <strong>android.view.inputmethod</strong> 。</p> </li>     <li> <p>例子创建了一个 <strong>EditText</strong> 并复写了它改变 <strong>InputConnection</strong> 的 <strong>onCreateInputConnection(EditorInfo)</strong> 方法. <strong>InputConnection</strong> 是IME和正在接收输入的沟通管道。</p> </li>     <li> <p>调用 <strong>super.onCreateInputConnection()</strong> 保留了内建的行为(包括发送和接收文本),并提供给我们一个 <strong>InputConnection</strong> 的引用。</p> </li>     <li> <p>setContentMimeTypes()向 <strong>EditorInfo</strong> 添加了一个所支持的MIME类型的列表。 需要保证在 <strong>setContentMimeTypes()</strong> 之前调用 <strong>super.onCreateInputConnection()</strong> 。</p> </li>     <li> <p>回调在IME提交内容是被执行。 <strong>onCommitContent()</strong> 方法有一个对包含了内容URI的 <strong>InputContentInfoCompat</strong> 的引用。</p>      <ul>       <li>当我们的应用运行在API Level 25或者更高并且IME设置了 <strong>INPUT_CONTENT_GRANT_READ_URI_PERMISSION</strong> flag时,我们应该请求并且释放权限。否则,我们应该在此之前就拥有content URI的访问权限,一是因为权限是由IME授权的,二是content provider不对访问进行约束。</li>      </ul> </li>     <li> <p>createWrapper()包装了inputConnection和已修改的editorInfo,新的InputConnection的回调并且返回。</p> </li>    </ul>    <p>下面是一些实践小技巧。</p>    <ul>     <li> <p>不支持rich content的Editor不应该调用 <strong>setContentTypes()</strong> 并把 <strong>EditorInfo.contentMimeTypes</strong> 设置为null。</p> </li>     <li> <p>Editor应该忽略掉在 <strong>InputConnectionInfo</strong> 中指定的MIME类型和所接收类型不通的内容。</p> </li>     <li> <p>rich content不影响也不被文本指针的位置所影响。editor在进行内容处理是可以直接忽略掉光标的位置。</p> </li>     <li> <p>在editor的 <strong>OnCommitContentListener.onCommitContent()</strong> 方法中,我们可以异步的返回true,甚至是在加载内容之前。</p> </li>     <li> <p>不同于文本内容在被提交之前可以在IME中被编辑,rich content会被立即提交。需要注意特性,如果想要提供编辑或者删除内容的能力,我们需要自己提供处理逻辑。</p> </li>    </ul>    <p>为了测试APP,需要确保你的设备或者虚拟机的键盘能够发送rich content。你可以在Android 7.1或者更高的系统中使用Google Keyboard,或者是安装 CommitContent IME sample .</p>    <p>你可以在 CommitContent App sample 获取到完整的示例代码。</p>    <p>Adding Image Support to IMEs</p>    <p>想要IME支持发送rich content,需要引入下面所展示的Commit Content API。</p>    <ul>     <li> <p>复写 <strong>onStartInput()</strong> 或者 <strong>onStartInputView()</strong> ,并读取来自目标editor的支持内容类型列表。</p> <pre>  <code class="language-java">@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        }    }</code></pre> </li>     <li> <p>当用户选择了一张图片时,将内容提交给APP。当IME有正在编辑的文本时,应该避免调用 <strong>commitContent()</strong> ,因为这样可能导致editor失去焦点。下面的代码片段展示了怎样提交一张GIF图片。</p> <pre>  <code class="language-java">/**     * 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);    }</code></pre> </li>     <li> <p>作为一个IME开发者,有很大可能你需要引入你自己的content provider来响应content URI请求。如果你的IME支持来自像 <strong>MediaStore</strong> 这样已经存在的content provider倒是可以例外。</p> </li>     <li> <p>如果正在创建自己的content provider,建议不要export(将 android:export 设置为false)。通过设置 <strong>android:grandUriPermission</strong> 为true允许在provider内部进行权限授予替代。然后,你的IME在内容提交时可以授予访问content URI的权限。有两种实现的方法:</p>      <ul>       <li> <p>在Android 7.1(API Level 25)或更高的系统中,当调用 <strong>commitContent</strong> 方法时,将flag参数设置为 <strong>INPUT_CONTENT_GRANT_READ_URI_PERMISSION</strong> 。然后,APP收到的 <strong>InputContentInfo</strong> 对象可以通过调用 <strong>requestPermission()</strong> 方法和 <strong>releasePermission()</strong> 请求和释放临时访问权限。</p> </li>       <li> <p>在Android 7.0(API Level 24)或者更低的系统中, <strong>INPUT_CONTENT_GRANT_READ_URI_PERMISSION</strong> 直接被忽略,所以我们需要手动的授予内容访问权限。方法就是 <strong>grantUriPermission()</strong> ,但是我们也可以引入满足自己要求的机制。</p> </li>      </ul> </li>    </ul>    <p>权限授予的例子,我们可以在 CommitContent IME sample 中的doCommitContent()方法。</p>    <p>为了测试IME,确保我们的设备或者模拟器拥有接收rich content的的应用。我们可以在Android 7.1或者更高的系统中使用Google Messenger应用或者安装 CommitContent App Sample 。</p>    <p>获取完整的示例代码,可以访问 <a href="/misc/goto?guid=4959732588203862671" rel="nofollow,noindex">CommitContent IME Sample</a> 。</p>    <h3>Summary</h3>    <p>Google在刷新版本号的路上简直是在策马奔腾了,嘚儿驾。我们也能够看到Google的努力,Android也在变的越来越好,加油吧,小机器人。</p>    <p> </p>    <p> </p>    <p>来自:http://www.jianshu.com/p/f56f2e709ad8</p>    <p> </p>