Android7.x技术分享

王鑫  |  2017. 04. 24   |  阅读 2403 次
无线开发

一、高版本适配

1 应用间共享文件

在Android7.0系统上,Android 框架强制执行了 StrictMode API 政策禁止向你的应用外公开 file:// URI。 如果一项包含文件 file:// URI类型 的 Intent 离开你的应用,应用失败,并出现 FileUriExposedException 异常,如调用系统相机拍照或裁切图片。

实际案例:

2 删除三项隐式广播

1.在 Android 7.0上 应用不会收到 CONNECTIVITYACTION广播,即使你在manifest清单文件中设置了请求接受这些事件的通知。但,在前台运行的应用如果使用BroadcastReceiver 请求接收通知,则仍可以在主线程中侦听 CONNECTIVITYCHANGE。

2.在 Android 7.0上应用无法发送或接收ACTIONNEWPICTUREACTIONNEWVIDEO 类型的广播。

应对策略:

  • Android 框架提供多个解决方案来缓解对这些隐式广播的需求。

例如,JobSchedulerAPI 提供了一个稳健可靠的机制来安排满足指定条件(例如连入无限流量网络)时所执行的网络操作。 您甚至可以使用 JobSchedulerAPI 来适应内容提供程序变化。

3 NDK 应用链接至平台库

从 Android 7.0 开始,系统将阻止应用动态链接非公开 NDK 库,这种库可能会导致您的应用崩溃。此行为变更旨在为跨平台更新和不同设备提供统一的应用体验。即使您的代码可能不会链接私有库,但您的应用中的第三方静态库可能会这么做。因此,所有开发者都应进行相应检查,确保他们的应用不会在运行Android 7.0 的设备上崩溃。如果您的应用使用原生代码,则只能使用公开 NDK API

image

实际案例:

二、新特性介绍

1 多窗口Playground

1.1 在相邻的窗口启动Activity

Intent intent = new Intent(MainActivity.this, SecondActivity.class);  
intent.addFlags(  
    Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT | 
    Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);  

image

1.2 启动一个禁止分屏的Activity

<activity  
    android:name=".sample.UnresizableActivity"
    android:resizeableActivity="false"
    android:taskAffinity="" />

image

1.3 跨窗口拖动数据

image

2 活动通知

2.1 捆绑通知

image

image

  • 捆绑通知包含组头通知成员通知
  • 一个组最多展示10条通知(组头通知也算一条通知)。
  • 去除成员通知后,未展示的成员通知会展示出来。
  • 长按某一条通知,出现一个单选菜单。可以选择屏蔽该App的通知。
  • 左/右滑动(滑出屏幕)组员通知可以去除该条通知;左/右滑动(滑出屏幕)组头通知会去除整个组的通知。

3 消息传递服务

3.1 直接回复

image

Notification支持在通知栏内回复。

4 直接启动

当设备已开机但用户尚未解锁设备时,AndroidN 将在安全的“直接启动”模式下运行。 为支持此操作,系统为数据提供两个存储位置:

  • 凭据加密存储,这是默认存储位置,仅在用户解锁设备后可用。
  • 设备加密存储,该存储位置在“直接启动”模式下和用户解锁设备后均可使用。

开启方式(该操作会清除用户数据并重启设备):设置->开发者选项->转换为文件加密

5 作用域目录访问

应用(如照片应用)通常只需要访问外部存储中的特定目录,例如Pictures 目录。现有的外部存储访问方法未经专门设计,无法轻松地为这些类型的应用提供目标目录访问。 例如:

  • 在您的清单中请求 READEXTERNALSTORAGE 或 WRITEEXTERNALSTORAGE 将允许访问外部存储上的所有公共目录,这可能导致访问的内容超出应用需要的内容。

  • 使用存储访问框架通常会让您的用户通过一个系统 UI 选取目录,如果应用始终访问同一个外部目录,则该操作没有任何必要。

    Android N 提供简化的全新 API 以访问通用外部存储目录。

