您好,登錄后才能下訂單哦!
引言
Service 服務是 Android 系統最常用的四大部件之一,Android 支持 Service 服務的原因主要目的有兩個,一是簡化后臺任務的實現,二是實現在同一臺設備當中跨進程的遠程信息通信。
Service 服務主要分為 Local Service 本地服務與 Remote Service 遠程服務兩種,本地服務只支持同一進程內的應用程序進行訪問,遠程服務可通過AIDL(Android Interface Definition Language)技術支持跨進程訪問。服務可以通過Context.startService()和Context.bindService()進行啟動,一般Local Service本地服務可使用其中一種方法啟動,但Remote Service遠程服務只能使用Context.bindService()啟動,而兩種調用方式在使用場景與活動流程中都存在差異。還有通過多線程技術處理 Service 服務的延時操作等技術,下文將針對Android 系統的 Service 服務的一系列操作進行深入探討。
目錄
一、Service 服務的概念與說明
二、Service 服務的生命周期
三、Local Service 的應用原理與開發實例
四、通過多線程方式處理 Service 服務的延時性操作
五、淺談 Remote Service 遠程服務原理
一、Android Service的概念與說明
1.1 Service 服務的定義
Android Service 是 Android 平臺最常用的部件之一,其概念與 Windows Service 類似,熟悉Windows開發的朋友應該對此概念會有所了解。當 Android 系統需要對現有的程序數據進行監聽,或者對現有 Actitvity 提供數據服務支撐時,就會使用到 Android Service 。例如:對用戶地理位置的檢測,對SD卡定時掃描,對當地氣候的定期檢測都會使用到 Service 服務,Service 一般都是運行于后臺,不需要用戶界面支撐。Service 服務不會自動創建線程,如果開發人員沒有為Service服務添加異步操作,那Service服務將運行于主線程當中。
1.2 Service 服務的類型
1.2.1 按照 Service 的生命周期模型一共分為兩種類型
第一類是直接通過Context.startService()啟動,通過Context.stopService() 結束Service,其特點在于調用簡單,方便控制。缺點在于一旦啟動了 Service 服務,除了再次調用或結束服務外就再無法對服務內部狀態進行操控,缺乏靈活性。
第二類是通過Context.bindService()啟動,通過Context.unbindService() 結束,相對其特點在運用靈活,可以通過 IBinder 接口中獲取 Service 的句柄,對 Service 狀態進行檢測。
從 Android 系統設計的架構上看,startService() 是用于啟動本地服務,bindService() 更多是用于對遠程服務進行綁定。當然,也可以結合兩者進行混合式應用,先通過startService()啟動服務,然后通過 bindService() 、unbindService()方法進行多次綁定,以獲取 Service 服務在不同狀態下的信息,最后通過stopService()方法結束Service運行,在下面文章里將舉例一一說明。
1.2.2 按照 Service 的寄存方式分為兩種類型
本地服務 (Local Service) 寄存于當前的進程當中,當前進程結束后 Service 也會隨之結束,Service 可以隨時與 Activity 等多個部件進行信息交換。Service服務不會自動啟動線程,如果沒有人工調用多線程方式進行啟動,Service將寄存于主線程當中。
遠程服務 (Remote Service ) 獨立寄存于另一進程中, 通過 AIDL (Android Interface Definition Language)接口定義語言,實現Android設備上的兩個進程間通信(IPC)。AIDL 的 IPC 機制是基于 RPC (Remote Proceduce Call) 遠程過程調用協議建立的,用于約束兩個進程間的通訊規則,供編譯器生成代碼。進程之間的通信信息,首先會被轉換成AIDL協議消息,然后發送給對方,對方收到AIDL協議消息后再轉換成相應的對象,其使用方法在下文將會詳細說明。
回到目錄
二、Android Service 的生命周期
2.1 Service 服務的常用方法
方法 | 說明 |
void onCreate() | 當Service被啟動時被觸發,無論使用Context.startServcie還是Context.bindService啟動服務,在Service整個生命周期內只會被觸發一次 |
int onStartCommand(Intent intent, int flags, int startId) | 當通過Context.startService啟動服務時將觸發此方法,但當使用 Context.bindService 方法時不會觸發此方法,其中參數 intent 是 startCommand 的輸入對象,參數 flags 代表 service 的啟動方式,參數 startId 當前啟動 service 的唯一標式符。返回值決定服務結束后的處理方式,下文將再作詳細說明。 |
void onStart(Intent intent,int startId) | 2.0舊版本的方法,已被Android拋棄,不推薦使用,默認在onStartCommand 執行中會調用此方法 |
IBinder onBind(Intent intent) | 使用 Context.bindService 觸發服務時將調用此方法,返回一個IBinder 對象,在遠程服務時可用于對 Service 對象進行遠程操控 |
void onRebind(Intent intent) | 當使用startService啟動Service,調用bindService啟動Service,且 onUnbind 返回值為 true 時,下次再次調用 Context.bindService 將觸發方法 |
boolean onUnbind(Intent intent) | 調用 Context.unbindService 觸發此方法,默認返回 false, 當返回值 true 后,再次調用 Context.bindService 時將觸發 onRebind 方法 |
void onDestory() | 分三種情況:1.以Context.startService啟動service,調用Context.stopService結束時觸發此方法;2.以Context.bindService啟動service,以Context.unbindService結束時觸發此方法;3.先以Context.startService 啟動服務,再用Context.bindService綁定服務,結束時必須先調用Context.unbindService解綁再使用Context.stopService結束service才會觸發此方法。 |
表2.1
細說onStartCommand 方法
由于手機的RAM、內部資源有限,所以很多Service都會因為資源不足而被Kill掉,這時候返回值就決定了Service被Kill后的處理方式,一般 int onStartCommand(intent,flags,startId)的返回值分為以下幾種:
START_STICKY
如果service進程被kill掉,系統會嘗試重新創建Service,如果在此期間沒有任何啟動命令被傳遞到Service,那么參數intent將為null。
START_NOT_STICKY
使用這個返回值時,如果在執行完onStartCommand()后,服務被異常kill掉,系統不會自動重啟該服務。
START_REDELIVER_INTENT
使用這個返回值時,如果在執行完onStartCommand()后,服務被異常kill掉,系統會自動重啟該服務,并將intent的值傳入。
START_STICKY_COMPATIBILITY
START_STICKY的兼容版本,但不保證服務被kill后一定能重啟。
而輸入參數flags正是代表此次onStartCommand()方法的啟動方式,正常啟動時,flags默認為0,被kill后重新啟動,參數分為以下兩種:
START_FLAG_RETRY
代表service被kill后重新啟動,由于上次返回值為START_STICKY,所以參數 intent 為null
START_FLAG_REDELIVERY
代表service被kill后重新啟動,由于上次返回值為START_REDELIVER_INTENT,所以帶輸入參數intent
2.2 Service 的運作流程
上文曾經提到 Service 的啟動方法有Context.startService(intent),Context.bindService(intent,serviceConnection,int) 兩種,下面詳細介紹一下它們工作流程。
當系統調用Context.startService()方法時,先會觸發Service的onCreate()方法,這一般用于對Service的運行條件作初始化處理,且在Service的生命周期內只會被觸發一次。然后系統將觸發Service的onStartCommand()方法,用戶每次調用startService()方法,都會觸發onStartCommand()方法。之后,Service 除非在資源不足的情況下被系統 kill 掉,否則Service不會自動結束,直至系統調用Context.stopService()方法時,Service 才會結束。在Service結束時將自動啟動onDestory()方法對運轉中的Service作最后處理。
注意:即使系統多次調用 startService()或 bindService()方法, onCreate() 方法只會在第一次調用時被觸發。同理 onDestory () 方法也只會在服務完結時被觸發,其原理可看第2.1節該方法的詳細說明。
當系統調用Context.bindService()方法時,也會觸發Service的onCreate()方法對Service對象的運行條件作初始化處理,然后觸發Service 的 onBind ()方法對服務進行綁定,成功獲取Service的句柄后,系統就會通過用戶自定義的serviceConnection對象onServiceConnected(ComponentName name, IBinder service)方法,對 Service 對象作出處理。最后當系統調用Context.unbindService()結束服務時,就會激發Service的onDestory()方法對運轉中的 Service 作最后的處理。
注意:系統調用 Context.bindService()方法,完成 Service.onBind() 綁定后就會觸發 serviceConnection對象的 onServiceConnected()方法,但只要系統未使用 Context.unbindService()方法對 service 服務進行解綁,即使多次調用bindService(),系統也只會在第一次綁定時調用onBind() 和 onServiceConnected方()法一次。這正是 startService()與 bindService()方法其中的區別,單從字面上理解 startService () 啟動服務是可以多次執行,所以多次調用 startService()方法都會觸發 onStartCommand()事件,而bindService() 是綁定服務,所以只要服務已經被綁定,在未解綁時也不會多次執行onServiceConnected()綁定后的操作,這也是兩者在使用場景上的區別所在。
Service 生命周期 圖2.2
Service 的運轉流程就先介紹到這里,具體的使用方法將在下面的章節中詳細介紹。
回到目錄
三、Local Service 應用原理與開發實例
3.1 通過 Context.startService 啟動 Service 服務
首先建立MyService繼承Service,實現onCreate()、onDestory()、onStartCommand()、onStart()等幾個方法,使用日志記錄其運作信息。在Activity中通過Intent綁定Service服務,通過Context.startService()啟動服務,通過Context.stopService()結束服務。
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void btnStart_onclick(View view){ //通過Intent綁定MyService,加入輸入參數 Intent intent=new Intent(MainActivity.this,MyService.class); intent.putExtra("Name", "Leslie"); Log.i(Context.ACTIVITY_SERVICE, "----------onClick startService-----------"); //啟動MyService startService(intent); } public void btnStop_onclick(View view){ Intent intent=new Intent(MainActivity.this,MyService.class); Log.i(Context.ACTIVITY_SERVICE, "----------onClick stopService------------"); //停止MyService stopService(intent); } } public class MyService extends Service{ @Override public void onCreate(){ Log.i(Context.ACTIVITY_SERVICE,"Service onCreate"); super.onCreate(); } @Override public void onDestroy() { Log.i(Context.ACTIVITY_SERVICE, "Service onDestroy"); super.onDestroy(); } @Override public void onStart(Intent intent, int startId){ Log.i(Context.ACTIVITY_SERVICE,"Service onStart"); super.onStart(intent, startId); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(Context.ACTIVITY_SERVICE, "Service onStartCommand"); String name=intent.getStringExtra("Name"); Log.i(Context.ACTIVITY_SERVICE,"His name is "+name); return super.onStartCommand(intent, flags, startId); } }
AndroidManifest.xml 文件綁定
<activity android:name=".MainActivity" android:label="@string/title_activity_main" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name="android.services.MyService" android:enabled="true"> </service>
Service 配置說明:
android:name 服務類名,注意如果Service與Activity不在同一個包中,在android:name上必須寫上Service的全路徑
android:label 服務的名字,如果為空,默認顯示的服務名為類名
android:icon 服務的圖標
android:permission 申明此服務的權限,這意味著只有提供了該權限的應用才能控制或連接此服務
android:process 表示該服務是否運行在另外一個進程,如果設置了此項,那么將會在包名后面加上這段字符串表示另一進程的名字
android:enabled 如果此項設置為 true,那么 Service 將會默認被系統啟動,默認值為 false
android:exported 表示該服務是否能夠被其他應用程序所控制或連接,默認值為 false
查看處理結果可清楚看到,多次調用startService()后,使用stopService()結束Service服務,onCreate()、onDestory()只會在Service啟動和結束時被調用一次。只有Service中的onStartCommand()方法會被多次調用。而Android 2.0以下舊版的方法onStart()會在onStartCommand()調用過程中被激發。
3.2 通過Context.bindService啟動Service服務
在介紹Context.bindService()前,先講解一下與此相關的常用類 Binder、ServiceConnection,首先 IBinder 是 Binder 遠程對象的基本接口,是為高性能而設計的輕量級遠程調用機制的核心部分。這個接口定義了與遠程對象交互的協議,但它不僅用于遠程調用,也用于進程內調用。系統可以通過它以獲取Service的句柄,在此先簡單介紹它的基本用法,在下面關于Remote Service遠程服務對象時再詳細講述IBinder的主體功能。ServiceConnection主要用于通過Binder綁定Service句柄后,對Service對象進行處理,它主要有兩個方法void onServiceConnected(ComponentName name, IBinder service)和void onServiceDisconnected(ComponentName name)。在Context.bindService()完成綁定后,系統就會調用 onServiceConnected() 方法,用戶可以通過 IBinder 參數獲取Service句柄,對Service進行處理。而 onServiceDisconnected() 方法一般不會被調用,只有Service被綁定后,由于內存不足等問題被意外 kill 時才會被調用。下面舉個例子說明一下bindService()的用法。
public class MainActivity extends Activity { private MyServiceConnection serviceConnection; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); serviceConnection=new MyServiceConnection(); } public void btnBind_onclick(View view){ //綁定 MyService Intent intent=new Intent(this,MyService.class); Log.i(Context.ACTIVITY_SERVICE, "----------onClick bindService-----------"); //通過bindService(intent,serviceConnection,int)方式啟動Service bindService(intent,this.serviceConnection,Context.BIND_AUTO_CREATE); } public void btnUnbind_onclick(View view){ Log.i(Context.ACTIVITY_SERVICE, "----------onClick unbindService----------"); unbindService(serviceConnection); } } public class MyService extends Service{ private MyBinder myBinder; @Override public IBinder onBind(Intent intent) { Log.i(Context.ACTIVITY_SERVICE,"Service onBind"); return this.myBinder; } @Override public boolean onUnbind(Intent intent){ Log.i(Context.ACTIVITY_SERVICE,"Service onUnbind"); return super.onUnbind(intent); } @Override public void onCreate(){ super.onCreate(); Log.i(Context.ACTIVITY_SERVICE,"Service onCreate"); myBinder=new MyBinder(); } @Override public void onDestroy() { Log.i(Context.ACTIVITY_SERVICE, "Service onDestroy"); super.onDestroy(); } public String getDate(){ Calendar calendar = Calendar.getInstance(); return calendar.getTime().toString(); } public class MyBinder extends Binder { public MyService getService(){ return MyService.this; } } } public class MyServiceConnection implements ServiceConnection{ @Override public void onServiceConnected(ComponentName name, IBinder service){ Log.i(Context.ACTIVITY_SERVICE, "Service Connected"); String data=null; //通過IBinder獲取Service句柄 MyService.MyBinder myBinder=(MyService.MyBinder)service; MyService myService=myBinder.getService(); data=myService.getDate(); Log.i(Context.ACTIVITY_SERVICE,data); } @Override public void onServiceDisconnected(ComponentName name) { Log.i(Context.ACTIVITY_SERVICE, "Service Disconnected"); } }
在運行時多次點擊按鈕激發btnBind_ view)方法后再使用btnUnbind_ view)結束服務,請留意處理結果。當系統調用Context .bindService()后,Service將跟隨onCreate()、onBind()、onUnbind()、onDestory()的流程走下去。在成功完成onBind()綁定后,就會激發ServiceConnection對象的onServiceConnected()方法,在此用戶可對Service進行處理。記得第2.2節所提過的問題,即使多次調用Context.bindService()方法,只要沒有調用unbindService()結束綁定,系統只會在第一次調用時激發Service.onBind()和onServiceConnected()方法,這點從運行結果中可得到證實。
注意:調用 Context .bindService() 啟動 Service 后 ,只能調用 unbindService() 一次,如重復多次調用此方法系統將會拋出錯誤異常。所以最簡單的處理方式是設置一個靜態變量 boolean connected,在調用 unbindService() 前先作出判斷
public class MainActivity extends Activity { private MyServiceConnection serviceConnection; private static boolean connected; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); serviceConnection=new MyServiceConnection(); } public void btnBind_onclick(View view){ connected=true; //綁定 MyService Intent intent=new Intent(this,MyService.class); Log.i(Context.ACTIVITY_SERVICE, "----------onClick bindService-----------"); //通過bindService(intent,serviceConnection,int)方式啟動Service bindService(intent,this.serviceConnection,Context.BIND_AUTO_CREATE); } public void btnUnbind_onclick(View view){ Log.i(Context.ACTIVITY_SERVICE, "----------onClick unbindService----------"); if(connected){ unbindService(serviceConnection); connected=false; } } }
3.3 Service 服務的綜合運用
在前兩節只是從初級階段介紹了Service服務的使用原理,無論是使用startService()或者bindService()啟動服務,Service服務的運行都是階段性,當使用stopService()、unbindService()后,Service服務就會結束。然而從現實應用層面上看,Service 服務很多時候是長駐后臺的,它會記錄程序運行的流程,當今的狀態等重要信息。此時,更多的使用方式就是結合startService()、bindService()兩種方式調用Service服務,startService()負責管理Service服務的啟動,輸入初始化參數,bindService()負責定時對Service服務進行檢測。而且流程是有規律性,以startService()啟動服務后,每使用bindService()綁定服務,就通過serviceConnection對服務進行檢測,然后以unbindService()結束綁定。注意,此時服務并未結束,而是長期運行于后臺,直到系統以stopService()方法結束服務后,Service才會最終完結。
public class MainActivity extends Activity { private MyServiceConnection serviceConnection; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); serviceConnection=new MyServiceConnection(); } public void btnBind_onclick(View view){ //綁定 MyService Intent intent=new Intent(this,MyService.class); Log.i(Context.ACTIVITY_SERVICE, "----------onClick bindService-----------"); //通過bindService(intent,serviceConnection,int)方式啟動Service bindService(intent,this.serviceConnection,Context.BIND_AUTO_CREATE); } public void btnUnbind_onclick(View view){ Log.i(Context.ACTIVITY_SERVICE, "----------onClick unbindService----------"); unbindService(serviceConnection); } public void btnStart_onclick(View view){ //通過Intent綁定MyService,加入初始參數 Intent intent=new Intent(MainActivity.this,MyService.class); intent.putExtra("param",0.88); Log.i(Context.ACTIVITY_SERVICE, "----------onClick startService-----------"); //啟動MyService startService(intent); } public void btnStop_onclick(View view){ Intent intent=new Intent(MainActivity.this,MyService.class); Log.i(Context.ACTIVITY_SERVICE, "----------onClick stopService------------"); //停止MyService stopService(intent); } } public class MyService extends Service{ private MyBinder myBinder; private double param; @Override public void onStart(Intent intent, int startId){ Log.i(Context.ACTIVITY_SERVICE,"Service onStart"); super.onStart(intent, startId); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(Context.ACTIVITY_SERVICE, "Service onStartCommand"); //獲取Context.startService設置的param初始值 this.param=intent.getDoubleExtra("param",1.0); return super.onStartCommand(intent, flags, startId); } @Override public IBinder onBind(Intent intent) { Log.i(Context.ACTIVITY_SERVICE,"Service onBind"); return this.myBinder; } @Override public boolean onUnbind(Intent intent){ Log.i(Context.ACTIVITY_SERVICE,"Service onUnbind"); return super.onUnbind(intent); } @Override public void onCreate(){ super.onCreate(); Log.i(Context.ACTIVITY_SERVICE,"Service onCreate"); myBinder=new MyBinder(); } @Override public void onDestroy() { Log.i(Context.ACTIVITY_SERVICE, "Service onDestroy"); super.onDestroy(); } //獲取處理后的值 public double getValue(int value){ return value*param; } public class MyBinder extends Binder { public MyService getService(){ return MyService.this; } } } public class MyServiceConnection implements ServiceConnection{ @Override public void onServiceConnected(ComponentName name, IBinder service){ Log.i(Context.ACTIVITY_SERVICE, "Service Connected"); //通過IBinder獲取Service句柄 MyService.MyBinder myBinder=(MyService.MyBinder)service; MyService myService=myBinder.getService(); //生成隨機數輸入 Random random=new Random(); double value=myService.getValue(random.nextInt(10)*1000); //顯示計算結果 Log.i(Context.ACTIVITY_SERVICE,String.valueOf(value)); } @Override public void onServiceDisconnected(ComponentName name) { Log.i(Context.ACTIVITY_SERVICE, "Service Disconnected"); } }
通過startService() 啟動服務后,多次使用bindService()綁定服務,unbindService()解除綁定,最后通過stopService()結束服務后,可以看到下面的結果
這時候 Service 的onBind()方法和onUnbind()方法只在第一個bindService流程中觸發,其后多次調用bindService(),此事件都不會被觸發,而只會觸發onServiceConnected()事件。這是因為在默認情況下,系統在綁定時會先搜索IBinder接口,如果Service已經綁定了Binder對象,系統就會直接跳過onBind()方法。
既然 onBind(),onUnbind()方法只會在第一次啟動綁定時被調用,如果在多次綁定時需要有不同的處理方式又該如何,還好Android為大家預備了一個備用方法void onRebind(intent),Service服務中 boolean onUnbind(intent)的默認返回值為false,只要將此方法的返回值修改為true,則系統在第二次調用Context.bindService()開始,就會激活Service.onRebind(intent)方法。在此對上面的方法作出少量修改,就會看到下面的處理結果。
public class MyService extends Service{ ........... @Override public void onRebind(Intent intent){ Log.i(Context.ACTIVITY_SERVICE,"Service onRebind"); super.onRebind(intent); } @Override public boolean onUnbind(Intent intent){ Log.i(Context.ACTIVITY_SERVICE,"Service onUnbind"); //將返回值設置為true return true; } ........... ........... }
運行結果
注意:此使用方法只適用 startService()、bindServcie()同時被調用的情況下,如果只調用其中一個方法,無論onUnbind()返回值為何值都無法觸發onRebind()方法
回到目錄
四、通過多線程方式處理 Service 的延時性操作
4.1 以 Runnable接口實現 Service 多線程操作
由于Android 系統的資源有限,而且對屏幕顯示,事件發應,用戶體現都有較高的要求,所以在CPU、RAM、GPU、GPU都有獨立的運行機制。當主線程中存在大文件讀取、圖片批量處理、網絡連接超時等操作時,一旦時間超過5秒,Android 系統就會出現 “設置運行緩慢” 的提示,Logcat日志上也會顯示 “The application may be doing too much work on its main thread” 等提示。在開發Service服務時,若存在此類操作時,開發人員就應該嘗試使用多線程方式進行開發,避免主線程被長時間占用。下文將以簡單的 Runnable 接口方式實現多線程作為例子。
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void btnStart_onclick(View view){ Intent intent=new Intent(MainActivity.this,MyService.class); Log.i(Context.ACTIVITY_SERVICE, "----------onClick startService-----------------"); startService(intent); } public void btnStop_onclick(View view){ Intent intent=new Intent(MainActivity.this,MyService.class); Log.i(Context.ACTIVITY_SERVICE, "----------onClick stopService------------------"); stopService(intent); } } public class MyService extends Service{ @Override public void onCreate(){ Log.i(Context.ACTIVITY_SERVICE,"Service onCreate"); super.onCreate(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(Context.ACTIVITY_SERVICE, "Service onStartCommand"); Log.i(Context.ACTIVITY_SERVICE,"Main thread id is "+Thread.currentThread().getId()); //以異步方式進行模擬操作 Thread background=new Thread(new AsyncRunnable()); background.start(); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { Log.i(Context.ACTIVITY_SERVICE, "Service onDestroy"); super.onDestroy(); } } public class AsyncRunnable implements Runnable { @Override public void run() { try { Log.i(Context.ACTIVITY_SERVICE,"Async thread id is "+Thread.currentThread().getId()); //虛擬操作 for(int n=0;n<8;n++){ Thread.sleep(1000); Log.i(Context.ACTIVITY_SERVICE,"****Do Work****"); } } catch (InterruptedException e) { // TODO 自動生成的 catch 塊 e.printStackTrace(); } } }
請留意運行結果,主線程與onStartCommand()方法內部操作存在于不同的線程當中完成
4.2 IntentService 服務簡介
在Service服務中出現延時性操作是普遍遇到的情況,有見及此 Android 系統早為開發人員提供了一個Service的子類IntentService,當IntentService執行 startService()方法時,系統將使用一個循環程序將該服務加入到一個子線程隊列當中,以便執行服務當中的操作。下面為大家提供 IntentService的源代碼,讓各位更好的理解IntentService的運行方式。
public abstract class IntentService extends Service { private volatile Looper mServiceLooper; private volatile ServiceHandler mServiceHandler; private String mName; private boolean mRedelivery; private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { onHandleIntent((Intent)msg.obj); stopSelf(msg.arg1); } } public IntentService(String name) { super(); mName = name; } public void setIntentRedelivery(boolean enabled) { mRedelivery = enabled; } @Override public void onCreate() { super.onCreate(); HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); thread.start(); mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); } @Override public void onStart(Intent intent, int startId) { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; mServiceHandler.sendMessage(msg); } @Override public int onStartCommand(Intent intent, int flags, int startId) { onStart(intent, startId); return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; } @Override public void onDestroy() { mServiceLooper.quit(); } @Override public IBinder onBind(Intent intent) { return null; } protected abstract void onHandleIntent(Intent intent); }
從代碼中可以看到,系統沒有在onStartCommand()中創建新線程,而是在onCreate()方法中建立了獨立的工作線程,這是由于onCreate()方法只會在新建服務時被調用一次,可見這樣的目的是為了讓系統在單個線程中執行多個異步任務。當系統調用Context.startService()方法時,系統將通過onStart()方法使用異步方式,調用ServiceHandler.handleMessage(msg)進行處理,而handleMessage(msg)正是調用了虛擬方法onHandleIntent(intent),然后以stopSelf()結束服務。所以用戶只需要在繼承類中重寫onHandleIntent(intent)方法,便可以以異步方法執行IntentService。
4.3 IntentService 應用
以下面一個簡單的例子說明一下IntentService的應用,建立一個MyIntentService類繼承IntentService,實現onHandleIntent(Message msg)方法。然后在MainActivity活動分別3次以不同參數調用intentService,觀察其運行的線程狀態。
public class MyIntentService extends IntentService { public MyIntentService() { super(null); } @Override protected void onHandleIntent(Intent intent) { String msg=intent.getStringExtra("msg"); Log.i(Context.ACTIVITY_SERVICE,msg+"'s thread id is "+Thread.currentThread().getId()); } } public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void btnStart_onclick(View view){ Log.i(Context.ACTIVITY_SERVICE, "----------onClick startService--------------"); Log.i(Context.ACTIVITY_SERVICE,"Main thread id is "+Thread.currentThread().getId()); Intent intent1=new Intent(this,MyIntentService.class); intent1.putExtra("msg", "intentService1"); startService(intent1); Intent intent2=new Intent(this,MyIntentService.class); intent2.putExtra("msg", "intentService2"); startService(intent2); Intent intent3=new Intent(this,MyIntentService.class); intent3.putExtra("msg", "intentService3"); startService(intent3); } public void btnStop_onclick(View view){ Intent intent=new Intent(MainActivity.this,MyIntentService.class); Log.i(Context.ACTIVITY_SERVICE, "----------onClick stopService-------------"); stopService(intent); } }
在AndroidManifest.xml 文件設置服務
<application> .......... <service android:name="android.services.MyIntentService" android:enabled="true"> </service> </application>
從運行結果中可以看出,同一時間多次啟動startService()調用intentService,它們都將運行于同一個異步線程當中,這一點在這里得到了證實。
回到目錄
五、淺談 Remote Service 原理
5.1 跨進程通信的使用場景
以上章節所舉的例子都是使用Local Service 技術,Serivce服務端與Client客戶端都是在于同一進程當中,當APP被卸御,Service服務也被同時卸御。要是想把服務端與客戶端分別放在不同的進程當中進行跨進程信息交換的話,就需要使用到下面介紹的遠程通信服務 Remote Service。使用Remote Service可以把服務端與客戶端分離,當一方被卸御,另一方不會被影響。當今有很多企業都有多個獨立的APP,如阿里巴巴旗下就天貓、淘寶、聚劃算、支付寶等多個APP,這時候就有需要把Service服務放在一獨立的后臺進程當中,作為多個APP之間信息交換的橋梁。這樣如用戶信息,用戶登錄,身份驗證等多個共用的模塊都可以在Service服務中實現,以供不同的APP進行調用。而且當APP被關閉時,Service服務還會寄存在后臺當中,對用戶的操作進行檢測。如今越來越多的企業都使用這種開發方式,以收集用戶像所在地點,通信錄,短信,彩信等個人信息,方便企業針對用戶的個人資料進行產品推廣。
5.2 Remote Service 技術背景
Android 系統與 Windows 系統的通信原則基本一致,進程就是安全策略的邊界,不同的APP屬于不同進程 Process,一個進程不能直接訪問其他進程的資源。需要實現多進程間的通信,就要使用IPC(Inter Process Commnication)進程間通信技術。Android 系統的 IPC 機制是基于 RPC (Remote Proceduce Call) 遠程過程調用協議建立的,與 Java 使用的 RMI(Rmote Methed Invocation)遠程方法調用相比,不同之處在于Android的IPC機制是基于AIDL(Android Interface Definition Language)接口定義語言定制進程間的通訊規則的。系統會基于 AIDL 規則把信息進行序列化處理,然后發送到另一個進程當中,Android 系統把這種基于跨進程通信的服務稱作 Remote Service 。
5.3 IPC 運作原理
從底層架構分析, Android 系統中 IPC 的運作主要依賴于 “ServiceManager” 和 “Binder Driver” 兩個核心元件,下面給大家簡單介紹一下它們的運作原理:
ServiceManager 簡介
ServiceManager是Android系統內的服務管理器,主要負責管理 Service 服務的管理,注冊,調用等任務。在Google提供的Android原始代碼中可以找到(文件夾路徑:frameworks/base/cmds/servicemanager),有C語言開發基礎且有興趣的朋友可以下載看一下,當中包含了幾個核心的函數:
int svcmgr_handler(struct binder_state *bs, struct binder_txn *txn, struct binder_io *msg, struct binder_io *reply)
int do_add_service(struct binder_state *bs, uint16_t *s, unsigned len, void *ptr, unsigned uid)
void *do_find_service(struct binder_state *bs, uint16_t *s, unsigned len)
void binder_loop(struct binder_state *bs, binder_handler func)
ServiceManager 啟動后會通過 binder_loop 循環對 Binder Driver 進行監聽,當發現了有新的Service服務請求后,就會調用 svcmgr_handler() 函數對檢測的Service服務進行處理,通過*do_find_service()函數可以在svclist集中檢測Service服務,若當前svclist服務集中未存在當前服務,就會通過do_add_service()進行注冊,把當前服務及其唯一標識符加入到svclist中,這樣當前的 Service 服務被綁定后就完成在ServiceManager的注冊。Binder Driver 會按照規定的格式把它轉化為 Binder 實體發送到內核當中,當被 Client 調用時 ServiceManager 會根據 Service 服務的標識符在 svclist 中找到該 Binder 實體,并把 Binder 實體的引用發送給Client。完成計算后 Binder Driver 會進行數據處理,把計算結果發回到Client客戶端。由于Binder實體是以強類型的形式存在,所以即使被多次引用,系統都會指向同一個Binder實體,除非所有都結束鏈接,否則Binder實體會一直存在。
圖 5.3
Binder Driver簡介
Binder Driver運行于Android 內核當中,它以 “字符驅動設備” 中的 “misc設備注冊” 存在于設備目錄 dev/binder,由于權限問題,在一般手機中沒有權限進行復制,對此有興趣的朋友可以在google 提供的 android 源代碼中查看。它提供open(),mmap(),poll(),ioctl() 等函數進行標準化文件操作,負責進程之間Binder通信的建立,Binder實體在進程之間的傳遞,Binder實體引用的計數管理,數據包在進程之間的傳遞與交互等一系列底層操作。
5.4 Remote Service 常用接口
在 5.3 節以 Android 底層結構的方式簡單介紹了一下 IPC 通信的原理,下面將以 JAVA 應用層方式再作介紹。
IBinder 接口
IBinder 是 Remote Service 遠程服務的常用接口,Binder是它的實現類,它是為高性能而設計的輕量級遠程調用機制的核心部分。IBinder 內部比較重要的方法就是 boolean transact(int code, Parcel data, Parcel reply, int flags) ,它負責在服務器與客戶端之間進行信息交換,調用遠程方法進行處理,然后把返回值轉換成可序列化對象送回客戶端。
5.5 Remote Service 開發實例
首先新建一個項目作為服務端, 建立 AIDL 文件 ITimerService.aidl,系統會根據接口描述自動在gen文件夾內生成對應的類文件 ITimerService.java ,當中 Stub 擴展了 android.os.Binder 并利用 transact ()實現了 ITimerService 接口中方法的遠程調用。
package com.example.remoteservice; interface ITimerService{ String getTimeNow(); }
gen\com\example\remoteservice\ITimerService.java (自動生成)
package com.example.remoteservice; public interface ITimerService extends android.os.IInterface { /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.example.remoteservice.ITimerService { private static final java.lang.String DESCRIPTOR = "com.example.remoteservice.ITimerService"; /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.example.remoteservice.ITimerService interface, * generating a proxy if needed. */ public static com.example.remoteservice.ITimerService asInterface(android.os.IBinder obj) { if ((obj==null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin!=null)&&(iin instanceof com.example.remoteservice.ITimerService))) { return ((com.example.remoteservice.ITimerService)iin); } return new com.example.remoteservice.ITimerService.Stub.Proxy(obj); } @Override public android.os.IBinder asBinder() { return this; } @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_getTimeNow: { data.enforceInterface(DESCRIPTOR); java.lang.String _result = this.getTimeNow(); reply.writeNoException(); reply.writeString(_result); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.example.remoteservice.ITimerService { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public java.lang.String getTimeNow() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.lang.String _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getTimeNow, _data, _reply, 0); _reply.readException(); _result = _reply.readString(); } finally { _reply.recycle(); _data.recycle(); } return _result; } } static final int TRANSACTION_getTimeNow = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); } public java.lang.String getTimeNow() throws android.os.RemoteException; }
然后建立服務TimerService,建立內置類TimerServiceImpl實現接口ITimerService中的方法,由于使用 Remote Service 只能使用 bindService()方式對服務進行遠程綁定,所以TimerService中須利用 onBind() 方法綁定 TimerServiceImpl 對象。
public class TimerService extends Service { @Override public IBinder onBind(Intent intent) { // TODO 自動生成的方法存根 return new TimerServiceImpl(); } public class TimerServiceImpl extends ITimerService.Stub{ @Override public String getTimeNow() throws RemoteException { // 獲取當時時間與服務器端的進程Id Date date=new Date(); SimpleDateFormat formatter = new SimpleDateFormat("E yyyy.MM.dd 'at' hh:mm:ss a zzz"); return "Time now is "+ formatter.format(date)+"\nService processId is "+Process.myPid(); } } }
在 AndroidManifest.xml 文件設置服務綁定,在 action 項的 android:name 中綁定當前服務的接口
<application> ........ <service android:name=".TimerService" android:process=":remote"> <intent-filter> <action android:name="com.example.remoteservice.ITimerService"/> </intent-filter> </service> </application>
服務器端完成配置后建立一個客戶端項目,把ITimerService.aidl文件copy到客戶端,此時客戶端也會在gen文件夾中自動生成ITimerService.java文件。在Activity中調用Remote Service時請注意,android 4.0 及以下版本,可通過 Intent(string action) 構造函數生成后直接調用。android 5.0 及以上版本需通過intent.setPackage(string packageName)指定action的包名稱。
public class MainActivity extends Activity { private MyServiceConnection serviceConnection; private boolean connected; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //建立ServiceConnection對象 serviceConnection=new MyServiceConnection(); } public void btnBind_onclick(View view){ Intent intent=new Intent(); //綁定遠程服務接口 intent.setAction("com.example.remoteservice.ITimerService"); intent.setPackage("com.example.remoteservice"); this.connected=true; Log.i(Context.ACTIVITY_SERVICE, "-------onClick bindService--------"); bindService(intent,this.serviceConnection,Context.BIND_AUTO_CREATE); } public void btnUnbind_onclick(View view){ Log.i(Context.ACTIVITY_SERVICE, "-------onClick unbindService---------"); if(connected){ unbindService(serviceConnection); connected=false; } } } public class MyServiceConnection implements ServiceConnection{ @Override public void onServiceConnected(ComponentName name, IBinder service){ // TODO 自動生成的方法存根 Log.i(Context.ACTIVITY_SERVICE, "Service Connected"); //獲取遠程對象 ITimerService timerService=ITimerService.Stub.asInterface(service); String data=null; try { data=timerService.getTimeNow()+"\nClient processId is "+Process.myPid(); } catch (RemoteException e) { // TODO 自動生成的 catch 塊 e.printStackTrace(); } Log.i(Context.ACTIVITY_SERVICE,data); } }
從運行結果可清晰看到Service與Client運行于不同的進程當中
5.6 Remote Service 復雜類型數據傳輸
當 Remote Service 需要使用自定義類型的數據進行傳輸時,數據對象需要經過序列化處理,而 Android 對象的序列化處理有兩種方式,一是常用方式 Serializable 接口,另一個是 Android 獨有的Parcelable 接口。由于常用的 Serializable 接口,會使用大量的臨時變量耗費內存而導致大量的GC垃圾回收,引起手機資源不足,因此 Android 研發出 Parcelable 接口實現對象的序列化。它可被看作為一個 Parcel 容器,通過 writeToParcel() 與 createFormParcel() 方法把對象讀寫到 Parcel 當中,Parcelable接口如下:
public interface Parcelable { //內容描述接口 public int describeContents(); //對象序列化方式 public void writeToParcel(Parcel dest, int flags); //反序列化對象,使用泛型方式在Parcel中構造一個實現了Parcelable的類的實例處理。 //接口分別定義了單個實例和多個實例 public interface Creator<T> { public T createFromParcel(Parcel source); public T[] newArray(int size); } }
首先建立服務端,新建Person.aidl文件
package com.example.remoteservice; parcelable Person;
建立Person類,實現Parcelable接口
public class Person implements Parcelable { private String name; private Integer age; private String desc; public Person(){ } public Person(String name, Integer age, String desc) { // TODO 自動生成的構造函數存根 this.name=name; this.age=age; this.desc=desc; } public String getName(){ return this.name; } public void setName(String name){ this.name=name; } public Integer getAge(){ return this.age; } public void setAge(Integer age){ this.age=age; } public String getDesc(){ return this.desc; } public void setDesc(String desc){ this.desc=desc; } @Override public int describeContents() { // TODO 自動生成的方法存根 return 0; } @Override public void writeToParcel(Parcel dest, int flags) { // TODO 自動生成的方法存根 dest.writeString(name); dest.writeInt(age); dest.writeString(desc); } public static final Parcelable.Creator<Person> CREATOR = new Creator<Person>() { /** * 創建一個要序列號的實體類的數組,數組中存儲的都設置為null */ @Override public Person[] newArray(int size) { return new Person[size]; } /*** * 根據序列號的Parcel對象,反序列號為原本的實體對象 * 讀出順序要和writeToParcel的寫入順序相同 */ @Override public Person createFromParcel(Parcel source) { String name = source.readString(); int age = source.readInt(); String desc = source.readString(); Person Person = new Person(); Person.setName(name); Person.setAge(age); Person.setDesc(desc); return Person; } }; }
建立服務IPersonService.aidl文件
package com.example.remoteservice; import com.example.remoteservice.Person; interface IPersonService{ Person getPerson(String number); }
此時在gen\com\example\remoteservice文件夾內將自動生成成IPersonService.java類
/* * This file is auto-generated. DO NOT MODIFY. * Original file: D:\\Java_Projects\\RemoteService\\src\\com\\example\\remoteservice\\IPersonService.aidl */ package com.example.remoteservice; public interface IPersonService extends android.os.IInterface { /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.example.remoteservice.IPersonService { private static final java.lang.String DESCRIPTOR = "com.example.remoteservice.IPersonService"; /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.example.remoteservice.IPersonService interface, * generating a proxy if needed. */ public static com.example.remoteservice.IPersonService asInterface(android.os.IBinder obj) { if ((obj==null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin!=null)&&(iin instanceof com.example.remoteservice.IPersonService))) { return ((com.example.remoteservice.IPersonService)iin); } return new com.example.remoteservice.IPersonService.Stub.Proxy(obj); } @Override public android.os.IBinder asBinder() { return this; } @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_getPerson: { data.enforceInterface(DESCRIPTOR); java.lang.String _arg0; _arg0 = data.readString(); com.example.remoteservice.Person _result = this.getPerson(_arg0); reply.writeNoException(); if ((_result!=null)) { reply.writeInt(1); _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } else { reply.writeInt(0); } return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.example.remoteservice.IPersonService { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public com.example.remoteservice.Person getPerson(java.lang.String number) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); com.example.remoteservice.Person _result; try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeString(number); mRemote.transact(Stub.TRANSACTION_getPerson, _data, _reply, 0); _reply.readException(); if ((0!=_reply.readInt())) { _result = com.example.remoteservice.Person.CREATOR.createFromParcel(_reply); } else { _result = null; } } finally { _reply.recycle(); _data.recycle(); } return _result; } } static final int TRANSACTION_getPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); } public com.example.remoteservice.Person getPerson(java.lang.String number) throws android.os.RemoteException; }
然后建立服務PersonService,建立內置類PersonServiceImpl實現接口IPersonService中的方法,在 PersonService 中須利用 onBind() 方法綁定 PersonServiceImpl 對象。
public class PersonService extends Service { @Override public IBinder onBind(Intent intent) { // TODO 自動生成的方法存根 return new PersonServiceImpl(); } public class PersonServiceImpl extends IPersonService.Stub{ @Override public Person getPerson(String number) throws RemoteException { // TODO 自動生成的方法存根 switch(number){ case "0": return new Person("Jack Mokei",34,"Project Manager"); case "1": return new Person("Mike Tlea",24,"Team Leader"); default: return null; } } } }
在 AndroidManifest.xml 文件設置服務綁定,在 action 項的 android:name 中綁定當前服務的接口
<application> ........ <service android:name=".PersonService" android:process=":remote"> <intent-filter> <action android:name="com.example.remoteservice.IPersonService"/> </intent-filter> </service> </application>
服務器端完成配置后建立一個客戶端項目,把Person.aidl、IPersonService.aidl文件copy到客戶端,此時客戶端也會在gen文件夾中自動生成 Person.java和 IPersonService.java文件
public class MainActivity extends Activity { private MyServiceConnection serviceConnection; private boolean connected; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //建立ServiceConnection對象 serviceConnection=new MyServiceConnection(); } public void btnBind_onclick(View view){ Intent intent=new Intent(); //綁定遠程服務接口 intent.setAction("com.example.remoteservice.IPersonService"); intent.setPackage("com.example.remoteservice"); this.connected=true; Log.i(Context.ACTIVITY_SERVICE, "----------onClick bindService----------"); bindService(intent,this.serviceConnection,Context.BIND_AUTO_CREATE); } public void btnUnbind_onclick(View view){ Log.i(Context.ACTIVITY_SERVICE, "----------onClick unbindService--------"); if(connected){ unbindService(serviceConnection); connected=false; } } } public class MyServiceConnection implements ServiceConnection{ @Override public void onServiceConnected(ComponentName name, IBinder service){ Log.i(Context.ACTIVITY_SERVICE, "Service Connected"); //綁定遠程對象 IPersonService personService=IPersonService.Stub.asInterface(service); String data=null; try { Person person=personService.getPerson("0"); data=person.getName()+"'s age is "+person.getAge(); } catch (RemoteException e) { // TODO 自動生成的 catch 塊 e.printStackTrace(); } Log.i(Context.ACTIVITY_SERVICE,data); } }
運行結果
回到目錄
本章總結
通過文章的例子大家可以了解到Local Service本地服務與Remote Service遠程服務之間的區別,以及Context.startService()方法以及Context.bindService()方法不同的使用場景。希望文章有幫于大家對Service服務有更深入的了解,在不同的開發環境中靈活運用。由于時間倉促,文章當中有不明確的地方或有錯漏敬請點明。
Android開發筆記
全面剖釋 Android Service 服務
Cordova(PhoneGap)通過CordovaPlugin插件調用 Activity 實例
最新版Cordova 5.1.1(PhoneGap)搭建開發環境
作者:風塵浪子
http://www.cnblogs.com/leslies2/p/5401813.html
原創作品,轉載時請注明作者及出處
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。