原文鏈接?Background Optimizations
前言
后臺進程是內存和電池敏感的。一個隱式的broadcast可能會啟動很多監聽它的后臺進程,即使這些進程可能做得工作不多。這可能丟設備性能和用戶體驗都有比較大的影響。
為了緩解這種問題,7.0(API 24)做了以下限制:
- Target為 Android 7.0 (API level 24)的App,將不會再收到在mainfest中注冊的?
CONNECTIVITY_ACTION
廣播。運行中的App仍然可以在Main Thread中通過Context.registerReceiver()
注冊?CONNECTIVITY_CHANGE
?廣播來監聽
- App 將不能夠發送或者接收?
ACTION_NEW_PICTURE
?or?ACTION_NEW_VIDEO
。這種優化會影響到所有的app,不僅是target為Android7.0的設備。`
因此如果你使用了這些intennt,應該盡快的移除對它們的依賴,以便你的app可以在Target為Android 7.0的設備上正常運行。Android框架提供了幾種解決方案去減小對這些隱式廣播的依賴。比如,JobScheduler
?and?GcmNetworkManager
提供了強健的機制去調度特定情況下的網絡操作。比如,你也可以使用JobScheduler去響應content provider的變化。JobInfo
對象封裝了JobScheduler
用于調度job的參數。當滿足指定的條件的時候,系統會通JobService過執行該job。
這篇文章將會告訴你如何使用替代的方法,比如JobScheduler去為你的app做這些限制的適配。
一 CONNECTIVITY_ACTION的限制
上面提到,Android 7.0 (API level 24) 將不再能夠收到mainfest中注冊的?CONNECTIVITY_ACTION
?廣播。Android框架中已經提供了幾種替代方案,如何選擇依賴于你的具體實現。
注意:一個通過?Context.registerReceiver()
注冊的BroadcastReceiver?在app運行期間是可以繼續收到廣播的。
在不可預測的網絡的情況下調度Network Jobs
當使用?JobInfo.Builder
?類構建?JobInfo
對象的時候, 通過?setRequiredNetworkType()
?方法并傳遞JobInfo.NETWORK_TYPE_UNMETERED
參數。下面的示例代碼演示了當設備連接到一個未知的網絡并且是在充電的時候,去調度一個service去執行的情景:
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 service的應用,并且target是5.0或者以下的,可以使用?GcmNetworkManager
?并指定?Task.NETWORK_STATE_UNMETERED。
在APP運行期間監測網絡連接
運行期間的App仍然可以監聽CONNECTIVITY_CHANGE
?,但是,?ConnectivityManager
?提供了更多強大的方法在特定網絡條件滿足的時候去觸發一個回調。
NetworkRequest
對象定義了NetworkCapabilities
相關網絡回調的參數,你可以通過NetworkRequest.Builder
類構建NetworkRequest對象,
registerNetworkCallback(),然后將NetworkRequest
傳遞對象到系統中去。當網絡條件滿足的時候,app就會受到一個回調去執行定義在?ConnectivityManager.NetworkCallback
中的?onAvailable()
方法。
App會一直接收注冊的回調,除非app退出或者調用unregisterNetworkCallback()
方法。
二 NEW_PICTURE 和 NEW_VIDEO 的限制
Android 7.0 (API level 24),中,app將不能夠發送和接收這兩個廣播。當幾個不同的app必須喚醒設備去處理一個新的Image或者video的時候,這樣的限制可以改善性能和用戶體驗的影響。Android 7.0 (API level 24) 擴展了?JobInfo
?和?JobParameters
來提供一種替代方案。
新的JobInfo方法
為了讓content URI的變化去觸發job,Android 7.0 (API level 24)擴展了?JobInfo
?的以下方法:
JobInfo.TriggerContentUri()
? 封裝了contentn URL變化需要的參數
-
JobInfo.Builder.addTriggerContentUri()
傳遞一個?
TriggerContentUri
?對象給JobInfo
。一個ContentObserver
?監測器封裝的content URI。如果這里有多個TriggerContentUri
?對象關聯到某個job上,只要其中某個URI變化,系統都會觸發回調事件。
TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS
?標志在任何給定URI的子集有變化的時候,都會觸發job。該標志對應于傳遞給registerContentObserver()
的notifyForDescendants
參數。
注意:?TriggerContentUri()
?cannot be used in combination with?setPeriodic()
?or?setPersisted()
. To continually monitor for content changes, schedule a new?JobInfo
?before the app’s?JobService
?finishes handling the most recent callback.
注意:TriggerContentUri()
?不能夠和?setPeriodic()
?或者?setPersisted()
一起使用。為了持續地監測content 的變化,可以在JobService
?處理完最近的回調之前去調度一個新的JobInfo。
下面的代碼演示了當系統上報一個MEDIA_URI 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()); } |
當系統上報指定的conent URI(s),你的app會收到一個回調和一個傳遞給onStartJob()
方法( 在MediaContentJob.class
中)的?JobParameters
?對象。
新的JobParameter方法
Android 7.0 (API level 24)也擴展了JobParameters
允許app接收有用的信息,該信息包含了具體是哪些content authorities 和 URIs 觸發了job。
Uri[] getTriggeredContentUris()
返回一個觸發了該Job的URIs數組。如果沒有URIs觸發job,或者URIs的數量大于50,那么該數組將為null(有可能job是由于其它原因觸發,比如一個deadline)。
String[] getTriggeredContentAuthorities()
Returns a string array of content authorities that have triggered the job. If the returned array is not?null
, use?getTriggeredContentUris()
?to retrieve the details of which URIs have changed.
The following sample code overrides the?JobService.onStartJob()
?method and records the content authorities and URIs that have triggered the job:
返回一個觸發了該Job的content authorities數組。如果返回的數組不為null,可以使用?getTriggeredContentUris()
方法獲取URIs變化的具體信息。
下面的代碼復寫了?JobService.onStartJob()
?方法,并且記錄了觸發job的 content authorities 和URIs :
@Override publicboolean onStartJob(JobParametersparams){ StringBuilder sb =newStringBuilder(); 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()); returntrue; } |
三 Further Optimizing Your App
為低內存設備或者在低內存條件做優化,可以提升系能和用戶體驗。移除對后臺的service的依賴和靜態方式注冊的隱式廣播,可以幫助你的app在這樣的設備上運行的更好。盡管7.0上采取了一些措施減少了這些問題,但還是建議去優化app,即使在完全沒有使用后臺進程的情況也可以正常運行。
Android 7.0 (API level 24)引入了一些?Android Debug Bridge (ADB)?命令,可以幫助你測試app在禁止后臺進程的情況下app的行為:
-
模擬隱式廣播和后臺service不可用的情況,可以使用下面的命令
$ adb shell cmd appops set RUN_IN_BACKGROUND ignore
-
重新開啟隱式廣播和后臺service
$ adb shell cmd appops set RUN_IN_BACKGROUND allow
來源:http://www.lightskystreet.com/2016/10/16/android-optimize-background/