Android(O) 8.0 怎樣在後臺啟動service
在Android8.0之後, google對後臺啟動service進行了更加嚴格的限制, 具體可以參考官網文件, 上不了外網的朋友看這個網址:android中文官網, 這裡摘出和後臺service相關的內容:
後臺服務限制
在後臺中執行的服務會消耗裝置資源,這可能降低使用者體驗。 為了緩解這一問題,系統對這些服務施加了一些限制。系統可以區分 前臺 和 後臺 應用。(用於服務限制目的的後臺定義與記憶體管理使用的定義不同;一個應用按照記憶體管理的定義可能處於後臺,但按照能夠啟動服務的定義又處於前臺。)如果滿足以下任意條件,應用將被視為處於前臺:
• 具有可見 Activity(不管該 Activity 已啟動還是已暫停)。
• 具有前臺服務。
• 另一個前臺應用已關聯到該應用(不管是通過繫結到其中一個服務,還是通過使用其中一個內容提供程式)。 例如,如果另一個應用繫結到該應用的服務,那麼該應用處於前臺:
1. IME
2. 桌布服務
3. 通知偵聽器
4. 語音或文字服務
如果以上條件均不滿足,應用將被視為處於後臺。
處於前臺時,應用可以自由建立和執行前臺服務與後臺服務。 進入後臺時,在一個持續數分鐘的時間窗內,應用仍可以建立和使用服務。
在該時間窗結束後,應用將被視為處於 空閒狀態。 此時,系統將停止應用的後臺服務,就像應用已經呼叫服務的“Service.stopSelf()”方法。
在這些情況下,後臺應用將被置於一個臨時白名單中並持續數分鐘。 位於白名單中時,應用可以無限制地啟動服務,並且其後臺服務也可以執行。
處理對使用者可見的任務時,應用將被置於白名單中,例如:
• 處理一條高優先順序 Firebase 雲訊息傳遞 (FCM) 訊息。
• 接收廣播,例如簡訊/彩信訊息。
• 從通知執行 PendingIntent。
繫結服務不受影響這些規則不會對繫結服務產生任何影響。 如果您的應用定義了繫結服務,則不管應用是否處於前臺,其他元件都可以繫結到該服務。
官方也給出了響應的解決辦法:
- 在很多情況下,您的應用都可以使用 JobScheduler 作業替換後臺服務。 例如,CoolPhotoApp
需要檢查使用者是否已經從朋友那裡收到共享的照片,即使該應用未在前臺執行。 - Android 8.0 引入了一種全新的方法,即Context.startForegroundService(),以在前臺啟動新服務。在系統建立服務後,應用有五秒的時間來呼叫該服務的startForeground() 方法以顯示新服務的使用者可見通知。如果應用在此時間限制內未呼叫
startForeground(),則系統將停止服務並宣告此應用為 ANR。
先來看第一個方法利用startForegroundService啟動後臺service
Intent service = new Intent(TheApplication.getInstance().getApplicationContext(), MyBackgroundService.class);
service.putExtra("startType", 1);
if (Build.VERSION.SDK_INT >= 26) {
TheApplication.getInstance().startForegroundService(service);
} else {
TheApplication.getInstance().startService(service);
}
簡單介紹說明一下, 主要就是第四行和第六行的程式碼, 第四行就是啟動前臺service, 在啟動之前判斷一下如果當前手機系統版本高於26就啟用前臺service, 否則還是正常startService即可
啟動完前臺service, 一定記得在5s以內要執行如下程式碼, 否則程式會報ANR問題
if (Build.VERSION.SDK_INT >= 26) {
startForeground(1, new Notification());
}
一樣還是先判斷系統版本, 如果高於26就呼叫startForeground方法好了, 使用startForegroundService 方法啟動後臺service這麼使用即可
第二種方法使用JobScheduler代替Service
- 使用步驟如下
- 1.建立一個類繼承自JobService
public class TestJobService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
return true;
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
}
這兩個方法很好理解, 一個是開始任務, 一個是停止任務, 這兩個方法都是系統呼叫的
onStartJob實在任務開始的時候執行, 返回值如果是false表明呼叫onStartJob後系統任務任務已經處理完成, 注意:當返回false的時候, 當系統在收到取消請求的時候, 會認為當前已經沒有任務在執行, 就不會呼叫對應的onStopJob方法了; 如果返回的是true, 表示告訴系統我的任務還在執行, 這時候系統就會把任務的結束呼叫交給使用者去做, 也就是我們需要在任務執行完畢的時候手動去呼叫jobFinished(JobParameters params, boolean needsRescheduled)來通知系統. 這裡的第一個引數params就是onStartJob方法的params, 第二個引數是表示是否在條件滿足時重複執行
注意onStartJob本身是在主執行緒中的, 所以如果要做耗時的操作記得另開子執行緒處理
例如:
@Override
public boolean onStartJob(JobParameters params) {
Log.d(TAG, "===onStartJob===");
task = new Task();
task.setJobParameters(params);
// 啟動對應的任務
task.start();
return true;
}
在裡面開一個子執行緒處理我們的任務, 然後返回true, 這樣系統就知道我們會主動去掉用jobFinished方法來通知系統任務完成
然後我們可以在task裡當我們的耗時任務執行完畢的時候去呼叫jobFinished()方法, 以下就是整個JobScheduler的示例程式碼:
public class TestJobScheduler extends JobService {
private String TAG = "TestJobScheduler";
private Task task;
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "===onCreate===");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "===onDestroy===");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "===onStartCommand===");
return super.onStartCommand(intent, flags, startId);
}
@Override
public boolean onStartJob(JobParameters params) {
Log.d(TAG, "===onStartJob===");
task = new Task();
task.setJobParameters(params);
// 啟動對應的任務
task.start();
return true;
}
@Override
public boolean onStopJob(JobParameters params) {
Log.d(TAG, "===onStopJob===");
return false;
}
class Task extends Thread {
private JobParameters params;
public void setJobParameters(JobParameters params) {
this.params = params;
}
@Override
public void run() {
Log.d(TAG, " start record ");
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
dealResult(params);
}
}
private void dealResult(JobParameters params) {
try {
// do something
} catch (Exception e) {
} finally {
Log.d(TAG, "===finally===");
jobFinished(params, false);
}
}
}
最後就是怎麼開啟一個任務了, 先看程式碼
JobScheduler mJobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder( 1,
new ComponentName("com.wb.demo.jobschedule",
TestJobScheduler.class.getName() ) );
builder.setMinimumLatency(10);
builder.setPersisted(false);
mJobScheduler.schedule(builder.build());
第一個行就是獲取一個JobScheduler物件, 第2到4行就是建立一個JobInfo.Builder物件,並設定響應的屬性, 第一個引數1 是一個類似任務id的引數, 第二個ComponentName就是要啟動的JobSchedulerService的包名和類名, 在呼叫schedule()這個方法的後, 如果預設定的條件滿足, 系統就會呼叫你的JobSchedulerService的onStartJob()方法, 任務就會開始執行.
關於其他的幾個設定介紹如下:
- setMinimumLatency(long minLatencyMillis): 表示多少毫秒後開始執行任務,這個函式與setPeriodic(long time)方法互斥,這兩個方法同一時候呼叫了就會引起異常, 與setPeriodic(long time)互斥;
- setPeriodic(long intervalMillis): 表示多長時間重複執行一次
- setOverrideDeadline(long maxExecutionDelayMillis): 設定任務最大的延遲時間,也就是到設定的時間後, 不管其他條件是否滿足都會開始任務. 與setMinimumLatency(long time)一樣,該方法也會與setPeriodic(long time)互斥。
- setPersisted(boolean isPersisted): 表明當裝置重新啟動後你的任務是否還要繼續執行。
- setRequiresCharging(boolean requiresCharging): 是否需要當裝置在充電時任務才會被執行。
- setRequiresDeviceIdle(boolean requiresDeviceIdle): 表示任務只有當用戶沒有在使用該裝置且有一段時間沒有使用時才會啟動。
- setRequiredNetworkType(int networkType): 指明在滿足指定的網路條件時才會被執行。預設條件是JobInfo.NETWORK_TYPE_NONE, 即無論是否有網路這個任務都會被執行。另外兩個可選型別。一種是JobInfo.NETWORK_TYPE_ANY,它表明須要隨意一種網路才使得任務能夠執行。還有一種是JobInfo.NETWORK_TYPE_UNMETERED,它表示裝置不是蜂窩網路( 比方在WIFI連線時 )時任務才會被執行。剩下的還有兩種分別是NETWORK_TYPE_NOT_ROAMING 不是漫遊, NETWORK_TYPE_METERED. wifi