android之Binder設計分析

       接着上篇binder簡要介紹,我們來分析binder機制的設計。binder主要框架分爲三個部分:服務端,binder驅動,客戶端。

       binder在android最常見的使用場景就是一個程序的activity與系統service進行交互。比如我通過wifi的service來獲取wifi控制代理對象,來對wifi進行相關的操作。注意:這裏的系統service是指System server,而不是sdk 中的Service類。

       從Linux空間上來看,Activity,系統service它們都分屬不同的進程,不同進程之間的數據交換就是涉及到了IPC通信。而如果我們從開發者調用角度來看,我們看不到進程的概念。從公共對象請求代理的高度來看Binder,我們會驚異於這種設計思路。Binder相對於Activtiy,service來說是一個很低層的概念。當設計android程序涉及到IPC,我們無需考慮底層的實現細節,而去只關心怎麼去獲取相關服務並通信,這也使我們更專注於軟件的開發,而非傳統基於C/S架構去思考它們之間的數據是如何去實現交換的。

在用戶空間,我們需要做的就是去請求相關有能力的服務對象,不必去了解這個通訊是如何完成。這種設計架構給不僅解決了通訊,引入了一種新的設計理念,也與java面向對象的開發思想契合在一起。這裏,我們看不到binder,我們感覺就像是客戶端直接身服務端請求,然後通過服務端的一個代理對象處理相關工作。Activity與service之間彷彿是一種很直接的,自然的通信。

                                                   

       對於android外部性空間來說,我們不知道服務對象在哪裏,我們只需通過公共代理對象去請求服務。Android系統中,系統級的service都是由serviceManager來管理。藉着serviceManger就可以獲取service的對象引用。

      先了解下serviceManger,它本身也是一個service,但它管理着系統其它的service。Framework提供了一個系統函數BinderInternal.getContextObject(),可以獲取該Service對應的Binder引用。通過這個靜態函數返回的ServiceManager提供的方法又可以獲取其它系統Service的Binder引用。所以serviceManager是整個系統service的總管,也是系統的一個核心對象,它是開機就自啓動的。其它的service都要向它進行註冊並保管引用,這樣保證所有的服務都可以通過servericeManger獲取到引用。這種設計模式的一個好處就是僅暴露一個Binder引用,而其它的系統服務可以隱藏起來,從而有助於系統服務的擴展,以及調用系統服務的安全檢查 。現在我們來看下serviceManger是怎樣處理service的註冊和查詢的。我先看下serviceManger源碼:

在源碼裏有一個HashMap,HashMap裏保存着系統service的名字,和引用。

 private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();
繼續看源碼瞭解客戶端向服務的請求是怎樣完成的:在客戶端通過ServiceManager類getService(String name)方法傳遞一個需要的服務,然後ServcieManager在sCache的HashMap中去查詢與name對應的service,然後再將這個service的IBinder引用返回給客戶端。
public static IBinder getService(String name) {
         try {
             IBinder service = sCache.get(name);
             if (service != null) {
                 return service;
             } else {
                 return getIServiceManager().getService(name);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "error in getService", e);
         }
         return null;
     }

與此同時,ServiceManager還提供了addService,checkService兩個重要方法,用來維護sCache列表登記的Service的名稱以及引用。我們用一個圖來描述這整個過程


                                             


通過上面的分析瞭解:客戶端首先需要通過Binder的進程都需要先獲得ServiceManager代理對象才能進行Binder通訊。所以,ServiceManager在C/C++層面提供服務代理,又在Java層面提供服務代理。

             接着我們通過一個簡單跨進程通訊Demo來加深對Binder的瞭解和使用。我們分三步:

             1.設計服務端,新建 一個基於Binder的類。

              2.設計客戶端,獲取遠程Binder對象。

              3.操作獲取的Binder對象執行操作。

           創建Service端

                   我們只要基於Binder類新建一個服務類即可。在設計這個客戶端之前,我們要考慮兩個問題:

                    a.客戶端如何獲得服務端的Binder的引用。

                    b.客戶端和服務端必須事先約定好 服務端函數的參數在包裹中的順序。

            對於第一個問題,我們向一個本地的service類進行連接,在這個客戶端與servcie建立連接後返回一個Binder,即我們設計的服務端。

           對於第二個問題,android中SDK中爲我們提供了一個aidl工具,藉着這個工具我們可以將aidl文件上轉化爲一個java類文件,在該java文件中,同時重載了transact和onTransact()方法,統一了存入包裹和讀取包裹參數。

  設計服務端

        先看下工程目錄,瞭解整個代碼的構成


 1)、創建一個IAidlBinder服務,這個服務裏有兩個服務函數,getInfo(),getFruit(),這裏我們就編寫一個IAidlBinder文件,代碼如下:

