Android 后台优化

英文原文地址: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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值