
作者:李旺成
時(shí)間:2016年4月19日
接上篇:AndroidStudyDemo之Android5.x新控件介紹(三)
新特性簡介

關(guān)于 Android 6 的新特性的文章非常多,這里我不打算做過多的介紹。在搜索了幾篇文章后,我對 Android 6 的新特性做了個(gè)簡單的分類(粗糙的分了下,別介),有興趣的可以稍微瞄一眼。
這里只是對 Android 6 的新特性做個(gè)簡單的總結(jié)歸納,為了縮短篇幅,就不貼圖了。想了解更多,可以下載文末的思維導(dǎo)圖,里面有備注。
硬件相關(guān)
- 指紋識別
- Doze 電量管理
- 相機(jī)新增專業(yè)模式
- 支持 RAW 格式照片
- Chrome Custom Tabs
- 外置存儲融入系統(tǒng)存儲中
- 藍(lán)牙 SAP
- 支持 MIDI
- 支持 WIFI 熱點(diǎn)2.0
- USB Type-C 端口支持
安全相關(guān)
- 更完整的應(yīng)用權(quán)限管理
- 啟動驗(yàn)證
體驗(yàn)優(yōu)化
- 鎖屏下語音搜索
- 支持 4K 顯示
- 存儲管理
支持文件夾拖拽應(yīng)用 - Now on Tap功能
- App Links
- 改進(jìn)通知
- Google Now Launcher
- 文字選擇界面
- 音量控制界面
- 鎖屏界面
- 直接分享
- 全新的啟動動畫
- 系統(tǒng)界面調(diào)諧器
快速設(shè)置
狀態(tài)欄
顯示電池百分比
演示模式
性能優(yōu)化
- App Standby
- 內(nèi)存管理
其他
- Android Pay
- 分屏顯示
- USB 連接選項(xiàng)
- 一些謎之特性
消失的 Dark Theme
改進(jìn)Android for Work
整合Android Wear
或許存在的一鍵關(guān)閉全部最近應(yīng)用
增加信息中心
更快的更新機(jī)制
更開放應(yīng)用程序卸載選項(xiàng)
新 API 介紹#
一、動態(tài)權(quán)限申請
什么是動態(tài)權(quán)限申請

動態(tài)權(quán)限申請也就是運(yùn)行時(shí)權(quán)限,是 Android 6.0 上帶來的權(quán)限管理新模式(就不說 iOS 上很早就有這個(gè)了,MIUI 上也很早就有了該特性...)。
在 6.0 以前,Android 對權(quán)限的處理是一刀切,就是安裝的時(shí)候給出一個(gè)權(quán)限列表提示,用戶同意了那就可以安裝(然后,就沒有然后了...)。而在 Android 6.0 以后,可以直接安裝,當(dāng) App 真正用到或者說申請某些權(quán)限時(shí),系統(tǒng)會給出讓用戶授權(quán)的提示,這時(shí)可以拒絕。當(dāng)然也提供了設(shè)置界面對每個(gè) App 的權(quán)限進(jìn)行管理。
Android 6.x 權(quán)限分類
在 Android 6.x 上對權(quán)限進(jìn)行了分類,包括正常權(quán)限和危險(xiǎn)權(quán)限。
正常權(quán)限:
定義:
訪問外部數(shù)據(jù)或操作的行為對用戶的隱私暴露風(fēng)險(xiǎn)很小的權(quán)限。
列舉:
ACCESS_LOCATION_EXTRA_COMMANDS
CHANGE_WIFI_STATE
FLASHLIGHT
SET_ALARM INSTALL_SHORTCUT
UNINSTALL_SHORTCUT
危險(xiǎn)權(quán)限:
定義:
需要訪問用戶的個(gè)人數(shù)據(jù),或是影響用戶已保存的數(shù)據(jù)時(shí)發(fā)生的權(quán)限。
列舉:
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 上危險(xiǎn)權(quán)限進(jìn)行了分組處理,這有什么作用?
在 Android 6.x 上,授權(quán)機(jī)制是這樣的:如果 App 申請某個(gè)危險(xiǎn)的權(quán)限,假設(shè)該 App 已被授權(quán)過該組的某個(gè)權(quán)限,那么系統(tǒng)會立即授權(quán),而不需要用戶再點(diǎn)擊授權(quán)。
例如,如果你對某 App 授權(quán)過 SEND_SMS,那么當(dāng)該 App 申請 RECEIVE_SMS 等屬于 SMS Group 下的權(quán)限時(shí),系統(tǒng)會直接授權(quán)通過。
注意:上圖中的權(quán)限申請對話框上面的文本說明是對權(quán)限組的說明,而不是單個(gè)權(quán)限(PS:這個(gè)對話框是系統(tǒng)提供的)。
說明:在定義上述權(quán)限是,沒有給出前綴的,其前綴都是“ android.permission.”,這里為了清晰起見就沒有添加了。
Android 6.x 危險(xiǎn)權(quán)限處理
危險(xiǎn)權(quán)限處理流程

通過上面的流程圖基本上可以了解 Andorid 6.x 對危險(xiǎn)權(quán)限的處理流程,這里就不再用文字描述。
權(quán)限處理相關(guān) API
先看看效果:

1、檢查是否有權(quán)限
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
2、解釋權(quán)限申請?jiān)?,引?dǎo)用戶授權(quán)
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// 顯示對話框解釋為什么要申請權(quán)限
showMessage("測試一下對SD卡進(jìn)行讀寫操作",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
startAppSettings();
}
});
return;
}
3、申請權(quán)限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
REQUEST_CODE_ASK_EXTERNAL_STORAGE_PERMISSON);
4、獲取用戶是否授權(quán)結(jié)果
@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, "用戶確認(rèn)授權(quán)操作SD卡權(quán)限", Toast.LENGTH_SHORT).show();
} else { // Permission Denied
Toast.makeText(DynamicPermissionActivity.this, "用戶拒絕授權(quán)操作SD卡權(quán)限", Toast.LENGTH_SHORT).show();
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
break;
}
}
危險(xiǎn)權(quán)限測試
1、按組列出權(quán)限
$ adb shell pm list permissions -d -g

2、開啟或者禁用某個(gè)權(quán)限
$ adb shell pm [grant|revoke] PACKAGE <permission-name>
示例:
$ adb shell pm grant com.diygreen.android6new android.permission.WRITE_EXTERNAL_STORAGE
自己去試試吧!這里就不貼圖了。
動態(tài)權(quán)限使用示例
1. 申請權(quán)限
<!--危險(xiǎn)權(quán)限-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
2. 使用示例
示例代碼很簡單,直接看代碼:
private void testSDCardPermission() {
if (mCheckSwitch.isChecked()) {
// 1. 判斷是否有權(quán)限
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// 在彈出權(quán)限選擇的對話框前給用戶show一個(gè)dialog,用于引導(dǎo)用戶進(jìn)行選擇
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// 解釋為什么要申請權(quán)限
showMessage("測試一下對SD卡進(jìn)行讀寫操作",
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, "有權(quán)限了開始讀寫SD卡吧!", Toast.LENGTH_LONG).show();
}
} else {
// 1. 判斷是否有權(quán)限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
&&
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// 2. 彈出對話框申請權(quán)限給用戶選擇
// 第二個(gè)參數(shù)code與onRequestPermissionResult()方法中的code對應(yīng)
ActivityCompat.requestPermissions(this, EXTERNAL_STORAGE_PERMISSIONS, REQUEST_CODE_ASK_EXTERNAL_STORAGE_PERMISSON);
} else {
Toast.makeText(DynamicPermissionActivity.this, "有權(quán)限了開始讀寫SD卡吧!", Toast.LENGTH_LONG).show();
}
}
}
private void showMessage(String message,
DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(this)
.setMessage(message)
.setNegativeButton("取消", null)
.setPositiveButton("設(shè)置", okListener).create().show();
}
// 啟動應(yīng)用的設(shè)置
private void startAppSettings() {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse(PACKAGE_URL_SCHEME + getPackageName()));
startActivity(intent);
}
Fragment 中動態(tài)權(quán)限處理
在 Fragment 中動態(tài)權(quán)限的使用與 Activity 中稍有差別,有幾個(gè)需要注意的地方:
- 申請權(quán)限
上面的示例程序中都是使用 ActivityCompat.requestPermissions() 靜態(tài)方法在 Activity 中申請權(quán)限的,可以看一下該方法的方法簽名:
public static void requestPermissions(final @NonNull Activity activity,
final @NonNull String[] permissions, final int requestCode)
第一個(gè)參數(shù)是 Activity,當(dāng)然在 Fragment 中可以通過 getActivity() 方法獲取它所 attach 的 Activity,但這就導(dǎo)致了一個(gè)問題 —— requestPermissions() 方法是異步的,它的返回值會回調(diào)到第一個(gè)參數(shù)傳入的 Activity 中的 onRequestPermissionsResult() 方法上。
當(dāng)然,你可以將回調(diào)結(jié)果再傳給 Fragment,但是,這樣做不是平添了麻煩嘛。
這里可以直接使用 Fragment 中提供的 requestPermissions() 方法,這個(gè)是要注意的地方。
- Fragment 嵌套
Fragment 嵌套往往有不少地方需要注意,在動態(tài)權(quán)限申請的時(shí)候也一樣。你會發(fā)現(xiàn)在嵌套的子 Fragment 中使用上述方法申請權(quán)限后,onRequestPermissionsResult() 回調(diào)方法并沒有執(zhí)行。其實(shí)解決的方法和剛才提過的思路是一樣的 —— 交給父 Fragment 去處理(上面的例子就是可以交給 Activity 去處理),然后再將回調(diào)結(jié)果傳給子 Fragment。
示例代碼:
// 在子 Fragment 中通過父 Fragment 申請權(quán)限
getParentFragment().requestPermissions(permissions, requestCode);
// 在父 Fragment 中分發(fā)回調(diào)結(jié)果
@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);
}
}
}
}
動態(tài)權(quán)限小結(jié)
- 動態(tài)權(quán)限相關(guān) API 建議使用靜態(tài)方法(兼容包中提高)
- 可嘗試使用第三方庫簡化代碼(這里不做介紹,有興趣自己去看看),例如:
運(yùn)行時(shí)注解庫:PermissionGen
編譯時(shí)注解庫:MPermissions - 如果不使用第三方庫,建議使用工具類來管理危險(xiǎn)權(quán)限
- 在 Fragment 中處理動態(tài)權(quán)限的時(shí)候的一些注意事項(xiàng)
- 在 AndroidStudio 中會提醒檢測權(quán)限是否開啟,可以自動生成代碼

二、獲取硬件標(biāo)識符
簡介
在 Andorid 6.x 中,為了更好的保護(hù)用戶的數(shù)據(jù),移除了從代碼中通過 Wi-Fi 和藍(lán)牙的 API 訪問硬件標(biāo)識符。因此 WifiInfo.getMacAddress() 和BluetoothAdapter.getAddress() 將始終返回 02:00:00:00:00:00 而為了能夠通過Wi-Fi和藍(lán)牙掃描時(shí),獲取周邊設(shè)備的硬件標(biāo)識符,應(yīng)用必須具有ACCESS_FINE_LOCATION 和 ACCESS_COARSE_LOCATION權(quán)限:
WifiManager.getScanResults()
BluetoothDevice.ACTION_FOUND
BluetoothLeScanner.startScan()
(參考自:值得你關(guān)注的Android6.0上的重要變化(一))
注意:當(dāng)運(yùn)行 Android6.0(API level 23) 的設(shè)備啟動后臺 Wi-Fi 或藍(lán)牙掃描時(shí), 此操作對外部設(shè)備是可見的,且被顯示為一個(gè)隨機(jī)MAC的地址。
三、MIDI API
Android 6.x 提供了對 MIDI 的支持,這些 API 都在 android.media.midi 包下。MIDI API 可以用于從連接的 MIDI 輸入設(shè)備接收和播放 MIDI 信息。從 Google 的展示來看,幾乎任意可以輸出 MIDI 音符和 CC 數(shù)據(jù)的設(shè)備都可以。

Google 提供了一個(gè)示例程序:Android MidiSynth。該示例程序演示了 MIDI API 的基本功能:
- 枚舉當(dāng)前的可用設(shè)備(包括名稱、廠商、功能等)
- 當(dāng) MIDI 設(shè)備插入或拔出時(shí)提示
- 接收和處理 MIDI 信息

