天天看點

Android四大元件之Service使用詳解

Service作為安卓的四大元件之一,适合長期在背景運作而不需要使用者界面的操作,Service預設運作在UI線程中,是以Service中同樣不适合做耗時操作,如果需要做耗時操作需要開啟子線程。

常用Service的開啟包括start和bind兩種啟動方式。

  1. 建立自定義Service
public class ServiceTest extends Service {
    /**
     * 繼承Service時必須要實作的方法
     *bind調用時,擷取Intent傳遞的參數,或執行綁定後的操作
     * @param intent
     * @return
     */
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * Service首次運作時會執行該方法
     * 如果Service已經啟動則不會調用(在onStartCommand()和onBind()之前調用)
     */
    @Override
    public void onCreate() {
        super.onCreate();
    }

    /**
     * 通過start調用時執行該方法,每調用一次,執行一次該方法
     * @param intent
     * @param flags
     * @param startId
     * @return
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    /**
     * Service方法銷毀時,執行該方法
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}
           

以上示範代碼的注釋已很詳細的講解Service每個方法的含義,自定義Service其實很簡單,隻需要繼承Service方法即可,然後根據業務需要重寫在onStartCommand()或onBind()執行相應的操作。下面簡述每個方法的含義。

  • onCreate():Service啟動前調用的方法,在在onStartCommand()和onBind()之前調用,處理一些初始化的操作;
  • onBind():由于該方法在Service設定為abstract,是以繼承Service必須實作的方法,同時也是通過bind調用時的回調方法;
  • onStartCommand():如果通過start方式啟動,必須重寫該方法,在該回調方法中處理相應的業務邏輯,每次調用都會執行該方法;
  • onDestroy():Service銷毀時執行該方法,可處理一些資源關閉或釋放的操作。

2 注冊Service

作為四大元件之一,同樣在使用Service的使用,需要繼承Service類,重寫onBind方法,并在AndroidManifest.xml中注冊新建立的Servcie對象。

<service android:name=".ServiceTest"
            android:enabled="true"
            android:exported="true"
            android:isolatedProcess="true"
            android:process=":procressName"/>
           
  • name:表示新建立的Service的類名稱;
  • enabled:是否被執行個體化,預設為true;
  • exported:表示該Service是否支援隐式調用,其預設值是由Service中是否包含intent-filter決定,如果包含intent-filter則預設為true,否則為false,即使設定為true也無法隐式調用,因為如果不添加intent-filter,當通過Intent調用時,安卓就無法知道具體那個控件(Service/Activity/Broadcast Receiver)來響應該請求;
  • isolatedProcess:如果設定為true,服務将會在特殊的程序中運作(獨立應用的程序),通過start或bind與其進行資料通信;
  • process:程序設定,是否需要在單獨的程序中運作,在設定該參數時需注意,如果設定為“:procressName”和“procressName”,前者表示“App-package:procressName”而後者表示“procressName”程序,兩者表示不同的程序。

3 Service啟動

Service的啟動包括兩種方式,一種是通過start方式,還有一種是bind方式,下面具體介紹該兩種方式的不同含義。

Android四大元件之Service使用詳解

3.1 Bind方式

使用流程如下

  • 建立自定義Servcie類,重寫onBind()對象,傳回自定義的Binder類并傳入自定義的Service對象;
  • 建立自定義Binder類,編寫針對Service的處理方法;
  • 在bindServcie時,傳入ServiceConnection對象,擷取是否連接配接狀态。

建立自定義的Service類,編寫綁定成功後的方法類。

public class ServiceTest extends Service {

    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder(this);  //傳回自定義的Binder類
    }

    /**
     * Service的處理方法
     * @param str
     */
    public void serviceMethod(String str){
        Log.i(TAG, "setMethod: str "+str);
    }
           

建立自定義Binder類,編寫針對Service的處理方發

public class  MyBinder extends Binder {
    private static final String TAG = "MyBinder";
    private ServiceTest mServiceTest;

    public MyBinder(ServiceTest serviceTest) {
        mServiceTest = serviceTest;
    }
    //Binder操作後的方法
    public void testMethod(String str){
        Log.i(TAG, "testMethod: "+"XIAOHAN "+str);
        mServiceTest.serviceMethod(str);
        mMyBinderInterface.binderCall(str+"recall");
    }
    private MyBinderInterface mMyBinderInterface;

    public void setMyBinderInterface(MyBinderInterface myBinderInterface) {
        mMyBinderInterface = myBinderInterface;
    }

    public   interface MyBinderInterface {
        void binderCall(String msg);
    }
}
           

實際使用時,bindService時,需要傳入一個ServiceConnection參數,在onServiceConnected可以擷取到自定義的Binder類對象,通過該對象可以擷取自定義Binder類中的回調處理方法,拿到自定義的Binder類後,說明bind成功,可通過binder對象,傳入參數,間接調用自定義的Service中的處理方法。

ServiceConnection serviceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                mMyBinder = (MyBinder) service;
                mMyBinder.setMyBinderInterface(new MyBinder.MyBinderInterface() {
                    @Override
                    public void binderCall(String msg) {
                        Log.i(TAG, "XIAOHAN binderCall: "+msg);
                    }
                });
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        };

        Intent intent = new Intent(MainActivity.this,ServiceTest.class);
        bindService(intent,serviceConnection,BIND_AUTO_CREATE);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i(TAG, "XIAOHAN onClick: "+mMyBinder);
                mMyBinder.testMethod("hangzhou");
            }
        });
           

執行流程列印日志如下:

02-24 02:13:53.165 4172-4172/com.example.xiaohan.test22 I/MainActivity: onCreate: XIAOHAN serviceConnection
02-24 02:13:53.175 4172-4172/com.example.xiaohan.test22 I/MainActivity: onCreate: XIAOHAN bindService 
02-24 02:13:53.205 4172-4172/com.example.xiaohan.test22 I/ServiceTest: XIAOHAN onBind: 
02-24 02:13:53.205 4172-4172/com.example.xiaohan.test22 I/MyBinder: XIAOHAN MyBinder: 
           

執行順序:建立serviceConnection對象–bindService—調用onBind方法----建立MyBinder對象

當點選onclick時,即執行mMyBinder.testMethod(“hangzhou”)時的執行日志如下

02-24 02:16:20.115 4172-4172/com.example.xiaohan.test22 I/MainActivity: XIAOHAN onClick: [email protected]
02-24 02:16:20.115 4172-4172/com.example.xiaohan.test22 I/MyBinder: testMethod: XIAOHAN hangzhou
02-24 02:16:20.115 4172-4172/com.example.xiaohan.test22 I/ServiceTest: XIAOHAN setMethod: hangzhou
02-24 02:16:20.115 4172-4172/com.example.xiaohan.test22 I/MainActivity: XIAOHAN binderCall: hangzhourecall
           

執行順序:自定義Biner的執行方法–Service的綁定處理方法—ServiceConnection的onServiceConnected()回調

注意:如果在使用bind調用Service時,如果在AndroidManifest.xml中對綁定的元件(activity或servcie)設定了程序名,但未設定自定義的Service的相同的程序,則會報如下錯誤:

java.lang.ClassCastException:android.os.BinderProxy
           

處理的方法見該文章。

3.2 Start方式

可通過其他元件(如Activity)調用startServce方法來開啟Service,每次調用一次start方法,都會回調一次 onStartCommand()方法,但隻要執行一次stopService方法,就會關閉service方法,是以該執行的順序是:

onCreate()—onStartCommand() (n次)—onDestroy()

對于該流程的操作相信大家都已熟悉,但實際使用中我們更多的是關注onStartCommand()方法的使用。

@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }
           

在檢視該方法前,我們先通過檢視源代碼,擷取該方法的參數類型和傳回值類型,點選源碼,可以檢視如下代碼塊,通過該代碼塊可以看到我們需要的參數類型。

public @StartResult int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) {
        onStart(intent, startId);
        return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
           

方法參數的設定:

  • Intent:啟動Service傳遞過來的Intent對象,也可通過Intent傳遞數值;
  • flags:包括兩個參數:START_FLAG_REDELIVERY和START_FLAG_RETRY,其中START_FLAG_REDELIVERY:表示之前已經傳遞過Intent資訊,但是由于Service被kill,再次重傳時保留之前Intent的值,表示之前的傳遞還未結束,START_FLAG_RETRY:如果onStartCommand()回調一直沒有傳回值會重新調用onStartCommand()方法;
  • startId:指明目前Service的唯一對象,與stopSelfResult (int startId)方法配合使用,可更安全的停止服務。

傳回值的類型:

  • START_STICKY_COMPATIBILITY:與START_STICKY類似,主要是相容低版本的作用;
  • START_STICKY:線程被殺死,會嘗試再次建立該服務,并會回調onStartCommand()方法,但Intent參數可能為空,是以回調該方法時需要做非空判斷;
  • START_NOT_STICKY:線程被殺死時,不會嘗試重新開機該服務,除非程式檢測被殺後,重新開啟,但已經不是被殺調之前的狀态了(建立新的服務對象);
  • START_REDELIVER_INTENT:如果線程被殺,重新建立,并傳遞最後一個服務傳遞的Intent的值調用onStartCommand()方法,與START_STICKY不同,該傳遞的Intent值是非空的。

startService啟動Service的方式包括兩種,一種是通過顯示啟動,一種是通過隐式啟動,兩種啟動方式分析如下

1.顯示啟動:

在需要的地方啟動該服務。

Intent intent = new Intent(MainActivity.this, ServiceTest.class);
//        intent.putExtra("xiaohan","杭州");//添加資料
        Bundle bundle = new Bundle();   //通過Bundle方式傳遞
        bundle.putString("xiaohan","杭州");
        intent.putExtras(bundle);
        startService(intent);
           

在自定義的Service的onStartCommand方法中處理回調。

//回調中處理該資料
 @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
       /* String xiaohan = intent.getStringExtra("xiaohan");
        Log.i(TAG, "onStartCommand: "+xiaohan);*/
        Bundle extras = intent.getExtras();
        String xiaohan = extras.getString("xiaohan");
        Log.i(TAG, "onStartCommand: "+xiaohan);
        return START_STICKY;
    }
           

該方式是我們比較常見的啟動方式,啟動時可通過Intent傳遞參數(支援基本資料類型(及其對應的數組)、String和序列化後的對象),如果包含多個參數,可通過Bundle傳遞,但需要注意通過該種方式傳遞的資料不能太大(不超過1M)。

2.隐式啟動:

在AndroidManifest.xml中聲明自定義的Service,添加action和category。

<service android:name=".ServiceTest">
            <intent-filter >
                <action android:name="com.example.xiaohan.service"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </service>
           

采用Bind的方式

ServiceConnection serviceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                mMyBinder = (MyBinder) service;
                mMyBinder.setMyBinderInterface(new MyBinder.MyBinderInterface() {
                    @Override
                    public void binderCall(String msg) {
                        Log.i(TAG, "XIAOHAN binderCall: "+msg);
                    }
                });
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        };
		 Intent service = new Intent();
        service.setAction("com.example.xiaohan.service"); //service 的 action 值
        service.setPackage("com.example.xiaohan.test22"); //遠端服務所在包名
        //綁定服務
        bindService(service, serviceConnection, Context.BIND_AUTO_CREATE);
		 //啟動服務
     // startService(service);