編輯:關於Android編程
一.Service簡介
service可以說是一個在後台運行的Activity,它不是一個單獨的進程,它只需要應用告訴它要在後台做什麼就可以了,它要實現和用戶的交互的話需要通過通知欄或則是發送廣播,UI去接收顯示。它的應用十分廣泛,尤其是在框架層,應用更多的是對系統服務的調用。它用於處理一些不干擾用戶使用的後台操作。如下載,網絡獲取。播放音樂,他可以通過INTENT來開啟,同時也可以綁定到宿主對象(調用者例如ACTIVITY上)來使用。服務是一個應用程序組件代表應用程序執行一個長時間操作的行為,雖然不與用戶交互或供應功能供其它應用程序使用。每個服務類必須有一個相應的包的AndroidManifest.xml中
二.服務的類型
按使用范圍分為本地服務個遠程服務兩種。
本地服務:用於應用程序內部,實現應用程序自己的一些耗時任務,比如查詢升級信息,並不占用應用程序比如Activity所屬線程,而是單開線程後台執行,這樣用戶體驗比較好。
在Service可以調用Context.startService()啟動,調用Context.stopService()結束。在內部可以調用Service.stopSelf() 或 Service.stopSelfResult()來自己停止。無論調用了多少次startService(),都只需調用一次stopService()來停止。
遠程服務:用於android系統內部的應用程序之間,可被其他應用程序復用,比如天氣預報服務,其他應用程序不需要再寫這樣的服務,調用已有的即可。可以定義接口並把接口暴露出來,以便其他應用進行操作。客戶端建立到服務對象的連接,並通過那個連接來調用服務。調用Context.bindService()方法建立連接,並啟動,以調用 Context.unbindService()關閉連接。多個客戶端可以綁定至同一個服務。如果服務此時還沒有加載,bindService()會先加載它。
按運行類別分為前台服務和後台服務兩種。
前台服務:前台服務需要調用 startForeground ( android 2.0 及其以後版本 )或 setForeground (android 2.0 以前的版本)使服務成為 前台服務。
使用前台服務可以避免服務在後台運行的時候被系統KILL。
後台服務:後台服務就是處於後台運行的。
三.Service生命周期
注意:本地服務中,onStart已經被onStartCommand方法取代,Service和Activity都是由Context類派生的,可以通過getApplicationContext()方法獲取上下文對象,和Activity一樣,它有著自己的生命周期,可是和Activity相比,它所執行的過程略有不同,如上圖所示。
四.3種服務通信類型
3種服務通信類型,分別是通過startService()直接啟動服務,通過bindService()的方式啟動和使用AIDL方式的Service。
1. context.startService() 啟動流程(後台處理工作),只能實現啟動和停止服務
流程:UI ——>Service
操作:使用Intent進行數據傳遞,通過服務中的onStartCommand方法進行接受(和Activity間傳遞方式一樣)
啟動流程:
context.startService() -> onCreate() -> onStartCommand() -> Service running -> context.stopService() -> onDestroy() -> Service stop
所以調用startService的生命周期大致為:
onCreate(只在創建的時候調用一次直到被摧毀) --> onStartCommand (服務開啟後,可多次調用) --> onDestroy
服務中的onStartCommand(Intent intent, int flags, int startId)方法會返回一個唯一的整數標識符來識別啟動請求,啟動請求可以是START_STICKY、START_STICKY_COMPATIBILITY、START_NOT_STICKY、START_REDELIVER_INTENT等,標志位可以是START_FLAG_REDELIVERY、START_FLAG_RETRY。
通過這種方式,服務並不會隨著綁定組建的摧毀而摧毀,而是服務自我摧毀。(所以這種方式適用於文件下載,上傳等請求自行運行的場景)。
從生命周期圖中我們可以看出,onCreate方法只在創建時候被調用了一次,這說明:Service被啟動時只調用一次onCreate()方法,如果服務已經被啟動,在次啟動的Service組件將直接調用onStartCommand()方法,通過這樣的生命周期,可以根據自身需求將指定操作分配進onCreate()方法或onStartCommand()方法中。
使用方法:
(1)創建服務類
public class MyService extends Service { @Override public void onCreate() { } @Override public int onStartCommand(Intent intent, int flags, int startId) { //接受傳遞過來的intent的數據等 return START_STICKY; } @Override public void onDestroy() { } }
(2)在AndroidManifest.xml文件中注冊Service
(3)啟動服務
Intent intent = new Intent(getApplicationContext(), MyService.class); startService(intent);
2. context.bindService()啟動流程(在本地同一進程內與Activity交互),單向交互
流程:UI ——>Service
啟動流程:
context.bindService() -> onCreate() -> onBind() -> Service running -> onUnbind() -> onDestroy() -> Service stop
bindService的生命周期簡化為為: onCreate --> onBind --> onUnbind --> onDestory。
通過該方法,服務啟動時會調用onCreate()來啟動服務,可是它不會調用onStartCommand() 方法,並且只有在所有的服務都接觸了後,服務才會自動停止運行。通過服務的onBind()方法,可以獲的一個客戶端與服務器進行通信的IBdiner接口。IBind允許客戶端回調服務的方法,比如得到Service的實例、運行狀態或其他操作。這個時候把調用者(Context,例如Activity)會和Service綁定在一起,Context退出了,Srevice就會調用onUnbind->onDestroy相應退出。
注:綁定服務的Android組建在摧毀前應解除綁定,否則會造成內存洩漏。
使用方法:
(1)創建服務類
private LocalService extends Service{ @Override public void onCreate() { } /** 綁定的IBinder */ private final IBinder mBinder = new LocalBinder(); public class LocalBinder extends Binder { public LocalService getService() { return LocalService.this; } } @Override public IBinder onBind(Intent intent) { return mBinder; } @Override public boolean onUnbind(Intent intent) { // TODO Auto-generated method stub return super.onUnbind(intent); } }
(2)在AndroidManifest.xml文件中注冊Service
同startService的一樣。
(3)綁定服務
綁定服務需要需要設置ServiceConnection和標志位,方法如下:
bindService(Intent service, ServiceConnection conn, int flags)
ServiceConnection可以監聽服務的狀態,在進行服務綁定的時,其標志位可以為以下幾種(這裡列出3種):
1).Context.BIND_AUTO_CREATE
說明:表示收到綁定請求的時候,如果服務尚未創建,則即刻創建,在系統內存不足需要先摧毀優先級組件來釋放內存,且只有駐留該服務的進程成為被摧毀對象時,服務才被摧毀
2).Context.BIND_DEBUG_UNBIND
說明:通常用於調試場景中判斷綁定的服務是否正確,但容易引起內存洩漏,因此非調試目的的時候不建議使用
3).Context.BIND_NOT_FOREGROUND
說明:表示系統將阻止駐留該服務的進程具有前台優先級,僅在後台運行,該標志位位於Froyo中引入。
注意:綁定服務的以異步方式運行的。綁定服務必須在當前的上下文環境中運行,某些場景中,通過上下文進行添加綁定接觸方法如下:
getApplicationContext().bindService(service, conn, flags)
Activity中代碼如下:
/** 是否綁定 */ boolean mIsBound = false; /** 綁定服務 */ public void doBindService() { bindService(new Intent(MainActivity.this, LocalService.class), mConnection,Context.BIND_AUTO_CREATE); mIsBound = true; } /** 解除綁定服務 */ public void doUnbindService() { if (mIsBound) { // Detach our existing connection. unbindService(mConnection); mIsBound = false; } } private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mBoundService = ((LocalService.LocalBinder) service).getService(); Toast.makeText(MainActivity.this, 服務連接, Toast.LENGTH_SHORT) .show(); } @Override public void onServiceDisconnected(ComponentName name) { mBoundService = null; Toast.makeText(MainActivity.this, 服務未連接, Toast.LENGTH_SHORT) .show(); } };
3. 使用AIDL方式的Service(進行跨進程通信),雙向交互
流程:UI <——>Service
操作:注冊綁定廣播接受器,之後通過廣播來進行2者間通信
啟動流程:和bindService一樣,實現上是在bindService 的基礎上使用AIDL進行通信。
使用方法:
遠程綁定調用service主要是用來不同進程的信息共享。就比如服務器和客戶端,在服務器端設置好一個service提供方法或信息,然後客戶端可以直接調用服務器端service提供方法或信息。這裡有個前提是客戶端必須有和服務器端一份一樣的AIDL,然後服務器端在客戶端使用的系統上有注冊過(也就是安裝運行過一次),之後客戶端就可以遠程綁定調用服務器端的service了。
具體的步驟如下:
1.首先,是服務器的
1)創建一個android應用工程 ,命名為AidlServer
2) 在主Aitivity所在的包裡新建個AIDL文件,這是ADT會自動幫你在gen目錄下生成一個和AIDL文件同名的JAVA文件(這裡的AidlService.java是下一步驟生成的,這裡可以先忽略)
3)如上圖所示,創建一個用來使用service的類(AidlService.java)
具體每個文件的代碼如下:
IAidlService.idal文件:
package com.example.aidlserver; interface IAidlService { String getValue(); }
AidlService.java文件:
public class AidlService extends Service { private class MyBinder extends IAidlService.Stub { @Override public String getValue() throws RemoteException { // TODO Auto-generated method stub return test remote service; } } @Override public void onCreate() { super.onCreate(); Log.i(AidlService,onCreate); } @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub Log.i(AidlService,service on bind); return new MyBinder(); } @Override public void onDestroy() { Log.i(AidlService,service on destroy); super.onDestroy(); } }
MainActivity.java文件:
public class MainActivity extends Activity { private TextView show; private IAidlService iservice; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); show = (TextView)findViewById(R.id.show); } /*點擊第二次的時候次可以改變TextView的值,因為第一次只是綁定,返回的對象是空。*/ public void onGetAidlServerDataBtnClick(View v){ Intent service = new Intent(IAidlService.class.getName()); bindService(service, connection, BIND_AUTO_CREATE); if (iservice != null) { try { show.setText(iservice.getValue()); } catch (RemoteException e) { e.printStackTrace(); } } } private ServiceConnection connection = new ServiceConnection() { public void onServiceConnected(ComponentName name, IBinder service) { // TODO Auto-generated method stub /*獲取Service對象*/ iservice = (IAidlService) IAidlService.Stub.asInterface(service); Log.i(Client,Bind Success: + service); } public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub iservice = null; Log.i(Client,onServiceDisconnected); } }; }
AndroidManifest.xml
這裡,服務器端的工作做好了,接下來是客戶端的工作。
2.接著,是客戶端的:
1)首先,創建一個工程,命名為AidlClient
2)接著,把服務器端的一些文件拷貝過來(創建com.ds.server這個包,然後往裡面復制進入如下圖的服務器端的文件(IAidlService.java)
下面是個文件的內容
AidlClientActivity.java
public class MainActivity extends Activity { private TextView show; private IAidlService iservice; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); show = (TextView)findViewById(R.id.show); } /*點擊第二次的時候次可以改變TextView的值,因為第一次只是綁定,返回的對象是空。*/ public void onGetAidlServerDataBtnClick(View v){ Intent service = new Intent(IAidlService.class.getName()); bindService(service, connection, BIND_AUTO_CREATE); if (iservice != null) { try { show.setText(iservice.getValue()); } catch (RemoteException e) { e.printStackTrace(); } } } private ServiceConnection connection = new ServiceConnection() { public void onServiceConnected(ComponentName name, IBinder service) { // TODO Auto-generated method stub /*獲取Service對象*/ iservice = (IAidlService) IAidlService.Stub.asInterface(service); Log.i(Client,Bind Success: + service); } public void onServiceDisconnected(ComponentName name) { // TODO Auto-generated method stub iservice = null; Log.i(Client,onServiceDisconnected); } }; }
注意:使用廣播進行雙向交互的時候,在服務退出的時候記得unregisterReceiver(receiver);注銷廣播接收器。
五.停止服務
1. 啟動服務停止有2種方法:
(1)stopSelf() 自我停止服務
(2)stopService(Intent name) 被動停止服務
2. 綁定服務的解除綁定方法如下
unbindService(ServiceConnection conn)
六.拓展
1. 如何檢查Android後台服務線程(Service類)是否正在運行
Android系統自己提供了一個函數ActivityManager.getRunningServices,可以列出當前正在運行的後台服務線程。
代碼如下:
private boolean isServiceRunning() { ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE); for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { if (com.example.MyService.equals(service.service.getClassName())) { return true; } } return false; }
2. Service和Thread的區別
我們拿服務來進行一個後台長時間的動作,為了不阻塞線程,然而,Thread就可以達到這個效果,為什麼我們不直接使用Thread去代替服務呢?
1). Thread:Thread 是程序執行的最小單元,它是分配CPU的基本單位。可以用 Thread 來執行一些異步的操作。
2). Service:Service 是android的一種機制,當它運行的時候如果是Local Service,那麼對應的 Service 是運行在主進程的 main 線程上的。如:onCreate,onStart 這些函數在被系統調用的時候都是在主進程的 main 線程上運行的。如果是Remote Service,那麼對應的 Service 則是運行在獨立進程的 main 線程上。因此請不要把 Service 理解成線程。
既然這樣,那麼我們為什麼要用 Service 呢?其實這跟 android 的系統機制有關,我們先拿 Thread 來說。Thread 的運行是獨立於 Activity 的,也就是說當一個 Activity 被 finish 之後,如果你沒有主動停止 Thread 或者 Thread 裡的 run 方法沒有執行完畢的話,Thread 也會一直執行。因此這裡會出現一個問題:當 Activity 被 finish 之後,你不再持有該 Thread 的引用。另一方面,你沒有辦法在不同的 Activity 中對同一 Thread 進行控制。
舉個例子:如果你的 Thread 需要不停地隔一段時間就要連接服務器做某種同步的話,該 Thread 需要在 Activity 沒有start的時候也在運行。這個時候當你 start 一個 Activity 就沒有辦法在該 Activity 裡面控制之前創建的 Thread。因此你便需要創建並啟動一個 Service ,在 Service 裡面創建、運行並控制該 Thread,這樣便解決了該問題(因為任何 Activity 都可以控制同一 Service,而系統也只會創建一個對應 Service 的實例)。
因此你可以把 Service 想象成一種消息服務,而你可以在任何有 Context 的地方調用 Context.startService、Context.stopService、Context.bindService,Context.unbindService,來控制它,你也可以在 Service 裡注冊 BroadcastReceiver,在其他地方通過發送 broadcast 來控制它,當然這些都是 Thread 做不到的。
今天隨便逛逛CSDN,看到主頁上推薦了一篇文章Android 快速開發系列 打造萬能的ListView GridView 適配器,剛好這兩天寫項目自己也封裝了類似的Com
ProgressDialog的基本用法ProgressDialog為進度對話框。android手機自帶的對話框顯得比較單一,我們可以通過ProgressDialog來自己
項目需要,今天學習了一下索引涉及到的技術:繪制右側的索引條點擊某個字母,定位到ListView控件的指定位置 布局文件: 自
先貼出本文程序運行結果的截圖,上面是播放/停止音頻,可用SeekBar來調進度,下面是播放/停止視頻,也是用SeekBar來調進度: main.xml的源碼: