Android四大組件之Service全解析

1. 簡介

這篇文章會從Service的一些小知識點,延伸到Android中幾種經常使用進程間通訊方法。html

2. 進程

       Service是一種不提供用戶交互頁面可是能夠在後臺長時間運行的組件,能夠經過在AndroidManifest.xml設置Service的android:process=":remote"屬性,讓Service運行另外一個進程中,也就是說,雖然你是在當前應用啓動的這個Service,可是這個Service和這個應用並非同一個進程。android

四大組件都支持android:process=":remote"這個屬性。緩存

由於Service能夠運行在不一樣的進程,這裏說一下Android中幾種進程的優先級,當系統內存不足時候,系統會從優先級低的進程開始回收,下面根據優先級由高到低列出Android中幾種進程。安全

  • 前臺進程,當前用戶操做所須要的進程網絡

    • 用戶正在交互的Activity(Activity執行了onResume方法)
    • 與正在交互的Activity綁定的Service
    • 設置爲前臺權限的Service(Service調用startForeground()方法)
    • 正在執行某些生命週期回調的Service,onCreate()、onStart()、onDestroy()
    • 正在執行onReceive()的BroadcastReceiver

    這種進程基本不會被回收,只有當內存不足以支持前臺進程同時運行時候,系統纔回回收它們,主要關注前三個。app

  • 可見進程,沒有與用戶交互所必須的組件,可是在屏幕上仍然可見其內容的進程ide

    • 調用了onPause()方法但仍對用戶可見的Activity
    • 與上面這種Activity綁定的Service
  • 服務進程,使用startService()啓動的Service且不屬於上面兩種類別進程的進程,雖然這個進程與用戶交互沒有直接關係,可是通常會在後臺執行一些耗時操做,因此,只有當內存不足以維持全部前臺進程和可見進程同時運行,系統纔回回收這個類別的進程。
  • 後臺進程,對用戶不可見的Activity進程,已調用了onStop()方法的Activity
  • 空進程,不包含任何活動應用組件的進程,保留這種進程惟一目的是做爲緩存,縮短引用組件下次啓動時間。一般系統會最優先回收這類進程。

此外,一個進程的級別可能會由於其餘進程對它的依賴而有所提升,即進程A服務於進程B(B依賴A),那麼A的進程級別至少是和B同樣高的。oop

3. Service配置

和其餘組件(Activity/ContentProvider/BroadcastReceiver)同樣,Service須要在Androidmanifest.xml中聲明。ui

<manifest ... >
  ...
  <application ... >
      <service android:name=".DemoService" />
      ...
  </application>
</manifest>

Service是運行在主線程中的,若是有什麼耗時的操做,建議新建子線程去處理,避免阻塞主線程,下降ANR的風險。this

       在另一篇文章中Intent以及IntentFilter詳解提到過,爲了確保應用的安全,不要爲Service設置intent-filter,由於若是使用隱式Intent去啓動Service時候,手機裏面那麼多應用,並不能肯定哪個Service響應了這個Intent,因此在項目中儘可能使用顯式Intent去啓動Service。在Android 5.0(API LEVEL 21)版本後的,若是傳入隱式Intent去調用bindService()方法,系統會拋出異常。

能夠經過設置android:exported=false來確保這個Service僅能在本應用中使用。

4. 服務啓動方式

服務能夠由其餘組件啓動,並且若是用戶切換到其餘應用,這個服務可能會繼續在後臺執行。到目前爲止,Android中Service總共有三種啓動方式。

  • Scheduled,可定時執行的Service,是Android 5.0(API LEVEL 21)版本中新添加的一個Service,名爲JobService,繼承Service類,使用JobScheduler類調度它而且設置JobService運行的一些配置。具體文檔能夠參考JobScheduler,若是你的應用最低支持版本是21,官方建議使用JobService。
  • Started,經過startService()啓動的Service。經過這種方式啓動的Service會獨立的運行在後臺,即便啓動它的組件已經銷燬了。例如Activity A使用startService()啓動了Service B,過了會兒,Activity A執行onDestroy()被銷燬了,若是Service B任務沒有執行完畢,它仍然會在後臺執行。這種啓動方式啓動的Service須要主動調用StopService()中止服務。
  • Bound,經過bindService()啓動的Service。經過這種方式啓動Service時候,會返回一個客戶端交互接口,用戶能夠經過這個接口與服務進行交互,若是這個服務是在另外一個進程中,那麼就實現了進程間通訊,也就是Messenger和AIDL,這個會是下篇文章的重點。多個組件能夠同時綁定同一個Service,若是全部的組件都調用unbindService()解綁後,Service會被銷燬。