這是官方提供的示例代碼運(yùn)行截圖,手邊沒有可以演示的設(shè)備,所以看不到什么數(shù)據(jù)。
本來打算分析一下該示例的源碼,本人實(shí)在是對這塊不了解,有興趣的童鞋自己下載代碼去看看吧。
Android MidiSynth 示例代碼
GitHub
四、直接分享
先看效果:

上圖左側(cè)是直接分享本該有的效果,右側(cè)是在小米 Note(汗,目前只有這個(gè)手機(jī)可以測試)上的效果。有點(diǎn)坑,還以為是哪里寫錯(cuò)了,有原生系統(tǒng)的可以去試試。
簡介
Android 6 提供了直接分享的功能,允許用戶在一個(gè)應(yīng)用里面分享內(nèi)容到其他地方,比如聯(lián)系人。
核心思想是,用戶可以直接分享相關(guān)內(nèi)容而無需先打開一個(gè)的應(yīng)用程序再去分享,這樣直接分享允許用戶跳過通常的分享流程中的一個(gè)步驟。
(參考自:Implementing Android Marshmallow Direct Share)
簡單使用
創(chuàng)建自定義 ChooserTargetService
ChooserTargetService 就是個(gè) Service,為直接分享提供 ChooserTarget 列表。
看下 ChooseTargetService 的繼承結(jié)構(gòu):

使用很簡單:
@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
<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>
解釋一下上面的配置:
- 配置權(quán)限
android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE" - 配置 intent-filter
<action android:name="android.service.chooser.ChooserTargetService" />
配置響應(yīng) ChooserTargetService 的 Activity
對于每一個(gè)你想要暴露給 ChooserTargetService 的 Activity,都需要加上 meta-data 。指定 name 是 android.service.chooser.chooser_target_service,在 Service 定義之前指明它的 value。當(dāng)一個(gè)隱式的 Intent 和其中一個(gè) Activity 匹配,這個(gè) intent 的選擇對話框?qū)@示包含這個(gè) Activity 的應(yīng)用的 ICON 同時(shí)還會顯示 ChooserTarget 列表的圖標(biāo)。選擇應(yīng)用的圖標(biāo)會像標(biāo)準(zhǔn)的分享 Intent 一樣,選擇 ChooserTarget icon 的時(shí)候會打開對應(yīng)的 Activity,根據(jù) Intent 傳遞過來的數(shù)據(jù)初始化數(shù)據(jù)(在 ChooserTargetService 中指定的)。
(參考自:實(shí)現(xiàn)安卓6.0的直接分享(Direct Share )功能)
清單文件中配置:
<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>
發(fā)起直接分享 Intent
直接看代碼:
final Intent intent = new Intent(Intent.ACTION_SEND)
.setType("text/plain")
.putExtra(Intent.EXTRA_TITLE, "直接分享");
startActivity(Intent.createChooser(intent, "ChooserTargetService"));
示例 Demo
未完待續(xù)...下一篇繼續(xù)介紹 Android 6.x 其余的新 API。
接下篇
AndroidStudyDemo之Android6.x新API介紹(二)
附件
參考
Android 6.0有哪些新功能新特性 安卓6.0功能詳細(xì)介紹
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 中的新技術(shù)總結(jié)
值得你關(guān)注的Android6.0上的重要變化(一)
值得你關(guān)注的Android6.0上的重要變化(二)
Everything every Android Developer must know about new Android’s Runtime Permission
Working with System Permissions
Permissions Best Practices
Android 6.0 運(yùn)行時(shí)權(quán)限處理完全解析
Android M 動態(tài)權(quán)限獲取
Android 6.0: 動態(tài)權(quán)限管理的解決方案
android 6.0權(quán)限全面詳細(xì)分析和解決方案
Android M 動態(tài)權(quán)限獲取
Android 6.0 運(yùn)行時(shí)權(quán)限處理
在Android 6.0 設(shè)備上動態(tài)獲取權(quán)限
Implementing Android Marshmallow Direct Share
安卓6.0新特性:直接分享功能