Android--Service之提高


前言

  上一篇博客講解了一下Android下Service組件的基本使用,對Service組件還不了解的朋友可以先去看看另外一篇Service基礎的博客:Android--Service之基礎。這篇博客講解一下Service組件的一些需要注意的地方以及高級的應用,並用幾個例子講解一下本文中提到的功能,最后依然會提供示例源碼下載。

  既然是深入講解Service,本片博客涉及的內容有點雜亂,這里列個導航方便查看。

  1. IntentService的使用
  2. Service與Thread的區別
  3. Service生命周期詳解
  4. 前台服務
  5. 服務資源被系統意外回收處理辦法 
  6. 不被銷毀的服務

IntentService

  對於Service而言,它依然是運行在主線程之上,所以一些無法在主線程上完成的功能,依然需要另外開啟工作線程來完成,並且一些耗時操作,如果直接放在Service的主線程中完成的話,會影響設備的運行流暢度。對於這樣的問題,有兩種解決方案,一種是在Service主線程中額外開啟一條工作線程,如何開啟工作線程的方法在以前的博客中已經介紹過了,這里不再重復介紹;另外一個方法就是使用IntentService這個父類來實現Service業務類,這里着重講解這個IntentService。

  IntentService是一個服務基類,直接繼承於Service,在需要的時候通過異步調用的方式處理請求。要使用IntentService啟動一個服務進行異步調用,需要實現它的一個抽象方法:onHandleIntent(Intent intent),在這個方法里,可以獲得訪問這傳來的Intent對象,並在其中進行異步操作的實現。對於IntentService而言,因為其繼承自Service類,所以其他的Service的聲明周期方法在此類中也適用,訪問者可以通過調用Context.startService(Intent intent)方法來啟動這個服務。

  使用IntentService作為服務基類,在其內部其實也是重新開啟了一條線程來完成操作,只是這里使用IntentService進行了封裝,並且它自己管理服務的結束。使用IntentService的服務類,在執行結束后執行結束,無需人為的結束它,比較適用於一些無需管理,但是又比較耗時的操作!

 

IntentService-Demo

  下面通過一個小示例演示IntentService的操作,在這個示例中,下載一張網絡上的圖片,保存在設備的本地目錄下。其中涉及到的本地存儲和訪問網絡下載圖片的內容,如果你不了解的朋友,可以查看另外兩篇博客:Android--數據持久化Android--HTTPClient

  IntentSer.java:

 1 package cn.bgxt.servicedemohigh;
 2 
 3 import java.io.File;
 4 import java.io.FileOutputStream;
 5 import java.io.IOException;
 6 import java.io.InputStream;
 7 import java.net.MalformedURLException;
 8 import java.net.URL;
 9 
10 import android.app.IntentService;
11 import android.content.Intent;
12 import android.util.Log;
13 import android.widget.Toast;
14 
15 public class IntentSer extends IntentService {
16     private final static String TAG = "main";
17     private String url_path="http://ww2.sinaimg.cn/bmiddle/9dc6852bjw1e8gk397jt9j20c8085dg6.jpg";
18     public IntentSer() {
19         super("IntentSer");
20     }
21     @Override
22     public void onCreate() {
23         Log.i(TAG, "Service is Created");
24         super.onCreate();
25     }
26     
27     @Override
28     public int onStartCommand(Intent intent, int flags, int startId) {
29         Log.i(TAG, "Service is started");
30         return super.onStartCommand(intent, flags, startId);
31     }
32     
33     @Override
34     public void onDestroy() {
35         Log.i(TAG, "Service is Destroyed");
36         super.onDestroy();
37     }
38     @Override
39     protected void onHandleIntent(Intent intent) {
40         Log.i(TAG, "HandleIntent is execute");
41         try {
42             // 在設備應用目錄下創建一個文件
43             File file=new File(this.getFilesDir(), "weibo.jpg");
44             FileOutputStream fos=new FileOutputStream(file);        
45             // 獲取網絡圖片的輸入流
46             InputStream inputStream = new URL(url_path).openStream();
47             // 把網絡圖片輸入流寫入文件的輸出流中
48             byte[] date=new byte[1024];
49             int len=-1;
50             while((len=inputStream.read(date))!=-1){
51                 fos.write(date, 0, len);
52             }
53             
54             fos.close();
55             inputStream.close();    
56             Log.i(TAG, "The file download is complete");
57         } catch (MalformedURLException e) {
58             e.printStackTrace();
59         } catch (IOException e) {
60             e.printStackTrace();
61         }
62     }
63 }

  編寫好服務類,僅需要適用一個Android組件即可啟動服務,這里使用一個Activity中來啟動服務,下面是主要代碼。

 1         btnDownload=(Button)findViewById(R.id.btnDownload);
 2         btnDownload.setOnClickListener(new View.OnClickListener() {
 3             
 4             @Override
 5             public void onClick(View v) {
 6                 // TODO Auto-generated method stub
 7                 Intent service=new Intent(MainActivity.this,IntentSer.class);
 8                 startService(service);
 9             }
10         });

   執行結果均寫入日志中,可以通過LoaCat查看。

 

  下載完成后,可以在應用的安裝目錄下/files下看到下載的weibo.jpg文件。

 

 

