Android 适配8.0

适配 Android 8.0

Android 8.0 行为变更:
https://developer.android.com/about/versions/oreo/android-8.0-changes

一、针对所有 API 级别的应用

1、后台执行限制(https://developer.android.com/about/versions/oreo/background)
如果满足以下任意条件,应用将被视为处于前台:

· 具有可见 Activity(不管该 Activity 已启动还是已暂停);
· 具有前台服务;
· 另一个前台应用已关联到该应用(不管是通过绑定到其中一个服务,还是通过使用其中一个内容提供程序)。 例如,如果另一个应用绑定到该应用的服务,那么该应用处于前台:
IME(输入法编辑器)
壁纸服务
通知侦听器
语音或文本服务

如果以上条件均不满足,应用将被视为处于后台。

限制
· 当应用进入已缓存状态时,如果没有活动的组件,系统将解除应用具有的唤醒锁;

· 后台运行的应用对后台服务的访问受到限制:
a、如果针对 Android 8.0 的应用尝试在不允许其创建后台服务的情况下使用 startService() 函数,则该函数将引发一个 IllegalStateException;
b、新的 Context.startForegroundService() 函数将启动一个前台服务。现在,即使应用在后台运行,系统也允许其调用 Context.startForegroundService()。不过,应用必须在创建服务后的五秒内调用该服务的 startForeground() 函数,如果应用在此时间限制内未调用 startForeground(),则系统将停止服务并声明此应用为 ANR;
c、这些规则不会对绑定服务产生任何影响;
d、在很多情况下,应用都可以使用 JobScheduler 作业替换后台服务,使用JobScheduler最低API版本是21,JobIntentService可以用来替代IntentService, JobIntentService在support库中,没有API版本限制;

· 应用无法使用其清单注册大部分隐式广播(即,并非专门针对此应用的广播):
a、应用可以继续在清单中注册显式广播;
b、应用可以在运行时使用 Context.registerReceiver() 为任意广播(不管是隐式还是显式)注册接收器;
c、需要签名权限的广播不受此限制所限,因为这些广播只会发送到使用相同证书签名的应用,而不是发送到设备上的所有应用;
d、在许多情况下,之前注册隐式广播的应用使用 JobScheduler 作业可以获得类似的功能;

2、Android 后台位置限制
在运行 Android 8.0 的设备上使用后台应用时,降低了后台应用接收位置更新的频率。
· Location Manager
提供给后台应用的位置更新只会根据 Android 8.0 行为变更中定义的间隔,按每小时几次的频率提供。

3、应用快捷键
应用快捷键和App shortcut功能统一由ShortcutManager管理。
参考链接:
android 7.1 使用:
http://qiangbo.space/2017-04-16/AndroidAnatomy_AppShortcut/
android 8.0 使用:
https://blog.csdn.net/zhanggang740/article/details/78554031

4、提醒窗口
如果应用使用 SYSTEM_ALERT_WINDOW 权限并且尝试使用以下窗口类型之一来在其他应用和系统窗口上方显示提醒窗口:
TYPE_PHONE
TYPE_PRIORITY_PHONE
TYPE_SYSTEM_ALERT
TYPE_SYSTEM_OVERLAY
TYPE_SYSTEM_ERROR
那么,这些窗口将始终显示在使用 TYPE_APPLICATION_OVERLAY 窗口类型的窗口下方。如果应用针对的是 Android 8.0,则应用会使用 TYPE_APPLICATION_OVERLAY 窗口类型来显示提醒窗口。

5、输入和导航
随着 Android 应用出现在 Chrome 操作系统和平板电脑等其他大尺寸设备上,用户在 Android 应用中重新开始使用键盘导航。在 Android 8.0 中,再次使用键盘作为导航输入设备,从而为基于箭头键和 Tab 键的导航构建了一种更可靠并且可预测的模型。
对元素焦点行为做出以下变更:
· 如果没有为 View 对象(前景或背景图片)定义任何焦点状态颜色,框架会为 View 设置默认的焦点突出显示颜色。此焦点突出显示标志是基于操作组件主题背景的涟漪图片。
· 如果不希望 View 对象在接收焦点时使用此默认突出显示标志,请在包含 View 的布局 XML 文件中将 android:defaultFocusHighlightEnabled 属性设置为 false,或者将 false 传递至应用界面逻辑中的 setDefaultFocusHighlightEnabled()。
· 要测试键盘输入对界面元素焦点有何影响,可以启用 Drawing > Show layout bounds 开发者选项。在 Android 8.0 中,此选项在当前具有焦点的元素上显示一个“X”图标。

6、网络连接和 HTTP(S) 连接
Android 8.0 对网络连接和 HTTP(S) 连接行为做出了以下变更:
· 无正文的 OPTIONS 请求具有 Content-Length: 0 请求头。之前,这些请求没有 Content-Length 标头。
· HttpURLConnection 在包含斜线的主机或颁发机构名称后面附加一条斜线,使包含空路径的网址规范化。例如,它将 http://example.com 转化为 http://example.com/。
· 通过 ProxySelector.setDefault() 设置的自定义代理选择器仅针对所请求的网址(协议、主机和端口)。因此,仅可根据这些值选择代理。传递至自定义代理选择器的网址不包含所请求的网址的路径、查询参数或片段。
· URI 不能包含空白标签,系统对格式错误的 URI 会返回 null。
· Android 8.0 在实现 HttpsURLConnection 时不会执行不安全的 TLS/SSL 协议版本回退。
7、安全性
Android 8.0 包含以下与安全性有关的变更:
· 此平台不再支持 SSLv3。
· 在与未正确实现 TLS 协议版本协商的服务器建立 HTTPS 连接时,HttpsURLConnection 不再尝试回退到之前的 TLS 协议版本并重试的权宜方法。
· 应用的 WebView 对象将在多进程模式下运行。网页内容在独立的进程中处理,此进程与包含应用的进程相隔离,以提高安全性。
· 您无法再假定 APK 驻留在名称以 -1 或 -2 结尾的目录中。应用应使用 sourceDir 获取此目录,而不能直接使用目录格式。

8、隐私性
Android 8.0 对平台做出了以下与隐私性有关的变更。

· 对于在 OTA 之前安装到某个版本 Android 8.0(API 级别 26)的应用,除非在 OTA 后卸载并重新安装,否则 ANDROID_ID 的值将保持不变。要在 OTA 后在卸载期间保留值,开发者可以使用密钥/值备份关联旧值和新值。
· 对于安装在运行 Android 8.0 的设备上的应用,ANDROID_ID 的值现在将根据应用签署密钥和用户确定作用域。应用签署密钥、用户和设备的每个组合都具有唯一的 ANDROID_ID 值。因此,在相同设备上运行但具有不同签署密钥的应用将不会再看到相同的 Android ID(即使对于同一用户来说,也是如此)。
· 只要签署密钥相同(并且应用未在 OTA 之前安装到某个版本的 O),ANDROID_ID 的值在软件包卸载或重新安装时就不会发生变化。

即使系统更新导致软件包签署密钥发生变化,ANDROID_ID 的值也不会变化。
· 查询 net.hostname 系统属性返回的结果为空。

9、Contacts provider 使用情况统计方法的变更
在之前版本的 Android 中,Contacts provider允许开发者获取每个联系人的使用情况数据。此使用情况数据揭示了与某个联系人相关联的每个电子邮件地址和每个电话号码的信息,包括与该联系人联系的次数以及上次联系该联系人的时间。请求 READ_CONTACTS 权限的应用可以读取此数据。
如果应用请求 READ_CONTACTS 权限,它们仍可以读取此数据。从 Android 8.0 开始,使用情况数据查询会返回近似值,而不是精确值。不过,Android 系统内部仍然会保留精确值,因此,此变更不会影响 auto-complete API。
此行为变更会影响以下查询参数:
TIMES_CONTACTED
TIMES_USED
LAST_TIME_CONTACTED
LAST_TIME_USED

10、集合的处理
AbstractCollection.removeAll() 和 AbstractCollection.retainAll() 当集合为空时会导致NullPointerException;之前,当集合为空时不会引发 NullPointerException。

二、针对 Android 8.0 的应用

1、内容变更通知
Android 8.0 更改了 ContentResolver.notifyChange() 和 registerContentObserver(Uri, boolean, ContentObserver) 在针对 Android 8.0 的应用中的行为方式。
这些 API 需要在所有 URI 中为颁发机构定义一个有效的 ContentProvider。使用相关权限定义一个有效的 ContentProvider 可帮助您的应用防范来自恶意应用的内容变更,并防止将可能的私密数据泄露给恶意应用。
在调用registerContentProvider 或者 notifyChange 的时候,如果没有注册明确的authorities的话,那么会抛一个SecurityException 的异常(Failed to find provider null for user 0; expected to find a valid ContentProvider for this authority)。
解决办法就是在AndroidManifest里去注册一下Providor,并且记住在NotifyChange的时候URI一定要传 content://authority/,authority别传null。

2、视图焦点
可点击的 View 对象现在默认也可以成为焦点。如果您希望 View 对象可点击但不可成为焦点,请在包含 View 的布局 XML 文件中将 android:focusable 属性设置为 false,或者将 false 传递至应用界面逻辑中的 setFocusable()。

3、安全性
如果应用的网络安全性配置选择退出对明文流量的支持,那么应用的 WebView 对象无法通过 HTTP 访问网站。每个 WebView 对象必须转而使用 HTTPS。在AndroidManifest文件的application标签下加入android:networkSecurityConfig="@xml/network_security_config"

res/xml/network_security_config.xml:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
		<domain-config cleartextTrafficPermitted="false">
    			<domain includeSubdomains="true">secure.example.com</domain>
		</domain-config>
</network-security-config>

4、隐私性
以下变更影响 Android 8.0 的隐私性。
· 系统属性 net.dns1、net.dns2、net.dns3 和 net.dns4 不再可用,此项变更可加强平台的隐私性。
· 对于在 OTA 之前安装到某个版本 Android 8.0(API 级别 26)的应用,除非在 OTA 后卸载并重新安装,否则 ANDROID_ID 的值将保持不变。要在 OTA 后在卸载期间保留值,开发者可以使用密钥/值备份关联旧值和新值。对于安装在运行 Android 8.0 的设备上的应用,ANDROID_ID 的值现在将根据应用签署密钥和用户确定作用域。应用签署密钥、用户和设备的每个组合都具有唯一的 ANDROID_ID 值。因此,在相同设备上运行。具有不同签署密钥的应用将不会再看到相同的 Android ID(即使对于同一用户来说,也是如此)。只要签署密钥相同(并且应用未在 OTA 之前安装到某个版本的 O),ANDROID_ID 的值在软件包卸载或重新安装时就不会发生变化。即使系统更新导致软件包签署密钥发生变化,ANDROID_ID 的值也不会变化。要借助一个简单的标准系统实现应用获利,请使用广告 ID。广告 ID 是 Google Play 服务针对广告服务提供的唯一 ID,此 ID 可由用户重置。

· 要获取 DNS 服务器之类的网络连接信息,具有 ACCESS_NETWORK_STATE 权限的应用可以注册 NetworkRequest 或 NetworkCallback 对象。这些类在 Android 5.0(API 级别 21)及更高版本中提供。
· Build.SERIAL 已弃用。需要知道硬件序列号的应用应改为使用新的 Build.getSerial() 函数,该函数要求具有 READ_PHONE_STATE 权限。
· LauncherApps API 不再允许工作资料应用获取有关主个人资料的信息。当某个用户在托管配置文件中时,LauncherApps API 的行为就像同一配置文件组的其他配置文件中未安装任何应用一样。和之前一样,尝试访问无关联的个人资料会引发 SecurityExceptions。

5、权限
在 Android 8.0 之前,如果应用在运行时请求权限并且被授予该权限,系统会错误地将属于同一权限组并且在清单中注册的其他权限也一起授予应用。
对于针对 Android 8.0 的应用,此行为已被纠正。系统只会授予应用明确请求的权限。然而,一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准。
例如,假设某个应用在其清单中列出 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE。应用请求 READ_EXTERNAL_STORAGE,并且用户授予了该权限。如果该应用针对的是 API 级别 24 或更低级别,系统还会同时授予 WRITE_EXTERNAL_STORAGE,因为该权限也属于同一 STORAGE 权限组并且也在清单中注册过。如果该应用针对的是 Android 8.0,则系统此时仅会授予 READ_EXTERNAL_STORAGE;不过,如果该应用后来又请求 WRITE_EXTERNAL_STORAGE,则系统会立即授予该权限,而不会提示用户。

6、集合的处理
在 Android 8.0 中,Collections.sort() 是在 List.sort() 的基础上实现的。在 Android 7.x(API 级别 24 和 25)中,则恰恰相反。在过去,List.sort() 的默认实现会调用 Collections.sort()。此项变更使 Collections.sort() 可以利用优化的 List.sort() 实现,但具有以下限制:
· List.sort() 的实现不能调用 Collections.sort(),因为这会导致堆栈因无限递归而溢出。相反,如果您需要 List 实现的默认行为,应避免重写 sort()。如果父类以不适当的方法实现 sort() ,通常最好使用在 List.toArray()、Arrays.sort() 和 ListIterator.set() 的基础上构建的实现重写 List.sort()。
· 现在,Collections.sort() 只是对调用 sort() 的 List 实现进行的一项结构性修改。例如,在 Android 8.0 之前的平台版本中,如果通过调用 List.sort() 进行排序,则当迭代处理 ArrayList 以及在迭代过程中调用 sort() 时,会引发 ConcurrentModificationException。而 Collections.sort() 则不会引发异常。此项变更使平台行为更加一致:现在,两种方法都会引发 ConcurrentModificationException。

方案和修改点:
一、通知:Android 8.0增加了通知渠道,用户可以自主控制接收哪个渠道的通知,并且设置各个渠道通知的优先级。
发送通知流程:
1、创建通知渠道
String channelId = “one”;
String channelName = “聊天消息”;
int importance = NotificationManager.IMPORTANCE_MAX;
createNotificationChannel(channelId, channelName, importance);

@TargetApi(Build.VERSION_CODES.O)
private void createNotificationChannel(String channelId, String channelName, int importance) {
NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
NotificationManager notificationManager = (NotificationManager) getSystemService(
NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(channel);
}

2、判断通知渠道的状态
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= 26) {
NotificationChannel channel = manager.getNotificationChannel(“one”);
if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
intent.putExtra(Settings.EXTRA_CHANNEL_ID, channel.getId());
startActivity(intent);
Toast.makeText(NotificationActivity.this, “请手动将通知打开”, Toast.LENGTH_SHORT).show();
}
}
3、发送通知
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
Notification notification = new NotificationCompat.Builder(NotificationActivity.this, “one”)
.setContentTitle(“收到一条聊天消息”)
.setContentText(“今天中午吃什么?”)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.s1a)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.s1a))
.setAutoCancel(true)
.build();
manager.notify(1, notification);

