Android各个版本特性介绍及适配
一、Android 6(api 23)
-
运行时权限
对于Android 6.0以上系统的手机,以下危险权限需要使用
checkSelfPermission()
方法来鉴别是否有该权限,如果没有该权限,需要使用requestPermissions()
来申请权限1、相机权限
Manifest.permission.CAMERA
2、存储权限
Manifest.permission.READ_EXTERNAL_STORAGE 读取存储权限
Manifest.permission.WRITE_EXTERNAL_STORAGE 写入存储权限
3、录音权限
Manifest.permission.RECORD_AUDIO
4、电话权限
Manifest.permission.CALL_PHONE 拨号权限
Manifest.permission.READ_PHONE_STATE
5、定位权限
Manifest.permission.ACCESS_FINE_LOCATION
Manifest.permission.ACCESS_COARSE_LOCATION
6、日历权限
Manifest.permission.READ_CALENDAR 读取日历权限
Manifest.permission.WRITE_CALENDAR 写入日历权限
7、通讯录权限
Manifest.permission.WRITE_CONTACTS 写入联系人权限
Manifest.permission.READ_CONTACTS 读取联系人权限
Manifest.permission.GET_ACCOUNTS 获取设备账号权限
判断是否有该权限示例:
//检查是否有相机权限 ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
请求示例:
//请求读取日历权限 ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR}, REQ_CODE.CALENDAR); //REQ_CODE.CALENDAR为选择后的回调requestCode
请求结束回调:
//待完善
如果在Android 6.0及以上手机中,不判断并且请求相关权限,会发生崩溃哦
在实际开发中,笔者比较喜欢用RxPermission的第三方库,以观察者模式的方式进行回调的监听,比较方便哦~
-
取消支持 Apache HTTP 客户端
-
通知
-
音频管理器变更
二、Android 7(api 24)
-
隐式广播
Android 7.0 移除了三项隐式广播,以帮助优化内存使用和电量消耗。此项变更很有必要,因为隐式广播会在后台频繁启动已注册侦听这些广播的应用。删除这些广播可以显著提升设备性能和用户体验。
移动设备会经历频繁的连接变更,例如在 WLAN 和移动数据之间切换时。目前,可以通过在应用清单中注册一个接收器来侦听隐式 CONNECTIVITY_ACTION 广播,让应用能够监控这些变更。由于很多应用会注册接收此广播,因此单次网络切换即会导致所有应用被唤醒并同时处理此广播。
同理,在之前版本的 Android 中,应用可以注册接收来自其他应用(例如相机)的隐式 ACTION_NEW_PICTURE 和 ACTION_NEW_VIDEO 广播。当用户使用相机应用拍摄照片时,这些应用即会被唤醒以处理广播。
为缓解这些问题,Android 7.0 应用了以下优化措施:
面向 Android 7.0 开发的应用不会收到 CONNECTIVITY_ACTION 广播,即使它们已有清单条目来请求接受这些事件的通知。在前台运行的应用如果使用 BroadcastReceiver 请求接收通知,则仍可以在主线程中侦听 CONNECTIVITY_CHANGE。 应用无法发送或接收 ACTION_NEW_PICTURE 或 ACTION_NEW_VIDEO 广播。此项优化会影响所有应用,而不仅仅是面向 Android 7.0 的应用。 如果您的应用使用任何 intent,您仍需要尽快移除它们的依赖关系,以正确适配 Android 7.0 设备。Android 框架提供多个解决方案来缓解对这些隐式广播的需求。例如,JobScheduler API 提供了一个稳健可靠的机制来安排满足指定条件(例如连入无限流量网络)时所执行的网络操作。您甚至可以使用 JobScheduler 来适应内容提供程序变化。 --- 参考自Google Android 官方文档
当你想要让之前老代码中的隐式广播能够继续正常work,只需要增加一行代码就可以解决:
intent.setPackage(ContextHolder.getContext().getPackageName());
设置Package就可以正常work了
-
应用间共享文件
此前获取uri路径方法:
Uri.fromFile(new file(localPath))
如果还用此方法会出现crash
这时候就要使用到推荐的FileProvider来解决此问题
首先需要在Manifest清单中注册我们的FileProvider
<provider android:name="android.support.v4.content.FileProvider" android:authorities="com.gnet.onemeeting.fileSelect.fileprovider" android:exported="false" android:grantUriPermissions="true"> <!-- 元数据 --> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
authorities为作者,在调用中需要使用到作者
exported = false 是设置让其他程序不可以调用到我的FileProvider
grantUriPermissions 属性值为“true”,则该 Provider 内所有的数据都能被授权访问。 可它如果设为“false”,则只有本元素指定的数据子集才能被授权
那么@xml/file_paths是什么呢?这是我们能够获取并且加密的所有文件,需要自己定义
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <files-path name="gnet_sdk_files" path="." /> <external-path name="gnet_sdk_external" path="." /> <cache-path name="gnet_sdk_cache" path="." /> <external-files-path name="gnet_sdk_external_files" path="." /> <external-cache-path name="gnet_sdk_external_cache" path="." /> </paths>
path = "."
path中设置可获取哪些文件,"."代表可获取此路径下所有文件files-path
代表 /data/user/0/<package_name>/files或者/data/data/<package_name>/files这两个目录指向相同的位置external-path
/storage/emulated/0或者/sdcard/cache-path
代表代表/data/user/0/<package_name>/cache或者/data/data/<package_name>/cacheexternal-files-path
代表/storage/emulated/0/Android/data/<package_name>/files 或者 /sdcard/Android/data/<package_name>/filesexternal-cache-path
代表/storage/emulated/0/Android/data/<package_name>/cache 或者 /sdcard/Android/data/<package_name>/cachename
为被加密后被暴露出去的时候的路径文件名称使用方法为:
FileProvider.getUriForFile(context, context.getPackageName() + ".fileSelect.fileprovider", new File(localPath));
第二个属性为之前manifest中配置的authorities(作者)
三、Android 8(api 26)
-
后台限制
问题:针对 Android 8.0 的应用尝试在不允许其创建后台服务的情况下使用
startService()
函数,则该函数将引发一个IllegalStateException
。解决方案:新的
Context.startForegroundService()
函数将启动一个前台服务。现在,即使应用在后台运行,系统也允许其调用Context.startForegroundService()
。不过,应用必须在创建服务后的五秒内调用该服务的startForeground()
函数。此方案会在用户的下拉更多窗口中看到一个常显的通知栏,告诉用户你的app正在前台运行
示例:
Intent intent = new Intent(context, LogService.class); try { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {//手机大于Android8及以上系统 context.startForegroundService(intent);//使用前台服务 } else { context.startService(intent); } } catch (Exception e) { LogUtil.w(TAG, "startService -> failed, exception :", e); }
然后需要在LogService的
onStartCommond()
生命周期中进行startForeGround()
方法开启前台,来解除5s的anr
String channelId = "com_gnet_bee_channel_id"; //通道的id,可随意选取,但是要在新建Notification时保持一致
String channelName = service.getString(R.string.uc_foreground_service_name); //通道的name
NotificationManager manager = (NotificationManager) service.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_NONE);
if (manager != null) {
manager.createNotificationChannel(channel); //创建通道
Notification notification = new Notification.Builder(service.getApplicationContext(), channelId).build();
service.startForeground(666, notification);
}
String channelId = "com_gnet_bee_channel_id"; //通道的id,可随意选取,但是要在新建Notification时保持一致
String channelName = service.getString(R.string.uc_foreground_service_name); //通道的name
NotificationManager manager = (NotificationManager) service.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_NONE);
if (manager != null) {
manager.createNotificationChannel(channel); //创建通道
Notification notification = new Notification.Builder(service.getApplicationContext(), channelId).build();
service.startForeground(666, notification);//开启前台
}
- 语言区域和国际化
Android 7.0(API 级别 24)引入能指定默认类别语言区域的概念,但是某些 API 在本应使用默认 DISPLAY 类别语言区域时,仍然使用不带参数的通用 Locale.getDefault()
函数。现在,在 Android 8.0 中,以下函数使用 Locale.getDefault(Category.DISPLAY)
来代替 Locale.getDefault()
:
Currency.getDisplayName()
Currency.getSymbol()
Locale.getDisplayScript()
当为 Locale 参数指定的 displayScript 值不可用时,Locale.getDisplayScript(Locale)
同样回退到 Locale.getDefault()
。
与语言区域和国际化有关的其他变更如下:
调用 Currency.getDisplayName(null)
会引发 NullPointerException
,以与文档规定的行为保持一致。
时区名称的分析方法发生变化。之前,Android 设备使用在启动时取样的系统时钟值,缓存用于分析日期时间的时区名称。因此,如果在启动时或其他较为罕见的情况下系统时钟出错,可能对分析产生负面影响。
现在,一般情况下,在分析时区名称时分析逻辑将使用 ICU 和当前系统时钟值。此项变更可提供更加准确的结果,如果您的应用使用 SimpleDateFormat
等类,此结果可能与之前的 Android 版本不同。--- 摘自Google Android官方文档
- 提醒窗口
- 悬浮窗
在Android 8.0以上手机上,需要使用TYPE_APPLICATION_OVERLAY
类型,不可以使用TYPE_PHONE
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
mLayoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}
四、Android 9(P)(api 28)
- 限制Http网络请求 Android 9.0 中限制了 HTTP(明文传输)网络请求,若仍继续使用HTTP请求,则会在日志中提示以下异常(只是无法正常发出请求,不会导致应用崩溃):
适配方法如下:
在 AndroidManifest.xml application添加
useCleartextTraffic="true"
- 弃用 Apache HTTP Client 由于官方在 Android 9.0 中移除了所有 Apache HTTP Client 相关的类,因此我们的应用或是一些第三方库如果使用了这些类,就会抛出找不到类的异常:
java.lang.NoClassDefFoundError: Failed resolution of: Lorg/apache/http/conn/scheme/SchemeRegistry;
适配方法如下:
在AndroidManifest.xml中添加下述配置
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
- Build.SERIAL 弃用
Android 9.0 之前,开发者可以使用 Build.SERIAL
获取设备的序列号。现在这个方法被弃用了,Build.SERIAL
将始终设置为 "UNKNOWN" 以保护用户的隐私。
适配的方法为先请求 READ_PHONE_STATE
权限,然后调用 Build.getSerial()
方法。
五、Android 10(Q)(api 29)
未完待续......