Service與Thread的區別

  對Service了解后,會發現它實現的大部分功能使用Thread也可以解決,並且Thread使用起來比Service方便的多,那么為什么還需要使用Service呢,下面來詳細解釋一下。

  首先,Thread是程序執行的最小單元,它是分配系統資源的基本單位,主要用於執行一些異步的操作。而Service是Android的一種機制,當它使用bindService()被綁定的時候,是運行在宿主主進程的主線程上的,當使用startService()啟動服務的時候,是獨立運行在獨立進程的主線程上的,因此它們的核心沒有任何關系。

  其次,對於Thread而言,它是獨立於啟動它的組件的,如使用一個Activity啟動了一個Thread,當這個Activity被銷毀前,沒有主動停止Thread或者Thread的run()方法沒有執行完畢的話,Thread也會一直執行下去,這樣就很容易導致一些問題,當這個Activity被銷毀之后,將不再持有這個Thread的引用,也就是說,無法再在另外一個Activity中對同一個Thread進行控制。而Service不同,在Android系統中,無論啟動或綁定幾次,只會創建一個對應的Service實例,所以只要這個Service在運行,就可以在能獲取到Context對象的地方控制它,這些特點是Thread無法做到的。

 

Service生命周期詳解

  上一篇博客簡單講解了一下Service的生命周期,但是講解的不夠詳細,這里另外再解釋一下,下圖是官方API中給定的關於Service生命周期的圖示:

  從上圖可以看出,對於啟動服務和綁定服務存在不同的生命周期,但是大部分調用的生命周期方法是一樣的,如onCreate()、onDestroy()等,並且無論是啟動或是綁定多次同一個服務,onCreate()、onDestroy()等這些共用的生命周期方法也僅被調用一次。它們唯一的區別就是當使用startService()啟動服務的時候,回調的是onStatrCommand()方法,而使用bindService()綁定服務的時候,回調的是onBind()方法,並且解除綁定的時候還會回調onUnbind()方法。

  下面詳細解說一下啟動服務和綁定服務的生命周期:

  • 啟動服務:如果一個Service被Android組件調用startService()方法啟動,那么不管這個Service對象是否使用bindService()方法被訪問者綁定過,該Service都會在后台運行。因為Android系統只會為一個Service服務創建一個實例,所以無論啟動幾次,onCreate()方法僅執行一次,但是每次啟動都會執行onStartCommand()方法,一旦服務啟動,不管訪問者是否被銷毀,服務將一直執行下去,除非被調用stopService()方法或者服務自身的stopSelf()方法,當然系統資源不足的時候,也有可能回收結束服務回收資源。
  • 綁定服務:如果一個Service被某個Android組件調用bindService()方法綁定服務,同樣因為一個Service只會被創建一個實例,所以不管bindService()被不同的組件調用幾次,onCreate()方法都只被回調一次,轉而還會執行onBind()方法進行Binder對象的綁定。在綁定連接被建立后,Service將一直運行下去,除非宿主調用unbindService()方法斷開連接或者之前的宿主被銷毀了,這個時候系統會檢測這個Service是否存在宿主綁定,當宿主綁定的數量為0的時候,系統將會自動停止服務,對應的onDestroy()將被回調。

   正如上面提到的,Android系統僅會為一個Service創建一個實例,所以不管是使用啟動服務或是綁定服務,都操作的是同一個Service實例。但是如果兩種服務運行方式均被調用,那么綁定服務將會轉為啟動服務運行,這時就算之前綁定的宿主被銷毀了,也不會影響服務的運行,而啟動服務並不會因為有宿主調用了bindService()方法而把原本的啟動服務轉為綁定服務,但是還是會與宿主產生綁定,但這時即使宿主解除綁定后,服務依然按啟動服務的生命周期在后台運行,直到有Context調用了stopService()或是服務本身調用了stopSelf()方法才會真正銷毀服務。這樣理解感覺啟動服務的優先級要比綁定服務高,當然不管哪種情況,被系統回收的資源不在此討論的范圍內。

  所以綜上所述,對於一個既使用startService()啟動又使用bindService()綁定的服務,除非這個服務的兩條生命周期均完結,否則不會被銷毀。也就是說,在不考慮系統在資源不足的時候,主動回收資源銷毀服務的情況下,使用startService()啟動的服務,必須使用stopService()或是服務本身的stopSelf()停止服務,使用bindService()綁定的服務,必須使用unbindService()或是銷毀宿主來解除綁定,否則服務一直運行。

  在實際項目中,經常會使用開始服務於綁定服務混合使用,這樣既保證了一個有效的服務在后台長期運行,又可以在需要的時候通過bindService()綁定服務,從而與服務進行交互。

 

 前台服務

  一個Service不管是被啟動或是被綁定,默認是運行在后台的,但是有一種特殊的服務叫后台服務,它是一種能被用戶意識到它存在的服務,因此系統默認是不會自動銷毀它的,但是必須提供一個狀態欄通知,Notification,在通知欄放置一個持續的標題,所以這個通知是不能被忽略的,除非服務被停止或從前台刪除。關於通知欄的內容,可以查看另外一篇博客:Android--通知之Notification

   這類服務主要用於一些需要用戶能意識到它在后台運行,並且隨時可以操作的業務,如音樂播放器,設置為前台服務,使用一個Notification顯示在通知欄,可以試用戶切歌或是暫停之類的。

  前台服務與普通服務的定義規則是一樣的,也是需要繼承Service,這里沒有區別,唯一的區別是在服務里需要使用Service.startFroeground()方法設置當前服務為一個前台服務,並為其制定Notification。下面是startForeground()的完整簽名。

    public final void startForeground(int id,Notification notification)

  其中的參數id是一個唯一標識通知的整數,但是這里注意這個整數一定不能為0,notification為前台服務的通知,並且這個notification對象只需要使用startForeground()方法設置即可,無需像普通通知一樣使用NotificationManager對象操作通知。

  前台服務可以通過調用stopService(boolean bool)來使當前服務退出前台,但是並不會停止服務,傳遞false即可。

  有一點需要聲明一下,startForeground()需要在Android2.0之后的版本才生效,在這之前的版本使用setForeground()來設置前台服務,並且需要NotificationManager對象來管理通知,但是現在市面上的設備基本上已經很少有2.0或一下的設備了,所以也不用太在意。

