作者:李旺成
时间:2016年4月19日
新特性简介
关于 Android 6 的新特性的文章非常多,这里我不打算做过多的介绍。在搜索了几篇文章后,我对 Android 6 的新特性做了个简单的分类(粗糙的分了下,别介),有兴趣的可以稍微瞄一眼。
这里只是对 Android 6 的新特性做个简单的总结归纳,为了缩短篇幅,就不贴图了。想了解更多,可以下载文末的思维导图,里面有备注。
硬件相关
- 指纹识别
- Doze 电量管理
- 相机新增专业模式
- 支持 RAW 格式照片
- Chrome Custom Tabs
- 外置存储融入系统存储中
- 蓝牙 SAP
- 支持 MIDI
- 支持 WIFI 热点2.0
- USB Type-C 端口支持
安全相关
- 更完整的应用权限管理
- 启动验证
体验优化
- 锁屏下语音搜索
- 支持 4K 显示
- 存储管理
支持文件夹拖拽应用 - Now on Tap功能
- App Links
- 改进通知
- Google Now Launcher
- 文字选择界面
- 音量控制界面
- 锁屏界面
- 直接分享
- 全新的启动动画
- 系统界面调谐器
快速设置
状态栏
显示电池百分比
演示模式
性能优化
- App Standby
- 内存管理
其他
- Android Pay
- 分屏显示
- USB 连接选项
- 一些谜之特性
消失的 Dark Theme
改进Android for Work
整合Android Wear
或许存在的一键关闭全部最近应用
增加信息中心
更快的更新机制
更开放应用程序卸载选项
新 API 介绍
一、动态权限申请
什么是动态权限申请
动态权限申请也就是运行时权限,是 Android 6.0 上带来的权限管理新模式(就不说 iOS 上很早就有这个了,MIUI 上也很早就有了该特性…)。
在 6.0 以前,Android 对权限的处理是一刀切,就是安装的时候给出一个权限列表提示,用户同意了那就可以安装(然后,就没有然后了…)。而在 Android 6.0 以后,可以直接安装,当 App 真正用到或者说申请某些权限时,系统会给出让用户授权的提示,这时可以拒绝。当然也提供了设置界面对每个 App 的权限进行管理。
Android 6.x 权限分类
在 Android 6.x 上对权限进行了分类,包括正常权限和危险权限。
正常权限:
定义:
访问外部数据或操作的行为对用户的隐私暴露风险很小的权限。
列举:
ACCESS_LOCATION_EXTRA_COMMANDS
CHANGE_WIFI_STATE
FLASHLIGHT
SET_ALARM INSTALL_SHORTCUT
UNINSTALL_SHORTCUT
危险权限:
定义:
需要访问用户的个人数据,或是影响用户已保存的数据时发生的权限。
列举:
CALENDAE
READ_CALENDAR
WRITE_CALENDAR
CAMERA
CAMERA
CONTACTS
READ_CONTACTS
WRITE_CONTACTS
READ_PROFILE
WRITE_PROFILE
LOCATION
ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION
MICROPHONE
RECORD_AUDIO
PHONE
READ_PHONE_STATE
CALL_PHONE
READ_CALL_LOG
WRITE_CALL_LOG
com.android.voicemail.permission.ADD_VOICEMAIL
USE_SIP
PROCESS_OUTGOING_CALLS
SENSORS
BODY_SENSORS
USE_FINGERPRINT
SMS
SEND_SMS
RECEIVE_SMS
READ_SMS
RECEIVE_WAP_PUSH
RECEIVE_MMS
READ_CELL_BROADCASTS
STORAGE
READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE
在 Android 6.x 上危险权限进行了分组处理,这有什么作用?
在 Android 6.x 上,授权机制是这样的:如果 App 申请某个危险的权限,假设该 App 已被授权过该组的某个权限,那么系统会立即授权,而不需要用户再点击授权。
例如,如果你对某 App 授权过 SEND_SMS,那么当该 App 申请 RECEIVE_SMS 等属于 SMS Group 下的权限时,系统会直接授权通过。
注意:上图中的权限申请对话框上面的文本说明是对权限组的说明,而不是单个权限(PS:这个对话框是系统提供的)。
说明:在定义上述权限是,没有给出前缀的,其前缀都是“ android.permission.”,这里为了清晰起见就没有添加了。
Android 6.x 危险权限处理
危险权限处理流程
通过上面的流程图基本上可以了解 Andorid 6.x 对危险权限的处理流程,这里就不再用文字描述。
权限处理相关 API
先看看效果:
1、检查是否有权限1
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
2、解释权限申请原因,引导用户授权1
2
3
4
5
6
7
8
9
10
11
12if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// 显示对话框解释为什么要申请权限
showMessage("测试一下对SD卡进行读写操作",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
startAppSettings();
}
});
return;
}
3、申请权限1
2
3ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
REQUEST_CODE_ASK_EXTERNAL_STORAGE_PERMISSON);
4、获取用户是否授权结果1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE_ASK_EXTERNAL_STORAGE_PERMISSON:
// Permission Granted
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(DynamicPermissionActivity.this, "用户确认授权操作SD卡权限", Toast.LENGTH_SHORT).show();
} else { // Permission Denied
Toast.makeText(DynamicPermissionActivity.this, "用户拒绝授权操作SD卡权限", Toast.LENGTH_SHORT).show();
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
break;
}
}
危险权限测试
1、按组列出权限
1 | $ adb shell pm list permissions -d -g |
2、开启或者禁用某个权限
1 | $ adb shell pm [grant|revoke] PACKAGE <permission-name> |
自己去试试吧!这里就不贴图了。
动态权限使用示例
1. 申请权限1
2
3<!--危险权限-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
2. 使用示例
示例代码很简单,直接看代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51private void testSDCardPermission() {
if (mCheckSwitch.isChecked()) {
// 1. 判断是否有权限
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// 在弹出权限选择的对话框前给用户show一个dialog,用于引导用户进行选择
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// 解释为什么要申请权限
showMessage("测试一下对SD卡进行读写操作",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
startAppSettings();
}
});
return;
}
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
REQUEST_CODE_ASK_EXTERNAL_STORAGE_PERMISSON);
} else {
Toast.makeText(DynamicPermissionActivity.this, "有权限了开始读写SD卡吧!", Toast.LENGTH_LONG).show();
}
} else {
// 1. 判断是否有权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
&&
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// 2. 弹出对话框申请权限给用户选择
// 第二个参数code与onRequestPermissionResult()方法中的code对应
ActivityCompat.requestPermissions(this, EXTERNAL_STORAGE_PERMISSIONS, REQUEST_CODE_ASK_EXTERNAL_STORAGE_PERMISSON);
} else {
Toast.makeText(DynamicPermissionActivity.this, "有权限了开始读写SD卡吧!", Toast.LENGTH_LONG).show();
}
}
}
private void showMessage(String message,
DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(this)
.setMessage(message)
.setNegativeButton("取消", null)
.setPositiveButton("设置", okListener).create().show();
}
// 启动应用的设置
private void startAppSettings() {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse(PACKAGE_URL_SCHEME + getPackageName()));
startActivity(intent);
}
Fragment 中动态权限处理
在 Fragment 中动态权限的使用与 Activity 中稍有差别,有几个需要注意的地方:
- 申请权限
上面的示例程序中都是使用 ActivityCompat.requestPermissions() 静态方法在 Activity 中申请权限的,可以看一下该方法的方法签名:1
2public static void requestPermissions(final @NonNull Activity activity,
final @NonNull String[] permissions, final int requestCode)
第一个参数是 Activity,当然在 Fragment 中可以通过 getActivity() 方法获取它所 attach 的 Activity,但这就导致了一个问题 —— requestPermissions() 方法是异步的,它的返回值会回调到第一个参数传入的 Activity 中的 onRequestPermissionsResult() 方法上。
当然,你可以将回调结果再传给 Fragment,但是,这样做不是平添了麻烦嘛。
这里可以直接使用 Fragment 中提供的 requestPermissions() 方法,这个是要注意的地方。
- Fragment 嵌套
Fragment 嵌套往往有不少地方需要注意,在动态权限申请的时候也一样。你会发现在嵌套的子 Fragment 中使用上述方法申请权限后,onRequestPermissionsResult() 回调方法并没有执行。其实解决的方法和刚才提过的思路是一样的 —— 交给父 Fragment 去处理(上面的例子就是可以交给 Activity 去处理),然后再将回调结果传给子 Fragment。
示例代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 在子 Fragment 中通过父 Fragment 申请权限
getParentFragment().requestPermissions(permissions, requestCode);
// 在父 Fragment 中分发回调结果
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
List<Fragment> fragmentList = getChildFragmentManager().getFragments();
if (fragmentList != null) {
for (Fragment fragment : fragmentList) {
if (fragment != null) {
fragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}
}
动态权限小结
- 动态权限相关 API 建议使用静态方法(兼容包中提高)
- 可尝试使用第三方库简化代码(这里不做介绍,有兴趣自己去看看),例如:
运行时注解库:PermissionGen
编译时注解库:MPermissions - 如果不使用第三方库,建议使用工具类来管理危险权限
- 在 Fragment 中处理动态权限的时候的一些注意事项
- 在 AndroidStudio 中会提醒检测权限是否开启,可以自动生成代码
二、获取硬件标识符
简介
在 Andorid 6.x 中,为了更好的保护用户的数据,移除了从代码中通过 Wi-Fi 和蓝牙的 API 访问硬件标识符。因此 WifiInfo.getMacAddress() 和BluetoothAdapter.getAddress() 将始终返回 02:00:00:00:00:00 而为了能够通过Wi-Fi和蓝牙扫描时,获取周边设备的硬件标识符,应用必须具有ACCESS_FINE_LOCATION 和 ACCESS_COARSE_LOCATION权限:
WifiManager.getScanResults()
BluetoothDevice.ACTION_FOUND
BluetoothLeScanner.startScan()
(参考自:值得你关注的Android6.0上的重要变化(一))
注意:当运行 Android6.0(API level 23) 的设备启动后台 Wi-Fi 或蓝牙扫描时, 此操作对外部设备是可见的,且被显示为一个随机MAC的地址。
三、MIDI API
Android 6.x 提供了对 MIDI 的支持,这些 API 都在 android.media.midi 包下。MIDI API 可以用于从连接的 MIDI 输入设备接收和播放 MIDI 信息。从 Google 的展示来看,几乎任意可以输出 MIDI 音符和 CC 数据的设备都可以。
Google 提供了一个示例程序:Android MidiSynth。该示例程序演示了 MIDI API 的基本功能:
- 枚举当前的可用设备(包括名称、厂商、功能等)
- 当 MIDI 设备插入或拔出时提示
- 接收和处理 MIDI 信息
这是官方提供的示例代码运行截图,手边没有可以演示的设备,所以看不到什么数据。
本来打算分析一下该示例的源码,本人实在是对这块不了解,有兴趣的童鞋自己下载代码去看看吧。
Android MidiSynth 示例代码
GitHub
四、直接分享
先看效果:
上图左侧是直接分享本该有的效果,右侧是在小米 Note(汗,目前只有这个手机可以测试)上的效果。有点坑,还以为是哪里写错了,有原生系统的可以去试试。
简介
Android 6 提供了直接分享的功能,允许用户在一个应用里面分享内容到其他地方,比如联系人。
核心思想是,用户可以直接分享相关内容而无需先打开一个的应用程序再去分享,这样直接分享允许用户跳过通常的分享流程中的一个步骤。
(参考自:Implementing Android Marshmallow Direct Share)
简单使用
创建自定义 ChooserTargetService
ChooserTargetService 就是个 Service,为直接分享提供 ChooserTarget 列表。
看下 ChooseTargetService 的继承结构:
使用很简单:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 (Build.VERSION_CODES.M)
public class DirectShareService extends ChooserTargetService {
public List<ChooserTarget> onGetChooserTargets(ComponentName targetActivityName, IntentFilter matchedFilter) {
ComponentName componentName = new ComponentName(getPackageName(),
ShareActivity.class.getCanonicalName());
ArrayList<ChooserTarget> targets = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Bundle extras = new Bundle();
extras.putInt("directsharekey", i);
targets.add(new ChooserTarget(
"name_" + i,
Icon.createWithResource(this, R.mipmap.ic_logo),
0.5f,
componentName,
extras));
}
return targets;
}
}
在清单文件中配置 ChooserTargetService
1 | <service |
解释一下上面的配置:
- 配置权限
android:permission=”android.permission.BIND_CHOOSER_TARGET_SERVICE” - 配置 intent-filter
配置响应 ChooserTargetService 的 Activity
对于每一个你想要暴露给 ChooserTargetService 的 Activity,都需要加上 meta-data 。指定 name 是 android.service.chooser.chooser_target_service,在 Service 定义之前指明它的 value。当一个隐式的 Intent 和其中一个 Activity 匹配,这个 intent 的选择对话框将会显示包含这个 Activity 的应用的 ICON 同时还会显示 ChooserTarget 列表的图标。选择应用的图标会像标准的分享 Intent 一样,选择 ChooserTarget icon 的时候会打开对应的 Activity,根据 Intent 传递过来的数据初始化数据(在 ChooserTargetService 中指定的)。
(参考自:实现安卓6.0的直接分享(Direct Share )功能)
清单文件中配置:1
2
3
4
5
6
7
8
9
10<activity android:name=".newapi.ShareActivity">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<meta-data
android:name="android.service.chooser.chooser_target_service"
android:value=".newapi.DirectShareService" />
</activity>
发起直接分享 Intent
直接看代码:1
2
3
4final Intent intent = new Intent(Intent.ACTION_SEND)
.setType("text/plain")
.putExtra(Intent.EXTRA_TITLE, "直接分享");
startActivity(Intent.createChooser(intent, "ChooserTargetService"));
示例 Demo
未完待续…下一篇继续介绍 Android 6.x 其余的新 API。
接下篇
AndroidStudyDemo之Android6.x新API介绍(二)
附件
参考
Android 6.0有哪些新功能新特性 安卓6.0功能详细介绍
Android 6.0 新功能和新特性
Android:Android 6.0新特性
Android 6.0新特性[zz]
作为 Android 史上最人性化的升级,Android M 的新功能全在这
Android M 部分API变动研究
6.0 版本的 Android M 加入了 MIDI API
Android 6.0 中的新技术总结
值得你关注的Android6.0上的重要变化(一)
值得你关注的Android6.0上的重要变化(二)
Everything every Android Developer must know about new Android’s Runtime Permission
Working with System Permissions
Permissions Best Practices
Android 6.0 运行时权限处理完全解析
Android M 动态权限获取
Android 6.0: 动态权限管理的解决方案
android 6.0权限全面详细分析和解决方案
Android M 动态权限获取
Android 6.0 运行时权限处理
在Android 6.0 设备上动态获取权限
Implementing Android Marshmallow Direct Share
安卓6.0新特性:直接分享功能