@[toc]
Service基本介紹
Android四大元件之一,沒有介面的後臺服務。我們如果想要在後臺執行一些任務,會用到Service。比如:後臺下載更新;後臺播放音樂等等。
Service生命週期
-
Service有兩種啟動方式,通過兩種不同的啟動方式,生命週期執行方法也是不一樣的:
-
startService:onCreate --> onStartCommand --> onStart ---> onDestroy
相對應的停止方法為:stopService
重複呼叫 startService 不會重新走 onCreate方法,只會走 onStartCommand --> onStart
-
bindService: onCreate --> onBind ---> onUnBind ---> onDestroy
相對應的解綁方法為: unBindService
重複呼叫 bindService 方法,不會再次走 onCreate 方法,只會走 onBind 方法
-
-
混合啟動:
service的onCreate只會走一次。
先呼叫 startService,再呼叫 bindService。 onCreate --> onStartCommand --> onStart --(此時呼叫 bindService )---> onBind。
先呼叫 bindService,再呼叫 startService。 onCreate --> onBind --(此時呼叫 startService )---> onStartCommand --> onStart。
混合啟動想要銷燬 Service 必須 stopService、unBindService 都呼叫,否則無效。
Service的基本用法
-
首先建立一個class繼承Service
繼承 service 的服務,在裡面不能進行耗時操作,因為它本身也是依賴於主程式的,不是一個單獨的執行緒,無法進行耗時操作。
public class MyService extends Service {
public static final String TAG = "MyService";
@Override
public void onCreate() {
Log.i(TAG, "onCreate: ");
super.onCreate();
}
@Override
public void onStart(Intent intent, int startId) {
Log.i(TAG, "onStart: ");
super.onStart(intent, startId);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand: ");
//在這裡建立一個子執行緒,睡眠5秒鐘後,呼叫關閉 service 方法
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
stopService1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "onBind: ");
// 返回代理物件
return new MyBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "onUnbind: ");
return super.onUnbind(intent);
}
public void stopService1() {
Log.i(TAG, "停止服務");
stopSelf();
}
public void receiveMsg(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
@Override
public void onDestroy() {
Log.i(TAG, "onDestroy: ");
super.onDestroy();
}
//建立一個Service的代理類,通過它可以呼叫Service中的方法。
public class MyBinder extends Binder {
public void sendMsg(String msg) {
receiveMsg(msg);
}
}
}
複製程式碼
- 在 AndroidManifest.xml 檔案中註冊:
<!-- enabled 是否可以被啟用,如果設定為false,就無法啟用Service-->
<!-- exported 是否可以被外部*程式*啟用,-->
<service
android:name=".service.MyService"
android:enabled="true"
android:exported="false" />
複製程式碼
- 在Activity中啟動,以及關閉,以及怎麼呼叫Service中的方法:
public class ServiceActivity extends AppCompatActivity implements View.OnClickListener {
private Button mBtnStartService;
private Button mBtnStopService;
private Button mBtnBindService;
private Button mBtnUnbindService;
private MyService.MyBinder myIBinder;
private MyConnection myConnection;
private Button mBtnCallService;//呼叫Service中的方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_service);
initView();
initAction();
initData();
}
private void initView() {
mBtnStartService = findViewById(R.id.btn_start_service);
mBtnStopService = findViewById(R.id.btn_stop_service);
mBtnBindService = findViewById(R.id.btn_bind_service);
mBtnUnbindService = findViewById(R.id.btn_unbind_service);
mBtnCallService = findViewById(R.id.btn_call_service);
}
private void initAction() {
mBtnStartService.setOnClickListener(this);
mBtnStopService.setOnClickListener(this);
mBtnBindService.setOnClickListener(this);
mBtnUnbindService.setOnClickListener(this);
mBtnCallService.setOnClickListener(this);
}
private void initData() {
myConnection = new MyConnection();
}
@Override
public void onClick(View v) {
Intent service = new Intent(this, MyService.class);
switch (v.getId()) {
case R.id.btn_start_service:
startService(service);
break;
case R.id.btn_stop_service:
stopService(service);
break;
case R.id.btn_bind_service:
bindService(service, myConnection, BIND_AUTO_CREATE);
break;
case R.id.btn_unbind_service:
unbindService(myConnection);
break;
case R.id.btn_call_service:
myIBinder.sendMsg("我是Activity傳來的訊息");
break;
default:
break;
}
}
public class MyConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//獲取Service的代理
myIBinder = (MyService.MyBinder) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
}
複製程式碼
- xml佈局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".service.ServiceActivity">
<Button
android:id="@+id/btn_start_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="start_service" />
<Button
android:id="@+id/btn_stop_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="stop_service" />
<Button
android:id="@+id/btn_bind_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="bind_service" />
<Button
android:id="@+id/btn_unbind_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="unbind_service" />
<Button
android:id="@+id/btn_call_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="呼叫Service內部方法" />
</LinearLayout>
複製程式碼
-
實驗結果:
點選 bindService 繫結Service,然後點選 btn_call_service ,彈出吐司 “我是Activity傳來的訊息”,完成Activity和Service的呼叫
-
注意: Service是有可能繫結失敗的,具體原因和解決方案:bindService不呼叫onServiceConnected的問題
-
onStartCommand:
@Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent, flags, startId); } 複製程式碼
onStartComand使用時,返回的是一個(int)整形。
這個整形可以有四個返回值:start_sticky、start_no_sticky、START_REDELIVER_INTENT、START_STICKY_COMPATIBILITY。
它們的含義分別是:
-
START_STICKY:如果service程式被kill掉,保留service的狀態為開始狀態,但不保留遞送的intent物件。隨後系統會嘗試重新建立service,由於服務狀態為開始狀態,所以建立服務後一定會呼叫onStartCommand(Intent,int,int)方法。如果在此期間沒有任何啟動命令被傳遞到service,那麼引數Intent將為null。
-
START_NOT_STICKY:“非粘性的”。使用這個返回值時,如果在執行完onStartCommand後,服務被異常kill掉,系統不會自動重啟該服務
-
START_REDELIVER_INTENT:重傳Intent。使用這個返回值時,如果在執行完onStartCommand後,服務被異常kill掉,系統會自動重啟該服務,並將Intent的值傳入。
-
START_STICKY_COMPATIBILITY:START_STICKY的相容版本,但不保證服務被kill後一定能重啟。
flags表示啟動服務的方式:
Additional data about this start request. Currently either 0,START_FLAG_REDELIVERY,or START_FLAG_RETRY。
-
START_FLAG_REDELIVERY:如果你實現onStartCommand()來安排非同步工作或者在另一個執行緒中工作, 那麼你可能需要使用START_FLAG_REDELIVERY來讓系統重新傳送一個intent。這樣如果你的服務在處理它的時候被Kill掉, Intent不會丟失.
-
START_FLAG_RETRY:表示服務之前被設為START_STICKY,則會被傳入這個標記。
-
IntentService:
-
介紹
相對於Service而言,IntentService本身擁有一個子執行緒,可以進行耗時操作,在任務程式碼完成後,會自動銷燬服務,不用手動呼叫。
-
程式碼:
public class MyIntentService extends IntentService {
private static final String TAG = "MyIntentService";
public MyIntentService() {
super("MyIntentService");
}
多出一個onHandleIntent方法,可以在裡面做耗時操作
@Override
protected void onHandleIntent(Intent intent) {
try {
Thread.sleep(5000);
Log.i(TAG, "onHandleIntent: 睡了5秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void onDestroy() {
Log.i(TAG, "onDestroy: ");
super.onDestroy();
}
}
複製程式碼
-
步驟:和Service一樣,在 Activity 中呼叫 startService 方法開啟服務。只不過 IntentService 在開啟後,onHandleIntent 中的程式碼執行完後,會自動銷燬服務。
-
實驗結果:
2019-03-05 00:26:14.972 16106-16106/com.sjc.myapplication I/MyIntentService: onCreate: 2019-03-05 00:26:14.981 16106-16106/com.sjc.myapplication I/MyIntentService: onStart: 2019-03-05 00:26:19.983 16106-17272/com.sjc.myapplication I/MyIntentService: onHandleIntent: 睡了5秒 2019-03-05 00:26:19.985 16106-16106/com.sjc.myapplication I/MyIntentService: onDestroy: 複製程式碼
-
原始碼分析: IntentService原始碼並不長。
//本身是一個抽象類,繼承與Service
public abstract class IntentService extends Service {
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private String mName;
private boolean mRedelivery;
//在內部建立了一個 Handler物件
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
//執行完 onHandleIntent 方法後,呼叫了stopSelf方法,銷燬服務。
onHandleIntent((Intent)msg.obj);
//銷燬服務
stopSelf(msg.arg1);
}
}
public IntentService(String name) {
super();
mName = name;
}
public void setIntentRedelivery(boolean enabled) {
mRedelivery = enabled;
}
@Override
public void onCreate() {
super.onCreate();
//在onCreate 中建立了一個 HandlerThread 執行緒
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
//開啟執行緒
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
//onStrat 中傳送訊息,然後回撥ServiceHandler 的 handleMessage 方法
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
@Override
public void onDestroy() {
//退出Looper
mServiceLooper.quit();
}
@Override
@Nullable
public IBinder onBind(Intent intent) {
return null;
}
//抽象方法,在子類中重寫。
@WorkerThread
protected abstract void onHandleIntent(@Nullable Intent intent);
}
複製程式碼
- 總結: IntentService 只能通過 startService 方式開始有用,才會回撥 onHandleIntent 方法。且執行完子執行緒中的任務後,自動銷燬。如果使用 bindService 方式開啟,和普通 Service 沒有差別。