android 入行也大半年了,一直都沒有對基礎知識做一個梳理,今后就對基礎知識逐個總結,先從 Service 開始。
一、Servive 概念
我們一般把 Service 稱做 "后臺默默的勞動者"。現在的 智能手機應用程序都能在后臺運行,這就是 得益于 Service 。在
android 開發中,Service 就是用來解決程序后臺運行的解決方案,適合去執行一些不需要跟用戶交互而且要求長期運行的任務。不依賴任何用戶界面,即使程序切換到后臺,或者打開新的應用程序。但是需要注意以下兩點:
- Service 不是運行在一個獨立的進程中,而是依賴于創建該Service的 應用程序進程,一旦該程序被 kill ,Service 也隨之停止運行
- 不要以為 Service 是工作在后臺,就理所當然的認為 Service 會自動開啟線程。Service 默認是工作在 UI 線程的,一旦有耗時操作,必須開啟新線程,防止 ANR 異常。
二、Service 的定義
使用 android studio 快速定義一個 MyService 繼承自 Service.Service中 onBind() 為抽象方法,我們暫且先必須實現它,接著依次實現 onCreate() ,onStartCommand,onDestroy 生命周期方法。如下所示:
public class MyService extends Service {
private static final String TAG = "MyService";
public MyService() {
}
/**
* 跟其他組件綁定時使用
* @param intent
* @return
*/
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* Service 第一次創建時回調
*/
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "onCreate: excuted");
}
/**
* 每次啟動 Service 時回調
* @param intent
* @param flags
* @param startId
* @return
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "onStartCommand: excuted");
return super.onStartCommand(intent, flags, startId);
}
/**
* 銷毀 Service 時回調
*/
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy: excuted");
}
}
注意:身為 四大組件之一的 Service 也必須在 Manifest 里面注冊,不然會報錯,但是這里智能的 as 已經幫我們注冊好啦~
<service
android:name=".app.service.MyService"
android:enabled="true"
android:exported="true">
</service>
- enabled表示是否啟用該 服務
- exported表示是否允許除了當前應用程序之外的其他程序訪問這個服務。
三、Service 的啟動與停止
我們在 activity 中添加兩個 Button ,用來觸發 Service 的啟動與停止。這里使用黃油刀直接注入。啟動與停止的邏輯相當簡單,一般我們組件間通信都是用 intent ,這里也不例外,startService(),stopService() 傳入對應的 intent 即可。
@OnClick({R.id.start_service, R.id.stop_service})
public void onClick(View view) {
switch (view.getId()) {
case R.id.start_service:
Intent startIntent = new Intent(this, MyService.class);
//啟動service
startService(startIntent);
break;
case R.id.stop_service:
Intent stopIntent = new Intent(this, MyService.class);
//停止service
stopService(stopIntent);
break;
}
直接 run 程序跑到手機上,來看看 Service 具體的啟動過程,我們事先已經在對應的生命周期方法中打好 log 了。現在點擊 start 效果如下:
05-18 14:42:43.998 32095-32095/? E/MyService: onCreate: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onStartCommand: excuted
log 很清晰明了。當我們點擊 start按鈕啟動 Service 時,系統回調了onCreate(),onStartCommand() 方法。這時候我們大膽猜測 Service 已經啟動了,打開手機應用管理查看,如圖
從此圖明顯看出 Service 確實啟動成功了,同時也可以印證我們之前的觀點:
** Service 不是運行在一個獨立的進程中,而是依賴于創建該 Service 的 應用程序進程,一旦該程序被 kill ,Service 也隨之停止運行**
這個時候我們再點擊幾次 starService 按鈕,看看啟動流程。
05-18 14:42:43.998 32095-32095/? E/MyService: onCreate: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:37.532 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:42.776 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:48:09.794 32095-32095/? E/MyService: onStartCommand: excuted
啊?當我再次點擊三次 startService ,從 log 可以清晰的看出。這時候 onCreate() 已經不再回調了,而是重復回調了三次 onStartCommand()。 這時查看手機應用管理查看該 Service 也確實還在。我們抱著懷疑的態度繼續做下實驗。
點擊 stopService 執行停止 Service 的邏輯。
05-18 14:42:43.998 32095-32095/? E/MyService: onCreate: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:37.532 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:42.776 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:48:09.794 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:48:58.970 32095-32095/? E/MyService: onDestroy: excuted
看看 log 知道系統回調了 onDestroy() 方法。此時我們看下手機,啊?MyService 已經沒了。這樣我們銷毀 Service成功。我們繼續~
點擊 starService
05-18 14:42:43.998 32095-32095/? E/MyService: onCreate: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:37.532 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:42.776 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:48:09.794 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:48:58.970 32095-32095/? E/MyService: onDestroy: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onCreate: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onStartCommand: excuted
看看 log 。納尼?又從頭開始回調一遍,再看看手機中運行程序,臥槽?MyService 又回來了。ok,結論來了~
- 當我們第一次啟動 Service 時,Service 還不存在,系統會回調 onCreate() 創建Service ,同時回調 onStartCommand() 啟動 Service。
- Service 一旦被創建,假如你沒有 手動調用 stopService() 去銷毀它,或者 kill 程序。它就一直會處于后臺進程中,當我們再次啟動時,系統不會再去創建,而是直接啟動,即回調 onStartCommand() 方法。
四、Service 與 Activity 通信
之前的 Service 啟動方式似乎不那么靈活,我們新建一個服務是要他給我們干活的,然而現在我們只能 start 、stop 去啟動、停止 Servive 。start 完之后 Service 就在那邊不停的干活。怎么干活、干了多少 、做得如何 我們不得而知。那么我們就有必要換一種方式來啟動 Service 了。讓我們能夠擁有主動權。
假設一個場景:我們需要啟動一個 Service 去執行下載功能,我們要控制什么時候下載,知道下載進度。
仔細看看 MyService 中的代碼 、好像一開始就有一個回調方法在那邊沒用呢?哈哈,就從這個方法進去搞事情,修改 MyService 中的代碼。
public class MyService extends Service {
private static final String TAG = "MyService";
private DownloadBinder mBinder = new DownloadBinder();
/**
* 跟其他組件綁定時使用
*
* @param intent
* @return
*/
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return mBinder;
}
/**
* Binder---IBinder的實現類,綁定Service 的時候,返回實例給調用方使用
*/
public class DownloadBinder extends Binder {
public void startDownload() {
Log.e(TAG, "startDownload: excuted");
}
public int getProgress() {
Log.e(TAG, "getProgress: excuted");
return 0;
}
}
}
Binder 是 IBinder的實現類,綁定 Service 的時候,返回實例給調用方使用。我們自定義 Binder 類,在類中定義 public 方法,最終將 Binder 實例通過 onBind 返回給調用方,調用方只要有 Binder 實例就能隨便調用它的公共方法。
那么 Activity (調用方) 需要拿到 Binder 實例。定義全局 DownloadBinder 。實例化
ServiceConnection 回調 onServiceConnected,onServiceDisconnected方法。仔細一看,onServiceConnected 攜帶 IBinder 參數。這個就是一旦我們綁定成功,Service 回傳回來的實例,這時候通過向下轉型成 DownloadBinder 即可。之后就可以隨意控制方法調用啦~
private MyService.DownloadBinder mDownloadBinder;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mDownloadBinder = ((MyService.DownloadBinder) service);
mDownloadBinder.startDownload();
mDownloadBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
修改布局 Button.點擊 bindService 執行 bindService() 傳入對應的 Intent,ServiceConnection實例,標志位(這里表示綁定之后自動創建Service)。
@OnClick({R.id.bind_service, R.id.unbind_service})
public void onClick(View view) {
switch (view.getId()) {
case R.id.bind_service:
Intent bindIntent = new Intent(this, MyService.class);
bindService(bindIntent, mConnection, BIND_AUTO_CREATE);//綁定服務
break;
case R.id.unbind_service:
unbindService(mConnection);//解除綁定
break;
}
}
運行效果如下:
05-18 17:59:43.440 23371-23371/com.sunnada.jpushdemo E/MyService: onCreate: excuted
05-18 17:59:43.444 23371-23371/com.sunnada.jpushdemo E/MyService: startDownload: excuted
05-18 17:59:43.444 23371-23371/com.sunnada.jpushdemo E/MyService: getProgress: excuted
我們看到創建了 Service ,但是不會回調 onStartCommand 方法了。同時執行了下載的方法。
此時點擊 unBinderService 按鈕,看下log
05-18 17:59:43.440 23371-23371/com.sunnada.jpushdemo E/MyService: onCreate: excuted
05-18 17:59:43.444 23371-23371/com.sunnada.jpushdemo E/MyService: startDownload: excuted
05-18 17:59:43.444 23371-23371/com.sunnada.jpushdemo E/MyService: getProgress: excuted
05-18 18:00:13.051 23371-23371/com.sunnada.jpushdemo E/MyService: onDestroy: excuted
此時回調 onDestroy() 方法 證明 Service 已經銷毀。
注意:
- 我們一般在 activity 的 onDestroy() 生命周期函數中 unBinderServise 否則有內存泄漏的風險。
- 假如 使用 starService 啟動了 Service ,并 通過 bindService 又綁定啟動 Service 這時必須同時調用 stopService,unBindService 才會銷毀 Service。
五、前臺 Service
前面我們講的 Service 都是默默的在后臺運行的,系統優先級較低,在內存吃緊的情況下,有可能被系統回收,而前臺 Service 會一直有一個正在運行的圖標在系統的狀態欄顯示,類似一個通知。
修改 MyService 代碼:
/*
* Service 第一次創建時回調
*/
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "onCreate: excuted");
Intent intent = new Intent(this, LoginActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
Notification notification = new NotificationCompat
.Builder(this)
.setContentTitle("This is content title")
.setContentText("This is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
.setContentIntent(pendingIntent)
.build();
startForeground(1,notification);
}
這里使用 Builder 模式構造一個通知,最后調用 startForeground ,傳入通知的 id ,Notification 實例化對象。讓 MyService 變成一個前臺服務,并且在狀態欄顯示。重新 run 程序,點擊 BindService
over~下拉通知欄可以看到明顯的通知。
六、IntentService
Service 默認是執行在主線程的,如果需要執行耗時操作,必須開啟子線程,我們一般的做法是在 onStartCommand 中開啟子線程去執行。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
//執行邏輯
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
但是 Service 一旦啟動就會一直在后臺運行,不斷的執行耗時操作,這是相當消耗資源的,在這種情況下,通常在執行完具體邏輯之后,添加 stopSelf() 讓 Service 停止。但是這種做法有時候會讓我們忘記開線程,或者忘記 stopSelf ,為了解決這種情況,Android 特地提供了 IntentService 。
新建一個MyIntentService 繼承 IntentService,注意別忘了注冊。
public class MyIntentService extends IntentService {
private static final String TAG = "MyIntentService";
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
Log.e(TAG, "onHandleIntent: " + Thread.currentThread().getName());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy: excute");
}
}
一個空構造 調用父類有參構造,復寫 onHandleIntent 抽象方法,直接就是子線程,可以直接執行耗時操作,而且 執行完之后
IntentService 會自動停止。下面修改 activity 按鈕來測試看看。
run 程序,點擊 starService,看 log
05-18 22:34:42.568 22842-24560/com.sunnada.jpushdemo E/MyIntentService: onHandleIntent: IntentService[MyIntentService]
05-18 22:34:42.569 22842-22842/com.sunnada.jpushdemo E/MyIntentService: onDestroy: excute
從 log 可以看出我們啟動 IntentService 之后并沒有調用 stop ,而
onDestroy 已經回調了,并且 onHandleIntent 確實是工作在子線程的。
over~由于篇幅的原因,Service 梳理就先到這了,后續會總結一些 Service 的實戰應用。
聲明:以上只是本人對于基礎知識的梳理總結,部分參考自第一行代碼第二版,如有不足之處,還望指出。
更多原創文章會在公眾號第一時間推送,歡迎掃碼關注 張少林同學