Service 詳解

Service 詳解


Service 是一種程序後臺運行的方案,用於不須要用戶交互,長期運行的任務,Service 並非在單獨進程中運行,也是運行在應用程序進程的主線程中,在執行具體耗時任務過程當中要手動開啓子線程,應用程序進程被殺死,全部依賴該進程的服務也會中止運行android

目錄

-生命週期安全

-啓動方式bash

-Service 通訊服務器

-IntentServiceapp

-ForegroundServicedom

生命週期

Service 有兩種啓動服務的方式,對應的生命週期也不一致異步

Service生命週期

onCreate 首次建立服務時,系統將調用此方法。若是服務已在運行,則不會調用此方法,該方法只調用一次ide

onStartCommand 當另外一個組件經過調用 startService()請求啓動服務時,系統將調用此方法函數

onDestroy 當服務再也不使用且將被銷燬時,系統將調用此方法oop

onBind 當另外一個組件經過調用 bindService()與服務綁定時,系統將調用此方法

onUnbind 當另外一個組件經過調用 unbindService()與服務解綁時,系統將調用此方法

onRebind 當舊的組件與服務解綁後,另外一個新的組件與服務綁定,onUnbind()返回 true 時,系統將調用此方法

啓動方式

經過上面的生命週期咱們可知道,啓動 Service 的方式有兩種分別是 startService 和 bindService,接下來咱們來分別介紹一下

startService

1.在 AndroidManifest.xml 註冊咱們所要寫的 Service,咱們這裏以 AService 爲例

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.madreain"
    >

    ……

        <service android:name="com.madreain.AService" >
        </service>
    </application>

</manifest>
複製代碼

2.AService 繼承 Service 的相關代碼

public class AService extends Service{

    @Override
    public void onCreate() {
        super.onCreate();
    }

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

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

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}
複製代碼

咱們還能夠在相關的生命週期中打印 Thread.currentThread().getId()及其相關值

3.在 MainActivity 中使用開啓服務

Intent intent = new Intent(this, AService.class);
startService(intent);

複製代碼

4.屢次調用 Service 來觀察生命週期

//啓動三個相同服務
Intent intent1 = new Intent(this, AService.class);
startService(intent1);

Intent intent2 = new Intent(this, AService.class);
startService(intent2);

Intent intent3 = new Intent(this, AService.class);
startService(intent3);

//中止服務
Intent intent4 = new Intent(this, AService.class);
stopService(intent4);

//再啓動服務
Intent intent5 = new Intent(this, AService.class);
startService(intent5);

複製代碼

這時候咱們能夠在 Aservice 不一樣生命週期中打印生命週期執行函數及其 Thread ID 來分析得出: 1.Thread ID 打印出來是相同的,說明回調方法都在主線程執行的 2.屢次調用 startService,onCreate 方法只被執行一次,每啓動一次觸發一次 onStartCommand,屢次 startService 不會重複執行 onCreate 回調,但每次都會執行 onStartCommand 回調。

bindService

1.建立 BService

public class BService extends Service{

    //client 能夠經過Binder獲取Service實例
    public class MyBinder extends Binder {
        public BService getService() {
            return BService.this;
        }
    }

    //經過binder實現調用者client與Service之間的通訊
    private MyBinder binder = new MyBinder();

    private final Random generator = new Random();

    @Override
    public void onCreate() {
        super.onCreate();
    }

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

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

    @Override
    public boolean onUnbind(Intent intent) {
        return false;
    }

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

    //getRandomNumber是Service暴露出去供client調用的公共方法
    public int getRandomNumber() {
        return generator.nextInt();
    }
}
複製代碼

2.在 BActivity 中使用

public class BActivity extends Activity implements Button.OnClickListener {

    private BService service = null;

    private boolean isBind = false;

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            isBind = true;
            BService.MyBinder myBinder = (BService.MyBinder)binder;
            service = myBinder.getService();
            //int num = service.getRandomNumber();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            isBind = false;
        }
    };

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

        findViewById(R.id.btnBindService).setOnClickListener(this);
        findViewById(R.id.btnUnbindService).setOnClickListener(this);
        findViewById(R.id.btnFinish).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if(v.getId() == R.id.btnBindService){
            //單擊了「bindService」按鈕
            Intent intent = new Intent(this, TestTwoService.class);
            intent.putExtra("from", "ActivityB");
            bindService(intent, conn, BIND_AUTO_CREATE);
        }else if(v.getId() == R.id.btnUnbindService){
            //單擊了「unbindService」按鈕
            if(isBind){
                unbindService(conn);
            }
        }else if(v.getId() == R.id.btnFinish){
            //單擊了「Finish」按鈕
            this.finish();
        }
    }
    @Override
    public void onDestroy(){
        super.onDestroy();
    }
}
複製代碼

再建立一個相同的 CActivity 去綁定 Service,而後咱們去動手執行一下操做,而後在對應的生命週期中打印生命週期執行的方法以及 onServiceConnected 方法中打印調用狀況 1.BActivity 去 bindService 2.CActivity 去 bindService 3.CActivity 中的 unbindService 按鈕 4.BActivity 中的 unbindService 按鈕

上面四種狀況去操做可發現: 1.再次建立不會調用 onCreate、onBind 方法 2.只有都調用了 unbindService,BService 纔會去執行 onUnbind 方法,再執行 onDestroy

補充:系統資源回收

public int onStartCommand(Intent intent, int flags, int startId) {
  return START_NOT_STICKY | START_STICKY | START_REDELIVER_INTENT;
}
複製代碼

START_NOT_STICKY 當系統因回收資源而銷燬了 Service,當資源再次充足時再也不自動啓動 Service,除非有未處理的 Intent 準備發送。

START_STICKY 當系統因回收資源而銷燬了 Service,當資源再次充足時自動啓動 Service。並且再次調用 onStartCommand() 方法,可是不會傳遞最後一次的 Intent,相反系統在回調 onStartCommand() 的時候會傳一個空 Intent,除非有未處理的 Intent 準備發送。

START_REDELIVER_INTENT 當系統因回收資源而銷燬了 Service,當資源再次充足時自動啓動 Service,而且再次調用 onStartCommand() 方法,並會把最後一次 Intent 再次傳遞給 onStartCommand(),相應的在隊列裏的 Intent 也會按次序一次傳遞。此模式適用於下載等服務。

說到這就要說一下保活後臺服務的相關方法: 1).提升優先級

<!-- 爲防止Service被系統回收,能夠嘗試經過提升服務的優先級解決,1000是最高優先級,數字越小,優先級越低 -->
android:priority="1000"
複製代碼

2).把 service 寫成系統服務,將不會被回收:

在 Manifest.xml 文件中設置 persistent 屬性爲 true,則可以使該服務免受 out-of-memory killer 的影響。可是這種作法必定要謹慎,系統服務太多將嚴重影響系統的總體運行效率。

3).將服務改爲前臺服務 foreground service:

重寫 onStartCommand 方法,使用 StartForeground(int,Notification)方法來啓動 service。

注:通常前臺服務會在狀態欄顯示一個通知,最典型的應用就是音樂播放器,只要在播放狀態下,就算休眠也不會被殺,若是不想顯示通知,只要把參數裏的 int 設爲 0 便可。

同時,對於經過 startForeground 啓動的 service,onDestory 方法中須要經過 stopForeground(true)來取消前臺運行狀態。

這個方案將在-ForegroundService細說

4).利用 Android 的系統廣播

利用 Android 的系統廣播檢查 Service 的運行狀態,若是被殺掉,就再起來,系統廣播是 Intent.ACTION_TIME_TICK,這個廣播每分鐘發送一次,咱們能夠每分鐘檢查一次 Service 的運行狀態,若是已經被結束了,就從新啓動 Service。

Service 通訊

Activity 與 Service 進行通訊有四種方式

######經過 Intent 傳遞參數

在 MainActivity 中傳遞參數

intent = new Intent(MainActivity.this, MyService.class);
intent.putExtra("data", object);
startService(intent);
複製代碼

在 MyService 的 onStartCommand 接受參數

@Override
public int onStartCommand(final Intent intent, int flags, int startId) {
    data = intent.getStringExtra("data");
    return super.onStartCommand(intent, flags, startId);
}

複製代碼

######Binder 類

MyService 中咱們建立一個 Binder 類,讓其實現 android.os.Binder 類,而且定義一個方法 setData,而後咱們經過 onBind()方法將其對象返回 MainActivity。

設置 MyService 中設置參數數據的方法

public class MyService extends Service {
    private String data;
    public MyService() {
    }

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

    public class Binder extends android.os.Binder{
        public void setData(String data){
            MyService.this.data = data;
        }
    }
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }

}
複製代碼

在 MainActivity 中實現 ServiceConnection 方法,獲取到 MyService 中返回的 Binder 對象,接着咱們經過 Binder 對象調用它的方法 setData 向其傳遞數據

public class MainActivity extends AppCompatActivity implements View.OnClickListener, ServiceConnection {

    private Intent intent;
    private MyService.Binder myBinder = null;//①

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        intent = new Intent(MainActivity.this, MyService.class);
        findViewById(R.id.btySend).setOnClickListener(this);

    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btySend://想MyService傳遞數據
                if (myBinder != null) {
                    myBinder.setData("傳遞能想傳遞的數據");//③
                }
                break;
            default:
                break;
        }
    }
    //一旦綁定成功就會執行該函數
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        myBinder = (MyService.Binder) iBinder;//②
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {

    }

}

複製代碼

注意 ①②③ 的順序

######接口回掉 CallBack 在 MyService 設置 CallBack,可用於 Service 向 activity 傳遞參數,監聽服務中的進程的變化

public class MyService extends Service {
    private Boolean myflags = false;
    private String data ;
    private Callback callback;
    private static final String TAG = "MyService";
    public MyService() {
    }

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

    public class Binder extends android.os.Binder{
        public void setData(String data){
            MyService.this.data = data;
        }
        public MyService getMyService(){
            return MyService.this;
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        myflags = true;
        new Thread(){
            @Override
            public void run() {
                super.run();
                int i =1;
                while(myflags){
                    //System.out.println("程序正在運行.....");
                    try {
                        String str = i + ":" +data;
                        Log.d(TAG, str);
                        if (callback != null){
                            callback.onDataChange(str);
                        }
                        sleep(1000);
                        i++;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        Toast.makeText(MyService.this, "出錯了", Toast.LENGTH_SHORT).show();
                    }
                }
                Log.d(TAG, "服務器已中止");
            }
        }.start();
    }

    @Override
    public int onStartCommand(final Intent intent, int flags, int startId) {
        data = intent.getStringExtra("data");
        return super.onStartCommand(intent, flags, startId);
    }

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

    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }

    public void setCallback(Callback callback) {
        this.callback = callback;
    }

    public Callback getCallback() {
        return callback;
    }

    public static interface Callback{
        void onDataChange(String data);
    }
}

複製代碼

在 MainActivity 中咱們能夠經過 onServiceConnected 方法中的 iBinder 對象來實現 MyService 中 Callback 的接口,在 Android 中有一個安全機制,輔助線程是不能直接修改主線程中的數據的,所以咱們須要再定義一個 Hander 對象。

public class MainActivity extends AppCompatActivity implements  ServiceConnection {

    private MyService.Binder myBinder = null;

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

    //一旦綁定成功就會執行該函數
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        myBinder = (MyService.Binder) iBinder;
        myBinder.getMyService().setCallback(new MyService.Callback(){
            @Override
            public void onDataChange(String data) {
                Message msg = new Message();
                Bundle b = new Bundle();
                b.putString("data",data);
                msg.setData(b);
                hander.sendMessage(msg);
            }
        });
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {

    }

    private Handler hander = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //獲取變化的數據 msg.getData().getString("data")
        }
    };
}

複製代碼

######broadcast

經過廣播的的形式,可參考BroadcastReciver 詳解

IntentService

IntentService 是繼承並處理異步請求的一個類,在 IntentService 內有一個工做線程來處理耗時操做,啓動 IntentService 的方式和啓動傳統的 Service 同樣,同時,當任務執行完後,IntentService 會自動中止,而不須要咱們手動去控制或 stopSelf()。另外,能夠啓動 IntentService 屢次,而每個耗時操做會以工做隊列的方式在 IntentService 的 onHandleIntent 回調方法中執行,而且,每次只會執行一個工做線程,執行完第一個再執行第二個,以此類推。

查看 IntentService 類的部分源碼

@Override
    public void onCreate() {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.

        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }


ServiceHandler相關代碼

private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }
複製代碼

從源碼中看,onCreate 中 thread.start()開啓工做線程,thread.getLooper()單獨的消息隊列,經過 handler 中的 handleMessage 方法調用 onHandleIntent 執行處理任務

在實際應用中咱們繼承 IntentService,重寫 onHandleIntent 方法來執行異步耗時操做

public class MIntentService extends IntentService {

    public MIntentService(){
        super("MIntentService");
    }

    /**
     * Creates an IntentService.  Invoked by your subclass's constructor. * @param name Used to name the worker thread, important only for debugging. */ public MIntentService(String name) { super(name); } @Override public void onCreate() { super.onCreate(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent, flags, startId); } @Override protected void onHandleIntent(Intent intent) { //TODO耗時操做 實際應用有後臺靜默上傳數據 } @Override public void onDestroy() { super.onDestroy(); } } 複製代碼

ForegroundService

ForegroundService 前臺服務是那些被認爲用戶知道(用戶所承認的)且在系統內存不足的時候不容許系統殺死的服務。前臺服務必須給狀態欄提供一個通知,它被放到正在運行(Ongoing)標題之下——這就意味着通知只有在這個服務被終止或從前臺主動移除通知後才能被解除

應用場景:在通常狀況下,Service 幾乎都是在後臺運行,一直默默地作着辛苦的工做。但這種狀況下,後臺運行的 Service 系統優先級相對較低,當系統內存不足時,在後臺運行的 Service 就有可能被回收。 那麼,若是咱們但願 Service 能夠一直保持運行狀態且不會在內存不足的狀況下被回收時,能夠選擇將須要保持運行的 Service 設置爲前臺服務。咱們能夠以常見的音樂播放器舉例說明

建立音樂服務

public class MusicPlayerService extends Service {

        private static final String TAG = MusicPlayerService.class.getSimpleName();

        @Override
        public void onCreate() {
            super.onCreate();
            Log.d(TAG, "onCreate()");
        }

        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            Log.d(TAG, "onStartCommand()");
        }

        @Override
        public IBinder onBind(Intent intent) {
            Log.d(TAG, "onBind()");
            // TODO: Return the communication channel to the service.
            throw new UnsupportedOperationException("Not yet implemented");
        }
}
複製代碼

而後建立 Notification,在 Service 的 onStartCommand 中添加以下代碼:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand()");
        // 在API11以後構建Notification的方式
        Notification.Builder builder = new Notification.Builder
            (this.getApplicationContext()); //獲取一個Notification構造器
        Intent nfIntent = new Intent(this, MainActivity.class);

        builder.setContentIntent(PendingIntent.
        getActivity(this, 0, nfIntent, 0)) // 設置PendingIntent
        .setLargeIcon(BitmapFactory.decodeResource(this.getResources(),
           R.mipmap.ic_large)) // 設置下拉列表中的圖標(大圖標)
        .setContentTitle("下拉列表中的Title") // 設置下拉列表裏的標題
        .setSmallIcon(R.mipmap.ic_launcher) // 設置狀態欄內的小圖標
        .setContentText("要顯示的內容") // 設置上下文內容
        .setWhen(System.currentTimeMillis()); // 設置該通知發生的時間

        Notification notification = builder.build(); // 獲取構建好的Notification
        notification.defaults = Notification.DEFAULT_SOUND; //設置爲默認的聲音
}
複製代碼

在完成 Notification 通知消息的構建後,在 Service 的 onStartCommand 中可使用 startForeground 方法來讓 Android 服務運行在前臺。

// 參數一:惟一的通知標識;參數二:通知消息。
startForeground(110, notification);// 開始前臺服務

複製代碼

若是須要中止前臺服務,可使用 stopForeground 來中止正在運行的前臺服務。

@Override
public void onDestroy() {
      Log.d(TAG, "onDestroy()");
      stopForeground(true);// 中止前臺服務--參數:表示是否移除以前的通知
      super.onDestroy();
}
複製代碼
相關文章
相關標籤/搜索