您的位置:首页 > 移动开发 > Android开发

Android M 部分API变动研究

2015-11-08 15:45 711 查看
Android M发布有一段时间了,AndroidM在API上的改动,并不比AndroidL小。我们只能感谢Google又给android程序员带来的新的研究课题,以前开发的程序如何才能在AndroidM上顺利运行,因此,让无所事事的程序又要折腾一阵子了。

权限请求方式变更

动态权限请求是Google在对Android的安全问题进行一次非常有效的提升,对用户是非常有用的,用户可以很任性的关掉App一些权限,比如打电话,发短信。然而,对Android程序员来说,却是非常致命,曾经天经地义可以使用的API尽然会抛异常导致程序crash了。这让我们的代码逻辑完全不适用了。

要搞清这事儿,得先从Android 权限定义的变动说起。

权限的分类

首先Google把Android的权限分为了两类,一类叫正常权限(Normal Permission),另一类叫危险权限(Dangerous Permisson )。

正常权限:当你的应用程序访问外部数据或操作的行为对用户的隐私暴露风险很小,这种类型的权限定义为正常权限,比如打开闪光灯,访问网络,对用户没啥大影响。这类权限使用方式和以前一样,只要在manifest中声明了,系统就会自动赋予权限。

ACCESS_LOCATION_EXTRA_COMMANDS

ACCESS_NETWORK_STATE

ACCESS_ WIFI_STATE BLUETOOTH

CHANGE_NETWORK_STATE

CHANGE_WIFI_MULTICAST_STATE

CHANGE_WIFI_STATE

DISABLE_KEYGUARD

EXPAND_STATUS_BAR

FLASHLIGHT

GET_PACKAGE_SIZE INTERNET

KILL_BACKGROUND_PROCESSES

MODIFY_AUDIO_SETTINGS NFC

READ_SYNC_SETTINGS

READ_SYNC_STATS

RECEIVE_BOOT_COMPLETED

REORDER_TASKS

REQUEST_INSTALL_PACKAGES

SET_TIME_ZONE SET_WALLPAPER

SET_WALLPAPER_HINTS

TRANSMIT_IR USE_FINGERPRINT

VIBRATE WAKE_LOCK

WRITE_SYNC_SETTINGS

SET_ALARM INSTALL_SHORTCUT

UNINSTALL_SHORTCUT

危险权限:当应用程序需要访问用户的个人数据,或是影响用户已保存的数据时发生的权限。当使用这类权限时,那就必须显式的让用户同意(iOS也是这么做的)。危险权限本身是分组的,每一组之内的权限只需要申明一次。



比如上表中ACCESS_FINE_LOCATION和ACCESS_COARSE_LOCATION是同属于LOCATION这个权限组,那么只要在申请ACCESS_FINE_LOCATION时,用户同意了,则系统自动会赋与ACCESS_COARSE_LOCATION的权限,不需要重新申请了。

如果用户运行手机在Android 5.1以下(含),或者是编译时targetSDK在22以下(含), 那么你当然需要把所有用到的权限放到manifest中,用户一旦安装时同意了权限,你的程序就有了所有权限,如果用户不同意,则无法安装(我们一直就是这么做的,就不废话了)。

如果用户的设备是在Android 6以上,并且编译时的targetSDK不幸被Wizard自动赋予到23以上,那恭喜你,中奖了。你就需要在使用危险权限的时候,向用户说明申请,就不能偷偷的在后面打枪了。

检查权限

在使用危险权限的时候,首先检查自己当前是否具有权限。在v4包中有个类可以使用。

int permissionCheck = ContextCompat.checkSelfPermission(context, Manifest.permission.SEND_SMS);


当有权限时,返回PackageManager.PERMISSION_GRANTED,没有权限时,返回PackageManager.PERMISSION_DENIED。如果返回后者,那么就需要申请权限了。

告知用户为什么要用权限

在弹出权限申请对话框(系统对话框)之前,推荐的做法是在某些情况下先简单解释下为什么我需要这个权限,当然这个解释不能太罗嗦,要言简意赅。比如在使用美图秀秀时,告诉用户我要使用camera权限,用户都能很好理解。在support包中有个工具,

ActivityCompat.shouldShowRequestPermissionRationale(context, Manifest.permission.SEND_SMS)


当第一次请求或是用户关闭了这个权限时,返回true,其他情况下返回False,当然sdk 23以下的手机总是返回false.

如果收到true, 那么你需要提示用户你为什么需要权限。

申请权限

当你确定你需要申请权限(在检查权限方法返回PERMISSION_DENIED),你就可以调用申请权限的api。这个API支持批量申请,但是不建议一次申请太多,一般都是用到什么权限申请什么权限,因为用户需要对每一个权限做确认。

这个API也是在ActivityCompat中,他的函数签名是这样的。

requestPermissions(final @NonNull Activity activity, final @NonNull String[] permissions, final int requestCode)


第二个参数是要申请的权限列表,第三个参数很眼熟吧,requestCode,你猜对了,就像是startActivityForResult中的requestCode一样,这个是用来处理回调的。

回调方法是:

onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,

@NonNull int[] grantResults)

注意当你开始使用ActivityCompat时,你Activity的基类就需要是ActivityCompat或是它的子类

这个方法的参数中requestCode用于对应你之前requestPermission中的值,grantResults中的结果与permissions中的权限按顺序一一对应。grantResults的结果是PackageManager.PERMISSION_GRANTED或PackageManager.PERMISSION_DENIED之一。

实例

//首先,这段代码运行在继承了ActivityCompat的Activity中。
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {

//检查是不是要给用户解释为什么?
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.READ_CONTACTS)) {

//做点什么,弹个Toast或是SnackBar吧

}

//请求权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);

}


@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
//如果用户取消,permissions可能为null.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 可以安全的调用api。

} else {

// 洗洗睡吧,没戏了。
}
return;
}

}
}


测试

要模拟用户的实际操作以及一些额外的情况,和测试每一个权限的开启和关闭,可以借助adb工具来完成。

按组列出权限

$ adb shell pm list permissions -d -g




开启或禁用某个权限

$ adb shell pm [grant|revoke] PACKAGE <permission-name>


如:pm revoke com.win16.recyclerviewdemo android.permission.RECORD_AUDIO

或:pm grant com.win16.recyclerviewdemo android.permission.RECORD_AUDIO

检查危险权限

如果你的应用程序权限太多,不太清楚哪些是危险权限需要特别处理,那么可以使用这个python脚本进行检查。

https://github.com/hotchemi/m-permissions-checker

使用方法:

$ cd <root your app>
$ python permissions_checker.py

> Searching file: /Users/hoge/test/data/AndroidManifest.xml
> Unfortunately, you have to handle these permissions in MNC.
> android.permission.READ_CALENDAR
> android.permission.WRITE_CALENDAR
> android.permission.CAMERA


Apache HTTP Client的删除

在Android M中,Google已经删除了apache的http相应的包,如果你还是想继续使用,那么需要在gradle脚本中加入这一段,

android {
useLibrary 'org.apache.http.legacy'
}


但是,最好还是使用HttpURLConnection。这个API更加的高效,有透明的压缩功能和响应缓存,能耗也更低。

如果觉得这个比较原始,也可以使用okHttp库,这个库封装了网络的请求流程,使用也非常方便。

新的省电模式

从Android M起,引入了两种新的省电模式,Doze 和 App Standby。当手机没有插电源的时候,如果一段时间内应用程序没有与用户交互,则进入了App Standby模式,此时后台的网络活动会被延迟,如果有很长时间手机都没有使用手机,手机也是关屏状态,则会进入了Doze模式,此时包括CPU的任务和网络都有一定延迟。

Doze模式

前面说到,为了省电,在Doze模式下,应用的网络活动,CPU使用,甚至包括同步功能,和标准Alarm都统统被干掉了。但是,Google也知道,事情不能做得太绝,于是每隔一段时间,就会把应用程序喊起来干活(maintainance Window),处理事情后,又接着睡。再次进入Doze模式。干活的间隔也是越来越久,慢慢衰减。



在Doze模式下,以下活动会被推迟。

网络访问

Wake Lock也不起作用

标准的AlarmManager也会被推迟到下一窗口,

WIFI扫描也会停掉

任务调度(JobScheduler)停止活动

同步功能(sync adapters)停止活动

Doze模式适配

推荐情况下,不要修改任何代码,以加强用户手机的续航能力,但是通常产品经理都特别,总会有点特别的要求,那要怎么满足呢。Android实际上也为我们留了点后门。

AlarmManager中新引入了两个方法setAndAllowWhileIdle()或setExactAndAllowWhileIdle()能够在Doze模式下唤醒手机并工作,但是这个技能是有冷却时间的,每15分钟内只能使用一次。

如果对实时性有要求,Google推荐使用GCM….对此,中国开发者只能翻白眼。

Standby 模式

Standby模式只限制后台程序,对于前台的程序不限制,而前台程序的定义如下 :

用户显式起动的程序

程序的进程当前在前台

程序在锁屏界面或是在通知栏有活动

当插上电时,standby模式自动取消,各应用程序可自由活动,想干什么干什么。

后门模式

当然,很可能应用会有一些特殊的需求,通过GCM还是没能满足。这个时候,就可以使用后门模式,(白名单),你可以在应用中使用带有ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS的Intent向用户进行请求,要求用户把你加到白名单中,这样,这个应用就可以豁免部分Doze模式的限制。注意,这是部分,毕竟是走的后门,不能为所欲为。在白名单中的应用可以使用网络,也可以使用PARTIAL_WAKE_LOCK这个WAKELOCK, 但是,像同步,任务调度,甚至普通的Alarm都还是不能使用。你可以使用PowerManagerr的isIgnoringBatteryOptimizations方法来检查你是不是在白名单中。

测试

要模拟进入Doze模式,可以在关屏下使用以下adb命令:

$ adb shell dumpsys battery unplug
$ adb shell dumpsys deviceidle step


对于第二条命令要多次使用,直接进入Doze模式。

模拟Standby模式,使用以下Adb命令。

$ adb shell dumpsys battery unplug
$ adb shell am set-inactive <packageName> true


唤醒使用以下命令

$ adb shell am set-inactive <packageName> false
$ adb shell am get-inactive <packageName>


(To be continue。。。)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: