Android四大組件Service使用介紹

Android四大組件之Service

簡述

  Service 是Android中的組件之一,與Activity、Broadcast Receiver、Content Provider共稱爲四大組件。從名字也可以看出Service主要是提供服務的,它運行在後臺,甚至可以不依賴組件而單獨存在。

生命週期

下面是官方文檔中的生命週期圖:
Service生命週期
  從上圖可以看出Service有兩種啓動方式,不同的啓動方式下其生命週期是不同的。比較可以看出兩種啓動方式下onCreate和onDestroy是一致的,不同之處是中間服務執行的部分。
  對於startService方式啓動的服務,它執行的是onStartCommond,由於它啓動後就與組件沒有了關聯,因此只涉及到這一個方法。這樣的服務不手動關閉的話會一直運行在後臺。對於onBind方式啓動的服務,它與啓動的組將通過IBinder進行互動,因此它的生命週期中執行的是onBind和onUnbind方法,對應着綁定與解綁的操作。

使用

  Service作爲四大組件之一,使用服務的時候也需要在AndroidManifest中進行註冊(四大組件都需要註冊)。

<service android:description="string resource"
     android:directBootAware=["true" | "false"]
     android:enabled=["true" | "false"]
     android:exported=["true" | "false"]
     android:icon="drawable resource"
     android:isolatedProcess=["true" | "false"]
     android:label="string resource"
     android:name="string"
     android:permission="string"
     android:process="string" >
    ...
</service>

  description:對服務的描述信息,不是必要的。該屬性必須使用引用方式,不能直接添加描述。
  derestBootAware:服務是否可以運行在用戶未解鎖前,默認false
  enabled:當前服務是否可被實例化,默認true。false下服務無法啓動
  export:是否可以被其他應用程序啓動,默認false。
  isolatedProcess:true則運行在其他進程中,與系統進程獨立並且沒有權限,僅能通過綁定方式與其他組件通信
  label:展示給用戶的服務的名字
  name:對應的Service實現類的類名,必須聲明
  permission:權限
  process:服務運行的進程名字,默認情況下所有的組件都是運行在主進程中,主進程名則是應用包名。設置時使用冒號(:)可以在進程前加上當前包名。例如包名爲com.example.test,設置進程爲 android:process=":new",則服務運行在com.example.test:new 進程。若是不加冒號,則運行在new進程。

創建服務對象

  創建服務只需要繼承Service類,然後實現onBind方法即可,並且在AndroidManifest中聲明即可。

public class MyService extends Service {
    private static final String TAG = "MyService"; 
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    @Override
    public void onCreate() {
        Log.i(TAG, "onCreate: Service創建");
        super.onCreate();
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand: Service啓動");
        return super.onStartCommand(intent, flags, startId);
    }
    @Override
    public void onDestroy() {
        Log.i(TAG, "onDestroy: Service銷燬");
        super.onDestroy();
    }
}

  這裏創建了一個空的服務,並且onBind方法僅僅返回了一個null。這個方法是必須實現的,但是在非綁定方式啓動的服務中,是不涉及到這個onBind方法的,因此採用startService啓動的服務中,onBind直接返回null即可。

直接啓動:

  直接啓動是由startService進行啓動,然後通過stopService停止,該方式下,生命週期爲onCreate->onStartCommond->onDestroy
  當第一次調用startService的時候,服務會先調用onCreate再調用onStartCommond,並且在已啓動服務的情況下再次startService則只調用onStartCommond方法。

public class MainActivity extends AppCompatActivity {

    private Button mButtonStart;
    private Button mButtonStop;
    private Intent mIntent;

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

        mButtonStart = findViewById(R.id.btn_start);
        mButtonStop = findViewById(R.id.btn_stop);
        mIntent = new Intent(this,MyService.class);

        mButtonStart.setOnClickListener(v->{
            startService(mIntent);
        });

        mButtonStop.setOnClickListener(v->{
            stopService(mIntent);
        });
    }
}

  多次啓動,最後停止的打印日誌如下:

/com.example.servicedemo I/MyService: onCreate: Service創建
/com.example.servicedemo I/MyService: onStartCommand: Service啓動
/com.example.servicedemo I/MyService: onStartCommand: Service啓動
/com.example.servicedemo I/MyService: onStartCommand: Service啓動
/com.example.servicedemo I/MyService: onDestroy: Service銷燬

  值得注意的是,採用startService方式啓動的服務將一直運行在後臺,而不會主動銷燬。因此,當服務的操作結束後,要使用stopService(Intent)來將該服務進行關閉,或者在服務內部通過stopSelf來進行關閉。

int onStartCommand(Intent intent, int flags, int startId)

參數:
  intent :這是啓動服務時(startService)所攜帶的intent,可以用來傳遞數據。在系統殺死該服務並重啓後可能爲null。
  flags: 值爲0或者START_FLAG_REDELIVERY或者 START_FLAG_RETRY
  startId:唯一ID,用於stopSelf
  返回值:
    START_STICKY:如果服務因爲內存問題被系統殺死,則後面會重建該服務並調用onStartCommond,但這種情況下intent的值爲null。
    START_STICKY_COMPATIBILITY:兼容START_STICKY,但是並不保證一定能夠重建服務成功
    START_NOT_STICKY:如果服務因爲內存問題被系統殺死,則不重建該服務,因此這種情況下不會接收到值爲null的intent。
   START_REDELIVER_INTENT:如果服務因爲內存問題被系統殺死,則重建服務並且傳遞最後一個intent,這種情況下intent也不爲空。

Bind啓動

  Bind啓動方式是通過bindService進行綁定的,這種方式要求Service的onBind方法必須返回一個IBinder對象,這樣在其他組件中就能夠獲得該對象,從而進行交互。該方式下生命週期爲onCreate->onBind->onUnBind->onDestroy

public class MyService extends Service {

    private static final String TAG = "MyService";

    class MyBinder extends Binder {
        public MyService getService() {
            return MyService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind: 綁定服務");
        return new MyBinder();
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.i(TAG, "onUnbind: 解除綁定");
        return super.onUnbind(intent);
    }

    @Override
    public void onCreate() {
        Log.i(TAG, "onCreate: Service創建");
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand: Service啓動");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.i(TAG, "onDestroy: Service銷燬");
        super.onDestroy();
    }
}

  重新修改了MyService,在其內部定義了一個內部類,該內部類是繼承自Binder的,並且定義了一個方法用於獲取Service。這樣,在通過bindServce獲取的IBinder後就可以進而獲取Service對象,從而實現組件與服務通信。
  另外,綁定方式下啓動的服務是依賴於啓動組件的,當組件銷燬的時候,若是並未顯式解綁服務,則會自動調用onUnbind和onDestroy進行解綁。這與startService方式是有明顯不同的,startService方式下啓動的服務將一直運行在後臺,即使啓動它的組件已被銷,所以一定要記住手動停止服務。

private MyService mService;

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

    mButtonBind = findViewById(R.id.btn_bind);
    mButtonUnBind = findViewById(R.id.btn_unbind);
    mIntent = new Intent(this, MyService.class);

    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = ((MyService.MyBinder) service).getService();
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    mButtonBind.setOnClickListener(v -> {
        bindService(mIntent, connection, BIND_AUTO_CREATE);
    });

    mButtonUnBind.setOnClickListener(v -> {
        unbindService(connection);
    });
}

  通過Bind方式啓動Service還需要一個ServiceConnection對象,在連接到Service的時候,該對象的onServiceConnected方法就可以獲得該Service中onBind返回的IBinder,從而進一步獲得Service對象。

/com.example.servicedemo I/MyService: onCreate: Service創建
/com.example.servicedemo I/MyService: onBind: 綁定服務
/com.example.servicedemo I/MyService: onUnbind: 解除綁定
/com.example.servicedemo I/MyService: onDestroy: Service銷燬

兩種方式同時啓動

  同一個服務是隻存在一份實例的,也就是說只有第一次創建服務纔會調用onCreate方法,後面多次啓動只會調用onStartCommond或者onBind方法。並且,當同一個服務既被startService方式啓動又被bindService啓動的時候,只有同時調用停止和解綁兩個方法(stopService和unBindService的順序無關),服務纔會被銷燬。
  注意:服務可以同時由多個組件多次以startService方式啓動,並且由其他組件進行關閉;但是綁定方式(bindService)同時只能有一個組件進行綁定,並且只有綁定服務的組件才能進行解綁。

前臺服務

  前臺服務是藉助Notification來在通知欄顯示的服務,這種方式下的服務的優先級較高,當系統內部不足時會銷燬後臺服務而幾乎不會銷燬前臺服務。
  使用前臺服務可以與用戶進行交互,例如下載文件、後臺播放音樂等,都可以使用前臺服務。前臺服務是在Service中調用startForeground方式,可以在onStartCommond也可以在onBind中調用,代表着兩種啓動方式都可以成爲前臺服務。

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Log.i(TAG, "onStartCommand: 啓動服務");
    Intent notificationIntent = new Intent(this, MainActivity.class);
    PendingIntent pendingIntent =
            PendingIntent.getActivity(this, 0, notificationIntent, 0);

    NotificationCompat.Builder builder;
    NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    // android 8.0以上需要配置Channel
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        String channelId = "notification_channel_id";
        NotificationChannel channel = new NotificationChannel(channelId, "channel_name", NotificationManager.IMPORTANCE_HIGH);
        // 設置默認聲音
        channel.enableLights(true);
        channel.enableVibration(true);
        manager.createNotificationChannel(channel);
        builder = new NotificationCompat.Builder(getBaseContext(), channelId);
    } else {
        builder = new NotificationCompat.Builder(getBaseContext(),null);
    }

        builder.setContentTitle("服務標題")
            .setContentText("服務內容")
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentIntent(pendingIntent)
            .setWhen(System.currentTimeMillis());
    startForeground(1, builder.build());
    return super.onStartCommand(intent, flags, startId);
}

IntentService

  在服務中,所有的操作都是運行在主線程中的。因此,在服務中若是有耗時的操作的時候,都必須創建子線程,然後在子線程中進行執行,以避免ANR錯誤。
  而Service的子類IntentService,是系統爲我們封裝好的一個服務,就是爲了解決耗時操作的情況的。其內部創建了一個線程,通過Handler來處理請求。使用它只需要重寫onHandleIntent方法即可,另外構造方法接收一個String參數,這個參數是用來命名當前服務所運行的線程的線程名的。

public class MyIntentService extends IntentService {
    private static final String TAG = "MyService";
    public MyIntentService() {
        super("intent");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.i(TAG, "onHandleIntent: 處理服務   "+Thread.currentThread().getName());
    }
}

  使用IntentService不用擔心關閉的問題,因爲IntentService每次被啓動的時候,都會將onHandleIntent中的操作封裝後分發到消息隊列中依次執行,當所有的任務都執行完畢的時候就會銷燬服務。由於這種服務是依次執行任務的,是串行的,不適用於大量的併發任務。
  優點是使用簡單,另外,若是要重寫onCreate、onStartCommond、onDestroy,則必須調用其父類實現,以保證IntentService的正常運行。

總結

  Serivice分爲前臺服務和後臺服務。後臺服務是運行在後臺並且沒有界面的,用戶是無法感知的;前臺服務通過Notification方式,可以在通知欄顯示,用戶也可以與之交互,它是在Service的內部通過startForeground方法實現的。
  Service的啓動方式分爲直接啓動和綁定啓動。直接啓動方式下的服務將不依賴啓動組件,並且啓動後可以一直運行在後臺,直到手動停止。停止的方式有兩種,組件通過stopService停止服務或者服務內部調用stopSelf進行停止。綁定方式啓動的服務依賴於啓動組件,當組件銷燬的時候,服務也會自動銷燬,即使沒有調用解綁方法(unBindService)。
  直接啓動的方法可以啓動多次,但只有第一次啓動的時候執行onCreate方法,後面只會執行onStartCommond,並且可以由任意組件停止服務。綁定啓動的服務只能被一個組件綁定,其他組件無法綁定無法解綁,只能由綁定的組件進行解綁。
  Service是運行在主線程的,是無法進行耗時操作的。因此若是執行耗時操作需要在服務中開啓子線程進行執行任務,或者使用IntentService。IntentService將操作都放到子線程中進行執行,內部採用Handler方式。因此任務的執行是串行的,不適用於大量併發任務。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章