英文原文地址:https://developer.android.com/topic/performance/background-optimization.html
后台优化
后台进程一般对内存和电量消耗比较大。例如,一个隐式的广播,可能启动许多注册监听该广播的后台进程,即使这些进程并什么也不做。这就会对设备性能和用户体验造成明显的影响。
为了改善这个问题,Android 7.0(API level 24) 做了如下限制:
- target是Android 7.0 (API level 24)的app,不接收CONNECTIVITY_ACTION 类的广播,如果这些广播在manifest里声明。运行中的App 依然可以在主线程用Context.registerReceiver()注册一个BroadcastReceiver监听CONNECTIVITY_CHANGE。
- app不能发送或接收 ACTION_NEW_PICTURE 或 ACTION_NEW_VIDEO 广播,这个优化影响所有app,而不只是target为Android 7.0(API level 24)的。
如果你的应用使用这些Intent里的任意一个,你应尽可能快移除对他们的依赖, 才能让你的app在Android 7.0上良好运行。Android framework提供了一些解决方案去缓解对这些隐式广播的需求。例如, JobScheduler 和 GcmNetworkManager 提供了健壮的机制去计划网络操作–在特定的网络条件下(例如:连接到了免费网络)。 你现在也可以使用JobScheduler 去响应内容的改变(content provider)。JobInfo对象封装了JobScheduler用来计划你的任务的参数,当条件符合,系统会执行这个任务在你的app的JobService里。
在这个文档里,我们将学到如何使用可选的方法,例如JobScheduler去适应这些限制。
CONNECTIVITY_ACTION 限制
target是Android 7.0 (API level 24)的app,不接收CONNECTIVITY_ACTION广播,如果广播是在manifest里注册的话,所以app在广播发送的时候也就不能启动。需要在连接到不计费的网络(wifi)时执行较大的网络操作的app会出问题。一些绕过该限制的方法已经存在,但是选择合适的方案取决于你的app想怎么实现。
注意: 通过Context.registerReceiver() 方式注册的广播在app运行时时能接收这些被限制的广播的
在免费网络下计划网络操作
当使用Jobinfo.Builder类构建你的Jobinfo对象时,调用setRequireNetworkType()方法并且传递JobInfo.NETWORK_TYPE_UNMETERED参数。下面的代码计划执行一个服务,在免费网络并且充电的条件下。
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()方法。更多的JobScheduler示例,请看JobScheduler sample app.
使用GMSCore服务的应用,和target为Android 5.0及以下的,可以使用GcmNetworkManager并指定Task.NETWORK_STATE_UNMETERED.
app运行时监控网络连接
app运行时依然可以监听CONNECTIVITY_CHANGE通过注册BroadcastReceiver。然而,ConnectivityManager API提供了更健壮的方法请求满足更精确的网络条件的回调。
NetworkRequest 对象定义了网络回调的参数,按照NetworkCapabilities。你创建NetworkRequest对象通过NetworkRequest.Builder类。 registerNetworkCallback() 然后传递NetworkRequest对象给系统,当网络条件满足时,app接收到回调去执行声明在它的ConnectivityManager.NetworkCallback 里的onAvailable() 方法。
app在退出后依然能接收到回调方法,除非调用了unregisterNetworkCallback()。
NEW_PICTURE和NEW_VIDEO限制
在Android 7.0 (API level 24),app不能发送或接收NEW_PICTURE或NEW_VIDEO广播。当多个app必须唤醒以处理一个新图片或者视频的时候,这个限制帮助缓解性能压力和提高用户体验。Android 7.0 (API level 24)扩展了JobInfo和JobParameters来提供可替代方案。
新JobInfo方法
为了出发content URI改变,Android 7.0 (API level 24)扩展了JobInfo API通过以下方法:
JobInfo.TriggerContentUri()
封装了出发一个content URI改变的job所需的参数。
JobInfo.Builder.addTriggerContentUri()
传递一个TriggerContentUri对象给JobInfo。一个ContentObserver监视封装的content URI。如果有多个TriggerContentUri对象关联到一个job,系统提供一个回调,即使它报告了一个只有一个content URI的变化。
添加TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS 标志去触发一个job,如果给定的content URI的子内容改变。这个标志对应于传递给 registerContentObserver()的notifyForDescendants参数。
注意: TriggerContentUri() 不能和setPeriodic()于setPersisted()联合使用。为了连续的监控内容变化,在最后一个job处理完成之前,规划一个新的JobInfo。
下面的代码计划了一个任务,当系统发出一个MEDIA_URI变化时触发。
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());
}
当系统报告一个特定的content URI变化时,你的app接收到一个回调,一个JobParameters对象被传递给onStartJob()方法,在MediaContentJob.class 里。
新JobParameter方法
Android 7.0 (API level 24)同样扩展了JobParameter以允许你的app去接收有用的信息关于什么类型的content和URI触发了你的job。
Uri[] getTriggeredContentUris()
返回一个触发了job的URI的数组,如果没有URI变化(比如,job在最后期限被触发等),或者URI改变数大于50,返回null。
String[] getTriggeredContentAuthorities()
返回一个触发了job的content 的authority的数组。如果返回值不是null,使用getTriggeredContentUris()去检索那些URI变化的详细信息。
下面的代码重写了Jobservice.onStartJob()方法,记录了触发了job的content URL和authority。
@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 (API level 24)已经采取措施减少这些问题,但还是推荐你优化你的app,彻底不用后台进程。
Android 7.0 (API level 24)引入一些附加的adb 命令,你可以用他们去测试你的app在没有后台进程条件下的表现。
- 模拟隐式广播不可用,后台服务不可用,输入以下命令:
$ adb shell cmd appops set RUN_IN_BACKGROUND ignore - 恢复隐式广播和后台服务,输入以下命令:
$ adb shell cmd appops set RUN_IN_BACKGROUND allow