前台服務--示例

  下面通過一個示例來演示一個簡單的前台服務,這個前台服務展示為一個通知,並且點擊通知的時候會開啟一個新的Activity

   ForegroundService.java

 1 package cn.bgxt.servicedemohigh;
 2 
 3 import android.app.Notification;
 4 import android.app.NotificationManager;
 5 import android.app.PendingIntent;
 6 import android.app.Service;
 7 import android.content.Context;
 8 import android.content.Intent;
 9 import android.os.IBinder;
10 import android.support.v4.app.NotificationCompat;
11 
12 public class ForegroundSer extends Service {
13     private Notification notification;
14     @Override
15     public IBinder onBind(Intent intent) {
16         return null;
17     }
18     
19     @Override
20     public void onCreate() {
21         super.onCreate();
22         // 聲明一個通知,並對其進行屬性設置
23         NotificationCompat.Builder mBuilder=new NotificationCompat.Builder(ForegroundSer.this)
24         .setSmallIcon(R.drawable.ic_launcher)
25         .setContentTitle("Foreground Service")
26         .setContentText("Foreground Service Started.");
27         // 聲明一個Intent,用於設置點擊通知后開啟的Activity
28         Intent resuliIntent=new Intent(ForegroundSer.this, IntentSerActivity.class);
29         PendingIntent resultPendingIntent=PendingIntent.getActivity(ForegroundSer.this, 0, resuliIntent, PendingIntent.FLAG_CANCEL_CURRENT);
30         mBuilder.setContentIntent(resultPendingIntent);
31         notification=mBuilder.build();
32         // 把當前服務設定為前台服務,並指定顯示的通知。
33         startForeground(1,notification);
34     }
35     
36     @Override
37     public void onDestroy() {        
38         super.onDestroy();
39         // 在服務銷毀的時候,使當前服務推出前台,並銷毀顯示的通知
40         stopForeground(false);
41     }
42 }

  控制Service的Activity,與正常啟動服務無異。

 1 public class ForegroundActivity extends Activity {
 2     private Button btnForeStart,btnForeStop;
 3     private Intent service;
 4     @Override
 5     protected void onCreate(Bundle savedInstanceState) {
 6         // TODO Auto-generated method stub
 7         super.onCreate(savedInstanceState);
 8         setContentView(R.layout.layout_foreground);
 9         btnForeStart=(Button)findViewById(R.id.btnForeStart);
10         btnForeStop=(Button)findViewById(R.id.btnForeStop);
11         service=new Intent(ForegroundActivity.this, ForegroundSer.class);
12         btnForeStart.setOnClickListener(new View.OnClickListener() {            
13             @Override
14             public void onClick(View v) {
15                 // 開始服務
16                 startService(service);                            
17             }
18         });
19         btnForeStop.setOnClickListener(new View.OnClickListener() {
20             @Override
21             public void onClick(View v) {
22                 // 停止服務
23                 stopService(service);
24             }
25         });
26     }
27 }

  顯示就是一個普通的通知欄,但是它代表的是一個前台服務:

 

服務資源被系統意外回收

  眾所周知,Android系統會在系統資源不足的時候回收系統資源,如CPU、內存。這個時候就會強制銷毀一些優先級不高的后台組件,所以很可能用戶啟動的服務在還沒有完成既定的業務的時候就被系統給回收了,這個時候需要制定當Service被意外銷毀的時候,如何處理接下來的事情。

  使用bindService()綁定的Service是與宿主組件相關的,所有如果宿主組件沒有被系統回收銷毀,這個服務除非宿主主動解除綁定,直到這個服務沒有宿主綁定,即才會被銷毀,這種情況只需要關心與它綁定的組件的是否被銷毀即可。而對於使用startService()開啟的服務而言,服務一旦開啟,將與宿主無關,所以除了宿主調用stopService()或者服務自己調用stopSelf()外,它很可能在系統資源不足的時候被回收,一般如果一些比較重要的任務,比如說下載文件、發送備份數據等一些操作,是不允許服務被系統停止的,這里就需要設置服務被系統銷毀后,如何處理的問題。

  服務被系統銷毀后,如何繼續服務,可以使用Service.onStartCommand()方法的返回值設定,這個方法必須返回一個整型,用於設定系統在銷毀服務后如何處理,這些返回的整型被系統封裝成了常量,一般常用的有:

  • START_NOT_STICKY:在系統銷毀服務后,不重新創建服務,除非有額外的Intent啟動服務。
  • START_STICKY:在系統銷毀服務后,重新創建服務和調用onStartCommand(),但會依照一個空的Intent對象執行任務,就如僅開始服務,但不執行命令,等待服務的繼續工作
  • START_REDELIVER_INTENT:在系統銷毀服務后,重新創建和調用onStartCommand()方法,依據之前啟動它的Intent對象開啟服務,進行之前未執行完的任務,如下載文件。

  

不被銷毀的服務

  如果想使一個Service對象常駐后台運行,在任何時候都不被銷毀,這里涉及的內容比較多,有時間再細細的寫。這里主要提一下思路,為了保持一個Service示例常駐后台,需要考慮幾個問題:

  • 開機啟動:Android系統開啟的時候會發送一個action為android.intent.action.BOOT_COMPLETED的廣播,只需要一個廣播接受者來接受這個Action,然后在其中啟動服務即可。
  • 意外停止:對於意外停止的服務,可以在服務的onDestory()方法中重新啟動服務,這樣剛銷毀又重新啟動。
  • 意外銷毀:當Service被意外銷毀的時候,會發布一個action為android.intent.action.PACKAGE_RESTARTED的廣播,只需監聽這個廣播,在其中重新啟動服務即可。
  • 系統回收:系統在資源不足的情況下會回收資源,但是也是有一定的規則的,回收的資源必定是優先級低的資源,所以提高Service的優先級,也可以保證一定的常駐系統后台,服務的優先級可以在清單文件中,配置<service/>節點下的<intent-filter/>節點的時候增加android:priority屬性,將其的數值設定的比較高,此處數值越高,優先級越高,上線為1000。

   上面介紹的思路基本上已經涵蓋了讓Service不被銷毀的主要內容,還有一種極端的情況,可以使服務一旦啟動,將不會被銷毀,那在配置服務的清單文件的時候,在<application/>節點中增加android:persistent屬性,並將其設置為true。但是這是最極端的情況,也不推薦使用,因為如果系統中安裝了大量常駐組件,將會影響系統的使用。

  

  源碼下載

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM