Android 7.0篇
一、高版本适配
1 应用间共享文件
在Android7.0系统上,Android 框架强制执行了 StrictMode API 政策禁止向你的应用外公开 file:// URI。 如果一项包含文件 file:// URI类型 的 Intent 离开你的应用,应用失败,并出现 FileUriExposedException 异常,如调用系统相机拍照或裁切图片。
实际案例:
2 删除三项隐式广播
1.在 Android 7.0上 应用不会收到 CONNECTIVITY_ACTION广播,即使你在manifest清单文件中设置了请求接受这些事件的通知。但,在前台运行的应用如果使用BroadcastReceiver 请求接收通知,则仍可以在主线程中侦听 CONNECTIVITY_CHANGE。
2.在 Android 7.0上应用无法发送或接收ACTION_NEW_PICTURE 或ACTION_NEW_VIDEO 类型的广播。
应对策略:
- Android 框架提供多个解决方案来缓解对这些隐式广播的需求。
例如,JobSchedulerAPI 提供了一个稳健可靠的机制来安排满足指定条件(例如连入无限流量网络)时所执行的网络操作。 您甚至可以使用 JobSchedulerAPI 来适应内容提供程序变化。
3 NDK 应用链接至平台库
从 Android 7.0 开始,系统将阻止应用动态链接非公开 NDK 库,这种库可能会导致您的应用崩溃。此行为变更旨在为跨平台更新和不同设备提供统一的应用体验。即使您的代码可能不会链接私有库,但您的应用中的第三方静态库可能会这么做。因此,所有开发者都应进行相应检查,确保他们的应用不会在运行Android 7.0 的设备上崩溃。如果您的应用使用原生代码,则只能使用公开 NDK API。
实际案例:
二、新特性介绍
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);
1.2 启动一个禁止分屏的Activity
<activity
android:name=".sample.UnresizableActivity"
android:resizeableActivity="false"
android:taskAffinity="" />
1.3 跨窗口拖动数据
2 活动通知
2.1 捆绑通知
- 捆绑通知包含组头通知和成员通知。
- 一个组最多展示10条通知(组头通知也算一条通知)。
- 去除成员通知后,未展示的成员通知会展示出来。
- 长按某一条通知,出现一个单选菜单。可以选择屏蔽该App的通知。
- 左/右滑动(滑出屏幕)组员通知可以去除该条通知;左/右滑动(滑出屏幕)组头通知会去除整个组的通知。
3 消息传递服务
3.1 直接回复
Notification支持在通知栏内回复。
4 直接启动
当设备已开机但用户尚未解锁设备时,AndroidN 将在安全的“直接启动”模式下运行。 为支持此操作,系统为数据提供两个存储位置:
- 凭据加密存储,这是默认存储位置,仅在用户解锁设备后可用。
- 设备加密存储,该存储位置在“直接启动”模式下和用户解锁设备后均可使用。
开启方式(该操作会清除用户数据并重启设备):设置->开发者选项->转换为文件加密
5 作用域目录访问
应用(如照片应用)通常只需要访问外部存储中的特定目录,例如Pictures 目录。现有的外部存储访问方法未经专门设计,无法轻松地为这些类型的应用提供目标目录访问。 例如:
在您的清单中请求 READ_EXTERNAL_STORAGE 或 WRITE_EXTERNAL_STORAGE 将允许访问外部存储上的所有公共目录,这可能导致访问的内容超出应用需要的内容。
使用存储访问框架通常会让您的用户通过一个系统 UI 选取目录,如果应用始终访问同一个外部目录,则该操作没有任何必要。
Android N 提供简化的全新 API 以访问通用外部存储目录。
系统尝试授予对外部目录的访问权限,并使用一个简化的UI 向用户确认访问权限(如果需要):
不同于Android6.0的运行时权限访问。这种运行时权限是不需要在清单文件注册权限的。
6 其他
6.2随时随地低电耗模式
Android6.0 推出了低电耗模式,即设备处于空闲状态时,通过推迟应用的 CPU 和网络活动以实现省电目的的系统模式,例如,设备放在桌上或抽屉里时。
现在,在 Android N 中,低电耗模式又前进了一步,随时随地可以省电。只要屏幕关闭了一段时间,且设备未插入电源,低电耗模式就会对应用使用熟悉的CPU 和网络限制。这意味着用户即使将设备放入口袋里也可以省电。
6.3 ProjectSvelte:后台优化
ProjectSvelte 在持续改善,以最大程度减少生态系统中一系列 Android 设备中系统和应用使用的RAM。在 Android N 中,Project Svelte 注重优化在后台中运行应用的方式。
在 Android N 中,删除了三个常用隐式广播— CONNECTIVITY_ACTION、ACTION_NEW_PICTURE和 ACTION_NEW_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 中,我们已扩展“快速设置”的范围,使其更加有用更方便。
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篇
概述
随着Pixel/XL的发布。曾今的Nexus如今也沦为干儿子了。
这一次Pixel和Pixel XL售价与iPhone7和iPhone7Plus一致($649/$769),并宣称不再做性价比手机,进军高端手机市场。
Pixel/XL是首批搭载Android7.1的手机。除此之外最快能搭载的设备有Nexus5X 、Nexus 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桌面图标:
Android7.1桌面图标:
2 键盘支持图片输出
键盘不仅能够打字,也能够支持图片、表情和动图等更多内容的输出。这类功能其实已经在许多第三方输入法上实现了,但在原生Android 自带输入法上尚属首次。
包含常用脸部表情、小动物、食物、建筑、车辆、天气、月相、时钟、节日特色、运动、球类、扑克、乐器、学习用品、生活用品、工具、公共场所常见标志、星座、国旗等940个表情图片。
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:
使一个Shortcuts失效:
失效的后的Shortcuts再次生效,也无法回到菜单栏:
将Shortcuts放到桌面上:
哪些Shorrcuts不能被移除:
四、Android7.x总结
1 Andrid个版本市场占有率(截止2017年2月)
2 参考资料
- Android7.0行为变更:https://developer.android.com/about/versions/nougat/android-7.0-changes.html
- Android7.0示例:https://developer.android.com/about/versions/nougat/android-7.0-samples.html
- 安卓7.1开发者预览版10月底发布,正式版12月推出: https://www.oschina.net/news/77985/android-7-1-released-at-october
- Android 7.1 for Developers: https://developer.android.com/about/versions/nougat/android-7.1.html
- Android 7.0 Nougat Made for you: https://www.android.com/versions/nougat-7-0/
Android 8.0 预览版
由于Android8.0现在只有预览版,很多新特性还不确定,暂不整理,日后补充。