1. 程式人生 > >android Service 學習總結

android Service 學習總結

ops 我們 gif png pan case 擔心 service 學習android

  學習android開發已經四五個月,由於項目中職責的原因一直沒有接觸過Service的實際項目,今天重新學一遍Service用法。

  問題:

    作為四大組件,為什麽需要Service?

    它與Thread又有何區別?

    具體怎麽用?

    如何實現與Activity之間的通信?

一、Service 介紹

從官網中,我們可以看到這麽一句:

Most confusion about the Service class actually revolves around what it is not:

  • A Service is not a separate process. The Service object itself does not imply it is running in its own process; unless otherwise specified, it runs in the same process as the application it is part of.
  • A Service is not a thread. It is not a means itself to do work off of the main thread (to avoid Application Not Responding errors).

Thus a Service itself is actually very simple, providing two main features:

  • A facility for the application to tell the system about something it wants to be doing in the background (even when the user is not directly interacting with the application). This corresponds to calls to Context.startService(), which ask the system to schedule work for the service, to be run until the service or someone else explicitly stop it.
  • A facility for an application to expose some of its functionality to other applications. This corresponds to calls to Context.bindService(), which allows a long-standing connection to be made to the service in order to interact with it.

  簡單來說,Service不是一個獨立的進程,除非它被特殊指定,否則它也是我們應用程序的一部分,它需要依賴於創建服務時所在的應用程序。同時Service也不是一個線程,它其實是在主線程工作的,所以不能在Service中處理耗時的操作,不然就會出現ARN現象

,如果要處理耗時的操作,可以新開一個子線程,單獨處理。

  更進一步講,Service是 Android中實現程序後臺運行的解決方案,它非常適合用於去執行那 些不需要和用戶交互而且還要求長期運行的任務。服務的運行不依賴於任何用戶界面,即使 當程序被切換到後臺,或者用戶打開了另外一個應用程序,服務仍然能夠保持正常運行。

Service有兩種啟動方式,一種是通過startService()方式,一種是通過bindService()方式,那這兩種具體有什麽區別呢,我們可以看下它們的生命周期來看出端倪。

1.1 Service 生命周期 及兩種狀態

技術分享

  Service 分為兩種工作狀態: 一種是 啟動狀態用於執行後臺計算,另一種是 綁定狀態用於和其他組件和Service進行交互。需要註意的是這兩種狀態的Service是可以共存的,即Service既可以處於綁定狀態和啟動狀態。

startService()啟動的生命周期:

  當我們第一次使用startService啟動一個服務時,系統實例化一個Service實例,然後依次調用onCreate()和onStartCommand()方法,然後運行,需要註意的是,再次使用startService方法時,不會在創建一個新的服務對象了,但還是會再次執行onStartCommand()方法,如果我們想要停掉一個服務,可以用stopService方法,此時,onDestroy()方法就會被調用,不管前面使用了多少次的startService,stopService方法調用一次,就可停掉服務。

 自定義一個 MyService繼承自Service 

技術分享
public class MyService extends Service {

    public static final String TAG = "MyService";

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

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

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

}
View Code

當通國startService()啟動一個Service時

Intent startIntent = new Intent(this, MyService.class);  
startService(startIntent);  

log信息如下:

技術分享

再次啟動Service時:

技術分享

調用stopService()時,Service就會被停止。

Intent stopIntent = new Intent(this, MyService.class);  
stopService(stopIntent);  

這種方法Service和Activity的關系並不大,只是Activity通知了Service一下:“你可以啟動了。”然後Service就去忙自己的事情了

bindService()啟動的生命周期:

  當調用者首次使用bindService綁定一個服務時,系統會實例化一個Service實例,並一次調用其onCreate()方法和onBind()方法,然後調用者就可以和服務進行交互了,此後,如果再次使用bindService綁定服務,系統不會創建新的Service實例,也不會再調用onBind方法;如果我們需要解除與這個服務的綁定,可使用unbindService方法,此時onUnbind方法和onDestroy方法會被調用。

自定義Service;

public class MyService extends Service {

    public static final String TAG = "MyService";

    private MyBinder mBinder = new MyBinder();

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

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

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

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

    class MyBinder extends Binder {

        public void startDownload() {
            Log.d("TAG", "startDownload() executed");
            // 執行具體的下載任務
        }

    }

}

  這裏我們新增了一個MyBinder類繼承自Binder類,然後在MyBinder中添加了一個startDownload()方法用於在後臺執行下載任務,當然這裏並不是真正地去下載某個東西,只是做個測試,所以startDownload()方法只是打印了一行日誌。

在MainActivity中開啟和關閉Service:

public class MainActivity extends Activity implements OnClickListener {

    private Button startService;

    private Button stopService;

    private Button bindService;

    private Button unbindService;

    private MyService.MyBinder myBinder;

    private ServiceConnection connection = new ServiceConnection() {

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myBinder = (MyService.MyBinder) service;
            myBinder.startDownload();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startService = (Button) findViewById(R.id.start_service);
        stopService = (Button) findViewById(R.id.stop_service);
        bindService = (Button) findViewById(R.id.bind_service);
        unbindService = (Button) findViewById(R.id.unbind_service);
        startService.setOnClickListener(this);
        stopService.setOnClickListener(this);
        bindService.setOnClickListener(this);
        unbindService.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.start_service:
            Intent startIntent = new Intent(this, MyService.class);
            startService(startIntent);
            break;
        case R.id.stop_service:
            Intent stopIntent = new Intent(this, MyService.class);
            stopService(stopIntent);
            break;
        case R.id.bind_service:
            Intent bindIntent = new Intent(this, MyService.class);
            bindService(bindIntent, connection, BIND_AUTO_CREATE);
            break;
        case R.id.unbind_service:
            unbindService(connection);
            break;
        default:
            break;
        }
    }

}

  可以看到,這裏我們首先創建了一個ServiceConnection的匿名類,在裏面重寫了onServiceConnected()方法和onServiceDisconnected()方法,這兩個方法分別會在Activity與Service建立關聯和解除關聯的時候調用。在onServiceConnected()方法中,我們又通過向下轉型得到了MyBinder的實例,有了這個實例,Activity和Service之間的關系就變得非常緊密了。現在我們可以在Activity中根據具體的場景來調用MyBinder中的任何public方法,即實現了Activity指揮Service幹什麽Service就去幹什麽的功能。

1.2 同時startService和binderService

  如果我們既點擊了StartService按鈕,又點擊了Bind Service按鈕會怎麽樣呢?這個時候你會發現,不管你是單獨點擊Stop Service按鈕還是Unbind Service按鈕,Service都不會被銷毀,必要將兩個按鈕都點擊一下,Service才會被銷毀。也就是說,點擊Stop Service按鈕只會讓Service停止,點擊Unbind Service按鈕只會讓Service和Activity解除關聯,一個Service必須要在既沒有和任何Activity關聯又處理停止狀態的時候才會被銷毀。

  先點擊一下Start Service按鈕,再點擊一下Bind Service按鈕,這樣就將Service啟動起來,並和Activity建立了關聯。然後點擊Stop Service按鈕後Service並不會銷毀,再點擊一下Unbind Service按鈕,Service就會銷毀了,打印日誌如下所示:

技術分享

總結:

  這兩個啟動方式的不同,導致生命周期也不同。startService與調用者沒有必然的聯系,即調用者結束了自己的生命周期,只要沒有使用stopService方法停止這個服務,服務仍會運行。而bindService需要有個寄宿的對象,就相當於bind到某個宿主中去,誰綁定了,誰就要負責,負責它的生命周期,從開始到結束,如果宿主自己的生命周期結束了,bindService模式就要先把服務給銷毀掉。

  值得註意的一點是,如果調用者首先是先用startService方式啟動服務,然後再用bindService方式綁定某個服務的話,一定要先用unbindService方式解綁,然後才用stopService方式銷毀服務對象,不然的話,僅僅只是stopService是不夠的,沒解綁的對象還是在的,就容易造成內存泄露了。

  我們應該始終記得在Service的onDestroy()方法裏去清理掉那些不再使用的資源,防止在Service被銷毀後還會有一些不再使用的對象仍占用著內存。

二、Service 與 Thread 之間的關系

  Service與Thread這兩個是沒有什麽關系的。因為我們知道Service是運行在UI線程中,那麽當需要耗時操作的時候,就需要Thread幫助,不是說Service因為是在後臺運行,就跟Thread等同了。Thread是用於開啟一個子線程,在這裏去執行一些耗時操作就不會阻塞主線程的運行。而Service我們最初理解的時候,總會覺得它是用來處理一些後臺任務的,一些比較耗時的操作也可以放在這裏運行,這就會讓人產生混淆了。

  Android的後臺就是指,它的運行是完全不依賴UI的。即使Activity被銷毀,或者程序被關閉,只要進程還在,Service就可以繼續運行。比如說一些應用程序,始終需要與服務器之間始終保持著心跳連接,就可以使用Service來實現。

  我們可以在Service中再創建一個子線程,然後在這裏去處理耗時邏輯就沒問題。既然在Service裏要創建一個子線程,那為什麽不直接在Activity裏創建呢?這是因為Activity很難對Thread進行控制,當Activity被銷毀之後,就沒有任何其它的辦法可以再重新獲取到之前創建的子線程的實例。而且在一個Activity中創建的子線程,另一個Activity無法對其進行操作。但是Service就不同了,所有的Activity都可以與Service進行關聯,然後可以很方便地操作其中的方法,即使Activity被銷毀了,之後只要重新與Service建立關聯,就又能夠獲取到原有的Service中Binder的實例。因此,使用Service來處理後臺任務,Activity就可以放心地finish,完全不需要擔心無法對後臺任務進行控制的情況。

當需要在一個Service中創建一個子線程時,可以通過如下實現:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            // 開始執行後臺任務
        }
    }).start();
    return super.onStartCommand(intent, flags, startId);
}

class MyBinder extends Binder {

    public void startDownload() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 執行具體的下載任務
            }
        }).start();
    }

}

三、 創建前臺Service

  Service幾乎都是在後臺運行的,一直以來它都是默默地做著辛苦的工作。但是Service的系統優先級還是比較低的,當系統出現內存不足情況時,就有可能會回收掉正在後臺運行的Service。如果你希望Service可以一直保持運行狀態,而不會由於系統內存不足的原因導致被回收,就可以考慮使用前臺Service。前臺Service和普通Service最大的區別就在於,它會一直有一個正在運行的圖標在系統的狀態欄顯示,下拉狀態欄後可以看到更加詳細的信息,非常類似於通知的效果。當然有時候你也可能不僅僅是為了防止Service被回收才使用前臺Service,有些項目由於特殊的需求會要求必須使用前臺Service,比如說墨跡天氣,它的Service在後臺更新天氣數據的同時,還會在系統狀態欄一直顯示當前天氣的信息。

  那麽我們就來看一下如何才能創建一個前臺Service吧,其實並不復雜,修改MyService中的代碼,如下所示:

public class MyService extends Service {

    public static final String TAG = "MyService";

    private MyBinder mBinder = new MyBinder();

    @Override
    public void onCreate() {
        super.onCreate();
        Notification notification = new Notification(R.drawable.ic_launcher,
                "有通知到來", System.currentTimeMillis());
        Intent notificationIntent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
                notificationIntent, 0);
        notification.setLatestEventInfo(this, "這是通知的標題", "這是通知的內容",
                pendingIntent);
        startForeground(1, notification);
        Log.d(TAG, "onCreate() executed");
    }

    .........

}

  這裏只是修改了MyService中onCreate()方法的代碼。可以看到,我們首先創建了一個Notification對象,然後調用了它的setLatestEventInfo()方法來為通知初始化布局和數據,並在這裏設置了點擊通知後就打開MainActivity。然後調用startForeground()方法就可以讓MyService變成一個前臺Service,並會將通知的圖片顯示出來。

四、遠程Service(開辟一個新進程)

參考博客:

http://www.cnblogs.com/cr330326/p/5741464.html

http://blog.csdn.net/guolin_blog/article/details/11952435/

http://blog.csdn.net/guolin_blog/article/details/9797169

android Service 學習總結