編輯:關於Android編程
AndroidService 深度解析(2)
上一篇文章我們對Service的生命周期進行了測試及總結。這篇文章我們介紹下綁定運行的Service的實現。
綁定運行的Service可能是僅為本應用提供服務,稱為本地Service;也可能為其他應用提供跨進程服務,即遠程Service。下面分別進行介紹:
如果Service只服務於本應用,那麼我們只需要繼承Binder類,定義我們需要實現的方法即可,當發起綁定連接時,Service將會在onBind方法中返回這個繼承類的對象,使得客戶端與Service共享一個Binder對象。Binder就像一座橋梁,使客戶端與Service能夠互相聯系。下面貼出本地Service的實現示例:
LocalService代碼:
public classLocalService extends Service { private String TAG =getClass().getSimpleName(); MyBinder myBinder = new MyBinder(); ServiceListener myServiceListener; public LocalService() { } public interface ServiceListener { public String getActivityInfo(); } private void setListener(ServiceListenermyServiceListener) { this.myServiceListener = myServiceListener; } //綁定成功後,Service就可以通過這個方法獲得Activity的信息 private void getActivityInfo() { String activityInfo =myServiceListener.getActivityInfo(); Log.d(TAG, TAG ++activityInfo------> + activityInfo); } private String getInfo() { return Hello,我是LocalService的方法,你可以通過它的對象訪問我!; } public class MyBinder extends Binder { public String getServiceInfo() { return getInfo(); } public void setServiceListener(ServiceListenermyServiceListener) { setListener(myServiceListener); } } @Override public IBinder onBind(Intent intent) { Log.d(TAG, TAG +------>onBind()); return myBinder; } @Override public void onRebind(Intent intent) { Log.d(TAG, TAG +------>onRebind()); super.onRebind(intent); } @Override public boolean onUnbind(Intent intent) { Log.d(TAG, TAG +------>onUnbind()); //return false;這裡的返回值決定下一次綁定時是否執行onRebind return true; } }
LocalActivity代碼:
public classLocalActivity extends ActionBarActivity { private String TAG =getClass().getSimpleName(); Intent serviceIntent; @Override protected void onCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_local); Log.d(TAG, TAG +------>onCreate()); serviceIntent = new Intent(this,LocalService.class); } public void bindService(View v) { Log.d(TAG, TAG +------>bindService()); bindService(serviceIntent,serviceConnection, Service.BIND_AUTO_CREATE); } public void unBindService(View v) { Log.d(TAG, TAG +------>unBindService()); unbindService(serviceConnection); } @Override protected void onDestroy() { Log.d(TAG, TAG +------>onDestroy()); super.onDestroy(); } ServiceConnection serviceConnection = newServiceConnection() { @Override public voidonServiceConnected(ComponentName name, IBinder service) { Log.d(TAG, TAG +------>onServiceConnected()); LocalService.MyBinder binder =(LocalService.MyBinder) service; String localServiceInfo =binder.getServiceInfo(); Log.d(TAG, TAG ++onServiceConnected------> + localServiceInfo); binder.setServiceListener(newLocalService.ServiceListener() { @Override public String getActivityInfo(){ return Hello,我在LocalActivity中,LocalService可以調用我獲得LocalActivity的消息!; } }); } @Override public voidonServiceDisconnected(ComponentName name) { Log.d(TAG, TAG +------>onServiceDisconnected()); } }; }
Activity對應的布局就是兩個按鈕,分別實現綁定和解綁功能,如圖:
Activity與Service都是需要在Manifest文件中注冊的哦。
我們啟動Activity,先後綁定Service,輸出日志如下:
03-17 10:10:58.525 D/LocalActivity﹕LocalActivity------>onCreate() 03-17 10:11:00.955 D/LocalActivity﹕LocalActivity------>bindService() 03-17 10:11:00.975 D/LocalService﹕LocalService------>onBind() 03-17 10:11:00.995 D/LocalActivity﹕LocalActivity------>onServiceConnected() 03-17 10:11:00.995 D/LocalActivity﹕ LocalActivity+onServiceConnected------>Hello,我是LocalService的方法,你可以通過它的對象訪問我! 03-17 10:11:16.345 D/LocalActivity﹕LocalActivity------>unBindService() 03-17 10:11:16.345 D/LocalService﹕LocalService------>onUnbind()
上面的日志顯示,我們的確實現了Service的綁定與解綁工作,不僅如此,細心的你應該還發現了我們實現了Service與Activity中相互的調用吧。是的,在實際工作中我們不僅需要指示Service為我們提供服務,Service有時也需要獲取到客戶端的數據來更好地提供服務(LocalService中的getActivityInfo方法 通過回調實現)。
這裡我總結下具體的實現過程:
1、在Service類中,設計繼承Binder類的內部類MyBinder,添加需要向Activity提供的方法。本例中的getServiceInfo方法實現了獲取Service信息的功能,當然有時候為了簡便,我們直接提供方法返回Service對象,但是一般並不建議這樣做;同時注意到setServiceListener方法,它是實現Service調用Activity提供方法的重要環節,我們通過回調的方法實現了Service對Activity的訪問;
2、重寫onBind方法,並返回MyBinder對象。至此,Service類的設計就完成了;
3、在Activity中,重寫ServiceConnection接口的onServiceConnected與onServiceDisConnected方法;在onServiceConnected方法中,我們獲得了onBinder方法返回的MyBinder對象,然後調用setServiceListener方法設置Service訪問Activity所需要的回掉接口對象;
4、至此,Service與Activity之間的 “橋梁”搭建完畢。在Service中我們可以通過getActivityInfo方法獲得Activity的信息;而在Activity中,我們也可以通過getServiceInfo方法獲得Service的信息。
當我們的Service需要為其他應用提供服務的時候,我們就要用到遠程Service了。遠程Service有兩種實現方式,分別是Messenger方式與AIDL(Andriod進程間接口描述語言)方式。下面分別進行介紹。
在這種方式中,我們定義一個Handler來處理不同的Message對象。這個Handler是Messenger實現與客戶端共享IBinder的基礎,它允許客戶端通過Message對象向Service發送命令。另外,客戶端也可以定義一個Messenger,這樣,Service也可以把消息發送給客戶端。 這是實現進程間通信的最簡單的方式,因為Messenger隊列將會在單線程中執行,我們不需要去考慮線程安全。
使用Messenger實現進程間通信的步驟:
l 實現一個Handler,它用來處理傳遞的Message對象;
l 創建一個Messenger對象,將Handler對象作為構造參數;
l 使用Messenger對象創建一個IBinder對象,並通過onBind返回;
l 客戶端將接收到的IBinder對象作為Messenger的構造參數,實例化一個Messenger對象,這個Messenger對象將擁有Handler的引用;
l 在客戶端通過Handler發送Message對象,Service中就可以通過Handler的handleMessage處理這個Message。
下面提供一個示例:
我們在ServiceTest工程下新建MessengerService類:
public class MessengerService extendsService { /** * Command to the service to display a message */ static final int MSG_SAY_HELLO = 1; static final int MSG_GET_CLIENT_MESSENGER = 2; static final int MSG_FROM_SERVICE = 3; private String TAG = getClass().getSimpleName(); Messenger messengerToClient; /** * Handler of incoming messages from clients. */ class ServiceIncomingHandler extends Handler { @Override public void handleMessage(Message msg) { Log.d(TAG, TAG + ------>handleMessage()); switch (msg.what) { case MSG_SAY_HELLO: Log.d(TAG,handleMessage------>MSG_SAY_HELLO!); Toast.makeText(getApplicationContext(), hello!,Toast.LENGTH_SHORT).show(); break; case MSG_GET_CLIENT_MESSENGER: Log.d(TAG,handleMessage------>Service收到Activity的messenger對象!); //此處獲得可向客戶端發送消息的Messenger對象 messengerToClient =msg.replyTo; Message serviceMsg =Message.obtain(null, MSG_FROM_SERVICE, 0, 0); try {//向客戶端發送消息 messengerToClient.send(serviceMsg); } catch (RemoteException e){ e.printStackTrace(); } break; default: super.handleMessage(msg); } } } /** * 將這個serviceMessenger發送給客戶端,客戶端就可以通過它聯系Service了 */ final Messenger serviceMessenger = new Messenger(newServiceIncomingHandler()); /** * When binding to the service, we return an interface to our messenger * for sending messages to the service. */ @Override public IBinder onBind(Intent intent) { Log.d(TAG, TAG + ------>onBind()); Toast.makeText(getApplicationContext(), binding,Toast.LENGTH_SHORT).show(); return serviceMessenger.getBinder(); } }
新建MessengerTest工程,並創建ActivityMessenger類:
public classActivityMessenger extends Activity { /** * Messenger for communicating with theservice. */ Messenger messengerToService = null; /** * Flag indicating whether we have calledbind on the service. */ boolean mBound; private String TAG =getClass().getSimpleName(); /** * Command to the service to display amessage */ static final int MSG_SAY_HELLO = 1; static final intMSG_SEND_MESSENGER_TO_SERVICE = 2; static final int MSG_FROM_SERVICE = 3; /** * Class for interacting with the maininterface of the service. */ private ServiceConnection mConnection = newServiceConnection() { public voidonServiceConnected(ComponentName className, IBinder service) { // This is called when theconnection with the service has been // established, giving us theobject we can use to // interact with the service. We are communicating with the // service using a Messenger, sohere we get a client-side // representation of that from theraw IBinder object. Log.d(TAG, TAG +------>onServiceConnected()); messengerToService = newMessenger(service); mBound = true; Message msg = Message.obtain(null,MSG_SEND_MESSENGER_TO_SERVICE, 0, 0); msg.replyTo = activityMessenger; try {//Messenger對象發送消息,這個msg對象將交給Service類中的handleMessage處理 messengerToService.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } public voidonServiceDisconnected(ComponentName className) { // This is called when theconnection with the service has been // unexpectedly disconnected --that is, its process crashed. Log.d(TAG, TAG +------>onServiceDisconnected()); messengerToService = null; mBound = false; } }; /** * Handler of incoming messages fromservice. */ class IncomingHandler extends Handler { @Override public void handleMessage(Message msg){ Log.d(TAG, TAG + ------>handleMessage()); switch (msg.what) { case MSG_FROM_SERVICE: Log.d(TAG, TAG ++MSG_FROM_SERVICE------>Activity收到Service回復的消息!); Toast.makeText(getApplicationContext(), MSG_FROM_SERVICE!,Toast.LENGTH_SHORT).show(); break; default: super.handleMessage(msg); } } } /** * Target we publish for service to sendmessages to IncomingHandler. */ final Messenger activityMessenger = newMessenger(new IncomingHandler()); public void sayHello(View v) { Log.d(TAG, TAG +------>sayHello()); if (!mBound) return; // Create and send a message to theservice, using a supported 'what' value Message msg = Message.obtain(null,MSG_SAY_HELLO, 0, 0); try {//Messenger對象發送消息,這個msg對象將交給Service類中的handleMessage處理 messengerToService.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } @Override protected void onCreate(BundlesavedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, TAG +------>onCreate()); } @Override protected void onStart() { Log.d(TAG, TAG +------>onStart()); super.onStart(); // Bind to the service bindService(newIntent(com.example.servicestudy.remoteservie.MessengerService),mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { Log.d(TAG, TAG +------>onStop()); super.onStop(); // Unbind from the service if (mBound) { unbindService(mConnection); mBound = false; } } }
Acitivty與Service都需要在Manifest文件中注冊。Service需要設置android:exported屬性為true,並設置intent-filter。如下所示:
好了,編碼工作我們已經完成了,下面我們進行一下測試,打印日志如下:
03-1713:11:46.195 D/ActivityMessenger﹕ActivityMessenger------>onCreate() 03-1713:11:46.195 D/ActivityMessenger﹕ActivityMessenger------>onStart() 03-1713:11:46.215 D/MessengerService﹕MessengerService------>onBind() 03-17 13:11:46.235 D/ActivityMessenger﹕ActivityMessenger------>onServiceConnected() 03-1713:11:46.275 D/MessengerService﹕MessengerService------>handleMessage() 03-17 13:11:46.275 D/MessengerService﹕ handleMessage------>Service收到Activity的messenger對象! 03-17 13:11:46.285 D/ActivityMessenger﹕ActivityMessenger------>handleMessage() 03-17 13:11:46.285 D/ActivityMessenger﹕ ActivityMessenger+MSG_FROM_SERVICE------>Activity收到Service回復的消息! 03-1713:11:55.425 D/ActivityMessenger﹕ ActivityMessenger------>sayHello() 03-1713:11:55.425 D/MessengerService﹕MessengerService------>handleMessage() 03-1713:11:55.425 D/MessengerService﹕handleMessage------>MSG_SAY_HELLO! 03-1713:12:00.665 D/ActivityMessenger﹕ActivityMessenger------>onStop()
看見了嗎?完美實現了Activity與Service互相發送消息。它們都是基於Messenger對象,Activity從Ibinder處獲得一個可向Service發送消息的Messenger對象,接著Activity給Service發送了一個消息,並將可向Activity發送消息的Messenger對象攜帶過去,這樣就實現了他們之間的交互。邏輯上和便馬上都很簡單易懂吧,點個贊。
好了,既然Messenger這麼簡單易用,為什麼我們還需要繼續看AIDL方式呢?不知道你有沒有想過,Messenger方式在處理客戶端發送的消息時,是將所有消息排成一個隊列,然後依次處理,也就是單線程處理方式,這種處理方式的優點是簡便不容易引起其他問題,比如線程安全;但是,對於一些即時性要求比較高的服務,這種方式可能就不夠用了,也許我們需要采用多線程的方式,將接收到的請求盡快處理,這時候就可以直接使用AIDL方式了。
其實我悄悄告訴你,Messenger方式的底層實現也是基於AIDL方式實現的,系統為了方便跨進程的服務,為我們提供了一個Messenger類來便利的實現,但是它可能無法滿足我們的需求,這時候我們就需要直接基於AIDL方式實現了。
其實AIDL的實現不難,只是有很多細節需要注意。我這裡也不過多描述推薦一篇文章,有代碼和總結:http://blog.csdn.net/songjinshi/article/details/22918405
我把總結摘抄過來:
AIDL的創建方法:
AIDL語法很簡單,可以用來聲明一個帶一個或多個方法的接口,也可以傳遞參數和返回值。由於遠程調用的需要, 這些參數和返回值並不是任何類型.下面是些AIDL支持的數據類型:
1. 不需要import聲明的簡單Java編程語言類型(int,boolean等)
2.String, CharSequence不需要特殊聲明
3.List, Map和Parcelables類型, 這些類型內所包含的數據成員也只能是簡單數據類型, String等其他比支持的類型. ( (另外: 我沒嘗試Parcelables, 在Eclipse+ADT下編譯不過, 或許以後會有所支持).
下面是AIDL語法:
// 文件名: SomeClass.aidl // 文件可以有注釋, 跟java的一樣 // 在package以前的注釋, 將會被忽略. // 函數和變量以前的注釋, 都會被加入到生產java代碼中. package com.cmcc.demo;
//import 引入語句 import com.cmcc.demo.ITaskCallback;
interfaceITaskBinder {
//函數跟java一樣, 可以有0到多個參數 ,可以有一個返回值 boolean isTaskRunning();
voidstopRunningTask(); //參數可以是另外的一個aidl定義的接口 void registerCallback(ITaskCallback cb);
voidunregisterCallback(ITaskCallback cb);
//參數可以是String, 可以用in表入輸入類型, out表示輸出類型.
intgetCustomerList(in String branch, out String customerList);
}
實現接口時有幾個原則:
.拋出的異常不要返回給調用者. 跨進程拋異常處理是不可取的.
.IPC調用是同步的。如果你知道一個IPC服務需要超過幾毫秒的時間才能完成地話,你應該避免在Activity的主線程中調用。也就是IPC調用會掛起應用程序導致界面失去響應. 這種情況應該考慮單起一個線程來處理.
.不能在AIDL接口中聲明靜態屬性。
IPC的調用步驟:
1. 聲明一個接口類型的變量,該接口類型在.aidl文件中定義。
2. 實現ServiceConnection。
3. 調用ApplicationContext.bindService(),並在ServiceConnection實現中進行傳遞.
4. 在ServiceConnection.onServiceConnected()實現中,你會接收一個IBinder實例(被調用的Service). 調用YourInterfaceName.Stub.asInterface((IBinder)service)將參數轉換為YourInterface類型。
5. 調用接口中定義的方法。你總要檢測到DeadObjectException異常,該異常在連接斷開時被拋出。它只會被遠程方法拋出。
6. 斷開連接,調用接口實例中的ApplicationContext.unbindService()
至此,關於Service的全部學習均已完成。我們進行了Android Service的完整測試學習,主要包括生命周期測試,本地綁定運行Service實現、遠程綁定運行Service的Messenger方式與AIDL方式實現 。 所有BOUND SERVICE的示例均實現了Service與客戶端的交互功能,即Service可以調用客戶端的方法,客戶端也可以調用Service的方法。
我們先來看一張圖,它清晰的說明了整個Android系統的啟動流程,參考Android內核開發:圖解Android系統的啟動過程。第一階段:Android設備上電後,首先會
ActiveAndroid作為輕量級的ORM框架,在快速開發中,使用很簡單,滿足大部分對數據庫操作不復雜的應用。一,配置添加依賴build.gradle中添加:repos
依照郭霖老師的《第一行代碼Android》,今天我要來學習Activity,首先來初步了解Activity,基本上就是照葫蘆畫瓢的模式,有點回到當初敲java的hello
最近項目要求上傳多圖並且多圖顯示,而且要規則的顯示,就像微信朋友圈的圖片顯示一樣。利用GridView再適合不過了,GridView可以動態加載圖片的數量,而且還比較規律