1. 程式人生 > >FC 10 關於服務Service

FC 10 關於服務Service

Android從一開始就支援後臺功能,這樣就是的應用程式及時在關閉的情況下依然可以在後臺繼續執行。作為Android四大元件之一,它也很重要。

  • Service是什麼
  • Service基本用法
  • Service與Activity通訊
  • Service生命週期
  • 更多
    • 前臺Service
    • IntentService

 

Service是什麼

Service是Android實現程式後臺執行的解決方案,它非常適合去執行那些不需要和使用者互動而且還要長期執行的任務。服務不依賴於任何介面。

Service的執行不依賴於任何使用者介面,因此即便程式被切換到後臺或者使用者打開了另一個應用程式,Service仍能夠保持正常執行。但當某個應用程式程序被殺掉時,所有依賴於該程序的服務也會停止執行。

Service並不會自動開啟子執行緒,所有的程式碼都是預設執行在主執行緒當中的。也就是說我們需要在服務的內部手動建立子執行緒並執行具體的邏輯。否則有可能出現主執行緒被阻塞(ANR)的情況。關於多執行緒,請看這篇文章

Service基本用法(定義、啟動和停止服務)

定義一個Service

  • 右擊自己的專案,new==>Service==>Service,勾選Exported、Enabled(預設是勾選著的,Exported表示是否允許除當前程式之外的其他程式訪問這個服務;Enabled表示是否啟動這個服務)
  • 重寫oncreate、onStartCommand、onDestroy方法,並在方法中新增列印日誌語句
  • 在MainActivity中新增兩個按鈕用於啟動和溶質服務,編寫單擊事件

程式碼 Myservice.java

public class MyService extends Service {
    private static final String TAG = "MyService";
    public MyService() {
    }
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate excuted");
        super.onCreate();
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand excuted");
        return super.onStartCommand(intent, flags, startId);
    }
    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy excuted");
        super.onDestroy();
    }
}

程式碼 MainActivity.java

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private Button start,stop;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        start = (Button) findViewById(R.id.start);
        stop = (Button) findViewById(R.id.stop);
        start.setOnClickListener(this);
        stop.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.start:
                Intent startIntent = new Intent(this, MyService.class);
                startService(startIntent);
                break;
            case R.id.stop:
                Intent stopIntent = new Intent(this, MyService.class);
                stopService(stopIntent);
                break;
        }
    }
}

以上,我們定義了一個服務,並藉助Intent來啟動和停止服務(如果不點選停止服務按鈕,服務就會一直執行。可以在MyService裡任意一個位置呼叫stopSelf()方法來停止這個服務)。

執行程式,點選啟動服務,分別列印了日誌【onCreate excuted】【onStartCommand excuted】點選停止服務,列印了日誌【onDestroy excuted】

其中onCreate方法只在服務第一次建立的時候呼叫,onStartCommand方法在每次啟動服務的時候都會呼叫。(即啟動服務時兩個方法會執行,之後如果多次點選啟動服務,只有onStartCommand方法得到執行了)

Service與Activity通訊

  • 重寫MyService方法,新建DownLoadBinder匿名類,繼承自Binder,虛擬定義兩個方法。
  • 重寫MainActivity,建立ServiceConnection匿名內部類,重寫onServiceConnected和onServiceDisconnected方法,這兩個方法在繫結服務和解除繫結服務的時候呼叫。在繫結服務時建立DownLoadBinder例項,分別呼叫例項的兩個方法
  • 新增兩個按鈕用於繫結服務和活動:構建Intent物件,呼叫bindService方法將MainActivity和MyService繫結(其中方法的三個引數分別是Intent物件、建立的ServiceConnection例項、標誌位BIND_AUTO_CREATE(表示活動和服務進行繫結後自動建立服務,會使服務的oncreate方法執行,onStartCommand方法不會執行))
public class MyService extends Service {
private static final String TAG = "MyService";
private DownLoadBinder mBinder = new DownLoadBinder();
class DownLoadBinder extends Binder {
    public void startDownload() {
        Log.d(TAG, "startDownload excuted");
    }

    public int getProgress() {
        Log.d(TAG, "getProgress excuted");
        return 0;
    }
}
@Override
public IBinder onBind(Intent intent) {
    return mBinder;
}
  ...
}

 

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button start, stop, bind, unbind;
    private MyService.DownLoadBinder downLoadBinder;
    private ServiceConnection connection = new ServiceConnection(){
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downLoadBinder = (MyService.DownLoadBinder) service;
            downLoadBinder.startDownload();
            downLoadBinder.getProgress();
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        start = (Button) findViewById(R.id.start);
        stop = (Button) findViewById(R.id.stop);
        bind = (Button) findViewById(R.id.bind);
        unbind = (Button) findViewById(R.id.unbind);
        bind.setOnClickListener(this);
        unbind.setOnClickListener(this);
        start.setOnClickListener(this);
        stop.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.start:
                Intent startIntent = new Intent(this, MyService.class);
                startService(startIntent);
                break;
            case R.id.stop:
                Intent stopIntent = new Intent(this, MyService.class);
                stopService(stopIntent);
                break;
            case R.id.bind:
                Intent bindIntent = new Intent(this, MyService.class);
                bindService(bindIntent, connection, BIND_AUTO_CREATE);
                break;
            case R.id.unbind:
                unbindService(connection);
                break;
        }
    }
}

