Android Service 遠端服務
遠端服務的建立和呼叫需要使用AIDL語言,步驟如下:
使用AIDL語言定義遠端服務的介面
通過繼承Service類實現介面中定義的方法和屬性
繫結和使用遠端服務
以下為一個簡單Demo ,RemoteMathCallerDemo介面如下:
繫結遠端服務後,呼叫RemoteMathServiceDemo中的MathService服務進行加法運算。
1.使用AIDL語言定義遠端服務的介面
以Android Studio為例,首先需要建立對應目錄及aidl檔案,如下:
(比如直接在java目錄下的包上右鍵新建aidl檔案 IDE會自動生成aidl目錄及該目錄下的包和檔案這樣的小技巧我可不會隨便告訴別人)
IMathService.aidl檔案內容如下:
// IMathService.aidl
package com.example.remotemathservicedemo;
// Declare any non-default types here with import statements
interface IMathService {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
long Add(long a,long b);
}
然後在build目錄下會自動生成與該aidl檔案對應的java介面檔案,(若沒有生成則重新make project) 如下:
在看IMathService.java內容之前呢,不知道你有沒有注意到,我前兩張截圖都截到了上面的一個Module:remotemathcallerdemo,這個就是呼叫端,目前我們編輯的remotemathservicedemo是服務端。
下面為IMathService.java的完整程式碼,加上了我自己的理解和註釋:
/*
* 這個檔案是自動生成的。不要修改
*/
package com.example.remotemathservicedemo;
/* 在這裡宣告任何非預設型別
所有使用AIDL建立的介面都必須繼承 android.os.IInterface 基類介面
這個基類介面中定義了 asBinder()方法 用來獲取Binder物件
*/
public interface IMathService extends android.os.IInterface {
/**
* 本地IPC實現stub類
*/
public static abstract class Stub extends android.os.Binder implements com.example.remotemathservicedemo.IMathService {
private static final java.lang.String DESCRIPTOR = "com.example.remotemathservicedemo.IMathService";
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
//asInterface(IBinder) 是Stub內部的遠端服務介面,呼叫者可以通過該方法獲得遠端服務的例項
public static com.example.remotemathservicedemo.IMathService asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
//判斷android.os.IInterface例項是否為本地服務 若是返回android.os.IInterface
//若不是本地服務 構造Proxy物件並返回之
if (((iin != null) && (iin instanceof com.example.remotemathservicedemo.IMathService))) {
return ((com.example.remotemathservicedemo.IMathService) iin);
}
return new com.example.remotemathservicedemo.IMathService.Stub.Proxy(obj);
}
//實現了android.os.IInterface介面定義的asBinder()方法
@Override
public android.os.IBinder asBinder() {
return this;
}
/*
Parcel是Android系統應用程式間傳遞資料的容器,能夠在兩個程序中完成打包和拆包的工作
但Parcel不同於通用意義上的序列化
Parcel的設計目的是用於高效能IPC傳輸 不能將其儲存在持久儲存裝置上
*/
//接收Parcel物件,並從中逐一讀取每個引數,然後呼叫Service內部制定的方法,將結果寫進另一個Parcel物件,
// 準備將這個Parcel物件返回給遠端的呼叫者
@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_Add: {
data.enforceInterface(DESCRIPTOR);
long _arg0;
_arg0 = data.readLong();
long _arg1;
_arg1 = data.readLong();
long _result = this.Add(_arg0, _arg1);
reply.writeNoException();
reply.writeLong(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
//用來實現遠端服務呼叫
private static class Proxy implements com.example.remotemathservicedemo.IMathService {
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;
}
//以一定順序將所有引數寫入Parcel物件,以供Stub內部的onTransact()方法獲取引數
@Override
public long Add(long a, long b) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
long _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeLong(a);
_data.writeLong(b);
mRemote.transact(Stub.TRANSACTION_Add, _data, _reply, 0);
_reply.readException();
_result = _reply.readLong();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_Add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public long Add(long a, long b) throws android.os.RemoteException;
}
IMathService.aidl是對遠端服務介面的定義,自動生成的IMathService.java內部實現了遠端服務資料傳遞的相關方法,下一步介紹如何實現遠端服務,這需要建立一個Service類,並在該類中通過onBind()方法返回IBinder物件,這樣呼叫者使用獲取的IBinder物件就可以訪問遠端服務。
下面是MathService.java的完整程式碼:
package com.example.remotemathservicedemo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.widget.Toast;
/**
* Created by yinghao on 2016/5/7.
*/
public class MathService extends Service {
/*
1. 建立 IMathService.Stub的例項mBinder並實現AIDL檔案定義的遠端服務介面
2. 在onBind()方法中將mBinder返回給遠端呼叫者
*/
private final IMathService.Stub mBinder = new IMathService.Stub(){
@Override
public long Add(long a, long b) throws RemoteException {
return a + b;
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(MathService.this, "遠端繫結:MathService", Toast.LENGTH_SHORT).show();
return mBinder;
}
//Return true if you would like to have the service's onRebind method later called when new clients bind to it.
@Override
public boolean onUnbind(Intent intent) {
Toast.makeText(MathService.this, "取消遠端繫結", Toast.LENGTH_SHORT).show();
return false;
}
}
最後一步,註冊service:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.remotemathservicedemo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<service android:name=".MathService"
android:process=":remote">
<intent-filter>
<action android:name="com.example.remote.MathService" />
</intent-filter>
</service>
</application>
</manifest>
到這裡我們的服務端Module : remotemathservicedemo 就完成了。
這完成了文章開頭所列舉的三個步驟的前兩步,最後一步,讓我們來看看怎麼繫結和使用遠端服務吧。
首先,我們需要引入與服務端相同的aidl檔案並確保自動生成對應的IMathService.java介面檔案。
那麼為什麼要這樣做呢? 這就需要我們瞭解aidl檔案和對應介面檔案的用處到底是什麼,為了時資料能穿越程序邊界,所有資料都必須是“打包”,而自動生成的IMathService.java內部實現了遠端服務資料傳遞的相關方法,那麼服務端就有了將資料打包、拆包的能力。而呼叫端也需要發出資料和接收資料,也需要有將資料打包、拆包的能力,所以它也需要IMathService.java這個類。
然後對遠端服務的繫結與呼叫,其實與本地服務的繫結區別不大,不同之處主要包括兩處:
使用IMathService生命遠端服務例項
通過IMathService.Stub的asInterface()方法獲取服務例項
下面為remotemathcallerdemo Module中MainActivity.java的完整程式碼:
package com.example.remotemathcallerdemo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.example.remotemathservicedemo.IMathService;
import org.w3c.dom.Text;
public class MainActivity extends AppCompatActivity {
private TextView textView;
private Button bind;
private Button unbind;
private Button add;
private boolean isBound = false;
private IMathService mathService;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mathService = IMathService.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
mathService = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
bind = (Button) findViewById(R.id.bind);
unbind = (Button) findViewById(R.id.unbind);
add = (Button) findViewById(R.id.add);
bind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!isBound) {
final Intent serviceIntent = new Intent();
serviceIntent.setAction("com.example.remote.MathService");
bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
isBound = true;
}
}
});
unbind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isBound) {
unbindService(mConnection);
isBound = false;
mathService = null;
}
}
});
add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mathService == null) {
textView.setText("未繫結遠端服務");
return;
}
long a = Math.round(Math.random() * 100);
long b = Math.round(Math.random() * 100);
long result = 0;
try {
result = mathService.Add(a, b);
} catch (RemoteException e) {
e.printStackTrace();
}
String msg = String.valueOf(a) + "+" + String.valueOf(b) + "=" + String.valueOf(result);
textView.setText(msg);
}
});
}
}
在此例中傳遞的資料型別為基本資料型別,打包過程是自動完成的,但對於自定義的資料型別,使用者則需要實現Parcelable介面,使自定義的資料型別能夠轉換為系統級原語儲存在Parcel物件中,穿越程序邊界後可再轉換為初始格式,關於自定義資料型別的傳遞,在下一篇文章中歸納總結。