1. 程式人生 > >[Android]Service服務

[Android]Service服務

一、什麼是Service

Service(服務)--Android四大元件之一。

Service是Android中實現程式後臺執行的解決方案,它非常適用於去執行那些不需要和使用者互動而且還要求長期執行的任務。Service預設並不會執行在子執行緒中,它也不執行在一個獨立的程序中,它同樣執行在UI執行緒中,因此,不要在Service中執行耗時的操作,除非你在Service中建立了子執行緒來完成耗時操作。

Service的執行不依賴於任何使用者介面,即使程式被切換到後臺或者使用者開啟另一個應用程式,Service仍然能夠保持正常執行,這也正是Service的使用場景。當某個應用程式程序被殺掉時,所有依賴於該程序的Service也會停止執行

二、 Service分類

1.分類 

圖片轉自:https://blog.csdn.net/carson_ho/article/details/53160231

2.特點

 

3.本地Service

 3.1兩種啟動方式

(1)startService()方法開啟Service

  1. 定義一個類並繼承Service。
  2. 在AndroidManifest.xml檔案中配置該Service。
  3. 使用Context的startService(Intent)方法啟動該Service。 
  4. 不再使用該Service時,呼叫Context的stopService(Intent)方法停止該Service。

(2)bindService方法開啟Service

  1.  定義一個類並繼承Service。並在類中建立一個實現Binder介面的實現例項物件並提供公共方法給Activity呼叫。 
  2. 從onBind()回撥方法返回此Binder例項。 
  3. Activity中繼承ServiceConnection介面,從onServiceConnected回撥方法接收Binder例項,使用此Binder例項來呼叫Service提供給Activity的公共方法。
  4. 呼叫bindService(intent)來繫結Service後,便會執行onServiceConnected回撥方法。
  5. unbindService()來解除繫結Service.

 3.2 Service的生命週期 

  •  啟動服務-onCreate()-onStartCommand()-服務執行-onDestory()-服務被銷燬
  •  繫結服務-onCreate()-onBind()-服務執行-onUnBind()-onDestory()-服務被銷燬

   關於Service:

  1. 被啟動的服務的生命週期:如果一個Service被某個Activity 呼叫 Context.startService 方法啟動,那麼不管是否有Activity使用bindService繫結或unbindService解除繫結到該Service,該Service都在後臺執行。如果一個Service被startService 方法多次啟動,那麼onCreate方法只會呼叫一次,onStart將會被呼叫多次(對應呼叫startService的次數),並且系統只會建立Service的一個例項(因此你應該知道只需要一次stopService呼叫)。該Service將會一直在後臺執行,而不管對應程式的Activity是否在執行,直到被呼叫stopService,或自身的stopSelf方法。當然如果系統資源不足,android系統也可能結束服務。
  2. 被繫結的服務的生命週期:如果一個Service被某個Activity 呼叫 Context.bindService 方法繫結啟動,不管呼叫 bindService 呼叫幾次,onCreate方法都只會呼叫一次,同時onStart方法始終不會被呼叫。當連線建立之後,Service將會一直執行,除非呼叫Context.unbindService 斷開連線或者之前呼叫bindService 的 Context 不存在了(如Activity被finish的時候),系統將會自動停止Service,對應onDestroy將被呼叫。
  3. 被啟動又被繫結的服務的生命週期:如果一個Service又被啟動又被繫結,則該Service將會一直在後臺執行。並且不管如何呼叫,onCreate始終只會呼叫一次,對應startService呼叫多少次,Service的onStart便會呼叫多少次。呼叫unbindService將不會停止Service,而必須呼叫 stopService 或 Service的 stopSelf 來停止服務。
  4. 當服務被停止時清除服務:當一個Service被終止(1、呼叫stopService;2、呼叫stopSelf;3、不再有繫結的連線(沒有被啟動))時,onDestroy方法將會被呼叫,在這裡你應當做一些清除工作,如停止在Service中建立並執行的執行緒。

  特別注意:

  1. 在呼叫 bindService 繫結到Service的時候,就應當保證在某處呼叫 unbindService 解除繫結(儘管 Activity 被 finish 的時候繫結會自動解除,並且Service會自動停止);
  2. 注意 使用 startService 啟動服務之後,一定要使用 stopService停止服務,不管你是否使用bindService;
  3. 同時使用 startService 與 bindService 要注意到,Service 的終止,需要unbindService與stopService同時呼叫,才能終止 Service,不管 startService 與 bindService 的呼叫順序,如果先呼叫 unbindService 此時服務不會自動終止,再呼叫 stopService 之後服務才會停止,如果先呼叫 stopService 此時服務也不會終止,而再呼叫 unbindService 或者 之前呼叫 bindService 的 Context 不存在了(如Activity 被 finish 的時候)之後服務才會自動停止;
  4. 當在旋轉手機螢幕的時候,當手機螢幕在“橫”“豎”變換時,此時如果你的 Activity 如果會自動旋轉的話,旋轉其實是 Activity 的重新建立,因此旋轉之前的使用 bindService 建立的連線便會斷開(Context 不存在了),對應服務的生命週期與上述相同。 

 3.3 Service與Activity的通訊方式

4.遠端Service

 4.1 遠端Service與本地Service的區別

  •  遠端服務與本地服務最大的區別是:遠端Service與呼叫者不在同一個程序裡(即遠端Service是執行在另外一個程序);而本地服務則是與呼叫者執行在同一個程序裡。
  • 二者區別的詳細區別如下圖:

 4.2 具體使用

 為了讓遠端Service與多個應用程式的元件(四大元件)進行跨程序通訊(IPC),需要使用AIDL。

  1. IPC:Inter-Process Communication,即跨程序通訊
  2. AIDL:Android Interface Definition Language,即Android介面定義語言;用於讓某個Service與多個應用程式元件之間進行跨程序通訊,從而可以實現多個應用程式共享同一個Service的功能。 

 在多程序通訊中,存在兩個程序角色:伺服器端客戶端

伺服器端:

  • 新建定義AIDL檔案,並宣告該服務需要向客戶端提供的介面 
  • 在Service子類中實現AIDL中定義的介面方法,並實現其生命週期的各個方法。
  • 在AndroidMainfest.xml中註冊服務 & 宣告為遠端服務(android:process設定為remote)

客戶端:

  • 原封不動拷貝服務端的AIDL檔案到目錄下
  • 使用Stub.asInterface介面獲取伺服器的Binder,根據需要呼叫服務提供的介面方法 
  • 通過Intent指定服務端的服務名稱和所在包,繫結遠端Service  

Demo舉例:

使用兩個工程,一個做伺服器端,一個做客戶端。

伺服器端設定AIDL檔案,同時建立Service,將Activity傳來的資料進行操作。

客戶端繫結遠端服務,獲取Service返回的資料,進行更新UI顯示。

注:因客戶端獲取不到伺服器端Service的例項,所以無法實現監聽Service的資料變化進行實時的UI顯示。本地Service可以實現實時顯示,已在Service與Activity的兩種通訊方式中實現。

伺服器端原始碼: 

 AIDL檔案:

interface MyAIDLInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    //void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
     //       double aDouble, String aString);

     //void AIDLService();
     void TransferData(int mData);
     int getData();
}

Service檔案:

/**
 * 實現了AIDL介面的Service
 * 遠端Service
 */
public class MyService extends Service {

    private boolean isConnected = false;
    private Callback callback;
    public int data = 0;

    MyAIDLInterface.Stub binder = new MyAIDLInterface.Stub() {

        @Override
        public void TransferData(int mData) throws RemoteException {
            data = mData;
        }

        @Override
        public int getData() throws RemoteException {
            return data;
        }

    };

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

    @Override
    public void onCreate() {
        super.onCreate();
        isConnected = true;
        //開啟一個執行緒,對資料進行處理
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (isConnected) {
                    if (callback != null) {
                        callback.onDataChange(data + "");
                    }
                    data++;
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        isConnected = false;
    }
}

客戶端原始碼:

AIDL檔案:同伺服器端

Activity檔案:

/**
 * ServiceActivity 和 遠端Service進行通訊
 * 通過AIDL介面  Service在遠端或者其他app中
 */
public class ServiceActivity extends AppCompatActivity implements View.OnClickListener, ServiceConnection {

    private TextView textView;
    private boolean isBind = false;
    private int TransforData;
    //定義AIDL介面變數
    private MyAIDLInterface myAIDLInterface;

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

        TransforData = 0;
        textView = (TextView) findViewById(R.id.textView);
        Button bindBtn = (Button) findViewById(R.id.bind_service2);
        Button unBindBtn = (Button) findViewById(R.id.unbind_service2);
        Button clearBt = (Button) findViewById(R.id.clear);
        bindBtn.setOnClickListener(this);
        unBindBtn.setOnClickListener(this);
        clearBt.setOnClickListener(this);
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        //將binder物件轉換成了 MyAIDLInterface介面物件
        myAIDLInterface = MyAIDLInterface.Stub.asInterface(service);

        try {
            //向Service傳遞資料 TransforData
            myAIDLInterface.TransferData(TransforData);

        } catch (RemoteException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }


    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.bind_service2:
                if (!isBind) {
                    isBind = true;
                    //服務繫結後,會呼叫 onServiceConnected
                    //繫結遠端服務
                    Intent bindIntent = new Intent("com.example.cptestapp.MyAIDLInterface");
                    bindIntent.setPackage("com.example.cptestapp");
                    bindService(bindIntent, this, BIND_AUTO_CREATE);

                    Toast.makeText(this, "Bind Service Success!", Toast.LENGTH_SHORT).show();
                }
                break;
            case R.id.unbind_service2:
                if (isBind) {
                    try {
                        //從Service中獲取data數值
                        TransforData = myAIDLInterface.getData();
                        //獲取資料後,更新UI
                        Message msg = new Message();
                        msg.obj = TransforData;
                        handler.sendMessage(msg);

                        unbindService(this);
                        isBind = false;
                        Toast.makeText(this, "unBind Service Success!", Toast.LENGTH_SHORT).show();
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
                break;

            case R.id.clear:
                if (!isBind) {
                    TransforData = 0;
                    textView.setText("0");
                }
                break;
            default:
                break;
        }
    }

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //在此處更新UI
            textView.setText(msg.obj.toString());
        }
    };
}

 


參考博文:

  1. https://blog.csdn.net/clandellen/article/details/79276411
  2. https://blog.csdn.net/carson_ho/article/details/53160231