關於Service你須要知道這些

什麼是service

Service是一個能夠在後臺執行長時間運行操做而不提供用戶界面的應用組件。服務可由其餘應用組件啓動,並且即便用戶切換到其餘應用,服務仍將在後臺繼續運行。 此外,組件能夠綁定到服務,以與之進行交互,甚至是執行進程間通訊 (IPC)。 例如,服務能夠處理網絡事務、播放音樂,執行文件 I/O 或與內容提供程序交互,而全部這一切都可在後臺進行。java

通常的咱們用兩種方法與service進行聯繫:startService()和bindService()。android

方法 啓動方式 中止方式 與啓動它的組件之間的通訊方式 生命週期
startService 在其餘組件中調用startService()方法後,服務即處於啓動狀態 service中調用stopSelf()方法,或者其餘組件調用stopService()方法後,service將中止運行 沒有提供默認的通訊方式,啓動service後該service就處於獨立運行狀態 一旦啓動,service便可在後臺無限期運行,即便啓動service的組件已被銷燬也不受其影響,直到其被中止
bindService() 在其餘組件中調用bindService()方法後,服務即處於啓動狀態 全部與service綁定的組件都被銷燬,或者它們都調用了unbindService()方法後,service將中止運行 能夠經過 ServiceConnection進行通訊,組件能夠與service進行交互、發送請求、獲取結果,甚至是利用IPC跨進程執行這些操做 當全部與其綁定的組件都取消綁定(多是組件被銷燬也有多是其調用了unbindService()方法)後,service將中止

生命週期 api

image.png

  • 啓動的服務

startService()->onCreate()->onStartCommand()->running->stopService()/stopSelf()->onDestroy()->stopped 其中,服務未運行時會調用一次onCreate(),運行時不調用。bash

  • 綁定的服務

bindService()->onCreate()->onBind()->running->onUnbind()->onDestroy()->stopped網絡

如何建立service

  1. 建立一個類繼承自Service(或它的子類,如IntentService),重寫裏面的一些關鍵的回調方法,如onStartCommand(),onBind()等
  2. 在Manifest文件裏面爲其聲明,並根據須要配置一些其餘屬性。

在Manifest裏面聲明注意:多線程

<service 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>
複製代碼
  • android:enabled : 若是爲true,則這個service能夠被系統實例化,若是爲false,則不行。默認爲true
  • android:exported : 若是爲true,則其餘應用的組件也能夠調用這個service而且能夠與它進行互動,若是爲false,則只有與service同一個應用或者相同user ID的應用能夠開啓或綁定此service。它的默認值取決於service是否有intent filters。若是一個filter都沒有,就意味着只有指定了service的準確的類名才能調用,也就是說這個service只能應用內部使用,其餘的應用不知道它的類名。這種狀況下exported的默認值就爲false。反之,只要有了一個filter,就意味着service是考慮到外界使用的狀況的,這時exported的默認值就爲true
  • android:icon : 一個象徵着這個service的icon
  • android:isolatedProcess : 若是設置爲true,這個service將運行在一個從系統中其餘部分分離出來的特殊進程中,咱們只能經過Service API來與它進行交流。默認爲false。
  • android:label : 顯示給用戶的這個service的名字。若是不設置,將會默認使用的label屬性。
  • android:name : 這個service的路徑名,例如「com.lypeer.demo.MyService」。這個屬性是惟一一個必須填的屬性。
  • android:permission : 其餘組件必須具備所填的權限才能啓動這個service。
  • android:process : service運行的進程的name。默認啓動的service是運行在主進程中的。(注意:如android:process=":ramote" 必定要帶冒號否則會跑不起來)

startService

當一個service經過這種方式啓動以後,它的生命週期就已經不受啓動它的組件影響了,只要service自身沒有調用stopSelf()而且其餘的組件沒有調用針對它的stopService(),它能夠在後臺無限期的運行下去。另外,若是肯定了使用這種方式啓動service而且不但願這個service被綁定的話,除了傳統的建立一個類繼承service以外咱們有一個更好的選擇——繼承IntentService。併發

若是是擴建Service類的話,一般狀況下咱們須要新建一個用於執行工做的新線程,由於默認狀況下service將工做於應用的主線程,而這將會下降全部正在運行的Activity的性能。而IntentService就不一樣了。它是Service的子類,它使用工做線程來注意的處理全部的startService請求。若是不要求這個service要同時處理多個請求,那麼繼承這個類顯然要比直接繼承Service好不少。app

IntentService作了如下這些事:所以咱們只須要實現onHandleIntent()方法來完成具體的功能邏輯就能夠了。dom

  1. 建立默認的工做線程,用於在應用的主線程外執行傳遞給 onStartCommand() 的全部 Intent
  2. 建立工做隊列,用於將一個 Intent 逐一傳遞給 onHandleIntent() 實現,這樣的話就永遠沒必要擔憂多線程問題了
  3. 在處理完全部啓動請求後中止服務,今後媽媽不再用擔憂我忘記調用 stopSelf() 了
  4. 提供 onBind() 的默認實現(返回 null)
  5. 提供 onStartCommand() 的默認實現,可將 Intent 依次發送到工做隊列和 onHandleIntent() 實現
public class HelloIntentService extends IntentService {  
        /**  
          * A constructor is required, and must call the super IntentService(String) 
          * constructor with a name for the worker thread.    */  

    public HelloIntentService() {
        //構造方法      
        super("HelloIntentService");
    }
    /**    
     * The IntentService calls this method from the default worker thread with    
     * the intent that started the service. When this method returns, IntentService    
     * stops the service, as appropriate.    
     */ 

    @Override
    protected void onHandleIntent(Intent intent) {
        // Normally we would do some work here, like download a file.
        // For our sample, we just sleep for 5 seconds. 
        // 這裏根據Intent進行操做       
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            // Restore interrupt status. 
            Thread.currentThread().interrupt();
        }
    }
}
複製代碼

注意:若是須要重寫其餘的方法,好比onDestroy()方法,必定不要刪掉它的超類實現!由於它的超類實現裏面也許包括了對工做線程還有工做隊列的初始化以及銷燬等操做ide

下面是一個官網的例子,提供了service 類實現的代碼示例,該類執行的工做與上述使用的IntentService示例徹底相同。也就是說,對於每一個啓動請求,它均使用工做線程執行做業,且每次僅處理一個請求。

public class HelloService extends Service {
    private Looper mServiceLooper;
    private ServiceHandler mServiceHandler;

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

        @Override
        public void handleMessage(Message msg) {
            long endTime = System.currentTimeMillis() + 5 * 1000;
            while (System.currentTimeMillis() < endTime) {
                synchronized (this) {
                    try {
                        wait(endTime - System.currentTimeMillis());
                    } catch (Exception e) {
                    }
                }
            }
            stopSelf(msg.arg1);
        }
    }

    @Override
    public void onCreate() {
        HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        mServiceHandler.sendMessage(msg);
        return START_STICKY;
    }

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

    @Override
    public void onDestroy() {
        Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
    }
}
複製代碼

若是讓service同時處理多個請求的需求,這個時候就只能去繼承Service了。這個時候就要本身去處理工做線程那些事。上面示例並未這樣作,但若是但願如此,則可爲每一個請求建立一個新線程,而後當即運行這些線程(而不是等待上一個請求完成)。

注意:onStartCommand()的返回值是用來指定系統對當前線程的行爲的。它的返回值必須是如下常量之一:

  • START_NOT_STICKY:使用這個返回值時,若是在執行完onStartCommand後,服務被異常kill掉,系統不會自動重啓該服務。
  • START_STICKY : 若是service進程被kill掉,保留service的狀態爲開始狀態,但不保留遞送的intent對象。隨後系統會嘗試從新建立service,因爲服務狀態爲開始狀態,因此建立服務後必定會調用onStartCommand(Intent,int,int)方法。若是在此期間沒有任何啓動命令被傳遞到service,那麼參數Intent將爲null。
  • START_REDELIVER_INTENT:使用這個返回值時,若是在執行完onStartCommand後,服務被異常kill掉,系統會自動重啓該服務,並將以前的Intent的值傳入。
  • START_STICKY_COMPATIBILITY:這個實際上是用來兼容api5一下的,這個的做用和START_STICK同樣,可是這個返回值不能保證系統必定會從新建立service。

bindService

如需與 Activity 和其餘應用組件中的服務進行交互,或者須要經過進程間通訊 (IPC) 向其餘應用公開某些應用功能,則應建立綁定服務。要建立綁定服務,必須實現 onBind() 回調方法以返回IBinder,用於定義與服務通訊的接口。而後,其餘應用組件能夠調用bindService()來檢索該接口,並開始對服務調用方法。服務只用於與其綁定的應用組件,所以若是沒有組件綁定到服務,則系統會銷燬服務(您沒必要按經過onStartCommand()啓動的服務那樣來中止綁定服務)。

通常來說,咱們有三種方式能夠得到IBinder的對象:繼承Binder類,使用Messenger類,使用AIDL。

繼承Binder類

若是你的服務僅供本地應用使用,不須要跨進程工做,則能夠實現自有Binder類,讓你的客戶端經過該類直接訪問服務中的公共方法。

注意:此方法只有在客戶端和服務位於同一應用和進程內這一最多見的狀況下方纔有效。

  • 在service類中,建立一個知足如下任一要求的Binder實例:
  • 包含客戶端可調用的公共方法
  • 返回當前Service實例,其中包含客戶端可調用的公共方法
  • 返回由當前service承載的其餘類的實例,其中包含客戶端可調用的公共方法
  • 在onBind()方法中返回這個Binder實例
  • 在客戶端中經過onServiceDisconnected()方法接收傳過去的Binder實例,並經過它提供的方法進行後續操做

引用官網例子:

public class LocalService extends Service {
    // Binder given to clients
    private final IBinder mBinder = new LocalBinder(); 
    // Random number generator
    private final Random mGenerator = new Random(); 
            /** 
             * Class used for the client Binder. Because we know this service always 
             * runs in the same process as its clients, we don't need to deal with IPC. */ public class LocalBinder extends Binder { LocalService getService() { // Return this instance of LocalService so clients can call public methods return LocalService.this; } } @Override public IBinder onBind(Intent intent) { return mBinder;} /** method for clients */ public int getRandomNumber() {return mGenerator.nextInt(100)} } 複製代碼

LocalBinder 爲客戶端提供 getService() 方法,以檢索 LocalService 的當前實例。這樣,客戶端即可調用服務中的公共方法。 例如,客戶端可調用服務中的 getRandomNumber():

public class BindingActivity extends Activity {
    LocalService mService;
    boolean mBound = false;

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

    @Override
    protected void onStart() {
        super.onStart();
        // Bind to LocalService        
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // Unbind from the service        
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }

    /**
     * Called when a button is clicked (the button in the layout file attaches to      
     * this method with the android:onClick attribute)
     */
    public void onButtonClick(View v) {
        if (mBound) {
            // Call a method from the LocalService.
            // However, if this call were something that might hang, then this request should
            // occur in a separate thread to avoid slowing down the activity performance.
            int num = mService.getRandomNumber();
            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * Defines callbacks for service binding, passed to bindService()
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
            // We've bound to LocalService, cast the IBinder and get LocalService instance LocalBinder binder = (LocalBinder) service; mService = binder.getService(); mBound = true; } @Override public void onServiceDisconnected(ComponentName arg0) { mBound = false; } }; } 複製代碼

使用Messenger類

如需讓服務與遠程進程通訊,則可以使用 Messenger 爲你的服務提供接口。利用此方法,你無需使用 AIDL 即可執行進程間通訊 (IPC)。

方法步驟以下:

  • 服務端實現一個Handler,由其接受來自客戶端的每一個調用的回調
  • 使用實現的Handler建立Messenger對象
  • 經過Messenger獲得一個IBinder對象,並將其經過onBind()返回給客戶端
  • 客戶端使用 IBinder 將 Messenger(引用服務的 Handler)實例化,而後使用後者將 Message 對象發送給服務
  • 服務端在其 Handler 中(具體地講,是在 handleMessage() 方法中)接收每一個 Message

用這種方式,客戶端並無像擴展Binder類那樣直接調用服務端的方法,而是採用了用Message來傳遞信息的方式達到交互的目的。

下面之因此建兩個工程是爲了經過Messenger實現IPC,固然你也能夠寫在一個工程

新建一個工程,做爲服務端。

public class MessagerService extends Service {
    public static final int MSG = 0x0001;
    private Messenger mMessenger = new Messenger(new Ihandler());

    public class Ihandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == MSG) {
                Toast.makeText(getApplicationContext(), "服務開啓了", Toast.LENGTH_SHORT).show();
            }
            super.handleMessage(msg);
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
}
複製代碼

服務端就一個Service,能夠看到代碼至關的簡單,只須要去聲明一個Messenger對象,而後在onBind方法返回mMessenger.getBinder();

<service
            android:name=".service.MessagerService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.hw.playandroid.messenger"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </service>
複製代碼

說明:上述的 是爲了能讓其餘apk隱式bindService,經過隱式調用的方式來調起activity或者service,須要把category設爲default,這是由於,隱式調用的時候,intent中的category默認會被設置爲default。

主要是由於在這裏要跨進程通訊,因此在另一個進程裏面並無咱們的service的實例,此時必需要給其餘的進程一個標誌,這樣才能讓其餘的進程找到咱們的service。其實這裏的android:exported屬性不設置也能夠的,由於在有intent-filter的狀況下這個屬性默認就是true,

再新建一個工程,做爲客戶端

public class MessagerActivity extends AppCompatActivity {
    Messenger mService;
    boolean isBound;
    ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = new Messenger(service);
            isBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
            isBound = false;
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messager);
        ButterKnife.bind(this);
    }

    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent();
        intent.setAction("com.hw.playandroid.messenger");
        intent.setPackage("com.hw.playandroid");
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (isBound) {
            unbindService(mConnection);
            isBound = false;
        }
    }

    @OnClick(R.id.tv)
    public void onViewClicked() {
        if (isBound) {
            Message message = Message.obtain(null, MessagerService.MSG, 0, 0);
            try {
                mService.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
}
複製代碼

(注意:intent.setPackage("com.hw.playandroid");這句必定要加上否則會奔潰)

首先bindService,而後在onServiceConnected中拿到回調的service(IBinder)對象,經過service對象去構造一個mService =new Messenger(service);而後就可使用mService.send(msg)發給服務端了。上面的例子只有客戶端對服務端單方面的通訊,而要實現雙向通訊其實也很簡單,只要客戶端裏也建立一個Handler實例,讓它接收來自服務端的信息,同時讓服務端在客戶端給它發的請求完成了以後再給客戶端發送一條信息便可。Message在其中起到了一個信使的做用,經過它客戶端與服務端的信息得以互通。

Messenger爲何能進行IPC呢? 其實Messenger的內部實現的,實際上也是依賴於aidl文件實現的。 服務端的onBind()中返回了mMessenger.getBinder()

public IBinder getBinder() { return mTarget.asBinder(); }
複製代碼

能夠看到返回的是mTarget.asBinder();而mTarget在兩處被賦值,這兩處分別是Messenger的兩個構造方法,而上面使用的構造mMessenger對象的代碼:new Messenger(new Ihandler());能夠肯定其調用的構造方法

public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }
複製代碼

由此能夠知道是Handler返回的,咱們繼續跟進Handler,全局搜索getIMessenger()

final IMessenger getIMessenger() {
    synchronized (mQueue) {
        if (mMessenger != null) {
            return mMessenger;
        }
        mMessenger = new MessengerImpl(); return mMessenger;
    }
}
複製代碼

看到這裏應該就知道了,mTarget是一個MessengerImpl對象,那麼asBinder其實是返回this,也就是MessengerImpl對象; 這是個內部類,繼承自IMessenger.Stub,而後實現了一個send方法,該方法就是將接收到的消息經過 Handler.this.sendMessage(msg);發送到handleMessage。 實際上,Messenger就是依賴IMessenger該aidl文件生成的類,繼承了IMessenger.Stub類,實現了send方法,send方法中參數會經過客戶端傳遞過來,最終發送給handler進行處理。

使用AIDL(Android 接口定義語言)

使用場景:引用一段官網原話 只有容許不一樣應用的客戶端用 IPC 方式訪問服務,而且想要在服務中處理多線程時,纔有必要使用 AIDL。 若是您不須要執行跨越不一樣應用的併發 IPC,就應該經過實現一個Binder 建立接口;或者,若是您想執行 IPC,但根本不須要處理多線程,則使用Messenger類來實現接口。不管如何,在實現 AIDL 以前,請您務必理解綁定服務。

AIDL 支持下列數據類型:

  • Java 的基本數據類型(如 int、long、char、boolean 等等)
  • String 類型。
  • CharSequence類型。
  • List、Map(元素必須是 AIDL 支持的數據類型,Server 端具體的類裏則必須是 ArrayList 或者 HashMap)
  • 其餘類型必須使用import導入,即便它們可能在同一個包裏。

如何使用

  1. 新建一個工程做爲服務端,而後再在這個工程的main文件夾下aidl包用來存放.aidl文件,通常來講, aidl 包裏默認有着和 java 包裏的包結構。

    image2.png

  2. 建立一個實體類並使其實現Parcelable接口,實現序列化和反序列化。

public class Person implements Parcelable {
    String name;

    public String getName() { return name; }

    public void setName(String name) { this.name = name; }

    public Person() {}

    protected Person(Parcel in) { name = in.readString(); }
    public static final Creator<Person> CREATOR = new Creator<Person>() {
        @Override
        public Person createFromParcel(Parcel in) { return new Person( in ); }

        @Override
        public Person[] newArray(int size) { return new Person[size]; }
    };

    @Override
    public int describeContents() { return 0; }

    @Override
    public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); }
}
複製代碼
  1. 在裏面新建IXxxAidl.aidl文件和實體類的映射.aidl文件

(注意在建立IXxxAidl.aidl時,除了基本類型(int,long,char,boolean等),String,CharSequence,List,Map,其餘類型必須使用import導入,即便它們可能在同一個包裏,好比下面的Person,就是import進來的。另外,接口中方法的參數除了aidl支持的類型,其餘類型必須標識其方向:究竟是輸入仍是輸出或者是輸入輸出,用in,out或者inout來表示)

package com.example.com.test;
import com.example.com.test.bean.Person;
interface IMyAidl {
    Person getPerson();
}

複製代碼

通常的,都把實體類和映射文件放到一個包下,這樣作的緣由是方便移植。實體類和映射文件也能夠再也不在同一個包裏面,也能夠把實體類放到java包下,可是注意這時候,實體類Person的這個 Person.aidl映射文件的包名要和實體類包名一致,可是移植的時候就不那麼方便了,須要把這個實體類Person單獨進行移植。

package com.example.com.test.bean; 
parcelable Person;
複製代碼
  1. Make Project構建IXxxAidl.java文件。
    image3.png

注意在這一步可能會有一個坑等着你來踩,由於Android Studio 是使用 Gradle 來構建 Android 項目的,而 Gradle 在構建項目的時候會經過 sourceSets 來配置不一樣文件的訪問路徑,從而加快查找速度。Gradle 默認是將 java 代碼的訪問路徑設置在 java 包下的,這樣一來,若是 java 文件是放在 aidl 包下的話那麼系統是找不到這個 java 文件的。有兩種方法能夠解決

  • 修改 build.gradle 文件:在 android{} 中間加上
sourceSets { main { java.srcDirs = ['src/main/java', 'src/main/aidl'] } }
複製代碼
  • 把 java 文件放到 java 包下去,上面已經介紹了
  1. 建立service,編寫服務端代碼,在其中建立上面生成的Binder對象實例,並在onBind方法裏返回
public class AidlService extends Service {
    private IBinder mIBinder = new IMyAidl.Stub() {
        @Override
        public Person getPerson() throws RemoteException {
            Random random = new Random();
            Person mPerson = new Person();
            mPerson.setName("你們好,個人名字叫" + random.nextInt(10));
            return mPerson;
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mIBinder;
    }
}
複製代碼

記住在Manifest裏面註冊

<service android:name=".service.AidlService"
         android:enabled="true" 
         android:exported="true">
      <intent-filter>
             <action android:name="com.example.com.test.service.AidlService"/> 
             <category android:name="android.intent.category.DEFAULT"/>                                                       
      </intent-filter>
 </service>

複製代碼
  1. 新建一個工程做爲客戶端,而後將服務端的aidl整個包複製到客戶端的main文件夾下,而後Rebuild一下

    image4.png

  2. 編寫客戶端代碼,這裏須要注意兩點。

  • 當客戶端在onServiceConnected回調中收到IBinder時,它必須調用IXxxAidl.Stub.asInterface(service) 以將返回的參數轉換成 IXxxAidl 類型
  • 綁定服務時,必需要調用 intent.setPackage(xxx.xxx.xxx);其中包名爲服務端的包名
public class AidlActivity extends Activity {

    private IMyAidl mAidl;
    private boolean isBound;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mAidl = IMyAidl.Stub.asInterface(service);
            isBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mAidl = null;
            isBound = false;
        }
    };


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aidl);
        ButterKnife.bind(this);
    }

    @OnClick(R.id.tv)
    public void onViewClicked() {
        if (isBound){
            try {
                String name = mAidl.getPerson().getName();
                Toast.makeText(this,name,Toast.LENGTH_SHORT).show();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent();
        intent.setAction("com.example.com.test.service.AidlService");
        intent.setPackage(IMyAidl.class.getPackage().getName());
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (isBound) {
            unbindService(mConnection);
            isBound = false;
        }
    }
}
複製代碼
  1. 運行結果顯示
    AIDL顯示效果圖.gif

前臺服務

上面說到的service都是運行在後臺的,而這些運行在後臺的service的系統優先級相對較低,有可能會被殺死,好比系統內存不足就會回收掉正在後臺運行的service,想要service一直處在運行狀態的話就能夠將service設置爲前臺服務。

什麼是前臺服務

前臺服務被認爲是用戶主動意識到的一種服務,所以在內存不足時,系統也不會考慮將其終止。 前臺服務必須爲狀態欄提供通知,放在「正在進行」標題下方,這意味着除非服務中止或從前臺移除,不然不能清除通知。

如何建立前臺服務

很簡單和上面同樣先建立服務,再定義一個方法,在方法裏構建Notification,而後經過startForeground(110, notification);方法將服務設置到前臺這個方法裏的第一個參數不能夠爲0,中止的話用stopForeground(true);固然你能夠經過startService()直接開啓,或經過bindService()獲取service來直接調方法。開啓服務後咱們會收到一條通知,這條通知就是前臺服務提供的。注意在android8.0後 須要給otification設置一個channelId

注意開啓容許通知哦,否則收不到消息的

public class ForeGroundService extends Service {

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //在android8.0後 須要給notification設置一個channelId,否則會報錯:
        // Bad notification for startForeground: java.lang.RuntimeException: invalid channel for service notification
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            createNotificationChannel();
        } else {
            showNotification();
        }
        return START_STICKY;
    }

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

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

    public void showNotification() {
        Intent clickIntent = new Intent(this, NotificationClickReceiver.class); //點擊通知以後要發送的廣播
        int id = (int) (System.currentTimeMillis() / 1000);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, id, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        Notification.Builder builder = new Notification.Builder(this)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle("今天是公元10000年1月1日")
                .setContentText("天氣晴,溫度零下170度")
                .setWhen(System.currentTimeMillis())
                .setContentIntent(pendingIntent);
        Notification notification = builder.build();
        startForeground(1, notification);
    }

    @RequiresApi(Build.VERSION_CODES.O)
    private void createNotificationChannel() {
        String NOTIFICATION_CHANNEL_ID = "foreground_service";
        String channelName = "ForeGroundService";
        NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_NONE);
        chan.setLightColor(Color.BLUE);
        chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        if (manager != null) {
            manager.createNotificationChannel(chan);
        } else {
            stopSelf();
        }

        Intent clickIntent = new Intent(this, NotificationClickReceiver.class); //點擊通知以後要發送的廣播
        int id = (int) (System.currentTimeMillis() / 1000);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, id, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle("今天是公元10000年1月1日")
                .setContentText("天氣晴,溫度零下170度")
                .setWhen(System.currentTimeMillis())
                .setContentIntent(pendingIntent);
        Notification notification = builder.build();
        startForeground(2, notification);
    }
}
複製代碼

經過這幾句代碼能夠實現點擊通知以後要發送的廣播。

Intent clickIntent = new Intent(this, NotificationClickReceiver.class); //點擊通知以後要發送的廣播 
int id = (int) (System.currentTimeMillis() / 1000);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this.getApplicationContext(), id, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT);

複製代碼

經過廣播進行一些操做,如跳轉頁面,關閉服務等等。

public class NotificationClickReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context,"我是點擊通知過來的,我要關閉服務了",Toast.LENGTH_SHORT).show();
        context.stopService(new Intent(context, ForeGroundService.class));
        context.startActivity(new Intent(context, NotificationActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
    }
}
複製代碼

運行結果以下

前臺服務.gif
相關文章
相關標籤/搜索