package com.binderserver;

import com.binderserver.Fruit;
interface IAidlBinder{
	String getInfo();
	Fruit getFruit();
}

1.java原子類型,如int,long,String等變量。
   2.Binder引用。
   3.實現了Parcelable的對象。
這裏文件名稱第一個"I"的含義是IIterface類,即這是一個可以提供遠程服務的類。我們創建好文件後,aidi工具會以文件的名稱在gen目錄下生成一個java類。
    接着看看aidl生成的IAidlBinder.java代碼。

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: D:\\workspace\\BinderServer\\src\\com\\binderserver\\IAidlBinder.aidl
 */
package com.binderserver;

public interface IAidlBinder extends android.os.IInterface {
	/** Local-side IPC implementation stub class. */
	public static abstract class Stub extends android.os.Binder implements
			com.binderserver.IAidlBinder {
		private static final java.lang.String DESCRIPTOR = "com.binderserver.IAidlBinder";

		/** Construct the stub at attach it to the interface. */
		public Stub() {
			this.attachInterface(this, DESCRIPTOR);
		}

		/**
		 * Cast an IBinder object into an com.binderserver.IAidlBinder
		 * interface, generating a proxy if needed.
		 */
		public static com.binderserver.IAidlBinder asInterface(
				android.os.IBinder obj) {
			if ((obj == null)) {
				return null;
			}
			android.os.IInterface iin = (android.os.IInterface) obj
					.queryLocalInterface(DESCRIPTOR);
			if (((iin != null) && (iin instanceof com.binderserver.IAidlBinder))) {
				return ((com.binderserver.IAidlBinder) iin);
			}
			return new com.binderserver.IAidlBinder.Stub.Proxy(obj);
		}

		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_getInfo: {
				data.enforceInterface(DESCRIPTOR);
				java.lang.String _result = this.getInfo();
				reply.writeNoException();
				reply.writeString(_result);
				return true;
			}
			case TRANSACTION_getFruit: {
				data.enforceInterface(DESCRIPTOR);
				Fruit _result = this.getFruit();
				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.binderserver.IAidlBinder {
			private android.os.IBinder mRemote;

			Proxy(android.os.IBinder remote) {
				mRemote = remote;
			}

			public android.os.IBinder asBinder() {
				return mRemote;
			}

			public java.lang.String getInterfaceDescriptor() {
				return DESCRIPTOR;
			}

			public java.lang.String getInfo() 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_getInfo, _data, _reply, 0);
					_reply.readException();
					_result = _reply.readString();
				} finally {
					_reply.recycle();
					_data.recycle();
				}
				return _result;
			}

			public Fruit getFruit() throws android.os.RemoteException {
				android.os.Parcel _data = android.os.Parcel.obtain();
				android.os.Parcel _reply = android.os.Parcel.obtain();
				Fruit _result;
				try {
					_data.writeInterfaceToken(DESCRIPTOR);
					mRemote.transact(Stub.TRANSACTION_getFruit, _data, _reply,
							0);
					_reply.readException();
					if ((0 != _reply.readInt())) {
						_result = Fruit.CREATOR.createFromParcel(_reply);
					} else {
						_result = null;
					}
				} finally {
					_reply.recycle();
					_data.recycle();
				}
				return _result;
			}
		}

		static final int TRANSACTION_getInfo = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
		static final int TRANSACTION_getFruit = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
	}

	public java.lang.String getInfo() throws android.os.RemoteException;

	public Fruit getFruit() throws android.os.RemoteException;
}
這些代碼主要完成三個任務:
      1.定義一個Java interface,內部包含aidl文件聲明的服務函數,類名稱爲IAidlBinder,該類基於IIterface接口,即需要提供一個asBinder()函數。
      2.定義一個Proxy類,該類將作爲客戶端訪問服務端的代理。這裏代理主要任務就是統一包裹內寫入參數的順序。
      3.定義一個stub類,這是一個基於Binder的abstract類。該類實現了IAidlBinde接口,主要由服務端來使用。
     2)、 因爲在這個接口裏傳輸的有對象Fruit,所以要它要處理Paracelable,下面是它的源碼。


package com.binderserver;

import android.os.Parcel;
import android.os.Parcelable;

public class Fruit implements Parcelable {

	private String name;
	private String color;
	private int number;
	
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getColor() {
		return color;
	}

	public void setColor(String color) {
		this.color = color;
	}

	public int getNumber() {
		return number;
	}

	public void setNumber(int number) {
		this.number = number;
	}
	
	public static final Parcelable.Creator<Fruit> CREATOR = new Creator<Fruit>() {

		@Override
		public Fruit createFromParcel(Parcel source) {
			Fruit fruit = new Fruit();
			fruit.name = source.readString();
			fruit.color = source.readString();
			fruit.number = source.readInt();
			return fruit;
		}

		@Override
		public Fruit[] newArray(int size) {
			// TODO Auto-generated method stub
			return new Fruit[size];
		}
		
	};

	@Override
	public int describeContents() {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public void writeToParcel(Parcel dest, int flags) {
		dest.writeString(name);
		dest.writeString(color);
		dest.writeInt(number);
		
	}

}

 3)、創建一個名爲Book的aidl文件,代碼如下:

parcelable Fruit;

     4)、新建一個名爲ServerService的java文件,

package com.binderserver;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;

import com.binderserver.IAidlBinder.Stub;

public class ServerService extends Service {

	private Fruit mFruit;
	
	@Override
	public void onCreate() {
		// TODO Auto-generated method stub
		super.onCreate();
		mFruit = new Fruit();
		mFruit.setName("apple");
		mFruit.setColor("red");
		mFruit.setNumber(10);
	}

	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return serviceBinder;
	}

	private IAidlBinder.Stub serviceBinder = new Stub() {
		
		@Override
		public String getInfo() throws RemoteException {
			// TODO Auto-generated method stub
			return "I'm a server";
		}
		
		@Override
		public Fruit getFruit() throws RemoteException {
			// TODO Auto-generated method stub
			return mFruit;
		}
	};
	
}

        5)、將這個服務端運行起來,供客戶端調用,界面很簡單,如下 :



客戶端的設計

    客戶端的工程目錄如下 :


 客戶端的實現:

        1)、將服務端的的aidl文件及要被調用的類,直接拷貝到工程目錄下。注意:爲了客戶端調用遠程服務,不要改變原aidl文件的地址,不然會報錯:

Binder invocation to an incorrect interface

        2)、創建主Activity,用來調用Aidl服務。代碼如下:

package com.binderclient;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import com.binderserver.IAidlBinder;;

public class BinderClientActivity extends Activity implements OnClickListener {
    /** Called when the activity is first created. */
	private IAidlBinder binder;
	private Button mGetInfo;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mGetInfo = (Button) findViewById(R.id.getinfo);
        mGetInfo.setOnClickListener(this);
	//注意這裏intent要在ServerService進行靜態註冊。
        Intent intent = new Intent("com.binderserver.ServerService");
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }
    
    private ServiceConnection serviceConnection = new ServiceConnection(){

		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			binder = IAidlBinder.Stub.asInterface(service);
			
		}

		@Override
		public void onServiceDisconnected(ComponentName name) {
			binder = null;
		}
    	
    };
	@Override
	public void onClick(View v) {
		switch (v.getId()) {
		case R.id.getinfo:
			if(binder == null){
				Log.d("Lawrence", "@#$%^&*()_+!#$%^&*_!@#$%^&*()");
			}else {
				try {
					String print = "The name of this fruit is:   " + binder.getFruit().getName() + "\n"
							+ "The color of this fruit is:   " + binder.getFruit().getColor() + "\n"
							+ "The number of this fruit is:   " + binder.getFruit().getNumber() + "\n"
							+ "The server says:   " + binder.getInfo();
					mGetInfo.setText(print);
				} catch (RemoteException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			break;

		default:
			break;
		}
		
	}
}

       3)、運行工程,界面如下:


 4)、點擊getInfo按鈕,調用遠程服務。界面如下 :




上面就是一個以aidl爲例的遠程Binder調用的簡單設計。


附:Demo源碼地址:點擊打開鏈接



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章