執行程式,只點擊繫結服務按鈕,列印了日誌【onCreate excuted】【startDownload excuted】【getProgress excuted】說明我們已經在活動裡成功呼叫了服務裡提供的方法了。

任何一個服務在程式方位內都是通用的,MyService不僅可以和MainActivity繫結,還可以和其他的活動進行繫結,進而呼叫活動的方法。

Service生命週期

官方提供的生命週期圖如下:

其中涉及到五個方法

onCreate():服務第一次被建立時呼叫
onStartComand():服務啟動時呼叫
onDestroy():服務停止時呼叫
onBind():服務被繫結時呼叫
onUnBind():服務被解綁時呼叫
說明

呼叫Context的startService()方法可以啟動一個Service,並回調服務中的onStartCommand()。如果該服務之前還沒建立,那麼回撥的順序是onCreate()==>onStartCommand()。服務啟動了之後會一直保持執行狀態,直到stopService()stopSelf()方法被呼叫,服務停止並回調onDestroy()。另外,無論呼叫多少次startService()方法,只需呼叫一次stopService()或stopSelf()方法,服務就會停止了。

其它元件呼叫Context的bindService()可以繫結一個Service,並回調服務中的onBind()方法。類似地,如果該服務之前還沒建立,那麼回撥的順序是onCreate()==>onBind()。之後,呼叫方可以獲取到onBind()方法裡返回的IBinder物件的例項,從而實現和服務進行通訊。只要呼叫方和服務之間的連線沒有斷開,服務就會一直保持執行狀態,直到呼叫了unbindService()方法服務會停止,回撥順序onUnBind()==>onDestroy()。

注意:當使用startService()啟動Service之後,還可再使用bindService()繫結,只不過需要同時呼叫 stopService()和 unbindService()方法才能讓服務銷燬掉。

使用前臺服務

服務一般都是在後臺執行的,但是服務的系統優先順序是比較低的,當系統記憶體不足時,就有可能回收正在後臺執行的服務,如果希望服務可以一直保持執行的狀態,不會由於系統記憶體不足而導致被回收,這時就可以考慮使用前臺服務了。

前臺服務與普通服務最大的區別就是它會有一個正在執行的圖示在系統的狀態列顯示,下拉狀態列可以看到更加詳細的資訊(類似於通知的效果)(使用前臺服務或者為了防止服務被回收掉,或者由於特殊的需求,比如實時天氣狀況)

修改MyService裡的onCreate方法,構建notification物件,呼叫startForeground方法來將通知顯示出來。方法第一個引數是通知的id,第二個引數是notification物件。呼叫startForeground方法後就會讓MyService成為一個前臺服務,並在系統狀態列顯示出來。

@Override
public void onCreate() {
    super.onCreate();
    Log.d(TAG, "onCreate excuted");
    Intent intent = new Intent(this, MainActivity.class);
    PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
    Notification notification = new NotificationCompat.Builder(this)
            .setContentTitle("contentTitle")
            .setContentText("contentText")
            .setWhen(System.currentTimeMillis())
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentIntent(pi)
            .build();
    startForeground(1, notification);
}

使用IntentService

服務中的程式碼是預設執行在主執行緒中的,如果直接在服務裡去處理一些耗時的邏輯,就會很容易出現ANR(Application Not Responding)問題,所以就需要用到多執行緒程式設計的技術了,我們應該在服務的每個具體的方法開啟一個子執行緒,然後去處理耗時的邏輯。

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Log.d(TAG, "onStartCommand excuted");
    new Thread(new Runnable() {
        @Override
        public void run() {
            //新增具體邏輯
        }
    }).start();
    return super.onStartCommand(intent, flags, startId);
}

但是服務一旦啟動之後就會一直處於執行狀態,必須呼叫stopService或stopSelf方法才能讓服務停止下來。所以要想實現在執行完畢後自動停止的功能就可以這樣寫。

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Log.d(TAG, "onStartCommand excuted");
    new Thread(new Runnable() {
        @Override
        public void run() {
            //新增具體邏輯
            stopSelf();
        }
    }).start();
    return super.onStartCommand(intent, flags, startId);
}

但有時我們可能會忘記開啟執行緒,忘記呼叫stopSelf,為了可以簡單的建立一個非同步、自動停止的服務,Android專門提供了IntentService類。

新建MyIntentService類繼承自IntentService類,

  • 提供無參的建構函式,在內部呼叫父類的有參建構函式
  • 在子類中實現onHandleIntent方法,這個方法可以處理一些具體的邏輯,而且不用擔心ANR問題,列印日誌,輸出當前執行緒的id
  • 重寫onDestroy,新增列印日誌,看他是否自動停止了。
public class MyIntentService extends IntentService {
    private static final String TAG = "MyIntentService";
    public MyIntentService() {
        super("MyIntentService");
    }
    @Override
    protected void onHandleIntent(Intent intent) {
        Log.d(TAG, "onHandleIntent: thread id is:"+Thread.currentThread().getId());
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy: executed");
    }
}

新增相應的按鈕單擊事件,列印當前主執行緒的id,啟動IntentService

case R.id.intentService:
    Log.d("MainActivity", "thread id is "+Thread.currentThread().getId());
    Intent intentServvice = new Intent(this, MyIntentService.class);
    startService(intentServvice);
    break;

執行,啟動IntentService,檢視列印的日誌:【MainActivity: thread id is 1】【onHandleIntent: thread id is:3576】【onDestroy: executed】說明MyIntentService確實在開啟執行緒,執行完畢後自動停止了。