Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Service要點全解析

Service要點全解析

編輯:關於Android編程

1、Service概述

Service的主要作用是,讓系統可以在後台干一些不與用戶交互的操作,這些操作可能會比較耗時,比如去下載一些網絡資源等;也可能是一項長期運行的工作,比如說監聽電話來電、播放音樂等。初聽起來,Service與線程Thread很像,但Service和Thread完全是兩個不同的東西啊。

(1)Service不是運行在一個獨立的進程中,它和我們的應用程序在同一個進程中;

(2)Service也不是一個線程,相反,Service是運行在主線程的,因此我們不能直接在Service中干上面那些耗時操作,因為它會很耗CPU,阻塞主線程,很容易出現ANR錯誤(Application Not Responding),合適的做法是,在Service中開啟一個Thread,進行上面的耗時操作。

如果我們要在Service中開啟線程進行工作,我們也可以使用Service的一個子類IntentService,IntentService類中已經開啟了線程,我們只需要實現一個方法即可,後面具體介紹。

 

2、Service用法

和Activity類似,我們要使用Service,只需要通過Intent發出請求即可。當然,在使用Service前,記得在AndroidManifest.xml中進行聲明。啟動方式有兩種:

我們先在Activity中加入兩組四個按鈕,一組為startService的啟動按鈕和相應的停止按鈕;一組為bindService的啟動按鈕和相應的停止按鈕。如圖:

\

啟動方式一:startService()

在Activity中,我們如下使用:

public class MainActivity extends Activity implements OnClickListener{

    private Button startServiceBtn,stopServiceBtn,bindServiceBtn,unBindServiceBtn;

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

        startServiceBtn = (Button) findViewById(R.id.startService);
        stopServiceBtn = (Button) findViewById(R.id.stopService);
        bindServiceBtn = (Button) findViewById(R.id.bindService);
        unBindServiceBtn = (Button) findViewById(R.id.unBindService);
        startServiceBtn.setOnClickListener(this);
        stopServiceBtn.setOnClickListener(this);
        bindServiceBtn.setOnClickListener(this);
        unBindServiceBtn.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.startService://通過startService()的方式啟動Service
            Intent intent = new Intent(this,ServiceTest.class);
            startService(intent);
            break;
        case R.id.stopService:
            stopService(new Intent(this,ServiceTest.class));
            break;
        case R.id.bindService://通過bindService()的方式啟動Service
            //TODO
            break;
        case R.id.unBindService:
            //TODO
            break;
        }
    }
}
我們的Service如下:
public class ServiceTest extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("TAG", "onCreate");
    }

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

    @Override
    public IBinder onBind(Intent arg0) {
        Log.d("TAG", "onBind");
        return null;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d("TAG", "onUnbind");
        return super.onUnbind(intent);
    }

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

上面,點擊“startService”按鈕,我們就通過startService()將傳進來的Intent中的Service啟動了,啟動時Log打印如下:

\

說明,我們第一次啟動Service的時候,會執行onCreate()和onStartCommand()方法,如果我們這個時候,再次點擊“startService”按鈕,此時打印如下:

\

可以看到,如果一個Service已經運行了,再次啟動這個Service,只會進入onStartCommand(),onCreate()方法只會在第一次啟動的時候進行初始化。Service不像我們的Activity,Activity每次通過Intent啟動時都會創建一個新的Activity(默認模式下),然後放入到棧中。而同一個Service,應用中只會存在一個實例,在第一次創建時通過onCreate初始化,運行後,再次啟動只是進入onStartCommand。

 

點擊“stopService”後,Service被銷毀,進入onDestroy()方法。不管前面我們啟動了多少次Service,只要在外部調用一次Context.stopService()或者在Service內部自己調用一次stopSelf(),Service就會被銷毀。

 

上面這種啟動方式,在啟動完Service後,這個Service就開始在後台運行了,同時,也與啟動它的Activity失去了聯系,因為不能通過ServiceTest service = new ServiceTest()的方式啟動Service,因而我們的Activity中不能獲取到ServiceTest的實例,也就不能夠控制啟動後的Service干Activity想干的事,這個Service只能干它內部定義好的功能,沒有與啟動它的Activity交互能力。

 

為了解決與啟動Service的組件的通信能力,有一種解決方案就是通過廣播的形式,我們在Activity中發出一些想用操作廣播,在Service中注冊該廣播,Service接收到該廣播信息後,完成相應的功能。但是這種方案不夠優雅,頻繁發送廣播比較消耗性能,同時,由於廣播接受者中的onReceive()中,不能執行長時間的工作,時間超過後,可能就直接跳出了方法。因此,這種方案不是首選。

 

啟動方式二:bindService()

如上面所述,如果要求我們的Service能夠和啟動Service的組件進行通信,我們可以使用bindService的啟動方式。

首先改造Service,Service和組件之間是通過管道IBinder接口進行通信的。

public class ServiceTest extends Service {
    private MyLocalBinder mBinder = new MyLocalBinder();
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("TAG", "onCreate");
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("TAG", "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    class MyLocalBinder extends Binder{
        public ServiceTest getServiceInstance(){
            return ServiceTest.this;
        }
        //...這裡也可以繼續寫方法對外提供
    }
   
    @Override
    public IBinder onBind(Intent arg0) {
        Log.d("TAG", "onBind");
        return mBinder;
    }

     //對外提供的訪問方法
    public void downLoad(){
        System.out.println("下載操作方法...");
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d("TAG", "onUnbind");
        return super.onUnbind(intent);
    }

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

繼續改造我們的Activity,在ASctivity中獲得連接通道,如下:

private ServiceTest mService;
    private boolean isConnected = false;//我們在使用bindService時,最好定義一個是否連接的標志,方便我們在組件中通信前的判斷操作

    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className, IBinder binder) {
            ServiceTest.MyLocalBinder localBinder = (MyLocalBinder)binder;    //先獲得管道
            mService = localBinder.getServiceInstance();    //通過管道,拿到Service的實例
            isConnected = true;
            mService.downLoad();//拿到Service實例後想干嘛干嘛
        }

        //注意:這個方法當Service意外運行失敗時調用,如系統殺死這個Service或者運行Service時遇到崩潰,調用unBinderService並不會調用該方法
        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            isConnected = false;
        }
    };

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.startService://通過startService()的方式啟動Service
            Intent intent = new Intent(this,ServiceTest.class);
            startService(intent);
            break;
        case R.id.stopService:
            stopService(new Intent(this,ServiceTest.class));
            break;
        case R.id.bindService://通過bindService()的方式啟動Service
            Intent intent2 = new Intent(this,ServiceTest.class);
            bindService(intent2, connection, BIND_AUTO_CREATE);
            break;
        case R.id.unBindService:
            unbindService(connection);
            break;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(isConnected){
            unbindService(connection);
            isConnected = false;
        }
    }

點擊“bindService”按鈕,我們就通過bindService()將傳進來的Intent中的Service啟動了,啟動時Log打印如下:

\

\我們通過bindService()方法第一次啟動後,會進入Service的onCreate()和onBind()方法。如果我們另一個組件(如Activity),又對同一個Service發起了bindService()操作(也就是說在bindService()中傳入了不同的ServiceConnection),此時只會進入onBind()方法。也就是說onCreate也只是在Service第一次創建時執行。

我們點擊“unBindService”時,打印如下:

\

此時走的是onUnbind和onDestroy方法。

 

可以看到,不管通過哪種方式啟動Service,同一個Service在整個應用程序中只會有一個實例存在,如果通過bindService啟動,獲得的IBinder實例也都是同一個。四大組件中,只有在Activity、Service、Content Providers中通過bindService啟動Service。

 

3、Service生命周期

從上面的打印日志可以看到,兩種啟動Service方式走的生命周期方法是不同的,官方的生命周期圖很好地描述了兩種啟動方式下的回調:

\

 

兩種方式都是只有在第一次啟動沒有運行的Service時,才會進入onCreate()方法。當Service啟動後,後面多次繼;啟動該Service,只會進入onStartCommand()或者onBind()。

(1)當我們通過startService()方法啟動Service時,不管前面我們啟動了多少次Service,只要在外部調用一次stopService()或者在Service內部自己調用一次stopSelf(),Service就會被銷毀;

(2)當我們通過bindService()啟動Service時,前面我們多次啟動Service後,當沒有客戶端連接後(即所有客戶端發出了unBindService),這個Service將會被系統銷毀;

(3)當這個Service既被startService啟動,又被bindService啟動時,即使當所有連接的客戶端斷開連接後,Service也不會被銷毀,除非再調用一次stopService或內部使用stopSelf(),這個時候Service才會被銷毀。或者說如果我們調用了stopService或內部使用stopSelf(),Service也不會被銷毀,只有當所有的客戶端斷開連接後,Service才會被銷毀。也就是說,在這種情況下,Service必須在既沒有任何Activity關聯又停止的情況下,Service才會被銷毀。這種情況下的生命周期如下:

\

 

注意:為了讓我們的Service不過多的浪費CPU資源、內存資源,我們需要在必要的時候進行解除綁定。

(1)當我們的Service只在Activity被用戶可見的時候,才與Activity進行交互,那我們應該在Activity的onStart()中bindService,在onStop()中unBindService();

(2)如果我們希望Activity即使在後台時也能夠與Service交互,那我們應該在onCreate()中bindService(),在onDestroy()中unBindService(),即Activity在整個生命周期中要與Service交互。

 

4、IntentService

正如我們第一部分所談到的,Service和Thread完全是兩個不同的東西,Service主要功能是可以在後台執行一些操作,這些操作不需要與用戶交互。Service是直接運行在主線中中的,如果我們需要在Service中執行耗時操作,為了避免ANR錯誤,是需要在Service中開啟線程來執行的。對於使用Service有開啟線程需求的開發者來說,Android提供了IntentService給用戶,IntentService內部已經幫我們開啟了線程,我們只需要實現它定義的onHandleIntent()方法,在裡面實現我們的功能即可。注意,IntentService不能處理多個線程的請求,但是可以處理多個啟動Service的請求。

IntentService提供的功能:

(1)內部創建了一個工作線程,來處理每個啟動Service的請求傳來的Intent;

(2)內部有一個工作隊列,來分別處理多個啟動Service的請求,因此避免了多線程的問題;

(3)在所有的請求處理完後,自動停止服務,因此我們不必在Service內部自己寫stopSelf();

(4)提供了默認onBind()的實現,直接返回null,意味著IntentService只能通過startService()的方式啟動;

(5)提供了默認onStartCommand()的實現,它將我們的Intent放入到了工作隊列中,然後執行我們具體實現的onHandleIntent()方法。

一個簡單實例如下:

public class IntentServiceTest extends IntentService {
    public IntentServiceTest(){//提供一個默認的構造方法,調用父類構造方法,傳入工作線程的名字
        super("IntentServiceTest");
    }

    @Override
    protected void onHandleIntent(Intent arg0) {
        long endTime = System.currentTimeMillis() + 5*1000;
          while (System.currentTimeMillis() < endTime) {
              synchronized (this) {
                  try {
                      wait(endTime - System.currentTimeMillis());
                  } catch (Exception e) {
                  }
              }
          }
    }
}

使用IntentService主要是注意兩點,一是定義一個默認構造方法,裡面調用父類構造方法,並定義工作線程的名字;第二是實現onHandleIntent()方法,我們在這裡面具體實現我們Service需要做的事情。可以看到,如果我們要使用開啟線程的Service,IntentService提供了一種非常好的方案,讓用戶只需要關注與onHandleIntent接口的具體實現即可,同時用工作隊列的形式支持多啟動服務的訪問。

 

5、讓我們的Service到前台運行

我們的Service默認都是在後台默默運行的,用戶基本察覺不到有Service在運行。此時,Service的優先級是比較低的,當系統資源不足的時候,很容易被銷毀。因此,如果我們想讓用戶知道有Service在後台運行,如音樂播放器,或者想讓Service一直保持運行狀態,不容易被系統回收,此時,就可以考慮使用前台Service。前台Service必須要設置有一個Notification到手機的狀態欄,類似360一樣,因此前台Service能夠與用戶進行交互,它的優先級也就提高了,在內存不足的時候,不會優先去回收前台Service。

創建前台Service也很簡單,就是設置一個Notification到狀態欄,如下:

@Override
    public void onCreate() {
        super.onCreate();
        Log.d("TAG", "onCreate");
        Notification notification  = new Notification(R.drawable.ic_launcher,"前台Service通知來了",System.currentTimeMillis());
        Intent notificationIntent = new Intent(this,MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
        notification.setLatestEventInfo(this, "通知標題", "前台Service內容", pendingIntent);
        //設置到前台運行,第一個參數為通知notification的唯一ID
        startForeground(1, notification);
    }
運行效果如下:

\

如果,我們要移除掉這個前台Service,只需要調用stopService()即可。這個方法並不會停止Service,只是移除掉Notification。

 

6、如何保證Service不被殺死

(本部分內容來自:http://www.cnblogs.com/rossoneri/p/4530216.html)

 

這個倒是有點流氓軟件的意思,但有些特定情況還是需要服務能保持開啟不被殺死,當然這樣做我還是在程序裡添加了關閉服務的按鈕,也就是開啟了就殺不死,除非在軟件裡關閉。

服務不被殺死分3種來討論

1.系統根據資源分配情況殺死服務

2.用戶通過settings->Apps->Running->Stop方式殺死服務

3.用戶通過settings->Apps->Downloaded->Force Stop方式殺死服務

第一種情況:

用戶不干預,完全靠系統來控制,辦法有很多。比如onStartCommand()方法的返回值設為START_STICKY,服務就會在資源緊張的時候被殺掉,然後在資源足夠的時候再恢復。當然也可設置為前台服務,使其有高的優先級,在資源緊張的時候也不會被殺掉。

第二種情況:

用戶干預,主動殺掉運行中的服務。這個過程殺死服務會通過服務的生命周期,也就是會調用onDestory()方法,這時候一個方案就是在onDestory()中發送廣播開啟自己。這樣殺死服務後會立即啟動。如下:

@Overridepublicvoid onCreate() {
// TODO Auto-generated method stubsuper.onCreate();

mBR = new BroadcastReceiver() {
@Overridepublicvoid onReceive(Context context, Intent intent) {
// TODO Auto-generated method stubIntent a = new Intent(ServiceA.this, ServiceA.class);
startService(a);
}
};
mIF = new IntentFilter();
mIF.addAction("listener");
registerReceiver(mBR, mIF);
}

@Overridepublicvoid onDestroy() {
// TODO Auto-generated method stubsuper.onDestroy();

Intent intent = new Intent();
intent.setAction("listener");
sendBroadcast(intent);

unregisterReceiver(mBR);
}

當然,從理論上來講這個方案是可行的,實驗一下也可以。但有些情況下,發送的廣播在消息隊列中排的靠後,就有可能服務還沒接收到廣播就銷毀了(這是我對實驗結果的猜想,具體執行步驟暫時還不了解)。所以為了能讓這個機制完美運行,可以開啟兩個服務,相互監聽,相互啟動。服務A監聽B的廣播來啟動B,服務B監聽A的廣播來啟動A。經過實驗,這個方案可行,並且用360殺掉後幾秒後服務也還是能自啟的。到這裡再說一句,如果不是某些功能需要的服務,不建議這麼做,會降低用戶體驗。

 

也就是如下操作,啟動服務時,我們開啟兩個Service,A和B,在A的onCreate中注冊B的廣播事件,在B的onCreate中注冊A的廣播事件,當A被銷毀時,進入onDestroy()方法時,發送A的廣播,B接收到廣播之後再啟動A;同理,如果B被銷毀,發送廣播給A,讓A啟動B。

 

第三種情況:

強制關閉就沒有辦法。這個好像是從包的level去關的,並不走完整的生命周期。所以在服務裡加代碼是無法被調用的。處理這個情況的唯一方法是屏蔽掉force stopuninstall按鈕,讓其不可用。方法自己去找吧。當然有些手機自帶的清理功能就是從這個地方清理的,比如華為的清理。所以第三種情況我也沒有什麼更好的辦法了。

 


  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved