天天看點

Android service詳解

版權聲明:本文為部落客原創文章,遵循 CC 4.0 BY-SA 版權協定,轉載請附上原文出處連結和本聲明。

前言:

service服務,能夠使得應用程式即使在關閉的情況下仍然可以在背景繼續執行。背景功能屬于四大元件之一,其重要程度不言而喻,那讓我們接下來來來好好學習一下。

通過本文你可以學到以下内容

service是什麼

service的兩種狀态

Service在清單檔案中的聲明

Service啟動服務實作方式及其詳解

Service綁定服務的三種實作方式

關于啟動服務與綁定服務間的轉換問題

前台服務以及通知發送

服務Service與線程Thread的差別

管理服務生命周期的要點

Android 5.0以上的隐式啟動問題及其解決方案

保證服務不被殺死的實作思路

一、service是什麼

服務是Android中實作程式背景運作的解決方案,他非常适合是去執行那些不需要和使用者互動而且還要長期運作的任務。服務的運作不依賴于任何使用者界面,即使程式被切換到背景,或者使用者打開了另一個應用程式,服務仍然能夠保持獨立運作。不過需要注意的是,服務并不是運作在一個獨立的程序當中,而是依賴于建立服務時所在的應用程式程序。當某個應用程式被殺掉時,所有依賴該程序的服務也會停止運作。service基本上分為兩種形式:

本地服務:

該服務依附在主程序上而不是獨立的程序,這樣在一定程度上節約了資源,另外本地服務因為是在同一程序是以不需要IPC,也不需要AIDL。相應bindService會友善很多,當主程序被Kill後,服務便會終止。一般使用在音樂播放器播放等不需要常駐的服務。指的是服務和啟動服務的activity在同一個程序中。

遠端服務:

該服務是獨立的程序,對應程序名格式為所在包名加上你指定的android:process字元串。一般定義方式 android:process=":service" 由于是獨立的程序,是以在Activity所在程序被Kill的時候,該服務依然在運作,不受其他程序影響,有利于為多個程序提供服務具有較高的靈活性。由于是獨立的程序,會占用一定資源,并且使用AIDL進行IPC比較麻煩。一般用于系統的Service,這種Service是常駐的。指的是服務和啟動服務的activity不在同一個程序中。

注意(啟動本地服務用的是顯式啟動; 遠端服務的啟動要用到隐式啟動)

00

二、service的兩種狀态(四、五會進行詳講)

啟動狀态:

當應用元件(如 Activity)通過調用 startService() 啟動服務時,服務即處于“啟動”狀态。一旦啟動,服務即可在背景無限期運作,即使啟動服務的元件已被銷毀也不受影響,除非手動調用才能停止服務, 已啟動的服務通常是執行單一操作,而且不會将結果傳回給調用方。

綁定狀态:

當應用元件通過調用 bindService() 綁定到服務時,服務即處于“綁定”狀态。綁定服務提供了一個用戶端-伺服器接口,允許元件與服務進行互動、發送請求、擷取結果,甚至是利用程序間通信 (IPC) 跨程序執行這些操作。 僅當與另一個應用元件綁定時,綁定服務才會運作。 多個元件可以同時綁定到該服務,但全部取消綁定後,該服務即會被銷毀。

三、service在清單檔案中的聲明

不管是哪一種的 service ,也都需要在 AndroidManifest.xml中聲明  

 <service android:name=".myservice"
            android:enabled="true"
            android:exported="true"
            android:icon="@drawable/background_blue"
            android:label="string"
            android:process="string"
            android:permission="string">
 </service>
           

android:exported    表示是否允許除了目前程式之外的其他程式通路這個服務

android:enabled    表示是否啟用這個服務

android:permission    是權限聲明

android:process    是否需要在單獨的程序中運作,當設定為android:process=”:remote”時,代表Service在單獨的程序中運作。注意“:”很重要,它的意思是指要在目前程序名稱前面附加上目前的包名,是以“remote”和”:remote”不是同一個意思,前者的程序名稱為:remote,而後者的程序名稱為:App-packageName:remote。

android:isolatedProcess     設定 true 意味着,服務會在一個特殊的程序下運作,這個程序與系統其他程序分開且沒有自己的權限。與其通信的唯一途徑是通過服務的API(bind and start)。

四、service 啟動服務 以及 終止服務

  首先要建立服務,必須建立 Service 的子類(或使用它的一個現有子類如IntentService)。在實作中,我們需要重寫一些回調方法,以處理服務生命周期的某些關鍵過程,下面我們通過簡單案例來分析需要重寫的回調方法有哪些?

 
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import android.util.Log;
 
 
public class myservice extends Service {
 
    private static final String TAG = "myservice";
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
 
    @Override
    public void onCreate() {
        Log.e(TAG, "onCreate:");
        super.onCreate();
    }
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG, "onStartCommand:");
        return super.onStartCommand(intent, flags, startId);
    }
 
    @Override
    public void onDestroy() {
        Log.e(TAG, "onDestroy:");
        super.onDestroy();
    }
}
activity的點選事件  啟動 以及 關閉

    public void processClick(View v) {
        switch (v.getId()){
            case R.id.bt_demo:
                Intent start = new Intent(this,myservice.class);
                startService(start);
                break;
            case R.id.bt_demo1:
                Intent stop = new Intent(this,myservice.class);
                stopService(stop);
                break;
            default:
                break;
        }
    }
           

下面結果為測試案例

下方操作流程:第一次啟動服務- --》第二次調用啟動服務---》第三次關閉服務--》第四次打開服務--》第五次打開服務

Android service詳解

通過上面的操作案例我們可以得知,在我們第一次啟動服務的時候,會執行service中的 oncreate 還有 onStartCommand(前提是服務在之前還沒有進行啟動) 否則 他隻會單獨調用 onStartCommand方法 ,使用 stopservice() 就能夠實作中斷服務的啟動。

下面時啟動服務還有中斷服務的方式

啟動:

  Intent start = new Intent(this,myservice.class);
  startService(start);
           

 停止:

Intent stop = new Intent(this,myservice.class);
stopService(stop);
           

從上面的代碼myservice繼承了Service類,并重寫了onBind方法,該方法是必須重寫的,但是由于此時是啟動狀态的服務,則該方法無須實作,傳回null即可,隻有在綁定狀态的情況下才需要實作該方法并傳回一個IBinder的實作類(這個後面會詳細說),接着重寫了onCreate、onStartCommand、onDestroy三個主要的生命周期方法,關于這幾個方法說明如下:

onBind()

  當另一個元件想通過調用 bindService() 與服務綁定(例如執行 RPC)時,系統将調用此方法。在此方法的實作中,必須傳回 一個IBinder 接口的實作類,供用戶端用來與服務進行通信。無論是啟動狀态還是綁定狀态,此方法必須重寫,但在啟動狀态的情況下直接傳回 null。

onCreate()

  首次建立服務時,系統将調用此方法來執行一次性設定程式(在調用 onStartCommand() 或onBind() 之前)。如果服務已在運作,則不會調用此方法,該方法隻調用一次

onStartCommand()

  當另一個元件(如 Activity)通過調用 startService() 請求啟動服務時,系統将調用此方法。一旦執行此方法,服務即會啟動并可在背景無限期運作。 如果自己實作此方法,則需要在服務工作完成後,通過調用 stopSelf() 或 stopService() 來停止服務。(在綁定狀态下,無需實作此方法。)

onDestroy()

  當服務不再使用且将被銷毀時,系統将調用此方法。服務應該實作此方法來清理所有資源,如線程、注冊的偵聽器、接收器等,這是服務接收的最後一個調用。

五、service綁定服務

       通過第四節我們知道了啟動和停止服務的基本方法,不知道你有沒有發現,雖然服務是在活動裡啟動的,但在啟動了服務之後,活動與服務基本就沒有了什麼關系。确實如此,我們在活動裡調用了 startService 方法之後啟動 myservice 這個服務。然後myservice 的 oncreate 和 onstartCommand 方法就會得到執行。之後服務會一直處于運作狀态,但具體的運作的什麼邏輯,活動就已經控制不了。那就是代表我們無法通過元件對service進行控制嗎,不不不 ,不是的。這時候我們就要用到我們 service 中繼承的另一個方法 onbind 方法。

綁定服務是Service的另一種變形,當Service處于綁定狀态時,其代表着用戶端-伺服器接口中的伺服器。當其他元件(如 Activity)綁定到服務時(有時我們可能需要從Activity組建中去調用Service中的方法,此時Activity以綁定的方式挂靠到Service後,我們就可以輕松地方法到Service中的指定方法),元件(如Activity)可以向Service(也就是服務端)發送請求,或者調用Service(服務端)的方法,此時被綁定的Service(服務端)會接收資訊并響應,甚至可以通過綁定服務進行執行程序間通信 (即IPC,這個後面再單獨分析)。與啟動服務不同的是綁定服務的生命周期通常隻在為其他應用元件(如Activity)服務時處于活動狀态,不會無限期在背景運作,也就是說宿主(如Activity)解除綁定後,綁定服務就會被銷毀。

為了實作用戶端與伺服器的互動,我們一般都會通過下方三種方式進行處理。

擴充 Binder 類 

在service類中進行添加一個binder内部類,我們通過前台進行綁定後,當綁定後成功後,用戶端收到binder 後,可利用他直接通路 Binder 實作中以及Service 中可用的公共方法。如果我們的服務隻是自有應用的背景工作線程,則優先采用這種方法。前提:service服務端與用戶端相同的程序中運作。

使用 Messenger 

以後知道在補上,這是執行程序間通信 (IPC) 的最簡單方法,因為 Messenger 會在單一線程中建立包含所有請求的隊列,也就是說Messenger是以串行的方式處理用戶端發來的消息,這樣我們就不必對服務進行線程安全設計了。(小編未知,以後再進行補充)

使用 AIDL 

,如果我們想讓服務同時處理多個請求,則應該使用 AIDL。 在此情況下,服務必須具備多線程處理能力,并采用線程安全式設計。使用AIDL必須建立一個定義程式設計接口的 .aidl 檔案。Android SDK 工具利用該檔案生成一個實作接口并處理 IPC 的抽象類,随後可在服務内對其進行擴充。(小編未知,以後再進行補充)

上面介紹完對 service的各種綁定之後 ,我們接下去就是來說一下我們如何進行操作,首先我們先看一下擴充Binder類的實作方法。

擴充binder類

我們這裡的目的就是為了使 應用元件 能夠與應用的 service 服務進行互動。并且我們的服務僅供于本地服務的使用,不需要跨程序工作,然後我們就可以實作自有的 binder 類。

流程如下

1、建立 binder子類,讓前端能夠通過 binder 類實作對 service 的調用。

2、service 中的 onbinder方法傳回 binder 執行個體

3、在 前端中的 onserviceconnected 中接收傳回的 binder 執行個體。

myservice:

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;
 

public class myservice extends Service {
 
    private static final String TAG = "myservice";
    private LocalBinder mbinder = new LocalBinder();
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "onBind:" );
        return mbinder;
    }
 
    public class LocalBinder extends Binder{
        public myservice getservices(){
            return myservice.this;
        }
        public void start(){
            Log.e(TAG, "start:" );
        }
        public void end(){
            Log.e(TAG, "end:" );
        }
    }
 
    @Override
    public void onCreate() {
        Log.e(TAG, "onCreate:");
        super.onCreate();
    }
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG, "onStartCommand:");
        return super.onStartCommand(intent, flags, startId);
    }
 
    @Override
    public void onDestroy() {
        Log.e(TAG, "onDestroy:");
        super.onDestroy();
    }
 
    public  String myway(){
        Log.e(TAG, "myway:hello world");
        return "hello world";
    }
 
    @Override
    public boolean onUnbind(Intent intent) {
        Log.e(TAG, "onUnbind:");
        return super.onUnbind(intent);
    }
}
           

前端:

import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class DemoActivity extends BaseActivity {
    private myservice services;
 
    private ServiceConnection conn;
    private Button button1;
    private Button button2;
    private Button button3;
    private Button button4;
 
    private static final String TAG = "DemoActivity";
    @Override
    public int getLayoutId() {
        return R.layout.activity_demo;
    }
 
    @Override
    public void initViews() {
        button1 = findView(R.id.bt_demo);
        button2 = findView(R.id.bt_demo1);
        button3 = findView(R.id.btn_bind);
        button4 = findView(R.id.btn_unbind);
    }
 
    @Override
    public void initListener() {
        button2.setOnClickListener(this);
        button1.setOnClickListener(this);
        button3.setOnClickListener(this);
        button4.setOnClickListener(this);
    }
 
    /**
     * 裡面主要是為了實作一個可以從service的回調接口
     */
    @Override
    public void initData() {
        conn = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
                myservice.LocalBinder binder = (myservice.LocalBinder) iBinder;
                binder.start();
                binder.end();
                services = binder.getservices();
                services.myway();
            }
 
            @Override
            public void onServiceDisconnected(ComponentName componentName) {
                services = null;
                Log.e(TAG, "onServiceDisconnected:");
            }
        };
    }
 
    @Override
    public void processClick(View v) {
        switch (v.getId()){
            case R.id.bt_demo:
                Intent start = new Intent(this,myservice.class);
                startService(start);
                break;
            case R.id.bt_demo1:
                Intent stop = new Intent(this,myservice.class);
                stopService(stop);
                break;
            case R.id.btn_bind:
                //綁定服務
                Intent intent = new Intent(this, myservice.class);
                bindService(intent,conn, Service.BIND_AUTO_CREATE);
                break;
            case R.id.btn_unbind:
                //解綁服務
                Intent intent1 = new Intent(this, myservice.class);
                unbindService(conn);
                break;
            default:
                break;
        }
    }
}
           

在上面代碼中,我們在前端代碼中實作了一個 ServiceConnection 接口,該接口有兩個方法, onServiceConnected和onServiceDisconnected。

 onServiceConnected(ComponentName componentName, IBinder iBinder) 

當我們進行綁定的時候,onbind() 方法會傳回 binder 執行個體對象。進而我們可以對其進行調用。

onServiceDisconnected(ComponentName componentName)

Android 系統會在與服務的連接配接意外中斷時(例如當服務崩潰或被終止時)調用該方法。注意:當用戶端取消綁定時,系統“絕對不會”調用該方法。

 conn = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
                myservice.LocalBinder binder = (myservice.LocalBinder) iBinder;
                binder.start();
                binder.end();
                services = binder.getservices();
                services.myway();
            }
 
            @Override
            public void onServiceDisconnected(ComponentName componentName) {
                services = null;
                Log.e(TAG, "onServiceDisconnected:");
            }
        };
           

我們對服務的 綁定 還有 解綁 是如下的

綁定

                //綁定服務
                Intent intent = new Intent(this, myservice.class);
                bindService(intent,conn, Service.BIND_AUTO_CREATE);
           

解綁

                //解綁服務
                Intent intent1 = new Intent(this, myservice.class);
                unbindService(conn);
           

loge 日志如下

Android service詳解

前五行是進行綁定服務的 ,後面的是進行解綁

    通過Log可知,當我們第一次點選綁定服務時,LocalService服務端的onCreate()、onBind方法會依次被調用,此時用戶端的ServiceConnection#onServiceConnected()被調用并傳回 LocalBinder 對象,接着調用LocalBinder#getService方法傳回myservice 執行個體對象,此時用戶端便持有了myservice 的執行個體對象,也就可以任意調用myservice類中的聲明公共方法了。更值得注意的是,我們多次調用bindService方法綁定LocalService服務端,而LocalService得onBind方法隻調用了一次,那就是在第一次調用bindService時才會回調onBind方法。

使用Messenger

 前面我們了解通過擴充 Binder類來進行通信。接下來我們就來介紹一下不同程序之間的通信,我們下面就是采用最簡單的方式Messenger來進行通信,通過此來了解程序之間的通信,這也是最輕量級的方式。過程如下:

1、在service中建立handler,然後通過這個handler建立Messenger對象。

2、我們在用戶端通過綁定 onBinder 函數傳回 binder對象,然後我們在建立出Messenger對象

      建立Messenger對象 兩種方式

             Messenger(Binder);

             Messenger(Handler);
           

3、通過Messenger發送資訊   mService.send(msg);

message對象的建立方式:  

 Message.obtain(null, MessageService.MSG_SAY_HELLO, 0, 0)
           

  第二個參數為 int 類型的變量以作為資訊的區分。

下方高能出現 (案例代碼)

service
 
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import android.widget.Toast;

 
public class MessageService extends Service {
    static final int MSG_SAY_HELLO = 1;
    private static final String TAG = "MessageService";
 
    class Localhandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SAY_HELLO:
                    Log.e(TAG, "handleMessage : accepted!");
                    //資訊回饋
                    Messenger client = msg.replyTo;
                    Message replyMsg = Message.obtain(null, MessageService.MSG_SAY_HELLO,0,0);
                    Bundle bundle = new Bundle();
                    bundle.putString("reply", "ok~,I had receiver message from you! ");
                    replyMsg.setData(bundle);
                    try {
                        client.send(replyMsg);
                        Log.e(TAG, "handleMessage:" );
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
 
    //通過IncomingHandler對象建立一個Messenger對象,該對象是與用戶端互動的特殊對象
    final Messenger mMessenger = new Messenger(new Localhandler());
 
    /**
     * 當綁定Service時,該方法被調用,将通過mMessenger傳回一個實作
     * IBinder接口的執行個體對象
     */
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "onBind:");
        return mMessenger.getBinder();
    }
}
           

用戶端:

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
 
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
 
/**
 * 用戶端
 * Created by jie on 2018/9/28.
 */
 
public class ActivityMessenger extends Activity implements View.OnClickListener {
 
    @BindView(R.id.bindservice)
    Button bindservice;
    @BindView(R.id.unbindservice)
    Button unbindservice;
    @BindView(R.id.sendmsg)
    Button sendmsg;
    /**
     * 與服務端互動的Messenger
     */
    Messenger mService = null;
    /**
     * Flag indicating whether we have called bind on the service.
     */
    boolean mBound;
    private static final String TAG = "MessageService";
    private ServiceConnection myConnection = new ServiceConnection() {
 
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.e(TAG, "onServiceConnected: ");
            mService = new Messenger(iBinder);
            mBound = true;
        }
 
        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            mService = null;
            mBound = false;
        }
    };
    /**
     * 用于接收伺服器傳回的資訊
     */
    private Messenger mRecevierReplyMsg = new Messenger(new ReceiverReplyMsgHandler());
 
    private class ReceiverReplyMsgHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                //接收服務端回複
                case MessageService.MSG_SAY_HELLO:
                    Log.e(TAG, "receiver message from service:" + msg.getData().getString("reply"));
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
 
    public void sayHello(View v) {
        if (!mBound) return;
        // 建立與服務互動的消息實體Message
        Message msg = Message.obtain(null, MessageService.MSG_SAY_HELLO, 0, 0);
        msg.replyTo = mRecevierReplyMsg;
        try {
            //發送消息
            mService.send(msg);
            Log.e(TAG, "sayHello:");
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
 
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);
        ButterKnife.bind(this);
        initClick();
    }
 
    private void initClick() {
//        @OnClick(R.id.bindservice)
        bindservice.setOnClickListener(this);
        unbindservice.setOnClickListener(this);
        sendmsg.setOnClickListener(this);
    }
 
    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.bindservice:
                Intent intent = new Intent(this, MessageService.class);
                bindService(intent, myConnection, Context.BIND_AUTO_CREATE);
                Toast.makeText(this, "我們都好", Toast.LENGTH_SHORT).show();
                break;
            case R.id.unbindservice:
                if (mBound) {
                    Log.e("zj", "onClick-->unbindService");
                    unbindService(myConnection);
                    mBound = false;
                }
                break;
            case R.id.sendmsg:
                sayHello(view);
                break;
            default:
                break;
        }
    }
 
    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}
           

對了 還有一點就是我們的目的是為了驗證不同程序之間的通信,是以我們這裡需要為服務重新設定一個的單獨程序。

      <service
            android:name=".MessageService"
            android:enabled="true"
            android:exported="true"
            android:process=":remote"></service>
           

對了,可能會有一部分小夥伴會想提問,那麼如果我想重新從服務端發送資訊給用戶端那麼我應該如何操作。

其實道理也還是一樣的,我們隻需跟上方一樣進行相同的操作就能得到我們想要的結果。我們可以隻要在我們發送資訊給用戶端把我們的用戶端的 Messenger 對象也發送給 service 就可以了

用戶端:

        Message msg = Message.obtain(null, MessageService.MSG_SAY_HELLO, 0, 0);
        msg.replyTo = mRecevierReplyMsg;
           

服務端:

 //資訊回饋

    Messenger client = msg.replyTo;
                    Message replyMsg = Message.obtain(null, MessageService.MSG_SAY_HELLO,0,0);
                    Bundle bundle = new Bundle();
                    bundle.putString("reply", "ok~,I had receiver message from you! ");
                    replyMsg.setData(bundle);
                    try {
                        client.send(replyMsg);
                        Log.e(TAG, "handleMessage:" );
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
           

 我們通過用戶端進行綁定服務-》發送資訊 通過此我們得到如下的log日志。

主線程日志:

09-28 17:51:06.273 20031-20031/com.example.jie.foreverdemo E/MessageService: onServiceConnected: 
09-28 17:51:12.790 20031-20031/com.example.jie.foreverdemo E/MessageService: sayHello:
09-28 17:51:12.808 20031-20031/com.example.jie.foreverdemo E/MessageService: receiver message from service:ok~,I had receiver message from you! 
remote線程日志:

09-28 17:51:06.264 20492-20492/com.example.jie.foreverdemo:remote E/MessageService: onBind:
09-28 17:51:12.793 20492-20492/com.example.jie.foreverdemo:remote E/MessageService: handleMessage : accepted!
09-28 17:51:12.793 20492-20492/com.example.jie.foreverdemo:remote E/MessageService: handleMessage:
           

Meeeenger 程序通信圖

Android service詳解

綁定的細節注意點

   1.多個用戶端可同時連接配接到一個服務。不過,隻有在第一個用戶端綁定時,系統才會調用服務的 onBind() 方法來檢索 IBinder。系統随後無需再次調用 onBind(),便可将同一 IBinder 傳遞至任何其他綁定的用戶端。當最後一個用戶端取消與服務的綁定時,系統會将服務銷毀。

  2.通常情況下我們應該在用戶端生命周期(如Activity的生命周期)的引入 (bring-up) 和退出 (tear-down) 時刻設定綁定和取消綁定操作,以便控制綁定狀态下的Service,一般有以下兩種情況:

如果隻需要在 Activity 可見時與服務互動,則應在 onStart() 期間綁定,在 onStop() 期間取消綁定。

如果希望 Activity 在背景停止運作狀态下仍可接收響應,則可在 onCreate() 期間綁定,在 onDestroy() 期間取消綁定。需要注意的是,這意味着 Activity 在其整個運作過程中(甚至包括背景運作期間)都需要使用服務,是以如果服務位于其他程序内,那麼當提高該程序的權重時,系統很可能會終止該程序。

3、應用元件(用戶端)可通過調用 bindService() 綁定到服務,Android 系統随後調用服務的 onBind() 方法,該方法傳回用于與服務互動的 IBinder,而該綁定是異步執行的。

六、關于啟動服務與綁定服務之間的問題 

先綁定服務後啟動服務

  如果目前Service執行個體先以綁定狀态運作,然後再以啟動狀态運作,那麼綁定服務将會轉為啟動服務運作,這時如果之前綁定的宿主(Activity)被銷毀了,也不會影響服務的運作,服務還是會一直運作下去,指定收到調用停止服務或者記憶體不足時才會銷毀該服務。

先啟動服務後綁定服務

  如果目前Service執行個體先以啟動狀态運作,然後再以綁定狀态運作,目前啟動服務并不會轉為綁定服務,但是還是會與宿主綁定,隻是即使宿主解除綁定後,服務依然按啟動服務的生命周期在背景運作,直到有Context調用了stopService()或是服務本身調用了stopSelf()方法抑或記憶體不足時才會銷毀服務。

  以上兩種情況顯示出啟動服務的優先級确實比綁定服務高一些。

最後這裡有點需要特殊說明一下的,由于服務在其托管程序的主線程中運作(UI線程),它既不建立自己的線程,也不在單獨的程序中運作(除非另行指定)。 這意味着,如果服務将執行任何耗時事件或阻止性操作(例如 MP3 播放或聯網)時,則應在服務内建立新線程來完成這項工作,簡而言之,耗時操作應該另起線程執行。隻有通過使用單獨的線程,才可以降低發生“應用無響應”(ANR) 錯誤的風險,這樣應用的主線程才能專注于使用者與 Activity 之間的互動, 以達到更好的使用者體驗。

七、前台服務以及通知發送

服務幾乎都是在背景運作的,一直以來他都是默默地做着辛苦的工作。但是服務的系統優先級還是比較低的,當系統出現記憶體不足的情況時,就有可能會回收正在背景運作的服務。如果你希望可以一直保持運作狀态,而不會由于系統記憶體不足的原因導緻被回收,就可以考慮使用前台服務。前台服務和普通服務最大的差別就在于,他會一直有一個正在運作的圖示在系統的狀态欄顯示,下拉狀态欄後可以看到更加詳細的資訊,非常類似于通知的效果。當然有時候你也可能不僅僅是為了防止服務被回收掉才用前台服務,有些項目也有可能有着這樣的特殊要求。

不多說先介紹兩個api先

 startForeground(NOTIFICATION_DOWNLOAD_PROGRESS_ID,notification);
           

第一個參數為int數值,隻要你不要使用兩個相同的int就可以 ,第二個參數為彈框對象

 stopForeground(true);
           

隻有一個boolean參數 ,關閉彈框的出現。其餘我直接上案例代碼

用戶端

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
 
/**
 * Created by jie on 2018/9/28.
 */
 
public class ForegroundActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_foreground);
        Button btnStart= (Button) findViewById(R.id.startForeground);
        Button btnStop= (Button) findViewById(R.id.stopForeground);
        final Intent intent = new Intent(this,ForegroundService.class);
 
 
        btnStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                intent.putExtra("cmd",0);//0,開啟前台服務,1,關閉前台服務
                startService(intent);
            }
        });
 
 
        btnStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                intent.putExtra("cmd",1);//0,開啟前台服務,1,關閉前台服務
                startService(intent);
            }
        });
    }
 
}
           

服務端

import android.app.Notification;
import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
 
/**
 * Created by jie on 2018/9/28.
 */
 
public class ForegroundService extends Service {
    /**
     * id不可設定為0,否則不能設定為前台service
     */
    private static final int NOTIFICATION_DOWNLOAD_PROGRESS_ID = 0x0001;
 
    private boolean isRemove=false;//是否需要移除
 
    /**
     * Notification
     */
    public void createNotification(){
        //使用相容版本
        NotificationCompat.Builder builder=new NotificationCompat.Builder(this);
        //設定狀态欄的通知圖示
        builder.setSmallIcon(R.mipmap.ic_launcher);
        //設定通知欄橫條的圖示
        builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher_round));
        //禁止使用者點選删除按鈕删除
        builder.setAutoCancel(false);
        //禁止滑動删除
        builder.setOngoing(true);
        //右上角的時間顯示
        builder.setShowWhen(true);
        //設定通知欄的标題内容
        builder.setContentTitle("I am Foreground Service!!!");
        //建立通知
        Notification notification = builder.build();
        //設定為前台服務
        startForeground(NOTIFICATION_DOWNLOAD_PROGRESS_ID,notification);
    }
 
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        int i=intent.getExtras().getInt("cmd");
        if(i==0){
            if(!isRemove) {
                createNotification();
            }
            isRemove=true;
        }else {
            //移除前台服務
            if (isRemove) {
                stopForeground(true);
            }
            isRemove=false;
        }
 
        return super.onStartCommand(intent, flags, startId);
    }
 
    @Override
    public void onDestroy() {
        //移除前台服務
        if (isRemove) {
            stopForeground(true);
        }
        isRemove=false;
        super.onDestroy();
    }
 
 
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}
           

前台服務就是這麼的簡單,好啦我們開始進行翻頁了。

八、服務于service與線程的差別

兩者概念的迥異

Thread 是程式執行的最小單元,它是配置設定CPU的基本機關,android系統中UI線程也是線程的一種,當然Thread還可以用于執行一些耗時異步的操作。

Service是Android的一種機制,服務是運作在主線程上的,它是由系統程序托管。它與其他元件之間的通信類似于client和server,是一種輕量級的IPC通信,這種通信的載體是binder,它是在linux層交換資訊的一種IPC,而所謂的Service背景任務隻不過是指沒有UI的元件罷了。

兩者的執行任務迥異

在android系統中,線程一般指的是工作線程(即背景線程),而主線程是一種特殊的工作線程,它負責将事件分派給相應的使用者界面小工具,如繪圖事件及事件響應,是以為了保證應用 UI 的響應能力主線程上不可執行耗時操作。如果執行的操作不能很快完成,則應確定它們在單獨的工作線程執行。

Service 則是android系統中的元件,一般情況下它運作于主線程中,是以在Service中是不可以執行耗時操作的,否則系統會報ANR異常,之是以稱Service為背景服務,大部分原因是它本身沒有UI,使用者無法感覺(當然也可以利用某些手段讓使用者知道),但如果需要讓Service執行耗時任務,可在Service中開啟單獨線程去執行。

兩者使用場景

當要執行耗時的網絡或者資料庫查詢以及其他阻塞UI線程或密集使用CPU的任務時,都應該使用工作線程(Thread),這樣才能保證UI線程不被占用而影響使用者體驗。

在應用程式中,如果需要長時間的在背景運作,而且不需要互動的情況下,使用服務。比如播放音樂,通過Service+Notification方式在背景執行同時在通知欄顯示着。

兩者的真正關系

兩者沒有半毛錢關系。

可參考:https://www.cnblogs.com/carlo/p/4947342.html

              https://blog.csdn.net/jiangwei0910410003/article/details/17008687

              https://blog.csdn.net/wei_chong_chong/article/details/52251193#commentBox

              https://www.cnblogs.com/perfy/p/3820502.html

九、管理服務的生命周期

Android service詳解

        服務的整個生命周期從調用 onCreate() 開始起,到 onDestroy() 傳回時結束。與 Activity 類似,服務也在 onCreate() 中完成初始設定,并在 onDestroy() 中釋放所有剩餘資源。例如,音樂播放服務可以在 onCreate() 中建立用于播放音樂的線程,然後在 onDestroy() 中停止該線程。

銷毀的兩種情況:

啟動服務

該服務在其他元件調用 startService() 時建立,然後無限期運作,且必須通過調用 stopSelf() 來自行停止運作。此外,其他元件也可以通過調用 stopService() 來停止服務。服務停止後,系統會将其銷毀。

綁定服務

該服務在另一個元件(用戶端)調用 bindService() 時建立。然後,用戶端通過 IBinder 接口與服務進行通信。用戶端可以通過調用 unbindService() 關閉連接配接。多個用戶端可以綁定到相同服務,而且當所有綁定全部取消後,系統即會銷毀該服務。 (服務不必自行停止運作)

十、Android5.0以上的隐式啟動問題

顯隐存在的意義 

如果在同一個應用中,兩者都可以用。在不同應用時,隻能用隐式啟動

顯示啟動

直接上代碼一目了然,不解釋了。

//顯示啟動
Intent intent = new Intent(this,ForegroundService.class);
startService(intent);
           

隐式啟動

需要設定一個Action,我們可以把Action的名字設定成Service的全路徑名字,在這種情況下android:exported預設為true。如下

<service  
 
    android:name="com.dbjtech.acbxt.waiqin.UploadService"  
 
    android:enabled="true" >  
 
    <intent-filter android:priority="1000" >  
 
        <action android:name="com.dbjtech.myservice" />  
 
    </intent-filter>  
 
</service> 
           
final Intent serviceIntent=new Intent(); serviceIntent.setAction("com.android.ForegroundService");
serviceIntent.setPackage(getPackageName());//設定應用的包名
startService(serviceIntent);
           

好啦 顯隐式的介紹也就到此結束了 謝謝,下面就是本節的最後一點

十一、如何保證服務不會被殺死

第一點下面我就單純的介紹兩種傳回start_ticky

     當Service因記憶體不足而被系統kill後,一段時間後記憶體再次空閑時,系統将會嘗試重新建立此Service,一旦建立成功後将回調onStartCommand方法,但其中的Intent将是null,除非有挂起的Intent,如pendingintent,這個狀态下比較适用于不執行指令、但無限期運作并等待作業的媒體播放器或類似服務。

**

     * 傳回 START_STICKY或START_REDELIVER_INTENT
     * @param intent
     * @param flags
     * @param startId
     * @return
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
//        return super.onStartCommand(intent, flags, startId);
        return START_STICKY;
    }
           

第二點就是 提高service的優先權

<service  
 
    android:name="com.dbjtech.acbxt.waiqin.UploadService"  
 
    android:enabled="true" >  
 
    <intent-filter android:priority="1000" >  
 
        <action android:name="com.dbjtech.myservice" />  
 
    </intent-filter>  
 
</service> 
           

好啦 本節到此結束

大部分模仿并參考下列文章 。

https://blog.csdn.net/javazejian/article/details/52709857#t9