二、后台执行限制:

1.1、service方案
后台开启服务的第一种方案:
第一步:开启服务,代码如下
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Intent intent = new Intent(getApplicationContext(), MyServiceTwo.class);
startForegroundService(intent);
}
第二步:在5S内调用服务的startForeground()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
String channelId = “one”;
String channelName = “服务”;
int importance = NotificationManager.IMPORTANCE_MAX;
createNotificationChannel(channelId, channelName, importance);
Notification notification = new NotificationCompat.Builder(getApplicationContext(),channelId)
.setContentTitle(“一个服务就是我”)
.setContentText(“我是一个服务”)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.s1a)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.s1a))
.setAutoCancel(true)
.build();
startForeground(1,notification);
}

注意:
如果用户手动不显示此通知渠道,那么以后再次开启服务后,不会有通知显示出来
如果代码设置通知渠道的显示等级为NotificationManager.IMPORTANCE_NONE,开启服务后,通知渠道等级得到提升

后台开启服务的第二种方案之使用jobservice替代service(Android5.0):
第一步:创建一个类继承JobService,并覆盖onStartJob(JobParameters parms)方法和onStopJob(JobParameters params)
public class PollService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
}
执行任务时,服务就会启动,此时会在主线程上收到onStartJob()方法调用。该方法返回false结果表示:"交代的任务我已全力去做,现在做完了,会立即调用ondestroy方法”。返回 true 结果则表示:“任务收到,正在做,但是还没有做完。”
onStopJob()方法会在系统杀死我们服务时调用,如果此时返回true,则系统会重新计划启动这个服务,若返回false则不会
第二步:执行任务需要单开线程
private PollTask mCurrentTask;

