前言:最近准备着手优化APP的工作了
这篇文章关于Android7.0上的后台优化,并且我们需要使用什么方案来替代以前的一些做法。
Android N 对以下三种广播通知的改动:
**CONNECTIVITY_ACTION: 网络发生变化
ACTION_NEW_PICTURE:拍摄了新的图片
ACTION_NEW_VIDEO:拍摄了新的视频 **
比如,一个隐式的广播可能会唤醒很多监听它的后台进程,即使那些进程并没有做很多的工作。这会对设备性能和用户体验有很大的影响。
为了减轻这个问题,Android 7.0(API 24)使用了下面的限制:
1、对于目标系统为android 7.0或者更高的版本的APP,这些APP如果在Manifest文件上声明了广播接收器,但是这个广播接收器不会接收到CONNECTIVITY_ACTION广播,APP需要使用动态注册广播接收器的方式来进行监听才能够接收到CONNECTIVITY_ACTION的广播。
2、应用不能够发送或者接受ACTION_NEW_PICTURE 或者 ACTION_NEW_VIDEO 广播,这个优化不仅仅是针对目标系统为Android7.0的APP,而是针对所有的APP的。
如果你的APP使用上面提到的这些Intent,你应该尽快移除替修改这部分的功能,这样你就可以在Android 7.0的设备上正常的运行APP了。Android Framework提供了几个解决方案来减轻对这些隐式广播的需求。比如,当满足指定条件的时候,JobScheduler和GcmNetworkManager提供健全的机制来安排网络操作,比如连接到一个不按流量计费的网络。
你现在可以使用JobScheduler的回调当contentprovider发生改变时,JobInfo对象封装了JobScheduler用来设置Job的参数,当满足了工作的条件时,系统就会在你的APP的JobService执行Job。
接下来,我们学习这样使用其中之一的方法,比如JobScheduler,让你的APP来适应这些限制。
CONNECTIVITY_ACTION上的限制
如果在Manifest文件上注册来接收这个广播的话,目标是Android 7.0的APP接收不到CONNECTIVITY_ACTION广播,那些依赖这个广播的进程也就启动不了,这会给那些监听网络状态改变或者发出大量网络活动的应用产生影响,当设备连接到一个不按用量计费的网络时。有几个解决方案能够避开已经存在于Android Framework的限制,但选择正确的一个方案取决于你希望你的APP做什么。
注意:当app正在运行的时候,使用动态注册广播依然能够接收到这些广播。
在连接到一个不按流量计费的网络时安排网络任务
当使用JobInfo.Builder类来构建你的JobInfo对象,使用setRequiredNetworkType()方法并且传递JobInfo.NETWORK_TYPE_UNMETERED作为Job参数,下面的代码展示了当设备连接到一个不按流量计费的网络时安排运行一个服务:
public static final int MY_BACKGROUND_JOB = 0;
...
public static void scheduleJob(Context context) {
JobScheduler js =
(JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo job = new JobInfo.Builder(
MY_BACKGROUND_JOB,
new ComponentName(context, MyJobService.class))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
.setRequiresCharging(true)
.build();
js.schedule(job);
}
当条件满足的时候,你的APP就会接收到一个回调,执行JobService.class里的onStartJob()。
使用GMScore服务,并且目标Android为5.0(API 21)或者更低版本的应用,可以使用GcmNetworkManager并且指定Task.NETWORK_STATE_UNMETERED
在APP运行的时候监听网络连接
通过动态注册的广播接收器,运行中的APP依然能够接收到CONNECTIVITY_CHANGE的广播,但是,ConnectivityManagerAPI提供了一个更加健全的方法,在满足指定的网络条件的时候请求回调。
NetworkRequest对象定义在NetworkCapabilities中的网络回调的参数。通过NetworkRequest.Builder类来创建一个NetworkRequest对象,调用registerNetworkCallback()然后传递NetworkRequest对象到系统中,当网络条件满足时,app会接收到回调信息然后执行ConnectivityManager.NetworkCallback类中的onAvailable()方法
APP会继续接收回调直到APP离开或者调用了unregisterNetworkCallback()
NEW_PICTURE 和 NEW_VIDEO的限制
在Android 7.0(API 24),应用不能够发送和接收ACTION_NEW_PICTURE或者 ACTION_NEW_VIDEO 广播。这个限制在必须唤醒几个应用来处理一个新的图片或视频时减轻了对性能和用户体验的影响。Android 7.0扩展了JobInfo和JobParameters来提供一个可选方案。
新的JobInfo方法
为了能够在contentURI发生改变的时候触发jobs,Android 7.0扩展了JobInfo API的下列几个方法:
# 当content URI发生改变时,封装了请求参数来触发job
JobInfo.TriggerContentUri()
//传递一个TriggerContentUri对象给JobInfo,ContentObserver会监视被封装的content URI,如果有多个 TriggerContentUri对象关联到一个JobInfo,系统会提供一个回调即使只有contentURI的其中一个发生了变化。
//添加TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS标志,如果任意指定的URI的后代发生改变时触发Job,这个标志对应于notifyForDescendants参数传递到 registerContentObserver()
JobInfo.Builder.addTriggerContentUri()
注意: TriggerContentUri()不能够和setPeriodic()或者setPersisted()一起使用,为了持续监视content的变化,在应用的JobService结束最近的回调处理之前安排一个新的JobInfo。
当系统产生了一个变化给contentURI时,下列代码设置了一个job来进行触发
public static final int MY_BACKGROUND_JOB = 0;
...
public static void scheduleJob(Context context) {
JobScheduler js =
(JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(
MY_BACKGROUND_JOB,
new ComponentName(context, MediaContentJob.class));
builder.addTriggerContentUri(
new JobInfo.TriggerContentUri(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS));
js.schedule(builder.build());
}
当系统在一个在指定的contentURI上的产生了变化,你的APP接收到一个回调,并且传递一个JobParameters对象到MediaContentJob.class类中的onStartJob()方法
新的JobParameter方法
Android 7.0 (API level 24)同样扩展了JobParameter来允许你的APP接收关于content当局和触发job的URI的有用信息
//返回一个触发了Job的URI数组,如果没有URI触发了Job(比如,job的触发使由于deadline或者一些其他原因),或者发生改变的URI数量超过50,就会返回null
Uri[] getTriggeredContentUris()
//返回一个字符串数组关于触发了job的content当局,如果返回的数组不为空,使用
getTriggeredContentUris()来获取发生改变的URI的详细信息
String[] getTriggeredContentAuthorities()
下列代码重写了 JobService.onStartJob()方法并且记录content当局和触发job的URI
@Override
public boolean onStartJob(JobParameters params) {
StringBuilder sb = new StringBuilder();
sb.append("Media content has changed:\n");
if (params.getTriggeredContentAuthorities() != null) {
sb.append("Authorities: ");
boolean first = true;
for (String auth :
params.getTriggeredContentAuthorities()) {
if (first) {
first = false;
} else {
sb.append(", ");
}
sb.append(auth);
}
if (params.getTriggeredContentUris() != null) {
for (Uri uri : params.getTriggeredContentUris()) {
sb.append("\n");
sb.append(uri);
}
}
} else {
sb.append("(No content)");
}
Log.i(TAG, sb.toString());
return true;
}
进一步优化你的APP
优化你的APP,让它运行在低内存设备上,或者在低内存条件下,可以提高性能和用户体验。移除对后台服务的依赖和清单文件上的广播接收者可以帮助你的APP更好地运行在这些设备上。虽然Android 7.0有一些步骤来减少这些问题,它依然建议你进行优化来完全不使用这些后台进程。
Android 7.0介绍了一些额外的ADB命令来让你能在禁用那些后台进程来测试你的APP性能:
模拟隐式广播和后台服务都不可用的情况
$ adb shell cmd appops set RUN_IN_BACKGROUND ignore
重启隐式广播和后台服务
$ adb shell cmd appops set RUN_IN_BACKGROUND allow