Service之IPC遠程通信


一篇好的文章勢必是可以用簡短的文字就可以講透一個知識點,所以我一般寫文章都是把最本質的原理體現出來,如果你要閱讀哪些詳細的說明可以百度這個相關的技術點。跟着我的步驟操作我可以用最少的時間和精力讓你用起這個技術點,看到效果,只有自己操作一遍之後看到效果了纔可以學好一個技術點,我會把技術點的精華和本質給大家說明,同學們可以根據自己已有的知識去體會,融匯到以前學的知識中去,看看你現在學的知識點和以前的知識點有什麼聯繫和不同,只有多思考這些纔可以熟練的使用學到的知識點。

ok,閱讀這篇文章前我希望你是對service有所瞭解的,起碼要知道如何start一個Service和bind一個Service,如果你對這塊知識不是很熟練,可以查看我的另外一篇文章:Service的基本使用本篇文章是Service的高級應用,用來實現android的IPC遠程通信,所謂遠程通信就是不同的應用進程之間通信,一般android中一個app就是一個應用進程,所以本篇文章的效果是一個app調用另一個app中的Service方法並且被調用的app之前不是出於正在運行的狀態。所以開始之前請你新建兩個工程,一個用於被調用一個用於調用,這裏我新建的被調用的app叫ServiceServer,調用者爲ServiceClient。

一、Serviceserver

我們在這個app的src文件夾下新建一個包,這裏我新建的包名爲:com.xinxue.aidl,在這個包下面新建一個擴展名爲aidl的文件,我的這個文件名爲:IMyService.aidl,文件中的內容爲:

package com.xinxue.aidl;
import com.xinxue.aidl.Student;
interface IMyService{
List<Student> getStudent();
void addStudent(in Student student);
void printString( String msg);

}
上面的package和import都是需要手動寫的,這裏的Student也是我在這個包下面新建的一個類。IMyService爲一個接口,裏面定義了3個方法,方法裏面的參數如果不是基本數據類型需要使用in和out來標示這個參數是傳入的還是傳出的,顯然我們這裏是一個傳入的參數,所以使用in來標示,如果是基本數據類型就不需要用這個來標示。ok,既然student是我們需要傳輸的對象,而我們知道java不允許直接傳遞一個自定義的對象除非這個對象實現了parcelable接口,很顯然我們的student類也需要這麼做。下面我們在這個包下面新建一個student類,實現parcelable接口,具體代碼如下:

public class Student implements Parcelable {
	public int age;
	public String name;

	/**
	 * 兩個構成方法,一個私有化的只能內部使用
	 */
	private Student(Parcel in) {
		readFromParcel(in);
	}

	public Student() {
	}

	// 用來穿件對象的時候使用
	public static final Parcelable.Creator<Student> CREATOR = new Creator<Student>() {

		@Override
		public Student[] newArray(int size) {
			return new Student[size];
		}

		@Override
		public Student createFromParcel(Parcel source) {
			return new Student(source);
		}
	};

	// 直接返回0
	@Override
	public int describeContents() {
		return 0;
	}

	// 保存對象
	@Override
	public void writeToParcel(Parcel dest, int flags) {
		dest.writeString(name);
		dest.writeInt(age);
	}

	// 獲取對象
	public void readFromParcel(Parcel in) {

		name = in.readString();
		age = in.readInt();

	}

	@Override
	public String toString() {
		return "Student [age=" + age + ", name=" + name + "]";
	}

}
該類中readFromParcel方法和CREATOR是我們自己寫的,其餘的方法是重寫方法,類裏都有註釋而且很簡單這裏就不囉嗦了,相信你可以看懂如果哪裏不懂可以給我留言。

創建好了這個對象之後我們發現IMyService.aidl提示找不到Student類,這裏我們還需要定義一個aidl文件用於描述我們的Student對象。在同一個包下面新建一個Student.aild,裏面的代碼如下:

package com.xinxue.aidl;
parcelable Student;
非常簡單的兩行代碼,其中第二行的parcelable爲小寫,用來描述Student對象時parcelable的子類。

做好以上的工作之後eclipse的編譯工具就會自動給我們在gen文件下創建一個IMyService.java類,看下面截圖:




然後在主包下面新建一個供遠程調用的Service類,這裏就是在上面圖中的com.xinxue.serviceserver包下面,新建的文件爲RemoteService.java,裏面代碼如下:

public class RemoteService extends Service {
	private List<Student> list = new ArrayList<Student>();
	// 這裏使用到的就是自動生成的在gen文件夾下的java文類,重寫裏面的方法
	private IMyService.Stub mBinder = new Stub() {

		@Override
		public List<Student> getStudent() throws RemoteException {
			return list;
		}

		@Override
		public void addStudent(Student student) throws RemoteException {
			list.add(student);
		}

		@Override
		public void printString(String msg) throws RemoteException {
			Log.e("需要輸出的消息:", msg);

		}

		// 用來控制指定的應用纔可以綁定,可以不重寫
		public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
				throws RemoteException {
			String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
			if (packages != null && packages.length > 0) {
				String name = packages[0];
				if (!"com.example.serviceclient".equals(name)) {
					return false;
				}
			}

			return super.onTransact(code, data, reply, flags);
		};
	};

	@Override
	public IBinder onBind(Intent intent) {
		// 返回IMyService對象
		return mBinder;
	}

	@Override
	public void onCreate() {
		super.onCreate();
		// 初始化數據
		for (int i = 0; i < 6; i++) {
			Student student = new Student();
			student.age = i * 2 + 1;
			student.name = "小新";
			list.add(student);
		}
	}
}

裏面註釋都很詳細,代碼也和Service的使用代碼一直就不多說了,唯一需要指出的是onTransact()這個方法我們可以用來控制服務只能被指定的app調用。最後記得在manifest文件中註冊:

        <service android:name="com.xinxue.serviceserver.RemoteService" >
            <intent-filter>
                <action android:name="com.xinxue.serviceserver.RemoteService" >
                </action>

                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </service>

到這裏我們遠程的app就寫好了,下面就是寫測試用的app了。

二、ServiceClient

這個類用來綁定遠程的那個app,首先把我們在上面的app中新建的包和下面的所有文件都拷貝到本app的src文件夾下,然後只需要在MainActivity裏面寫上下面的代碼就好了:

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

	}

	private ServiceConnection sConn = new ServiceConnection() {

		@Override
		public void onServiceDisconnected(ComponentName name) {

		}

		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			// 因爲遠程的onBind方法返回的是一個IMyService對象,所以這裏可以直接轉換爲該對象
			IMyService myService = IMyService.Stub.asInterface(service);
			try {
				// 調用對象裏面定義的方法
				myService.printString(myService.getStudent().get(0).toString());
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}
	};

	public void bindRemoteService(View view) {
		// 綁定遠程Service
		Intent intent = new Intent("com.xinxue.serviceserver.RemoteService");
		intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		bindService(intent, sConn, Service.BIND_AUTO_CREATE);
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		// 解綁
		if (sConn != null)
			unbindService(sConn);
	}
}
代碼其實和bindService的使用差不多,唯一不同的是在bind上後使用自動生成的IMyService.Stub.asInterface方法把ibinder對象轉換爲IMyService對象,然後就可以調用這個對象裏面的方法了是不是很簡單呢??下面把兩個文件都按照到手機裏面,打開ServiceClient這個app,點擊按鈕我們就可以看到效果了:


其實我們是傳遞自定義對象所以代碼纔有點多,要是隻是傳遞基本數據類型簡單的幾行代碼就可以搞定的。

掃描關注我的微信公衆號:


總結:

Service的使用方式有兩種,一種是本地服務,一種是遠程服務,今天我們使用的就是遠程服務這種情況,其實要實現IPC遠程通信我們有兩種方式,一種是現在說的還有一種方式是使用handler發消息去實現。遠程服務使用其實很簡單,我們只需要自定義一個aidl文件,在這個文件裏面寫代碼其實和java的寫法一致,而aidl文件裏面我們只需要定義一個接口,接口裏面定義的方法就是我們提供給遠程調用的方法,遠程服務和本地服務還有一個不同就是需要在manifest文件定義的時候指定一個action用於遠程隱式的調用,這塊知識使用起來不是很難的,如果還有什麼問題可以給我留言,最後附上demo下載



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