@Override
public boolean onStartJob(JobParameters parms){
mCurrentTask = new PollTask();
mCurrentTask.execute(parms);
return true;
}
private class PollTask extends AsyncTask<JobParameters,Void,Void> {
private JobParameters mJobParameters;
@Override
protected Void doInBackground(JobParameters… params) {
mJobParameters = params[0];
//执行任务的逻辑
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
//任务执行完毕后,就可以调用jobFinished(JobParameters, boolean)方法通知结果
jobFinished(mJobParameters, false);
super.onPostExecute(aVoid);
}
}
第三步:使用JobService,必须在Manifest配置清单中添加权限
<service
android:name=".PollService"
//添加的权限控制只有JobScheduler才能运行它。
android:permission=“android.permission.BIND_JOB_SERVICE”
/>

第四步:开启服务,开启服务时必须设置至少一个条件否则无法开启服务造成异常
final int JOB_ID = 1;
JobScheduler scheduler=(JobScheduler)getSystemService(Context.JOB_SCHEDULER_SERVICE);
ComponentName jobService = new ComponentName(getPackageName()
, MyJobService.class.getName());
JobInfo jobInfo = new JobInfo.Builder(JOB_ID, jobService) //任务Id等于1
//.setMinimumLatency(0)// 任务最少延迟时间
.setPeriodic(1000 * 60 * 15)//设置任务重复执行间隔
//.setOverrideDeadline(60000)// 任务deadline,当到期没达到指定条件也会开始执行
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)// 网络条件,默认值NETWORK_TYPE_NONE
//.setRequiresCharging(true)// 是否充电
//.setRequiresDeviceIdle(false)// 设备是否空闲
.setPersisted(true) //设备重启后是否继续执行
//.setBackoffCriteria(3000,JobInfo.BACKOFF_POLICY_LINEAR) //设置退避/重试策略
.build();
scheduler.schedule(jobInfo);
/*
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
jobWork = new JobWorkItem(intent);
mJobScheduler.enqueue(builder.build(),jobWork);
}
*/
上述代码计划任务每15分钟运行一次,但前提条件是有Wi-Fi或有可用的不限流量网络。调用 setPersisted(true) 方法可保证服务在设备重启后也能按计划运行。

