版權聲明:本文為博主原創文章,未經博主允許不得轉載
PS:轉載請注明出處
作者: TigerChain
地址: http://www.lxweimin.com/p/1443fa4036dc
本文出自 TigerChain 簡書 Android 系列
教程簡介
- 1、閱讀對象
本篇教程適合新手閱讀,老手直接略過 - 2、教程難度
初級 - 3、Demo 地址
稍后 提供
正文
Service 是一個神奇的東西,它可以執行長時間后臺任務,并且在后臺默默無聞的工作著,沒有顯示界面(也不露臉去表現自己),說到這里大家可能會想到,fk–Thread 也能干這樣的事呀?那 Service 存在有必要嗎?在這一節里這些問題都會一一解答
一、什么是 Service
在講解 Service 之前我們先要知道什么是 Service,按照慣例,直接拿官方的解釋來看
Service is an application component that can perform long-running operations in the
background and does not provide a user interface ...
大體就是說 Service 是一個 Android 系統的組件可以在后臺執行一些長時間操作并且沒有用戶界面
Service一個進程嗎?是一個線程嗎?
Service 既不是一個進程,也不是一個線程,而且它默認是工作在主線程中的,有的同鞋會想運行在主線程?我們都知道在主線程進行耗時操作會 ANR 的,Service 是用來執行耗時操作的難道不會 ANR 嗎,如果不會 難道 ANR 的機制還有兩套,如果會,那么 Service 為什么還能執行耗時操作,別急這些疑問慢慢解答
我們從官方描述可以知道 Service 是不依賴 UI 運行有后臺用來執行耗時操作的一個的系統組件,我們知道 Thread 就是用來執行耗時操作的,那么 Android 有必要要有 Service 這個東西嗎?Thread 就夠了呀,答案是非常有必要有,因為存在就有必要(靠~~ 說了等于沒有說 _ )
Service 和 Thread 的關系
Service 和 Thread 沒有半毛錢關系,如果非要說有關系,也就是組合使用的關系,我們對Service 和 Thread 產生混淆的主要一點是由于官方說了 Service 是后臺任務來處理一些長時間的操作,這和 Thread 的功能非常類似,懂Handler的朋友尤其明白。其實 Thread 和 Service 的后臺意思不太一樣,前者是指不依賴UI后者運行在一個工作線程的后臺任務,而且 Service 是運行在主線程中的,一個主線程一個子線程程肯定兩者之間沒有半毛錢關系(除非用于線程間通信),至于為什么 Service 運行在主線程,我們后面再說Service 的特點
(1)、優先級高于 Acitivity,一般情況下即使 Activity 銷毀了 Service 任然可以運行,Service 優先級也高于 Activity 所創建的 Thread,優先級高就不會輕易被系統殺死,除非非常有必要
(2)、Service 有自己的生命周期,這樣的話就很好控制,而 Thread 的生命周期一般是依賴它被啟動的環境中
二、Service 的創建和啟動方式
Service 的創建
/**
* Created by TigerChain.
*/
public class MyService extends Service {
private static final String TAG = MyService.class.getSimpleName();
@Override
public void onCreate() {
Log.e(TAG,"onCreate") ;
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG,"onStartCommand") ;
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
Log.e(TAG,"onDestroy") ;
super.onDestroy();
}
}
Service 是系統組件,既然是系統四大組件之一,那么就要在 Mainfest 中去聲明
<service android:name="com.jun.servicedemo.MyService"></service>
在MainActivity中聲明一個按鈕并用綁定事件
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private static final String TAG = MainActivity.class.getSimpleName();
private Button start_service ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView() ;
}
private void initView() {
start_service = (Button) this.findViewById(R.id.start_service) ;
start_service.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.start_service:
Intent startServiceIntent = new Intent(MainActivity.this,MyService.class) ;
startService(startServiceIntent) ;
break ;
}
}
}
然后通過startService()來啟動Service
Intent startServiceIntent = new Intent(MainActivity.this,MyService.class) ;
startService(startServiceIntent) ;
然后我們運行并查看結果,如下圖所示
我們清楚的看到了,首次點擊 startService 按鈕的時候會依次調用 MyService 的 onCreate–>onStartCommand 方法
我們再點擊 startService 按鈕,圖如下
以后不管再調用多少次 StartService 方法都只會調用 onStartCommand 方法
我們在 Myservice 和 MainActivity 的 onCreate 中分別加入一條 Log 信息
//用來獲取當前類所在線程
Log.e(TAG,Thread.currentThread().getId()+"") ;
再次運行,查看 Log
神奇吧,居然 Service 和 Activity 是在同一個線程中,Activity 是在 UI 線程(主線程中),所以Service 默認也是在主線程中的,這就回答了上面的 Service 不是線程,也不是進程,它運行在主線程中,所以它執行耗時操作肯定會 ANR ,如何解決,答案是在 Service 中開啟一個 Thread 來執行耗時操作,當然還可以有別的辦法,我們后面再說
Service 的 ANR
我們來模擬一個 Service ANR 的效果,我們在 MyService 的 onStartCommand() 方法中添加如入代碼
try {
//模擬耗時操作
Thread.sleep(80000);
} catch (InterruptedException e) {
e.printStackTrace();
}
當我們點擊 startService 按鈕的時候會發現先卡頓一小會作,然后就會報 ANR,典型的主線程進行耗時操作所帶來的問題,如下圖:
那么如何解決呢,肯定要開子線程去處理耗時操作,代碼修改
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG,"onStartCommand") ;
//這里啟用一個子線程用來處理耗時操作
new Thread(new Runnable() {
@Override
public void run() {
try {
//模擬耗時操作
Thread.sleep(80000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
這樣處理以后,就永遠不會 ANR 了
總結:Service 運行在主線程中,如果想要操作耗時操作必須在 Service 中開啟子線程去處理。
調用 startService 以后,如果想要停止 Service 一定要手動調用 stopService 來停止,我們可看看 Service 的啟動方式
Service的啟動方式
1、
startService
通過上面的例子,我們已經了解了如何使用 startService 來啟動一個 Service 了2、
bindService
按照官網的說法就是通過 bindService 可以創建一個客戶端接口來和 Service 交互,并且還可以通過 aidl 來實現進程間通訊。
下面我們我們用實例來看看 bindService
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private static final String TAG = MainActivity.class.getSimpleName();
private Button start_service ,stop_service ,bind_service,unbind_service;
private MyService.MyBinder myBinder ;
//服務是否綁定的標志位
private boolean isBind ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView() ;
Log.e(TAG,"所在的線程id:"+Thread.currentThread().getId()+"") ;
}
private void initView() {
start_service = (Button) this.findViewById(R.id.start_service) ;
start_service.setOnClickListener(this);
stop_service = (Button) this.findViewById(R.id.stop_service) ;
stop_service.setOnClickListener(this);
bind_service = (Button) this.findViewById(R.id.bind_service) ;
bind_service.setOnClickListener(this);
unbind_service = (Button) this.findViewById(R.id.unbind_service) ;
unbind_service.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.start_service:
Intent startServiceIntent = new Intent(MainActivity.this,MyService.class) ;
startService(startServiceIntent) ;
break ;
case R.id.stop_service:
Intent stopServiceIntent = new Intent(MainActivity.this,MyService.class) ;
stopService(stopServiceIntent) ;
break ;
case R.id.bind_service:
Intent bindServiceIntent = new Intent(MainActivity.this,MyService.class) ;
bindService(bindServiceIntent,myServiceConnection, BIND_AUTO_CREATE) ;
break ;
case R.id.unbind_service:
if(isBind){
unbindService(myServiceConnection);
isBind = false;
}
break ;
}
}
private ServiceConnection myServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//和服務連接的時候調用
myBinder = (MyService.MyBinder) service ;
myBinder.progressLongTimeTask();
isBind = true ;
}
@Override
public void onServiceDisconnected(ComponentName name) {
//服務斷開的時候調用(由于異常時斷開) Service被停止或被系統殺死的時候調用
Log.e(TAG,"service 斷開") ;
myBinder = null ;
}
} ;
}
從代碼中可以看到,我們添加了兩個按鈕 bind_service 和 unbind_service 并添加相應的點擊事件,我們在調用 bindService(Intent service, ServiceConnection conn,int flags)
的時候需要傳入三個參數,第一個是 Intent,第二個是服務的連接類,第三個是標志位,這里傳入 BIND_AUTO_CREATE 表示 Activty 和 Service 建立關聯后自動創建 Service
相應的我們的 MyService 也要添加代碼
/**
* @author TigerChain
**/
public class MyService extends Service {
private static final String TAG = MyService.class.getSimpleName();
private MyBinder myBinder = new MyBinder() ;
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG,"onCreate") ;
Log.e(TAG,"所在的線程id:"+Thread.currentThread().getId()+"") ;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG,"onStartCommand") ;
new Thread(new Runnable() {
@Override
public void run() {
try {
//模擬耗時操作
Thread.sleep(80000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG,"IBinder") ;
return myBinder;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG,"onDestroy") ;
}
class MyBinder extends Binder{
//這里模擬耗時任務
public void progressLongTimeTask(){
Log.e(TAG,"處理耗時任務") ;
}
}
@Override
public boolean onUnbind(Intent intent) {
Log.e(TAG,"onUnbind") ;
return true ;
}
@Override
public void unbindService(ServiceConnection conn) {
super.unbindService(conn);
Log.e(TAG,"unbindService") ;
}
@Override
public void onRebind(Intent intent) {
super.onRebind(intent);
Log.e(TAG,"onRebind") ;
}
}
先來大概解釋一下,在這里我們定義了一個 MyBinder 類來繼承自 Binder,Binder 是 IBinder 的一個實現類,然后在 MyService 的 onBind() 方法中返回這個類的實例,當我們調用 bindService 的方法的時候就會觸發 onBind() 方法把這個 MyBinder 類的實例返回去,返回到那里呢?就是MainActivity 中的 onServiceConnected 中的 IBinder 中,然后我們就可以在 Activity 中拿到 Binder 了,然后就可以為所欲為了…
private ServiceConnection myServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//和服務連接的時候調用
myBinder = (MyService.MyBinder) service ;
myBinder.progressLongTimeTask();
isBind = true ;
}
@Override
public void onServiceDisconnected(ComponentName name) {
//服務斷開的時候調用(由于異常時斷開) Service被停止或被系統殺死的時候調用
Log.e(TAG,"service 斷開") ;
myBinder = null ;
}
} ;
廢話少說,我們點擊 bindService 和 unbindService 按鈕來看看 Log 信息
從 gif 圖中我們清楚的看到了點擊 bindservice 按鈕依次調用 MyService 的 oncreate()->onBind()
方法,而點擊 unbindservice 按鈕會依次調用 MyService 的 onUnbind()-> onDestroy()
方法,unbindservice 按鈕如果我們點擊多次就會報錯,說沒有注冊 Service,我們是程序員當然對這種異常是0容忍的,解決辦法上面代碼中已經體現,在 MainActivity 中添加一個標志位 isBind 然后判斷一下即可,當然你也可以有自己的解決方案
細心的朋友們發現我們重寫了 Service 的 onRebind
方法,那么這個方法有什么卵用,何時調用,首先 onRebind
方法的調用必須滿足兩個條件,我們來看這個方法注釋中雜說
/**
* Called when new clients have connected to the service, after it had
* previously been notified that all had disconnected in its
* {@link #onUnbind}. This will only be called if the implementation
* of {@link #onUnbind} was overridden to return true.
*
* @param intent The Intent that was used to bind to this service,
* as given to {@link android.content.Context#bindService
* Context.bindService}. Note that any extras that were included with
* the Intent at that point will <em>not</em> be seen here.
*/
public void onRebind(Intent intent) {
}
尼瑪,啥求意思呀,總結起來就兩點,也就是 onRebind() 調用滿足的條件
- 服務被綁定后沒有銷毀
- onUnbind 方法必須返回值為 true
從上面的代碼中可知,我們第二個條件是滿足了,我們給 onUnbind
方法手動的返回了 true
,第一種情況就要配合 startService
了,我們來看這種情況,為了清楚的看日志信息,我把無關的 Log 注釋掉了,如下:
如 gif 圖我們依次調用了 bindService->startService
,然后再調用 unBindService
然后再調用 bindService
,這樣就調用了 Service
的 onRebind
方法,以后只要不調用 stopService
方法,重復調用 unBindService 和 bindService
都會執行 onRebind
方法。在這里我們獲取到了一個重要信息,就是當調用了 startServcie
再調用 bindService
的時候,如果再調用 unBindService
是沒有銷毀 SerVice
的,不然的話 onRebind
方法是不會調用的,關于 startService
和 bindService
調用同一個 Service
的情況我們后面討論
三、當 bindService 遇上 startService
- 1、先看先
startService->bindService->unbindService->stopService
和startService->bindService->stopService->unBindService
這兩種情況
無圖無直相,直接上圖
圖中我們把上面說的兩種情況都實現了:
(1)、首先看 startService->bindService->unbindService->stopService
這種情況:我們清楚的看到依次調用 Service 的
onCreate
、onStartCommand
、onBind
、onUnbind
和 onDestory
方法,以下分別是 startService
和 bindService
對應的方法
startService: onCreate,onStartCommand onDestory
bindService:onBind,onUnbind
先調用 unbindService
再調用 stopService
,會分別調用 Service
的 onUnbind
方法和 onDestory
方法
(2)、其次看 startService->bindService->stopService->unBindService
這種情況:分別依次調用了 Service 的 onCreate
、onStartCommand
、onBind
、onUnbind
和 onDestory
我肋個去和上面一毛一樣,別急我們慢慢看,以下分別是 startService
和 bindService
對應的方法
startService: onCreate,onStartCommand
bindService:onBind,onUnbind onDestory
看到區別了沒,如果先調用
stopService
再調用unBindService
前者任何 log 都不打,只是把 Service 暫停了,再調用unBindService
的時候會依次調用 Service 的onUnbind
和onDestory
方法
- 2、再看
bindService->startService->unbindService->stopService
和bindService->startService->stopService->unBindService
這兩種情況,小二,上圖:
圖中我們看到兩種情況都實現了
(1)、首先行看 bindService->startService->unbindService->stopService
情況會依次調用 Service 的 onCreate->onBind->onStardCommand->onUnbind->onDestory
以下分別是 bindService
和 startService
調用方法
bindService: onCreate, onBind ,onUnbind
startService: onStardCommand , onDestory
解釋一下,先調用
bindService
會觸發 Service 的onCreate
方法和onBind
方法,再調用startService
會調用 onStardCommand 方法,再調用unbindService
方法會調用 Service 的onUnbind
方法,最后調用stopService
會調用 Service 的onDestory
方法
(2)、再看 bindService->startService->stopService->unBindService
情況會依次調用 Service 的 onCreate->onBind->onStardCommand->onUnbind->onDestory
以下分別是 bindService
和 startService
調用方法
indService: onCreate, onBind ,onUnbind,onDestory
startService: onStardCommand
區別就是先調用 stopService
什么方法都沒有調用沒有日志信息,只是把 Service 暫停了,再調用 unBindService
方法會依次調用 onUnbind
和 onDestory
方法
四、總結:
- 1、
startService
和bindService
可以啟動同一個 Service - 2、
startService
和bindService
啟動同一個 Service 的時候如果想銷毀 Service 就既要調用stopService
又要調用unBindService
方法,先后順序無關,但是最后成對調用
到此為止,我們就就把 Service 的基本用法說說完,一定要親自試試哦