Android6.x新API介绍(一)

Android6.x新API介绍(一)

作者:李旺成
时间:2016年4月19日


接上篇:Android5.x新控件介绍(三)

新特性简介

Android M

关于 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 介绍

一、动态权限申请

什么是动态权限申请

MIUI 权限申请页面

动态权限申请也就是运行时权限,是 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
12
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// 显示对话框解释为什么要申请权限
showMessage("测试一下对SD卡进行读写操作",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which)
{

startAppSettings();
}
});
return;
}

3、申请权限

1
2
3
ActivityCompat.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
@Override
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

检测权限的adb命令

2、开启或者禁用某个权限

1
2
3
4
$ adb shell pm [grant|revoke] PACKAGE <permission-name>

示例:
$ adb shell pm grant com.diygreen.android6new android.permission.WRITE_EXTERNAL_STORAGE

自己去试试吧!这里就不贴图了。

动态权限使用示例

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
51
private 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() {
@Override
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 中稍有差别,有几个需要注意的地方:

  1. 申请权限
    上面的示例程序中都是使用 ActivityCompat.requestPermissions() 静态方法在 Activity 中申请权限的,可以看一下该方法的方法签名:
    1
    2
    public 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() 方法,这个是要注意的地方。

  1. 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 中分发回调结果
@Override
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);
}
}
}
}

动态权限小结

  1. 动态权限相关 API 建议使用静态方法(兼容包中提高)
  2. 可尝试使用第三方库简化代码(这里不做介绍,有兴趣自己去看看),例如:
    运行时注解库:PermissionGen
    编译时注解库:MPermissions
  3. 如果不使用第三方库,建议使用工具类来管理危险权限
  4. 在 Fragment 中处理动态权限的时候的一些注意事项
  5. 在 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 数据的设备都可以。

MIDI 包

Google 提供了一个示例程序:Android MidiSynth。该示例程序演示了 MIDI API 的基本功能:

  • 枚举当前的可用设备(包括名称、厂商、功能等)
  • 当 MIDI 设备插入或拔出时提示
  • 接收和处理 MIDI 信息

Android MidiSynth

这是官方提供的示例代码运行截图,手边没有可以演示的设备,所以看不到什么数据。

本来打算分析一下该示例的源码,本人实在是对这块不了解,有兴趣的童鞋自己下载代码去看看吧。

Android MidiSynth 示例代码
GitHub

四、直接分享

先看效果:

直接分享示例

上图左侧是直接分享本该有的效果,右侧是在小米 Note(汗,目前只有这个手机可以测试)上的效果。有点坑,还以为是哪里写错了,有原生系统的可以去试试。

简介

Android 6 提供了直接分享的功能,允许用户在一个应用里面分享内容到其他地方,比如联系人。

核心思想是,用户可以直接分享相关内容而无需先打开一个的应用程序再去分享,这样直接分享允许用户跳过通常的分享流程中的一个步骤。
(参考自:Implementing Android Marshmallow Direct Share

简单使用

创建自定义 ChooserTargetService

ChooserTargetService 就是个 Service,为直接分享提供 ChooserTarget 列表。
看下 ChooseTargetService 的继承结构:

ChooseTargetService 类继承结构

使用很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@TargetApi(Build.VERSION_CODES.M)
public class DirectShareService extends ChooserTargetService {

@Override
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
2
3
4
5
6
7
8
<service
android:name=".newapi.DirectShareService"
android:label="@string/app_name"
android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">
<intent-filter>
<action android:name="android.service.chooser.ChooserTargetService" />
</intent-filter>
</service>

解释一下上面的配置:

  1. 配置权限
    android:permission=”android.permission.BIND_CHOOSER_TARGET_SERVICE”
  2. 配置 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
4
final Intent intent = new Intent(Intent.ACTION_SEND)
.setType("text/plain")
.putExtra(Intent.EXTRA_TITLE, "直接分享");
startActivity(Intent.createChooser(intent, "ChooserTargetService"));

示例 Demo

GitHub

未完待续…下一篇继续介绍 Android 6.x 其余的新 API。

接下篇
AndroidStudyDemo之Android6.x新API介绍(二)

附件

Andoid6思维导图

参考

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新特性:直接分享功能

坚持原创技术分享,您的支持将鼓励我继续创作!