注意事项:
使用scheduler.schedule(jobInfo)多次开启jobservice,如果上次任务还没有执行完,会调用onStopJob,onDestroy方法停止当前服务,接着调用onCreate,onStartJob。
使用mJobScheduler.enqueue(builder.build(),jobWork),多次开启jobservice,如果上次任务还没有执行完,不会停止上次服务,不做处理。
JobServce最长在后台运行10分钟
当应用退到后台,如果onStopJob函数返回false,一定时间内服务会被系统杀死。如果返回true,系统在十分钟以后将jobservice杀死,会在杀死后重新create jobservice,create后在一定时间内还会再次杀死服务,然后会再次ctreate,不断重复此过程。

测试onstopjob返回true的结果

参考链接:
http://gityuan.com/2017/03/10/job_scheduler_service/
http://www.cnblogs.com/chase1/p/7221916.html
https://blog.csdn.net/Gaugamela/article/details/56280518

后台开启服务的第三种方案之使用jobintentservice替代intentservice(support):
第一步:创建一个类继承JobIntentService,并覆onHandleWork方法
public class MyJobIntentService extends JobIntentService{
/**
* 这个Service 唯一的id
*/
static final int JOB_ID = 10111;
public static void enqueueWork(Context context, Intent work) {
enqueueWork(context, MyJobIntentService.class, JOB_ID, work);
}

@Override
public void onCreate() {
super.onCreate();
}

@Override
protected void onHandleWork(@NonNull Intent intent) {
ResultReceiver result = intent.getParcelableExtra(“result”);
result.send(1,null);
}

@Override
public void onDestroy() {
super.onDestroy();
}
}
第二步:使用JobIntentService,必须在Manifest配置清单中添加权限

第三步:开启服务
Intent workIntent = new Intent();
workIntent.putExtra(“work”,“work”);
workIntent.putExtra(“result”,new ResultReceiver(null){
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
super.onReceiveResult(resultCode, resultData);
}
});
MyJobIntentService.enqueueWork(getApplicationContext(),workIntent);

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值