Android探索之Service全面回顧及總結

總李寫程式碼發表於2016-05-17

什麼是Service?

     Service(服務)是Android提供的四大元件之一,是一個沒有使用者介面的在後臺執行執行耗時操作的應用元件。其他應用元件能夠啟動Service,並且當使用者切換到另外的應用場景,Service將持續在後臺執行。為了方便記憶,我們可以把Service看做是沒有頁面的Activity,它總是默默的後臺處理一些耗時的操作或者不干擾使用者使用的後臺操作,例如,一個service可能會處理網路操作,播放音樂,操作檔案I/O或者與內容提供者(content provider)互動,所有這些活動都是在後臺進行。

Service都有哪些?

1)按照使用範圍分類:

     1.本地服務(Local Service):用於應用程式內部

           Local Service執行於當前app的主程式中,如果當前app的程式被Kill掉了,Local Service也會隨著隨之終止。使用場景舉例:音樂播放器服務

     2.遠端服務(Remote Service):用於android系統內部的應用程式之間

          Remote Service是執行於一個獨立的程式中,可以被多個app複用,可以使用android:process宣告程式名字,由於執行於獨立的程式,Activity所在程式被Kill的時候不會影響Remote Service。

2)按照執行類別分類:

    1.前臺服務

        前臺服務是那些被認為使用者知道的並且在記憶體低的時候不允許系統殺死的服務,通過startForeground 使服務成為 前臺服務。

    2.後臺服務

       區別於前臺服務,建立的服務預設是後臺服務,在記憶體低的時候有可能被系統殺死。

3)按照使用方式分類:

    1.context.startService()

    2.context.bindService()

Service的如何使用?

    1.Service AndroidManifest.xml 宣告

<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:name 服務類名

android:label 服務的名字,如果此項不設定,那麼預設顯示的服務名則為類名

android:icon  服務的圖示

android:permission 申明此服務的許可權,這意味著只有提供了該許可權的應用才能控制或連線此服務

android:process  表示該服務是否執行在另外一個程式,如果設定了此項,那麼將會在包名後面加上這段字串表示另一程式的名字

android:enabled  如果此項設定為 true,那麼 Service 將會預設被系統啟動,不設定預設此項為 false

android:exported  表示該服務是否能夠被其他應用程式所控制或連線,不設定預設此項為 false

 具體寫個測試Service

public class TestService extends Service {

    private TestBinder binder=new TestBinder();
    private Thread testThread;
    private boolean isStart=false;

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e("TestService", "執行 onCreate()");
        startForeground();
    }

    private void startForeground(){
        Notification.Builder builder = new Notification.Builder(this);
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
                new Intent(this, MainActivity.class), 0);
        builder.setContentIntent(contentIntent);//設定目標跳轉
        builder.setSmallIcon(R.mipmap.ic_launcher);//設定顯示的圖片
        builder.setTicker("前臺服務開啟");// 狀態列上顯示
        builder.setContentTitle("前臺服務");//設定標題
        builder.setContentText("這是一個前臺服務");
        Notification notification = builder.build();
        startForeground(1, notification);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e("TestService", "執行 onStartCommand()");
        //startThread();
        //testANR();
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e("TestService", "執行 onDestroy()");
        stopThread();
        stopForeground(true);
    }


    @Override
    public IBinder onBind(Intent intent) {
        Log.e("TestService", "執行 onBind()");
        return binder;
    }

    class TestBinder extends Binder {

        public void testMethod() {
            Log.e("TestService", "執行 testMethod()");
        }

    }

    //測試是否ANR
    private  void testANR(){
        try {
            Thread.sleep(50000);
        } catch (Exception e) {
        }
    }

    private void startThread(){
        stopThread();
        isStart = true;
        if (testThread == null) {
            testThread = new Thread(runnable);
            testThread.start();
        }
    }

    private void stopThread(){
        try {
            isStart = false;
            if (null != testThread && Thread.State.RUNNABLE == testThread.getState()) {
                try {
                    Thread.sleep(500);
                    testThread.interrupt();
                } catch (Exception e) {
                    testThread = null;
                }
            }
            testThread = null;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            testThread = null;
        }
    }

    private Runnable runnable=new Runnable() {
        @Override
        public void run() {
            while(isStart) {
                Log.e("TestService", "runnable");
            }

        }
    };
}
View Code

 啟動/關閉,繫結/解綁 

         //開啟service
                Log.e("TestService", "執行 startService()");
                Intent intent =new Intent(MainActivity.this,TestService.class);
                startService(intent);

               //停止service
                Log.e("TestService", "執行 stopService()");
                Intent intent =new Intent(MainActivity.this,TestService.class);
                stopService(intent);    

                //繫結service
                Log.e("TestService", "執行 bindService()");
                Intent intent =new Intent(MainActivity.this,TestService.class);
                bindService(intent,connection,BIND_AUTO_CREATE);                 
 
                //解綁service
                Log.e("TestService", "執行 unbindService()");
                unbindService(connection);

   //測試ServiceConnection
    private ServiceConnection connection = new ServiceConnection() {

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            TestService.TestBinder testBinder = (TestService.TestBinder) service;
            testBinder.testMethod();
        }
    };
View Code

接下來了解下Service的生命週期:

1)startService()/stopService()方式  onCreate()---->onStartCommand()---->running()---->onDestory()

如果多次startService()會不會多次onCreate呢?Service只會呼叫一次onCreate(),但會多次呼叫onStartCommand()

如果多次呼叫stopService()呢?呼叫stopService()一次終止服務

2)bindService()/unbindService()方式  onCreate()---->onBind()---->running()---->onDestory()

3)同時呼叫了startService()/bindService()

 首先看下我們呼叫stopService() 看下執行結果如下:未執行onDestory()服務無法關閉;所有兩者同時使用時先呼叫unbindService()然後再呼叫stopService()才能真正關閉服務

接下來我們重點看下onStartCommand(Intent intent, int flags, int startId)方法中的flags引數。

START_NOT_STICKY:當Service因為記憶體不足而被系統kill後,接下來未來的某個時間內,即使系統記憶體足夠可用,系統也不會嘗試重新建立此Service。除非程式中Client明確再次呼叫startService(...)啟動此Service。

START_STICKY:當Service因為記憶體不足而被系統kill後,接下來未來的某個時間內,當系統記憶體足夠可用的情況下,系統將會嘗試重新建立此Service,一旦建立成功後將回撥onStartCommand(...)方法,但其中的Intent將是null,pendingintent除外。

START_REDELIVER_INTENT:與START_STICKY唯一不同的是,回撥onStartCommand(...)方法時,其中的Intent將是非空,將是最後一次呼叫startService(...)中的intent。

START_STICKY_COMPATIBILITY:預設flags值

通常我們為了防止Service在記憶體不足的時候被系統殺死,把flags設定成START_STICKY 但是在4.0之後部分手機也是無效的,被殺死之後不會重啟了。

 

我們該如何提供app服務不被殺死或者降低被殺死的概率呢?我們可以把設定服務為前臺服務,比如音樂播放器,app退出之後就變成前臺服務。直接看具體實現:

   //設定前臺服務
    private void startForeground(){
        Notification.Builder builder = new Notification.Builder(this);
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
                new Intent(this, MainActivity.class), 0);
        builder.setContentIntent(contentIntent);//設定目標跳轉
        builder.setSmallIcon(R.mipmap.ic_launcher);//設定顯示的圖片
        builder.setTicker("前臺服務開啟");// 狀態列上顯示
        builder.setContentTitle("前臺服務");//設定標題
        builder.setContentText("這是一個前臺服務");
        Notification notification = builder.build();
        startForeground(1, notification);
    }

   //關閉前臺服務,引數true 代表remove狀態列通知
   stopForeground(true);

具體效果:

 

 每當我們討論Service的時候總是聽到別人說:Service用來處理一個耗時的操作!!!接下來我們來驗證一下Service裡該如何處理耗時操作?

為了方便寫了一個模擬耗時的函式:

    //測試是否ANR
    private  void testANR(){
        try {
            Thread.sleep(50000);
        } catch (Exception e) {
        }
    }

測試執行:

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e("TestService", "執行 onStartCommand()");
        //startThread();
        testANR();
        return super.onStartCommand(intent, flags, startId);
    }

執行結果:

其實結果是在我們的意料之中的,經過上面的知識我們已經得知本地服務是依託附主程式的上的,處理耗時操作肯定會造成ANR的,如果改成遠端服務就不會造成ANR了。所以我們在Service處理耗時操作也是要像Activity一樣採用非同步處理的。需要主要的時如果我們在Service中起了一個執行緒,我們必須在onDestory ()函式中銷燬執行緒。因為我們stopService()的時候執行緒並不會隨之銷燬。因此可知:Service和Thread是兩碼事,沒有一毛錢的關係。

 

知識擴充:

1)檢查某個Service是否在執行中:

    private boolean isServiceRunning(String className) {
            boolean isRunning = false;
            ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
            List<ActivityManager.RunningServiceInfo> serviceList = activityManager.getRunningServices(30);

            if (!(serviceList.isEmpty())) {
                return false;
            }

            for (int i=0; i<serviceList.size(); i++) {
                if (serviceList.get(i).service.getClassName().equals(className) == true) {
                    isRunning = true;
                    break;
                }
            }
            return isRunning;
        }

2)本文中開啟Service採用的顯式Intent,同樣採用隱式Intent開啟Service,如果app被破譯則有安全隱患。

<permission android:name="com.whoislcj.testservice.permission"/>
<uses-permission android:name="com.whoislcj.testservice.permission"/>

如果一個實體執行或繫結一個服務,必須要擁有該服務的許可權。如果沒有許可權, startService() , bindService() 或 stopService() 方法將不執行, Intent 也不會傳遞到服務。

3)Service與UI之間的通訊。

      UI----->Service :Intent傳值

      UI----->Service  :繫結Service傳值

      UI<---->Service: 通過廣播

 4)Service與AIDL實現跨程式通訊  總結文章地址:http://www.cnblogs.com/whoislcj/p/5509868.html

    先認識幾個名詞:

  •     程式間通訊(IPC):Inter Process Communication
  •     AIDL:Android Interface Definition Language, Android介面定義語言

 

相關文章