系统尝试授予对外部目录的访问权限,并使用一个简化的UI 向用户确认访问权限(如果需要):

image

不同于Android6.0的运行时权限访问。这种运行时权限是不需要在清单文件注册权限的。

6 其他

6.2随时随地低电耗模式

Android6.0 推出了低电耗模式,即设备处于空闲状态时,通过推迟应用的 CPU 和网络活动以实现省电目的的系统模式,例如,设备放在桌上或抽屉里时。

现在,在 Android N 中,低电耗模式又前进了一步,随时随地可以省电。只要屏幕关闭了一段时间,且设备未插入电源,低电耗模式就会对应用使用熟悉的CPU 和网络限制。这意味着用户即使将设备放入口袋里也可以省电。

6.3 ProjectSvelte:后台优化

ProjectSvelte 在持续改善,以最大程度减少生态系统中一系列 Android 设备中系统和应用使用的RAM。在 Android N 中,Project Svelte 注重优化在后台中运行应用的方式。

在 Android N 中,删除了三个常用隐式广播— CONNECTIVITYACTION、ACTIONNEWPICTURE和 ACTIONNEW_VIDEO— 因为这些广播可能会一次唤醒多个应用的后台进程,同时会耗尽内存和电池。

6.4Data Saver

在移动设备的整个生命周期,蜂窝数据计划的成本通常会超出设备本身的成本。对于许多用户而言,蜂窝数据是他们想要节省的昂贵资源。

AndroidN 推出了 DataSaver 模式,这是一项新的系统服务,有助于减少应用使用的蜂窝数据,无论是在漫游,账单周期即将结束,还是使用少量的预付费数据包。Data Saver 让用户可以控制应用使用蜂窝数据的方式,同时让开发者打开Data Saver 时可以提供更多有效的服务。

用户在 Settings 中启用Data Saver 且设备位于按流量计费的网络上时,系统屏蔽后台流量消耗,同时指示应用在前台尽可能使用较少的流量— 例如,通过限制用于流媒体服务的比特率、降低图片质量、延迟最佳的预缓冲等方法来实现。用户可以将特定应用加入白名单以允许后台按流量的流量消耗,即使在打开 DataSaver 时也是如此。

AndroidN 扩展了 ConnectivityManager,以便为应用检索用户的 Data Saver 首选项并监控首选项变更提供一种方式。所有应用均应检查用户是否已启用 DataSaver 并努力限制前台和后台流量消耗。

6.5Vulkan API

AndroidN 将一项新的 3D渲染 APIVulkan™集成到平台中。就像 OpenGL™ES 一样,Vulkan 是3D 图形和渲染的一项开放标准,由KhronosGroup 维护。

Vulkan 是完全从零开始设计,以最小化驱动器中的CPU 开销,并能让您的应用更直接地控制GPU 操作Vulkan还允许多个线程同时执行工作,如命令缓冲区构建,以获得更好的并行化。

Vulkan 开发工具和库都已卷入Android NDK。它们包括:

验证层(调试库)

SPIR-V着色程序编译器

SPIR-V运行时着色器编译库

Vulkan 仅适用于已启用Vulkan硬件的设备上的应用,如 Nexus 5X、Nexus 6P 和Nexus Player。 我们正在与合作伙伴密切合作,以尽快使 Vulkan能面向更多的设备。

6.6 Quick Settings Tile API

“快速设置”通常用于直接从通知栏显示关键设置和操作,非常简单。

在 Android N 中,我们已扩展“快速设置”的范围,使其更加有用更方便。

image

image

6.7 其他的其他

  • 号码屏蔽
  • 来电过滤
  • 多区域设置支持、多语言
  • 新增表情符号
  • OpenGL™ES 3.2 API
  • AndroidTV 录制
  • Androidfor Work:谷歌最新推出的一项解决方案,旨在增加Android智能机对企业的吸引力。
  • Always on VPN
  • VR 支持:用户必须要搭配谷歌自家的VR平台“Daydream”使用
  • 打印服务增强
  • FrameMetricsListener API:监测App的 UI 渲染性能
  • 虚拟文件
  • ……

三、新功能实现

1 跨窗口拖动数据

拖动数据。数据的数据结构必须统一,这样发起方发送的数据才可以被接收方解析。这里使用的这种的数据格式是ClipData。发起方构建一个ClipData进行传输。接收方监听到Drop事件,从中取的ClipData,并进行解析。

发起方:

TextView tv_drag_data = (TextView) findViewById(R.id.tv_drag_data);  
tv_drag_data.setOnTouchListener((View v, MotionEvent event) -> {  
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        /** 构造一个ClipData,将需要传递的数据放在里面 */
        String data = tv_drag_data.getText().toString();
        ClipData.Item item = new ClipData.Item(data);
        String[] mimeTypes = {ClipDescription.MIMETYPE_TEXT_PLAIN};
        ClipData dragData = new ClipData(data, mimeTypes, item);
        View.DragShadowBuilder shadow = new View.DragShadowBuilder(tv_drag_data);
        /** startDragAndDrop是Android N SDK中的新方法,替代了以前的startDrag,flag需要设置为DRAG_FLAG_GLOBAL */
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            v.startDragAndDrop(dragData, shadow, null, View.DRAG_FLAG_GLOBAL);
        } else {
            //noinspection deprecation
            v.startDrag(dragData, shadow, null, View.DRAG_FLAG_GLOBAL);
        }
        return true;
    } else {
        return false;
    }

接收方:

TextView tv_receive_data = (TextView) findViewById(R.id.tv_receive_data);  
tv_receive_data.setOnDragListener((View v, DragEvent event) -> {  
    switch (event.getAction()) {
        case DragEvent.ACTION_DROP:
            Log.d(TAG, "ACTION_DROP event");
            /** 3.在这里显示接收到的结果 */
            String dragData = event.getClipData().getItemAt(0).getText().toString();
            Log.d(TAG, "dragData = " + dragData);
            tv_receive_data.setText(dragData);
            break;
        default:
            break;
    }
    return true;
});

2 捆绑通知

组头通知:

String notificationContent = getString(R.string.sample_notification_summary_content,  
        “” + numberOfNotifications);
final NotificationCompat.Builder builder = new NotificationCompat.Builder(getActivity())  
        .setSmallIcon(R.mipmap.ic_notification)
        .setStyle(new NotificationCompat.BigTextStyle()
                .setSummaryText(notificationContent))
        .setGroup(NOTIFICATION_GROUP)
        .setGroupSummary(true); //设置为Summary Notification(组头)
final Notification notification = builder.build();  
mNotificationManager.notify(NOTIFICATION_GROUP_SUMMARY_ID, notification); //id唯一

组员通知:

// 创建一个通知并发出
final int notificationId = getNewNotificationId();//id自增  
final NotificationCompat.Builder builder = new NotificationCompat.Builder(getActivity())  
        .setSmallIcon(R.mipmap.ic_notification) //必填项
        .setContentTitle(getString(R.string.sample_notification_title,notificationId))//必填项
        .setContentText(getString(R.string.sample_notification_content))//必填项
        .setAutoCancel(true)
        .setDeleteIntent(mDeletePendingIntent)
        .setGroup(NOTIFICATION_GROUP);//使用相同的Group名
final Notification notification = builder.build();  
mNotificationManager.notify(notificationId, notification); //id自增  

3 直接回复

//1 创建一个文本框
RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_REMOTE_REPLY)  
        .setLabel(getString(R.string.reply))
        .build();
//2 为回复绑定监听事件
NotificationCompat.Action actionReplyByRemoteInput = new NotificationCompat.Action.Builder(  
        R.drawable.notification_icon, getString(R.string.reply), replyIntent)
        .addRemoteInput(remoteInput)
        .build();
//3 把监听回复的事件绑定在通知上
NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext())  
        .setSmallIcon(R.drawable.notification_icon)
        .setContentText(messageForNotification.toString())
        .setContentTitle(conversation.getParticipantName()) 
        .set……
        ……
        .addAction(actionReplyByRemoteInput);

4 直接回复

1 注册广播监听器(手机开机未解锁的广播)

<receiver  
    android:directBootAware="true" >
    ...
    <intent-filter>
        <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
    </intent-filter>
</receiver>  

2 访问设备加密存储并打开现有应用数据文件:

Context directBootContext = appContext.createDeviceProtectedStorageContext();  
// Access appDataFilename that lives in device encrypted storage
FileInputStream inStream = directBootContext.openFileInput(appDataFilename);  
// Use inStream to read content...

5 作用域目录访问

若要使用作用域目录访问来访问可移动介质上的目录,首先要添加一个用于侦听 MEDIA_MOUNTED 通知的 BroadcastReceiver

<receiver  
    android:name=".MediaMountedReceiver"
    android:enabled="true"
    android:exported="true" >
    <intent-filter>
        <action android:name="android.intent.action.MEDIA_MOUNTED" />
        <data android:scheme="file" />
    </intent-filter>
</receiver>  

当用户装载可移动介质时,如 SD 卡,系统将发送一则 MEDIA_MOUNTED 通知。此通知在 Intent
数据中提供一个 StorageVolume 对象,您可用它访问可移动介质上的目录。 以下示例访问可移动介质上的 Pictures 目录:

// BroadcastReceiver has already cached the MEDIA_MOUNTED
// notification Intent in mediaMountedIntent
StorageVolume volume = (StorageVolume)  
        mediaMountedIntent.getParcelableExtra(StorageVolume.EXTRA_STORAGE_VOLUME);
volume.createAccessIntent(Environment.DIRECTORY_PICTURES); //Environment.DIRECTORY_PICTURES:访问的指定文件夹  
startActivityForResult(intent, request_code);  

6 Quick Tile

6.1 继承TileService重写生命周期方法

TileService生命周期:

  • onTileAdded:当开关被放置到快速设置栏
  • onStartListening:当开关被打开
  • onClick:当开关被点击
  • onStopListening:当开关被关上
  • onTileRemoved:当开关被移出快速设置栏

6.2 在AndroidManifest.xml中注册

<service  
    android:name=".sample.quicksettings.QuickSettingService"
    android:icon="@drawable/ic_android_black_24dp"
    android:label="@string/tile_label"
    android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
    <intent-filter>
        <action android:name="android.service.quicksettings.action.QS_TILE" />
    </intent-filter>
</service>  

Android 7.1篇

概述

image

随着Pixel/XL的发布。曾今的Nexus如今也沦为干儿子了。

这一次Pixel和Pixel XL售价与iPhone7和iPhone7Plus一致($649/$769),并宣称不再做性价比手机,进军高端手机市场。

Pixel/XL是首批搭载Android7.1的手机。除此之外最快能搭载的设备有Nexus5XNexus 6P以及Pixel C平板。不过,在 Pixel/XL 手机上出现的Google Assistant、Pixel Launcher 则不会出现在 Android 7.1 上,这些功能是Pixel们专享的。

Android 7.1新特性

1 圆形应用图标

在 Pixel/XL 我们已经看到原生 Android 图标圆形化的趋势,而 Google 打算将这一趋势推广到 Android 7.1 上。

Android6.0桌面图标:

image

Android7.1桌面图标:

image

2 键盘支持图片输出

键盘不仅能够打字,也能够支持图片、表情和动图等更多内容的输出。这类功能其实已经在许多第三方输入法上实现了,但在原生Android 自带输入法上尚属首次。

包含常用脸部表情、小动物、食物、建筑、车辆、天气、月相、时钟、节日特色、运动、球类、扑克、乐器、学习用品、生活用品、工具、公共场所常见标志、星座、国旗等940个表情图片。

image

3 App Shortcuts

这一功能与 iPhone 上 3D Touch 的功能相类似,就是可以在应用图标上直接添加快捷选项,点击就直接到达相关界面;但这一功能需要开发者的支持。目前开发者可以设置5 个动态和静态的快捷选项。

3.1 注册方式

  • 静态注册:定义一个xml文件,并在AndroidManifest.xml中注册
  • 动态添加:java代码动态添加

类似广播接收器(BroadcastReceiver)的静动态注册,但略有不同。

3.2 静态注册

先在res文件夹下新建一个xml文件夹,再在xml文件夹下简历一个shortcuts.xml

<?xml version="1.0" encoding="utf-8"?>  
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">  
    <shortcut
        android:shortcutId="add_website"
        android:enabled="true"
        android:icon="@drawable/add"
        android:shortcutLongLabel="@string/add_new_website"
        android:shortcutShortLabel="@string/add_new_website_short"
        android:shortcutDisabledMessage="@string/disabled">

        <intent
            android:action="android.intent.action.VIEW"
            android:targetPackage="wang.relish.android7"
            android:targetClass="wang.relish.android7.MainActivity" />
        <categories android:name="android.shortcut.conversation" />
    </shortcut>
</shortcuts>  

字段说明:

  • shortcutId:唯一id,string类型
  • enable:是否可用,默认为true
  • shortcutShortLabel:短名称,长名称显示不下时显示
  • shortcutLongLabel:长名称
  • shortcutDisabledMessage,:当前shortcut不可用时弹出的Toast信息
  • action:点击shortcut时触发的事件
  • targetPackage:应用包名。不是类包名
  • targetClass:完整类名
  • categories:目前官方只给提供了android.shortcut.conversation

3.3 动态注册

// 添加单个Shortcut
ShortcutManager mManager = getSystemService(ShortcutManager.class);  
ShortcutInfo shortcut = new ShortcutInfo  
        .Builder(this, id) //(Context, String)
        //必填项,显示文字
        .setShortLabel(shortLabel) //(String)
        //必填项,点击后触发该意图
        .setIntent(intent) //(Intent)
        //选填,显示图标
        .setIcon(icon) //(Icon)
    .build();
                //(List<ShortcutInfo>)
                //Arrays.asList(shortcut)
mManager.addDynamicShortcuts(Collections.singletonList(shortcut));  

3.4 App Shortcuts-相关……坑

  • 静态的Shortcuts使用场景:添加一个新联系人,添加一个对话等。(不可移除,不可更改)
  • 静态的Shortcuts一定是定义在Manifest里的(isDeclaredInManifest),一定是不可变的(isImmutable)
  • 动态的Shortcuts如果被放置到桌面快捷方式(pinned),即便被disable了,我们仍能取到它(getPinnedShortcuts),但它已经不在app的快捷方式栏中了,桌面图标也会变灰。
  • 动态的Shortcuts被disable后,再次enable。也无法在app的快捷方式栏里出现。
  • shortcutDisabledMessage的默认值是“无法使用快捷方式”。为了更友好,可以设置更为人性化的提醒。
  • 放置在桌面的Shortcuts(Pinned),不能被代码移除(remove),只能使其失效(disable),促使用户去手动移除。
  • 一个App最多5个Shortcuts,再次添加会报错: IllegalArgumentException: Max number of dynamic shortcutsexceeded

3.5 相关动态图

动态添加Shortcuts:

image

使一个Shortcuts失效:

image

失效的后的Shortcuts再次生效,也无法回到菜单栏:

image

将Shortcuts放到桌面上:

image

哪些Shorrcuts不能被移除:

image

四、Android7.x总结

1 Andrid个版本市场占有率(截止2017年2月)

image

2 参考资料

Android 8.0 预览版

由于Android8.0现在只有预览版,很多新特性还不确定,暂不整理,日后补充。

分享到

   
ReactNative 导航 Navigator 解析