Android 之Service使用攻略

lvxiangan發表於2019-01-03

Service無法自行啟動,訪問者啟動它的方式分為兩種,即startService(非繫結式)和bindService (繫結式),相關介紹如下:

    * startService:即非繫結式。訪問者使用這種方式啟動service後,service將不受訪問者控制,也無法與訪問者進行資料通訊,它會無限地執行下去,必須呼叫stopSelf()方法或者其他元件(包括訪問者)呼叫stopService()方法來停止。它的生命週期:onCreate->onStartCommand()->……>onDestory(),其中,onCreate用於初始化工作,多次呼叫startService啟動同一個服務,onCreate方法只會被呼叫一次,onStartCommand會被呼叫多次,onDestory在銷燬時也只會被呼叫一次。

    * bindService:即繫結式。訪問者使用這種方式啟動service後,被啟動的service受訪問者的控制,訪問者將通過一個IBinder介面的物件與被繫結的service進行通訊,並且可以通過unbindService()方法隨時關閉service。一個service可以同時被多個訪問者繫結,只有當多個訪問者都主動解除繫結關係之後,系統才會銷燬service。它的生命週期:onCreate->onBind->....>onUnbind->onDestory,其中,onBind用於返回一個通訊物件(IBinder)給訪問者,訪問者可以通過該IBinder物件呼叫service的相關方法。當多個訪問者繫結同一個service時,onCreate只會被呼叫一次,onBind、unOnbind方法會被呼叫與訪問者數目相關的次數,onDestory在銷燬時只會被呼叫一次。


 

startService和bindService,用哪個好?

開啟服務時,它們與activity之間的關係:
1、startService開啟服務以後,與activity就沒有關聯,不受影響,獨立執行
2、bindService開啟服務以後,與activity存在關聯,退出activity時必須呼叫unbindService方法,否則會報ServiceConnection洩漏的錯誤。
因此,根據實際需求使用最好。


 

Service類若呼叫startService啟動時,有個生命週期方法叫onStartCommand,這個方法有一個整型的返回值:
START_STICKY_COMPATIBILITY
START_STICKY
START_NOT_STICKY
START_REDELIVER_INTENT


預設情況下,當我們的服務因為系統記憶體吃緊或者其他原因被異常終止時,系統會嘗試在某個時刻重新啟動服務,

START_NOT_STICKY:服務不會重新建立,除非你再次呼叫startService

START_STICKY / START_STICKY_COMPATIBILITY:服務重新建立並啟動,依次回撥onCreate、onStartCommand,如果沒有新的intent傳給此service,onStartCommand接受的將是一個空的intent

START_STICKY_COMPATIBILITY:START_STICKY的相容版本,2.0之下使用,它不保證一定呼叫onStartCommand.

START_REDELIVER_INTENT:服務重新建立並啟動,依次回撥onCreate,onStartCommand,並且會把最後一次傳給此服務的intent重新發給onStartCommand

 

PS:
1、系統預設是START_STICKY。另外,為app保活著想,也應該設為START_STICKY
2、如果一個service被startService啟動,然後其他元件再執行bindService繫結該service,service的
onCreate不會再被回撥,而是直接回撥onBind方法;當該元件unBindService時,service的onUnbind方法被回撥,但是service不會被銷燬,直到自己呼叫stopSelf方法或者其他元件呼叫stopService方法時才會被銷燬。
 

 

 

1. Service分類

 

2.2 可通訊的服務Service

  • 接下來將在Service的基礎用法上,增設“與Activity通訊”的功能,即使用繫結Service服務(Binder類、bindService()、onBind()、unbindService()、onUnbind())
  • 步驟1:在新建子類繼承Service類,並新建一個子類繼承自Binder類、寫入與Activity關聯需要的方法、建立例項
public class MyService extends Service {

    private MyBinder mBinder = new MyBinder();

    @Override
    public void onCreate() {
        super.onCreate();
        System.out.println("執行了onCreat()");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        System.out.println("執行了onStartCommand()");
        return super.onStartCommand(intent, flags, startId);


    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        System.out.println("執行了onDestory()");
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        System.out.println("執行了onBind()");
        //返回例項
        return mBinder;
    }


    @Override
    public boolean onUnbind(Intent intent) {
        System.out.println("執行了onUnbind()");
        return super.onUnbind(intent);
    }

    //新建一個子類繼承自Binder類
    class MyBinder extends Binder {

        public void service_connect_Activity() {
            System.out.println("Service關聯了Activity,並在Activity執行了Service的方法");

        }
    }
}
  • 步驟2:在主佈局檔案再設定兩個Button分別用於繫結和解綁Service
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="scut.carson_ho.demo_service.MainActivity">

    <Button
        android:layout_centerInParent="true"
        android:id="@+id/startService"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="啟動服務" />

    <Button
        android:layout_centerInParent="true"
        android:layout_below="@+id/startService"
        android:id="@+id/stopService"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="停止服務" />

    <Button
        android:layout_centerInParent="true"
        android:layout_below="@id/stopService"
        android:id="@+id/bindService"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="繫結服務" />

    <Button
        android:layout_centerInParent="true"
        android:layout_below="@id/bindService"
        android:id="@+id/unbindService"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="解綁服務"
        />
</RelativeLayout>

  • 步驟3:在Activity通過呼叫MyBinder類中的public方法來實現Activity與Service的聯絡

即實現了Activity指揮Service幹什麼Service就去幹什麼的功能

MainActivity.java

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button startService;
    private Button stopService;
    private Button bindService;
    private Button unbindService;

    private MyService.MyBinder myBinder;


    //建立ServiceConnection的匿名類
    private ServiceConnection connection = new ServiceConnection() {

        //重寫onServiceConnected()方法和onServiceDisconnected()方法
        //在Activity與Service建立關聯和解除關聯的時候呼叫
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }

        //在Activity與Service解除關聯的時候呼叫
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //例項化Service的內部類myBinder
            //通過向下轉型得到了MyBinder的例項
            myBinder = (MyService.MyBinder) service;
            //在Activity呼叫Service類的方法
            myBinder.service_connect_Activity();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        startService = (Button) findViewById(R.id.startService);
        stopService = (Button) findViewById(R.id.stopService);

        startService.setOnClickListener(this);
        stopService.setOnClickListener(this);

        bindService = (Button) findViewById(R.id.bindService);
        unbindService = (Button) findViewById(R.id.unbindService);

        bindService.setOnClickListener(this);
        unbindService.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {

            //點選啟動Service
            case R.id.startService:
                //構建啟動服務的Intent物件
                Intent startIntent = new Intent(this, MyService.class);
                //呼叫startService()方法-傳入Intent物件,以此啟動服務
                startService(startIntent);
                break;

            //點選停止Service
            case R.id.stopService:
                //構建停止服務的Intent物件
                Intent stopIntent = new Intent(this, MyService.class);
                //呼叫stopService()方法-傳入Intent物件,以此停止服務
                stopService(stopIntent);
                break;

            //點選繫結Service
            case R.id.bindService:
                //構建繫結服務的Intent物件
                Intent bindIntent = new Intent(this, MyService.class);
                //呼叫bindService()方法,以此停止服務

                bindService(bindIntent,connection,BIND_AUTO_CREATE);
                //引數說明
                //第一個引數:Intent物件
                //第二個引數:上面建立的Serviceconnection例項
                //第三個引數:標誌位
                //這裡傳入BIND_AUTO_CREATE表示在Activity和Service建立關聯後自動建立Service
                //這會使得MyService中的onCreate()方法得到執行,但onStartCommand()方法不會執行
                break;

            //點選解綁Service
            case R.id.unbindService:
                //呼叫unbindService()解綁服務
                //引數是上面建立的Serviceconnection例項
                unbindService(connection);
                break;

                default:
                    break;

        }
    }
}

2.2.2 Demo

carson.ho的Github地址:Demo_for_Service

 

2.3 前臺Service

前臺Service和後臺Service(普通)最大的區別就在於:

  • 前臺Service在下拉通知欄有顯示通知(如下圖),但後臺Service沒有;

     
  • 前臺Service優先順序較高,不會由於系統記憶體不足而被回收;後臺Service優先順序較低,當系統出現記憶體不足情況時,很有可能會被回收

2.3.1 具體使用

用法很簡單,只需要在原有的Service類對onCreate()方法進行稍微修改即可,如下圖:

@Override
    public void onCreate() {
        super.onCreate();
        System.out.println("執行了onCreat()");

        //新增下列程式碼將後臺Service變成前臺Service
        //構建"點選通知後開啟MainActivity"的Intent物件
        Intent notificationIntent = new Intent(this,MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this,0,notificationIntent,0);

        //新建Builer物件
        Notification.Builder builer = new Notification.Builder(this);
        builer.setContentTitle("前臺服務通知的標題");//設定通知的標題
        builer.setContentText("前臺服務通知的內容");//設定通知的內容
        builer.setSmallIcon(R.mipmap.ic_launcher);//設定通知的圖示
        builer.setContentIntent(pendingIntent);//設定點選通知後的操作

        Notification notification = builer.getNotification();//將Builder物件轉變成普通的notification
        startForeground(1, notification);//讓Service變成前臺Service,並在系統的狀態列顯示出來

    }

2.3.2 測試結果

執行後,當點選Start Service或Bind Service按鈕,Service就會以前臺Service的模式啟動(通知欄上有通知),如下圖

 

3. 使用場景

各種Service的使用場景請看下圖:


Service 與 Thread的區別

  • 結論:Service與 Thread無任何關係
  • 之所以有不少人會把它們聯絡起來,主要因為Service的後臺概念

後臺:後臺任務執行完全不依賴UI,即使Activity被銷燬 / 程式被關閉,只要程式還在,後臺任務就可繼續執行

  • 關於二者的異同,具體如下圖:



     
  • 一般會將 Service和 Thread聯合著用,即在Service中再建立一個子執行緒(工作執行緒)去處理耗時操作邏輯,如下程式碼:
@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);  
}  
  
class MyBinder extends Binder {  
    public void service_connect_Activity() {  
  //新建工作執行緒
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                // 執行具體的下載任務  
            }  
        }).start();  
    }  
  
}  

 




連結:https://www.jianshu.com/p/d963c55c3ab9
連結:https://www.jianshu.com/p/e04c4239b07e
連結:https://blog.csdn.net/andrexpert/article/details/53485360 
https://www.jianshu.com/p/d870f99b675c

相關文章