安卓版本适配信息汇总,最后更新于2019年10月23日,持续收集中
-
1:安卓4.4 API-19
-
1.1:
READ_EXTERNAL_STORAGE
读取外置存储权限强制检查,此权限在低于4.4的版本默认获取。如果应用只在内部存储数据或者如下特定目录读取/写入文件,则不需要WRITE_EXTERNAL_STORAGE
或者READ_EXTERNAL_STORAGE
权限。如果app没有在别的地方读写存储但是minSdkVersion低于19,则可以在uses-permission标签内加入android:maxSdkVersion
适配:<uses-permission ... android:maxSdkVersion="18"/>
。支持的文件位置如下:Context API 文件指向位置 备注 getExternalFilesDir(String)
SD卡/Android/data/包名/String/ 非内置存储 getExternalCacheDir()
SD卡/Android/data/包名/cache/ 非内置存储 getExternalMediaDirs()
SD卡/Android/media/包名/ 非内置存储 getDataDir()
data/data/包名/ app内置存储根目录 getDir(自定义dir,mode)
data/data/包名/自定义dir/ 内置存储(api24开始mode缩小限定范围) getFilesDir()
data/data/包名/files/ 内置存储 getCacheDir()
data/data/包名/cache/ 内置存储 getPreferencesDir()
data/data/包名/shared_prefs/ 内置存储 getNoBackupFilesDir()
data/data/包名/no_backup/ 内置存储(api21) getCodeCacheDir()
data/data/包名/code_cache/ 内置存储(api21) getDatabasesDir()
data/data/包名/databases/ 内置存储(api27) 1.2:应用支持全屏模式,也就是常说的 沉浸式
1.3:虚拟按键可隐藏,既然沉浸式底部导航栏跟着支持了,那就顺路把相关控制API也暴露给开发者
1.4:为了加强
WebView
的功能,Google引入了Chromium
内核。但仍然还是存在 编辑的bug
-
-
2:安卓5.0 API-21
2.1:ART androidRunTime取代 Dalvik 成为平台默认设置,ART采用预先编译技术,改进了垃圾回收机制与调试支持
2.2:
Material Design
设计规范,编译版本提升至至少21时才能方便使用Appcompat-V7
RecyclerView-V7
CardView-V7
等框架,在UI上突出的变化还有SharedElementAnimation
elevation
等。2.3:提升用户隐私的安全性,弃用
activityManager.getRecentTask()
方法。对于向后兼容性,此方法仍会返回它的一小部分数据,包括应用自己的任务和可能的一些其他非敏感任务(如首页)。如果你的应用使用此方法检索它自己的任务,则改用getAppTasks
检索信息-
2.4:禁用隐式意图启动服务,运行时会直接抛出
IllegalArgumentException
异常Intent intent = new Intent();过时禁用 intent.setAction("com.example.myapplication"); startService(intent); 只能使用显式意图 Intent intent = new Intent("com.example.myapplication");
2.5:
Notification
模块不支持设置了rgb通道的smallIcon
,此bug 在后续版本已经修复,但是为了系统兼容性,建议开发者在安卓5.0及以上设备上更换smallIcon以达到版本一致性2.6:WebView默认会阻止混合内容(https当中不能加载http资源),要允许请调用
WebSettings#setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW)
。
默认阻止第三方 Cookie,要允许请调用CookieManager#setAcceptThirdPartyCookies(webView,true)
。2.7:现在可以智能地选择要绘制的 HTML 文档部分。这个新的默认行为有助于减少内存占用和提升性能。如果您要一次渲染整个文档,可通过调用
enableSlowWholeDocumentDraw()
停用此优化。2.8:自定义权限必须保证唯一,使用不同秘钥签名的应用不能使用相同的自定义权限。如果用户设备上存在一个应用使用了相同的自定义权限,那么新的应用将无法安装
2.9:移除了对锁定屏幕小部件的支持
2.10:新增
Intent.resolveActivity(*)
对发起的意图进行设备匹配
-
3:安卓6.0 API-23
-
3.1:移除了
Apache
的http
支持包,如有需要手动引入。android { useLibrary 'org.apache.http.legacy' }
3.2:废弃:
FloatMath
,Notification.setLastEventInfo()
3.3:移除了对设备本地硬件标识符的编程访问权。
WifiInfo.getMacAddress()
方法和BluetoothAdapter.getAddress()
方法现在会返回常量值02:00:00:00:00:00
。如果需要通过蓝牙和wifi扫描访问外部设备的硬件标识符,应用必须拥有ACCESS_FINE_LOCATION
或ACCESS_COARSE_LOCATION
权限。3.4:音频管理器变更:不再支持通过
AudioManager
类直接设置音量或将特定音频流静音。setStreamSolo()方法弃用,改为requestAudioFocus()
方法。setStreamMute() 方法弃用,改为adjustStreamVolume()
并传入方向值ADJUST_MUTE/ ADJUST_UNMUTE
。3.5:APK 验证更为严格。如果在清单中声明的文件在 APK 中并不存在,该 APK 将被视为已损坏。移除任何内容后必须重新签署 APK。
-
3.6:运行时权限管理,谷歌借鉴了苹果 和国内厂商的优化经验,对应用的权限不再一刀切,而是把相关权限分成了三组
- 特殊权限 每次冷启动APP都会重置状态,不建议应用申请
权限名称 权限说明 SYSTEM_ALERT_WINDOW
设置悬浮窗,TYPE_SYSTEM_ALERT已失效 WRITE_SETTINGS
修改系统设置 - 敏感权限 运行时申请,如果没有适配会直接抛出异常,许可状态会保存
权限组 权限名称 介绍 CALENDAR android.permission.READ_CALENDAR
读取系统日历 android.permission.WRITE_CALENDAR
写入系统日历 CAMERA android.permission.CAMERA
相机权限 CONTACTS android.permission.READ_CONTACTS
读取联系人 android.permission.WRITE_CONTACTS
写入联系人 android.permission.GET_ACCOUNTS
读取账号 LOCATION android.permission.ACCESS_FINE_LOCATION
获取精准位置 android.permission.ACCESS_COARSE_LOCATION
获取大概位置 MICROPHONE android.permission.RECORD_AUDIO
录音 PHONE android.permission.READ_PHONE_STATE
获取手机信息 android.permission.CALL_PHONE
打电话,不建议使用 android.permission.READ_CALL_LOG
读取通话记录 android.permission.WRITE_CALL_LOG
写入通话记录 android.permission.PROCESS_OUTGOING_CALLS
监听、控制、取消呼出电话的权限 android.permission.USE_SIP
使用sip(会话发起协议)网络电话 android.permission.ADD_VOICEMAIL
添加系统中的语音邮件 SENSORS android.permission.BODY_SENSOR
使用生命体征相关传感器 SMS android.permission.SEND_SMS
发送短信 android.permission.RECEIVE_SMS
接收短信 android.permission.READ_SMS
读取短信 android.permission.RECEIVE_WAP_PUSH
接收WapPush信息 android.permission.RECEIVE_MMS
接收彩信 android.permission.READ_CELL_BROADCASTS
不存在的权限 STORAGE android.permission.READ_EXTERNAL_STORAGE
读取存储 android.permission.WRITE_EXTERNAL_STORAGE
写入存储 运行时权限共9组,同一组下如果有一个权限被允许,其它权限会自动被允许。
开发时应减少申请权限的次数,以避免交互被打断。对于敏感权限可用intent来代替,让其它应用去处理
-
-
4:安卓7.0 API-24
4.1:应用分屏,支持java8。通过
Context.getDir(name,mode)
获取存储文件夹时不能再使用MODE_WORLD_READABLE
和MODE_WORLD_WRITEABLE
,与此相似的还有SharedPreference和DataBase。否则会抛出SecurityException
4.2:
Notification.Builder
通知消息可以通过setShowWhen()
设置定时显示4.3:删除了三个常用隐式广播 :
CONNECTIVITY_ACTION
,ACTION_NEW_PICTURE
,ACTION_NEW_VIDEO
。因为这些广播可能会一次唤醒多个应用的后台进程,消耗内存和电池4.4:优化了
SurfaceView
,使其在视频播放和3D方面表现更优于TextureView
。4.5:APK signature scheme v2,android 7.0 新引入应用签名方案,它能提供更快的应用安装时间和更多针对未授权 APK 文件更改的保护。
-
4.6:
StrictMode API
政策禁止在您的应用外部公开file:// URI
。如果启动一个包含文件 URI 的intent
会抛出FileUriExposedException
异常。受此影响的有拍照图片选择,下载apk安装等功能。要在应用间共享文件,您应发送一项content:// URI
,并授予 URI 临时访问权限。步骤如下:- 4.6.1:在AndroidManifest.xml中添加<provider>标签
<provider tools:replace="android:authorities"(如果使用的依赖module也存在provider则添加此tools) android:name="android.support.v4.content.FileProvider" android:authorities="替换为你的包名.fileProvider" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_provider_paths" /> </provider>
- 4.6.2:在res/xml文件夹里面创建xml文件file_provider_paths.xml,文件名要和上方的
android:resource
对应值保持一致各个元素的定义可参考android.support.v4.content.FileProvider <?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <root-path name="root_path_name" path="test"/>"/" <files-path path="file_path_name" name="images"/>Context.getFilesDir() 示例位置 <cache-path name="cache_path_name" path="images" />Context.getCacheDir() <external-path name="external_path_name" path=" ." />Environment.getExternalStorageDirectory() <external-files-path name="external_file_path_name" path="images" />Context#getExternalFilesDir(String) <external-cache-path name="external_cache_path_name" path="images" /> <external-path name="external_path_name_01" path="Android/data/com.papa.auction/"/> <external-path name="external_path_name_02" path="."/> </paths>
- 4.6.3:修改拍照intent示例:
Intent intentCamera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 判断设备是否有应用可以处理打开相机intent if(intent.resolveActivity(context.getPackageManager()) != null){ Uri cameraUri; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); cameraUri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileProvider", file); }else { cameraUri = Uri.fromFile(file); } intent.putExtra(MediaStore.EXTRA_OUTPUT, cameraUri); context.startActivityForResult(intent,requestCode); }
- 4.6.4:修改安装APK intent示例:
Intent installIntent = new Intent(Intent.ACTION_VIEW)); installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 根据启动位置决定是否添加 Uri apkFileUri; if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) { installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); apkFileUri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileProvider", apkFile); } else { apkFileUri = Uri.fromFile(apkFile); } installIntent.setDataAndType(apkFileUri, "application/vnd.android.package-archive"); context.startActivity(installIntent);
- 4.6.1:在AndroidManifest.xml中添加<provider>标签
-
4.7:加密库 Crypto废弃 将密码作为随机数生成器的种子换算出密钥 key这种做法已经被认定为是不安全的。相关的 Crypto provider 和 SHA1PRNG 算法同时废弃掉了,并计划在后续的 SDK 中完全移除相关的库,如下是官方给出的解决方案:
给出字符串的密码 String password = "password"; 密钥的比特位数,注意这里是比特位数 AES 支持 128、192 和 256 比特长度的密钥 int keyLength = 256; 盐值的字节数组长度,注意这里是字节数组的长度 其长度值需要和最终输出的密钥字节数组长度一致 由于这里密钥的长度是 256 比特,则最终密钥将以 256/8 = 32 位长度的字节数组存在 所以盐值的字节数组长度也应该是 32 int saltLength = 32; byte[] salt; 先获取一个随机的盐值 你需要将此次生成的盐值保存到磁盘上下次再从字符串换算密钥时传入 如果盐值不一致将导致换算的密钥值不同 保存密钥的逻辑官方并没写,需要自行实现 SecureRandom random = new SecureRandom(); byte[] salt = new byte[saltLength]; random.nextBytes(salt); 将密码明文、盐值等使用新的方法换算密钥 int iterationCount = 1000; KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt,iterationCount, keyLength); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); 到这里你就能拿到一个安全的密钥了 byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded(); SecretKey key = new SecretKeySpec(keyBytes, "AES");
-
5:安卓8.0 API-26 (8.1 API-27)
- 5.1:roundedIcon 自适应图标,在manifest文件中额外提供圆形应用icon
<application android:name=".App" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" ......
- 5.2:PHONE权限组新增两个运行时权限
-
ANSWER_PHONE_CALLS
:允许接听呼入电话。在应用中使用acceptRingingCall()
函数处理呼入电话。 -
READ_PHONE_NUMBERS
:允许应用读取设备中存储的电话号码。
-
- 5.3:安卓8.0中,Notification的通知渠道。用户可以根据渠道来屏蔽一些不想要的通知
void setNotifyChannel(NotificationManager manager, NotificationCompat.Builder builder, String channeId, String channelName) { if (TextUtils.isEmpty(channeId)||TextUtils.isEmpty(channelName)){ L.e("NotifyCompat: ".concat("安卓8.0的通知兼容库中 channeId 与 channelName 不能为empty")); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { //第三个参数设置通知的优先级别 NotificationChannel channel = new NotificationChannel(channeId, channelName, NotificationManager.IMPORTANCE_DEFAULT); channel.canBypassDnd();//是否可以绕过请勿打扰模式 channel.canShowBadge();//是否可以显示icon角标 channel.enableLights(true);//是否显示通知闪灯 channel.enableVibration(true);//收到小时时震动提示 channel.setBypassDnd(true);//设置绕过免打扰 channel.setLockscreenVisibility(NotificationCompat.VISIBILITY_SECRET); channel.setLightColor(Color.RED);//设置闪光灯颜色 channel.getAudioAttributes();//获取设置铃声设置 channel.setVibrationPattern(new long[]{100, 200, 100});//设置震动模式 channel.shouldShowLights();//是否会闪光 if (manager != null) { manager.createNotificationChannel(channel); //manager 与channel关联 } if (builder != null) { builder.setChannelId(channeId);//Notification 与channel关联 } } }
- 5.4:新增特殊权限:
android.permission.REQUEST_INSTALL_PACKAGES
当需要安装未知来源应用时使用,示例如下private void installAPK(){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { boolean hasInstallPermission = getPackageManager().canRequestPackageInstalls(); if (hasInstallPermission) { 安装应用 } else { 提示用户跳转至“安装未知应用”权限界面,引导用户开启权限 Uri selfPackageUri = Uri.parse("package:" + this.getPackageName()); Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, selfPackageUri); startActivityForResult(intent, REQUEST_CODE_UNKNOWN_APP); } }else { 安装应用 } }
- 5.6:SharedPreferences不能使用
MODE_WORLD_READABLE
,请修改为MODE_PRIVATE
- 5.6:新的广播接收器限制导致静态广播无法正常接收,应使用动态广播代替静态广播
- 5.7:
List.sort()
的实现不能调用Collections.sort()
,因为这会导致堆栈因无限递归而溢出 - 5.8:申请了
SYSTEM_ALERT_WINDOW
权限的应用需要在其他应用和系统窗口上方显示提醒窗口时只能使用TYPE_APPLICATION_OVERLAY
类型,之前的TYPE_PHONE
TYPE_PRIORITY_PHONE
TYPE_SYSTEM_ALERT
TYPE_SYSTEM_OVERLAY
TYPE_SYSTEM_ERROR
不再生效。 - 5.9:在 Android 8.0 之前,如果应用在运行时请求权限并且被授予该权限,系统会将同一权限组且在Manifest中注册的其他权限也一起授予应用。此行为在8.0被纠正:系统只会授予应用明确请求的权限。然而,一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准。
- 5.10:
Only fullscreen opaque activities can request orientation
,在Android 8.0上非全屏透明页面不允许设置方向(8.1以上谷歌就修复去掉了这个限制)解决方案1:在manifest中去掉此Activity的
screenOrientation
属性
解决方案2:在此Activity的style里设置<item name="android:windowIsTranslucent">false</item>
解决方案3:使用Dialog/DialogFragment/Popwindow
代替此Activity。
更多细节参阅:
https://developer.android.com/about/versions/oreo/android-8.0-changes#all-apps
https://developer.android.com/about/versions/oreo/android-8.1 - 5.1:roundedIcon 自适应图标,在manifest文件中额外提供圆形应用icon
-
6:安卓9.0 API-28
-
6.1:限制明文流量的网络请求,非加密的流量请求会被系统禁止掉
解决方案:在xml目录新建文件network_security_config.xml
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config cleartextTrafficPermitted="true" /> </network-security-config>
在manifest清单文件application节点配置
android:networkSecurityConfig="@xml/network_security_config"
6.2:使用apacheHttp支持包时出现classNotFound 错误,从 Android 9 开始,Apache HTTP的支持内容库已从 bootclasspath 中移除,且默认情况下应用无法使用它。请在Manifest文件的application节点中加入如下设置:
<uses-library android:name="org.apache.http.legacy" android:required="false" />
-
6.3:
java.lang.IllegalArgumentException: Invalid Region.Op - only INTERSECT and DIFFERENCE are allowed
安卓9.0对canvas#clipPath()中的Region.Op做了限制只能使用INTERSECT或者DIFFERENCE。if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { canvas.clipPath(mPath); } else { canvas.clipPath(mPath, Region.Op.REPLACE); }
6.4:弃用 Bouncy Castle 加密。Android 9 弃用了几个来自 Bouncy Castle 提供程序中的加密技术,代之以由 Conscrypt 提供程序提供的加密技术。调用请求 Bouncy Castle 提供程序的 getInstance() 时,会生成 NoSuchAlgorithmException 错误。要解决这些错误,请不要在 getInstance() 中指定提供程序(也就是请求默认实现)。
6.5:前台服务权限。要使用前台服务的应用必须注册普通权限 FOREGROUND_SERVICE。在未获得此权限的情况下启动前台服务将会引发 SecurityException。
6.6:移除对 Build.serial 的直接访问。使用Build.getSerial() 函数来替代 Build.serial获取标识符。应用必须请求READ_PHONE_STATE 权限。
6.7:Detected problems with API 弹窗的解决:https://blog.csdn.net/codekxx/article/details/86507470#comments
-
-
7:安卓10.0 API-29
- 7.1:TelephonyManager#getDeviceId(),需要申请READ_PRIVILEGED_PHONE_STATE权限,此权限只开发给系统app,按照谷歌的建议如果不是做framework层app开发就不要使用此api,普通app此处需要大量适配,因为三方支付类统计类支持包都会调用此api,如果不能全部更新就不要把target提升到29,否则会直接抛出SecurityException:
getDeviceId: The user 10104 does not meet the requirements to access device identifiers at android.os.Parcel.createException(Parcel.java:2071) at android.os.Parcel.readException(Parcel.java:2039) at android.os.Parcel.readException(Parcel.java:1987) at com.android.internal.telephony.ITelephony$Stub$Proxy.getDeviceId(ITelephony.java:10389)
在安卓9.0时此方法已经提示deprecated,建议开发者用getImei()返回GSM网络的IMEI,或者getMeid()返回CDMA网络的MEID。
- 7.1:TelephonyManager#getDeviceId(),需要申请READ_PRIVILEGED_PHONE_STATE权限,此权限只开发给系统app,按照谷歌的建议如果不是做framework层app开发就不要使用此api,普通app此处需要大量适配,因为三方支付类统计类支持包都会调用此api,如果不能全部更新就不要把target提升到29,否则会直接抛出SecurityException: