目录
一 应用向8.0迁移配置....3
1. 平台兼容性测试....3
2. 构建具有 Android 8.0 功能的应用.... 3
二 Android8.0运行时权限策略变化和适配方案....5
1. 安卓O运行时权限策略变化....5
2. 适配方案....5
三 Android 8.0针对服务、广播两个组件的限制以及适配方案.... 7
1. 安卓O对服务、广播两个组件的限制...7
2. 安卓O系统对服务、广播适配方案....10
四 List.sort()集合的处理....10
五 Android 8.0通知栏适配.... 10
六 Android8.0未知来源应用安装权限方案... 11
Android 8.0 向 Android 平台引入了一些变化,即便不对 targetSdkVersion 做任何变动,仍可能影响应用的行为或令其根本无法运行。
表 . 对运行在 Android 8.0 设备上的所有应用都有影响的关键变化。
除了提供新的 API 外,Android 8.0 还会在您更新 targetSdkVersion 时引发其他行为变更。
(一)获取 Android8.0 SDK
- 启动 AndroidStudio 3.0,然后点击 Tools > Android > SDK Manager 打开 SDK 管理器。
- 在 SDK Platforms标签中,选中 Show Package Details。在Android 8.0 Preview 下选中下列项:
a. Android SDK Platform O
b. Google APIs Intel x86 Atom System Image(只需在使用模拟器时选中)
- 切换到 SDK Tools 标签,选中所有已提供更新的项(点击每个显示破折号 的复选框)。这应该包括下列必需项:
a. Android SDK Build-Tools 26.0.0(rc2 或更高版本)
b. Android SDK Platform-Tools 26.0.0(rc2 或更高版本)
c. Android Emulator 26.0.0
d. Support Repository
- 点击 OK 安装所有选定的 SDK 软件包。
(二)更新构建配置
android {
compileSdkVersion 'android-O'
buildToolsVersion '26.0.0-rc2'
defaultConfig {
targetSdkVersion 'O'
}
...
}
dependencies {
compile'com.android.support:appcompat-v7:26.0.0-beta1'
}
// REQUIRED: Google's new Mavenrepo is required for the latest
// support librarythat is compatible with Android 8.0
repositories {
maven {
url'https://maven.google.com'
}
}
表 . targetSdkVersion 设置为“O”时影响应用的关键变化。
Android系统的运行时权限是从Android 6.0(Android M)开始加入的,在安卓O系统之前,如果应用在运行时动态请求权限并被用户授予了该权限,系统会同时将属于同一权限组并且在Manifiest清单中注册的其它所有权限也一起授予给应用。
对于安卓O系统的应用,此行为已经被纠正,系统只会授予应用明确请求的权限,权限授予后并不会一起把同一权限组的其它所有权限也授予给应用。
在安卓O系统,一旦用户为应用授予某个权限,则所有后续对该权限组中其它权限的权限请求都将被自动批准。举个例子,假设某个应用在其清单(Manifiest.xml)中列有READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE两个权限。应用动态运行时请求READ_EXTERNAL_STORAGE,并且用户授予了该权限,如果该应用针对的是API级别24或更低级别(targetVersion<= 24),系统还会同时授予WRITE_EXTERNAL_STORAGE,因为该权限也属于STORAGE权限组并且也在清单中注册过。如果该应用针对的是Android O(targetVersion = 25),则系统此时仅会授予READ_EXTERNAL_STORAGE,不过在该应用以后动态申请WRITE_EXTERNAL_STORAGE权限时,系统会立即授予该权限,而不会提示用户。
Android 8.0 引入了多个与电话有关的新权限:
--ANSWER_PHONE_CALLS 允许您的应用通过编程方式接听呼入电话。要在您的应用中处理呼入电话,您可以使用 acceptRingingCall() 函数。
--READ_PHONE_NUMBERS 权限允许您的应用读取设备中存储的电话号码。
这些权限均被划分为危险类别,属于 PHONE 权限组。
针对运行时权限管理,6.0系统适配时我已经封装了一个动态权限管理库,并在最大程度上兼容了国产机,该方案同样适用于Android8.0,不过有几点需要注意。
l 应用启动必须的权限需细化,如果WRITE_EXTERNAL_STORAGE和READ_EXTERNAL_STORAGE权限都需要的话,启动的时候得都申请上去。
l 所有需要用到动态权限的地方前端均需调用askForPermission方法,并在相应回调里面实现对应的业务功能逻辑(已实现)。
动态权限管理库介绍:
(一)动态权限适配时主要提供了下面几个方法
/**
2. * Request permissions.
3. *
4. * @param activity context(上下文必须继承CMActivity,否则动态权限申请不会回调)
5. * @param permissions permission list
6. */
7. public static synchronized void askForPermission(Activity activity, PermissionCallback permissionCallback, String... permissions)
8.
9.
10. /**
11. * Request floatWindow permissions.
12. *
13. * @param activity
14. * @param permissionCallback
15. */
16. public static synchronized void askForFloatWindowPermission(Activity activity, PermissionCallback permissionCallback)
17.
18.
19. /**
20. * Request floatWindow permissions.
21. *
22. * @param activity
23. * @param permissionCallback
24. */
25. public static synchronized void askForSysSetWritePermission(Activity activity, PermissionCallback permissionCallback)
(二)回调类PermissionCallback实现
/**
2. * 权限回调接口
3. *
4. * Created by Neil on 2016/10/21.
5. */
6. public interface PermissionCallback {
7. /**
8. * 权限通过
9. */
10. void onGranted();
11. /**
12. * 权限拒绝
13. */
14. void onDenied();
15. /**
16. * 用户以前拒绝过权限,且勾选了不再询问,或是手机系统本身禁止了该权限
17. */
18. void onNeverAsk();
19. }
(一)对服务的限制
前台服务不受影响,但后台服务会被限制。只要满足以下任意一个都认为是前台App:
--App有一个可见的Activiy
--App有一个前台是service
--App与其他前台App有交互,比如远程服务绑定、数据库监听、使用其中一个内容提供程序等(IME、壁纸服务、壁纸服务、壁纸服务)
如果以上条件均不满足,应用将被视为处于后台。前台App的service使用可以随心所欲,但是当应用退入后台进入idle状态时,系统会停止这个应用的service,停止方式与Service.stopSelf()方法等效。
Android8.0系统不允许后台应用创建后台服务。 因此,Android 8.0 引入了一种全新的方法,即 Context.startForegroundService(),以在前台启动新服务。
在用普通方法创建服务后,应用有五秒的时间来调用该服务的 startForeground() 方法以显示新服务的用户可见通知。如果应用在此时间限制内未调用 startForeground(),则系统将停止服务并抛出ANR异常。
(二)对广播的限制
从Android7.0已经对广播的做了限制。主要是对”CONNECTIVITY_ACTION”,“ ACTION_NEW_PICTURE or ACTION_NEW_VIDEO”三个广播做了限制。Android8.0 让这些限制更为严格。
Android 8.0 的应用无法继续在其清单(Manifiest.xml)中为隐式广播注册广播接收器。隐式广播是一种不专门针对该应用的广播。
应用可以继续在它们的清单中注册显式广播。
应用可以在运行时使用 Context.registerReceiver() 为任意广播(不管是隐式还是显式)注册接收器。
需要签名权限的广播不受此限制所限,因为这些广播只会发送到使用相同证书签名的应用,而不是发送到设备上的所有应用。
PS:很多隐式广播当前均已不受此限制所限。应用可以继续在其清单中为这些广播注册接收器,不管应用针对哪个 API 级别。
有关已豁免广播的列表如下:
1. ACTION_LOCKED_BOOT_COMPLETED、ACTION_BOOT_COMPLETED:原因:这些广播只在首次启动时发送一次,并且许多应用都需要接收此广播以便进行作业、闹铃等事项的安排。
2. ACTION_USER_INITIALIZE、”android.intent.action.USER_ADDED”、”android.intent.action.USER_REMOVED”:原因:这些广播受特权保护,因此大多数正常应用无论如何都无法接收它们。
3. “android.intent.action.TIME_SET”、ACTION_TIMEZONE_CHANGED:原因:时钟应用可能需要接收这些广播,以便在时间或时区变化时更新闹铃。
4. ACTION_LOCALE_CHANGED:原因:只在语言区域发生变化时发送,并不频繁。应用可能需要在语言区域发生变化时更新其数据。
5.ACTION_USB_ACCESSORY_ATTACHED、ACTION_USB_ACCESSORY_DETACHED、ACTION_USB_DEVICE_ATTACHED、ACTION_USB_DEVICE_DETACHED:原因:如果应用需要了解这些 USB 相关事件的信息,目前尚未找到能够替代注册广播的可行方案。
6. ACTION_HEADSET_PLUG:原因:由于此广播只在用户进行插头的物理连接或拔出时发送,因此不太可能会在应用响应此广播时影响用户体验。
7. ACTION_CONNECTION_STATE_CHANGED、ACTION_CONNECTION_STATE_CHANGED:原因:与ACTION_HEADSET_PLUG类似,应用接收这些蓝牙事件的广播时不太可能会影响用户体验。
8. ACTION_CARRIER_CONFIG_CHANGED,TelephonyIntents.ACTION_*_SUBSCRIPTION_CHANGED、”TelephonyIntents.SECRET_CODE_ACTION”:原因:原始设备制造商 (OEM) 电话应用可能需要接收这些广播。
9. LOGIN_ACCOUNTS_CHANGED_ACTION:原因:一些应用需要了解登录帐号的变化,以便为新帐号和变化的帐号设置计划操作。
10. ACTION_PACKAGE_DATA_CLEARED:原因:只在用户显式地从 Settings清除其数据时发送,因此广播接收器不太可能严重影响用户体验。
11. ACTION_PACKAGE_FULLY_REMOVED:原因:一些应用可能需要在另一软件包被移除时更新其存储的数据;对于这些应用,尚未找到能够替代注册此广播的可行方案。
12. ACTION_NEW_OUTGOING_CALL:原因:执行操作来响应用户打电话行为的应用需要接收此广播。
13. ACTION_DEVICE_OWNER_CHANGED:原因:此广播发送得不是很频繁;一些应用需要接收它,以便知晓设备的安装状态发生了变化。
14. ACTION_EVENT_REMINDER:原因:由日历提供程序发送,用于向日历应用发布事件提醒。因为日历提供程序不清楚日历应用是什么,所以此广播必须是隐式广播。
15. ACTION_MEDIA_MOUNTED、ACTION_MEDIA_CHECKING、ACTION_MEDIA_UNMOUNTED、ACTION_MEDIA_EJECT、 ACTION_MEDIA_UNMOUNTABLE、ACTION_MEDIA_REMOVED、ACTION_MEDIA_BAD_REMOVAL:原因:这些广播是作为用户与设备进行物理交互的结果(安装或移除存储卷)或启动初始化(作为已装载的可用卷)的一部分发送的,因此它们不是很常见,并且通常是在用户的掌控下。
16. SMS_RECEIVED_ACTION、WAP_PUSH_RECEIVED_ACTION:原因:这些广播依赖于短信接收应用。
默认情况下,这些更改仅影响针对O 的应用。 不过,用户可以从 Settings 屏幕为任意应用启用这些限制,即使应用并不是以 O 为目标平台。
建议的适配方案如下:
(1)如果服务容易被用户注意,请将其设为前台服务。对于后台服务的受限,谷歌提供了新的方法,创建服务时推荐使用NotificationManager.startServiceInForeground(),而不是 startService() 创建服务。
(2)后台服务限制会对唤起功能有影响,使用JobScheduler方案替换。
(3)检查在应用的清单(Manifiest)中定义的广播接收器。 如果您的清单为显式广播声明了接收器,必须予以替换。(暂时没发现)
(4)强烈推荐调用 Context.registerReceiver() 而不是在清单中声明接收器的方式在运行时创建接收器。
(5)推荐使用JobScheduler jobs来替代曾经的后台服务。
在 Android 8.0 之前的平台版本中,如果通过调用 List.sort() 进行排序,则当迭代处理 ArrayList 以及在迭代过程中调用 sort() 时,会引发ConcurrentModificationException。而 Collections.sort() 则不会引发异常。
现在,在Android8.0系统,两种方法都会引发 ConcurrentModificationException。
在 Android 8.0 中,通知被重新设计,引入了NotificationChannel的概念。如果不需要使用8.0系统引入的新特性,只需注意创建通知对象时NotificationCompat.Builder builder = newNotificationCompat.Builder(context, Integer.toString(notificationId));方法第二个参数意义降发生变法,不再是通知ID,而是变成了通知channel,如果没有按照NotificationChannel正确方法启动通知,将会无法弹出消息通知界面。
不想使用NotificationChannel等8.0系统引入的通知新特性,创建通知对象时,只传context上下文即可。
(一)NotificationChannel适配方案
(1)创建NotificationChannel
1. //ChannelId为"1",ChannelName为"Channel1"
2. NotificationChannel channel = new NotificationChannel("1",
3. "Channel1", NotificationManager.IMPORTANCE_DEFAULT);
4. channel.enableLights(true); //是否在桌面icon右上角展示小红点
5. channel.setLightColor(Color.GREEN); //小红点颜色
6. channel.setShowBadge(true); //是否在久按桌面图标时显示此渠道的通知
7. notificationManager.createNotificationChannel(channel);
(2)向NotificationChannel发送通知
1. public static void showChannel1Notification(Context context) {
2. int notificationId = 0x1234;
3. Notification.Builder builder = new Notification.Builder(context,"1"); //与channelId对应
4. //icon title text必须包含,不然影响桌面图标小红点的展示
5. builder.setSmallIcon(android.R.drawable.stat_notify_chat)
6. .setContentTitle("xxx")
7. .setContentText("xxx")
8. .setNumber(3); //久按桌面图标时允许的此条通知的数量
9. NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
10. notificationManager.notify(notificationId, builder.build());
11. }
(3)删除NotificationChannel
1. NotificationChannel mChannel = mNotificationManager.getNotificationChannel(id);
2. mNotificationManager.deleteNotificationChannel(mChannel);
Android8.0的诸多新特性中有一个非常重要的特性:未知来源应用权限。
以前安装未知来源应用的时候一般会弹出一个弹窗让用户去设置允许还是拒绝,并且设置为允许之后,所有的未知来源的应用都可以被安装。Android8.0的变化是,引入未知来源应用的管理列表(系统设置界面里面),需要在里面打开每个应用的未知来源的安装权限才能安装应用。
应用直接适配到Android8之后,内部启动应用安装是会被阻塞的,如果不处理好这个未知来源的权限,会导致应用根本无法下载升级安装,只能去应用市场重新下载。
适配方案:
(1)在清单文件中增加请求安装权限
(2) 代码里面对权限进行处理
应用内安装apk前用canRequestPackageInstalls()方法判断你的应用是否有这个权限
haveInstallPermission = getPackageManager().canRequestPackageInstalls();
如果haveInstallPermission为 true,则说明你的应用有安装未知来源应用的权限,你直接执行安装应用的操作即可。
如果haveInstallPermission为 false,则说明你的应用没有安装未知来源应用的权限,则无法安装应用。由于这个权限不是运行时权限,所以无法再代码中请求权限,还是需要用户跳转到设置界面中自己去打开权限。
具体的操作是:弹出dialog,告知用户->用户点击确定之后跳转到未知来源应用权限管理列表->在onActivityResult中去接收结果
1. Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
2. startActivityForResult(intent, 10086);
3.
4. if (resultCode == RESULT_OK && requestCode == 10086) {
5. installProcess();//再次执行安装流程,包含权限判等
}