APP開發實戰175-針對Android7.0及更高版本的後臺優化方案

xjbclz發表於2017-05-21

31.18 針對Android7.0及更高版本的後臺優化方案

   Android 7.0刪除了三項隱式廣播,以幫助優化記憶體使用和電量消耗。隱式廣播會在後臺頻繁啟動已註冊偵聽這些廣播的應用。刪除這些廣播可以顯著提升裝置效能和使用者體驗。

    移動裝置會經歷頻繁的連線變更,例如在 WLAN 和移動資料之間切換時。目前,可以通過在應用清單中註冊一個接收器來偵聽隱式 CONNECTIVITY_ACTION 廣播,讓應用能夠監控這些變更。由於很多應用會註冊接收此廣播,因此單次網路切換即會導致所有應用被喚醒並同時處理此廣播。

    同理,在之前版本的 Android 中,應用可以註冊接收來自其他應用(例如相機)的隱式 ACTION_NEW_PICTURE 和 ACTION_NEW_VIDEO 廣播。當使用者使用相機應用拍攝照片時,這些應用即會被喚醒以處理廣播。

    為了減少這樣的情況發生,從Android7.0(API level24)開始,系統做了以下限制:

    對於針對Android7.0(API level 24)或更高版本系統的APP,如果在manifest檔案中註冊了CONNECTIVITY_ACTION 廣播接收器,那不會再收到CONNECTIVITY_ACTION 廣播。如果使用Context.registerReceiver() 動態註冊的廣播接收器,且context還是有效的,那仍然能接收到CONNECTIVITY_ACTION 廣播。

    APP不能再傳送和接收到 ACTION_NEW_PICTURE和ACTION_NEW_VIDEO 廣播,這個影響到所有的APP,而不僅限於針對Android7.0(API level 24)或更高版本系統的APP。

如果APP使用了涉及上述廣播的intent,需要儘快刪除,以便APP可以正常在Android7.0(API level 24)或更高版本系統的裝置上執行。

Android框架提供了幾種解決方案,以減少這些隱式廣播的使用。例如,JobScheduler和GcmNetworkmManager提供了強大的機制在當特定的條件被滿足時,如裝置連線到wifi熱點時,排程網路操作,可以使用JobScheduler對內容提供者的變化做出反應。Jobinfo物件封裝了JobScheduler用於安排job的引數。當執行job的條件滿足時,系統呼叫APP的JobService執行這些job。

31.18.1 對於CONNECTIVITY_ACTION 限制的解決方案

1 排程網路Job(當使用Wifi上網時)

    當使用Jobinfo.Builder構建Jobinfo物件時,使用setRequirednNetworktType()方法傳遞Jobinfo.NETWORK_TYPE_UNMETERED作為job引數。下面的程式碼示例當裝置連線到wifi熱點時和充電時,會啟動一個服務:

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);
}

    當job執行的條件被滿足時,APP接收到一個回撥執行JobService類中的onStartJob() 方法。

    使用GMSCore服務的APP,當針對Android 5(API Level 21)或更低系統版本時,可以使用GcmnNetworkmManager和指定Task.NETWORK_STATE_UNMETERED實現類似功能。

2 當APP執行時,監聽網路連線狀態

    執行中的APP仍然可以接收到CONNECTIVITY_CHANGE廣播。然而,ConnectivityManager API提供了一個更強大的方法來請求一個回撥僅當特定的網路條件滿足時。

    NetworkrRequest物件定義和NetworkCapabilities相關的引數。用NetworkrRequest.Builder類的registerNetworkCallback()建立NetworkrReques物件,然後傳遞NetworkrRequest物件給系統。在網路條件具備的情況下,APP接收到一個回撥函式,執行ConnectivityManager.Networkcallback類定義的onAvailable()方法。

    APP會持續收到回撥直到APP退出或呼叫unregisterNetworkCCallback()。

31.18.2 關於NEW_PICTUREand NEW_VIDEO的限制

    Android7.0 (API level 24) 擴充套件 JobInfo和JobParameters 提供替代的解決方法。

1 新的JobInfo 方法

    Android7.0 (API level 24)擴充套件了JobInfo 如下方法:

    JobInfo.TriggerContentUri()

    封裝被要求觸發job(當content UR改變時)的引數

 

    JobInfo.Builder.addTriggerContentUri()

    傳遞TriggerContentUri 物件給 JobInfo

    增加TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS flag觸發job如果被給的URI的任何派生的URI發生變化時

    注:TriggerContentuUri()不可與setpPeriodic()或setPersisted()組合使用。如要持續監測內容的變化,需要在應用的JobService處理完最近收到的回撥之前建立一個新的Jobinfo物件。

    如下程式碼示例當系統報告content URI(MEDIA_URI)變化時,啟動一個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(
          newJobInfo.TriggerContentUri(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
         JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS));
  js.schedule(builder.build());
}

2 新的JobParameter方法

    Android7.0 (API level 24) 也擴充套件了 JobParameters 方法允許APP接什麼contentauthorities和 URIs觸發了job的資訊:

    Uri[]getTriggeredContentUris()

返回觸發job的URI陣列,當改變的URI個數超過50時,返回值為null

 

    String[]getTriggeredContentAuthorities()

    返回觸發job的content authorities 的字串陣列

    如下程式碼重寫了JobService.onStartJob()方法,記錄觸發job的content authorities 和URIs:

@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;
}

31.18.3 對於APP的測試

Android 7.0 (APIlevel 24) 提供了新的 AndroidDebug Bridge (ADB) 命名測試當APP的後臺程式不能啟動時,APP是否可以正常執行:

模擬隱式廣播和後臺服務都不能用,輸入以下命令:

$ adb shell cmd appops set<package_name> RUN_IN_BACKGROUND ignore

隱式廣播和後臺服務都可以用,輸入以下命令:

$ adb shell cmd appops set<package_name> RUN_IN_BACKGROUND allow

相關文章