startService和bindService能夠同時使用

5. 主要方法

Service是一個抽象類,使用須要咱們去實現它的抽象方法onBind(),Service有且僅有這一個抽象方法,還有一些其餘的生命週期回調方法須要複寫幫助咱們實現具體的功能。

  • onCreate(),在建立服務時候,能夠在這個方法中執行一些的初始化操做,它在onStartCommand()onBind()以前被調用。若是服務已經存在,調用startService()啓動服務時候這個方法不會調用,只會調用onStartCommand()方法。
  • onStartCommand(),其餘組件經過startService()啓動服務時候會回調這個方法,這個方法執行後,服務會啓動被在後臺運行,須要調用stopSelf()或者stopService()中止服務。
  • onBind(),其餘組件經過bindService()綁定服務時候會回調的方法,這是Service的一個抽象方法,若是客戶端須要與服務交互,須要在這個方法中返回一個IBinder實現類實例化對象,若是不想其餘客戶端與服務綁定,直接返回null。
  • onDestroy(),當服務不在仍是用且即將被銷燬時,會回調這個方法,能夠在這個方法中作一些釋放資源操做,這是服務生命週期的最後一個回調。

若是組件僅經過startService()啓動服務,不論服務是否已經啓動,都會回調onStartCommand()方法,並且服務會一直運行,須要調用stopSelfstopService方法關閉服務。

若是組件僅經過bindService()綁定服務,則服務只有在與組件綁定時候運行,一旦全部的客戶端所有取消綁定unbindService,系統纔會銷燬該服務。

屢次啓動同一個服務,只有在服務初次啓動時候會回調onCreate方法,可是每次都會回調onStartCommand,能夠利用這個向服務傳遞一些信息。

onStartCommand()的回調是在UI主線程,若是有什麼耗時的操做,建議新啓線程去處理。

6. 啓動和關閉服務

啓動服務:

  • JobScheduler.schedule()
  • startService(Intent)
  • bindService(Intent service, ServiceConnection conn, int flags)

關閉服務:

  • JobScheduler.cancel()或者JobScheduler.cancelAll(),對應JobScheduler.schedule()
  • Service自身的stopSelf()方法,組件的stopService(Intent)方法,對應startService啓動方法
  • unbindService(ServiceConnection conn),對應bindService

示例:

// 啓動服務
Intent intent = new Intent(this, DemoService.class);
startService(intent);

// 中止服務
stopService(intent)

// 綁定服務
ServiceConnection mConnection = ServiceConnection() { ... };
Intent intent = new Intent(this, DemoService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

// 解除綁定
unbindService(mConnection);

綁定服務bindService()第三個參數數值:

  • 0,若是不想設置任何值,就設置成0
  • Context.BIND_AUTO_CREATE,綁定服務時候,若是服務還沒有建立,服務會自動建立,在API LEVEL 14之前的版本不支持這個標誌,使用Context.BIND_WAIVE_PRIORITY能夠達到一樣效果
  • Context.BIND_DEBUG_UNBIND,一般用於Debug,在unbindService時候,會將服務信息保存並打印出來,這個標記很容易形成內存泄漏。
  • Context.BIND_NOT_FOREGROUND,不會將被綁定的服務提高到前臺優先級,可是這個服務也至少會和客戶端在內存中優先級是相同的。
  • Context.BIND_ABOVE_CLIENT,設置服務的進程優先級高於客戶端的優先級,只有當須要服務晚於客戶端被銷燬這種狀況才這樣設置。
  • Context.BIND_ALLOW_OOM_MANAGEMENT,保持服務受默認的服務管理器管理,當內存不足時候,會銷燬服務
  • Context.BIND_WAIVE_PRIORITY,不會影響服務的進程優先級,像通用的應用進程同樣將服務放在一個LRU表中
  • Context.BIND_IMPORTANT,標識服務對客戶端是很是重要的,會將服務提高至前臺進程優先級,一般狀況下,即時客戶端是前臺優先級,服務最多也只能被提高至可見進程優先級,
  • BIND_ADJUST_WITH_ACTIVITY,若是客戶端是Activity,服務優先級的提升取決於Activity的進程優先級,使用這個標識後,會無視其餘標識。

7. onStartCommand()返回值

onStartCommand()方法有一個int的返回值,這個返回值標識服務關閉後系統的後續操做。

返回值有如下幾種:

  • Service.START_STICKY,啓動後的服務被殺死,系統會自動重建服務並調用on onStartCommand(),可是不會傳入最後一個Intent(Service可能屢次執行onStartCommand),會傳入一個空的Intent,使用這個標記要注意對Intent的判空處理。這個標記適用於太依靠外界數據Intent,在特定的時間,有明確的啓動和關閉的服務,例如後臺運行的音樂播放。
  • Service.START_NOT_STICKY,啓動後的服務被殺死,系統不會自動從新建立服務。這個標記是最安全的,適用於依賴外界數據Intent的服務,須要徹底執行的服務。
  • Service.START_REDELIVER_INTENT,啓動後的服務被殺死,系統會從新建立服務並調用onStartCommand(),同時會傳入最後一個Intent。這個標記適用於可恢復繼續執行的任務,好比說下載文件。
  • Service.START_STICKY_COMPATIBILITY,啓動後的服務被殺死,不能保證系統必定會從新建立Service。

8. Service生命週期

Service生命週期(從建立到銷燬)跟它被啓動的方式有關係,這裏只介紹startServicebindService兩種啓動方法時候Service的生命週期。

  • startService啓動方式,其餘組件用這種方式啓動服務,服務會在後臺一直運行,只有服務調用自己的stopSelf()方法或者其餘組件調用stopService()才能中止服務。
  • bindService啓動方式,其餘組件用這種方法綁定服務,服務經過IBinder與客戶端通訊,客戶端經過unbindService接觸對服務的綁定,當沒有客戶端綁定到服務,服務會被系統銷燬。

這兩種生命週期不是獨立的,組件能夠同時用startService啓動服務同時用bindService綁定服務,例如跨頁面的音樂播放器,就能夠在多個頁面同時綁定同一個服務,這種狀況下須要調用stopService()或者服務自己的stopSelf()而且沒有客戶端綁定到服務,服務纔會被銷燬。

<div align="center">
圖-1Service生命週期圖</div>

左圖是使用startService()所建立的服務的生命週期,右圖是使用bindService()所建立的服務的生命週期。

9. 在前臺運行服務

服務能夠經過startForeground來使服務變成前臺優先級。

public final void startForeground(int id, Notification notification) {
    try {
        mActivityManager.setServiceForeground(
                new ComponentName(this, mClassName), mToken, id,
                notification, true);
    } catch (RemoteException ex) {
    }
}

第一個參數用於標識你應用中惟一的通知標識id,不能設爲0,最終會傳入NotificationManager.notify(int id, Notification notification)取消通知須要用到,第二個參數是通知具體內容。

前臺服務須要在狀態欄中添加通知,例如,將音樂播放器的服務設置爲前臺服務,狀態欄通知顯示正在播放的歌曲,並容許其餘組件與其交互。

// 設置Notification屬性
Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),System.currentTimeMillis());
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, getText(R.string.notification_title), getText(R.string.notification_message), pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);

要將服務從前臺移除,須要調用stopForeground(boolean removeNotification),參數是一個布爾值,用來標識服務從前臺服務移除時候,是否須要移除狀態欄的通知。若是服務在前臺運行時候被中止,狀態欄的通知也會被移除。

10. 與服務通訊

10.1 廣播

很少說,萬能的通訊。

10.2 本地數據共享

很少說,萬能的通訊,例如ContentProvider/SharePreference等等。

10.3 startService()

       使用這個方法啓動的服務,再次調用startService()傳入Intent便可與服務通訊,由於這種方式啓動的服務在完整的生命週期內onCreate()只會執行一次,而onStartCommand()會執行屢次,咱們再次調用startService()時候,能夠在oonStartCommand()去處理。

10.4 bindService()

使用這種方法啓動的服務,組件有三種與服務通訊的方式。

  • Service中實現IBinder
  • Messenger(AIDL的簡化版)
  • AIDL

下一篇文章具體介紹Messenger、AIDL,由於它們是屬於Android進程間通訊。

若是一個服務Service只須要在本應用的進程中使用,不提供給其餘進程,推薦使用第一種方法。

使用示例:

Service:

/**
 * 本地服務
 * <br/>
 * 和啓動應用屬於同一進程
 */
public class LocalService extends Service {
    /**
     * 自定的IBinder
     */
    private final IBinder mBinder = new LocalBinder();

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    /**
     * 提供給客戶端的方法
     *
     * @return
     */
    public String getServiceInfo() {
        return this.getPackageName() + " " + this.getClass().getSimpleName();
    }

    /**
     * 自定義的IBinder
     */
    public class LocalBinder extends Binder {
        public LocalService getService() {
            return LocalService.this;
        }
    }
}

Activity:

/**
 * 綁定本地服務的組件
 *
 * Created by KyoWang.
 */
public class BindLocalServiceActivity extends AppCompatActivity implements View.OnClickListener {

    private Button mShowServiceNameBtn;

    private LocalService mService;

    private boolean mBound = false;

    public ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            LocalService.LocalBinder binder = (LocalService.LocalBinder) service;
            mService = binder.getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mBound = false;
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.a_bind_local_service);
        mShowServiceNameBtn = (Button) findViewById(R.id.bind_local_service_btn);
        mShowServiceNameBtn.setOnClickListener(this);
    }

    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        if(mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }

    @Override
    public void onClick(View v) {
        int id = v.getId();
        if(id == R.id.bind_local_service_btn) {
            if(mBound) {
                String info = mService.getServiceInfo();
                Toast.makeText(BindLocalServiceActivity.this, info, Toast.LENGTH_SHORT).show();
            }
        }
    }
}

11. 服務長存後臺

關於網上通用的提高服務優先級以保證服務長存後臺,即保證服務不輕易被系統殺死的方法有如下幾種。

  • 設置android:persistent="true",這是application的一個屬性,官方都不建議使用。

    Whether or not the application should remain running at all times,
    "true" if it should, and "false" if not. 
    The default value is "false". 
    Applications should not normally set this flag; 
    persistence mode is intended only for certain system applications.
  • 設置android:priority優先級,這個並非Service的屬性。這個屬性是在intent-filter中設置的。官方解釋,這個屬性只對活動和廣播有用,並且這個是接受Intent的優先級,並非在內存中的優先級,呵呵。

    android:priority
    The priority that should be given to the parent component with regard to 
    handling intents of the type described by the filter. 
    This attribute has meaning for both activities and broadcast receivers。
  • 在Service的onDestroy中發送廣播,而後重啓服務,就目前我知道的,會出現Service的onDestroy不調用的狀況。
  • startForeground,這個上面提到過,是經過Notification提高優先級。
  • 設置onStartCommand()返回值,讓服務被殺死後,系統從新建立服務,上面提到過。

五個裏面就兩個能稍微有點用,因此啊,網絡謠傳害死人。

12. IntentService

敲黑板時間,重點來了,官方強力推薦。

前面提到兩點。

  • 由於Service中幾個方法的回調都是在主線程中,若是使用Service執行特別耗時的操做,建議單獨新建線程去操做,避免阻塞主線程(UI線程)
  • 啓動服務和中止服務是成對出現的,須要手動中止服務

       IntentService完美的幫咱們解決了這個問題,在內部幫咱們新建的線程,不須要咱們手動新建,執行完畢任務後會自動關閉。IntentService也是一個抽象類,裏面有一個onHandleIntent(Intent intent)抽象方法,這個方法是在非UI線程調用的,在這裏執行耗時的操做。

       IntentService使用非UI線程逐一處理全部的啓動需求,它在內部使用Handler,將全部的請求放入隊列中,依次處理,關於Handler能夠看這篇文章,也就是說IntentService不能同時處理多個請求,若是不要求服務同時處理多個請求,能夠考慮使用IntentService。

IntentService在內部使用HandlerThread配合Handler來處理耗時操做。

private final class ServiceHandler extends Handler {
    public ServiceHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        onHandleIntent((Intent)msg.obj);
        stopSelf(msg.arg1);
    }
}

public int onStartCommand(Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

public void onStart(Intent intent, int startId) {
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    msg.obj = intent;
    mServiceHandler.sendMessage(msg);
}

       注意msg.arg1它是請求的惟一標識,每發送一個請求,會生成一個惟一標識,而後將請求放入Handler處理隊列中,從源代碼裏面能夠看見,在執行完畢onHandleIntent方法後,會執行stopSelf來關閉自己,同時IntentService中onBind()方法默認返回null,這說明啓動IntetService的方式最好是用startService方法,這樣在服務執行完畢後纔會自動中止;若是使用bindService來啓動服務,仍是須要調用unbindService來解綁服務的,也須要複寫onBind()方法。

小盆宇:在ServiceHandler類的handleMessage方法中,執行onHandleIntent後緊接着執行stopSelf(int startId),把服務就給中止了,那第一個請求執行完畢服務就中止了,後續的請求怎麼會執行?

       注意stopSelf(int startID)方法做用是在其參數startId跟最後啓動該service時生成的id相等時纔會執行中止服務,當有多個請求時候,若是發現當前請求的startId不是最後一個請求的id,那麼不會中止服務,因此只有當最後一個請求執行完畢後,纔會中止服務。

相關文章
相關標籤/搜索