上一篇我們瞭解了Service的一些概念以及使用方式,這篇著重講解使用Service實現IPC通訊的2中方式。
藉助AIDL實現IPC通訊
一、程式碼實操---與遠端程式的Service繫結
上面的程式碼都是在當前程式內跟Service通訊,現在我們來實現一下,不同程式內Service如何繫結。
AIDL:Android Interface Definition Language,即Android介面定義語言。
Service跨程式傳遞資料需要藉助aidl,主要步驟是這樣的:
- 編寫aidl檔案,AS自動生成的java類實現IPC通訊的代理
- 繼承自己的aidl類,實現裡面的方法
- 在onBind()中返回我們的實現類,暴露給外界
- 需要跟Service通訊的物件通過bindService與Service繫結,並在ServiceConnection接收資料。
我們通過程式碼來實現一下:
-
首先我們需要新建一個Service
public class MyRemoteService extends Service { @Nullable @Override public IBinder onBind(Intent intent) { Log.e("MyRemoteService", "MyRemoteService thread id = " + Thread.currentThread().getId()); return null; } } 複製程式碼
-
在manifest檔案中宣告我們的Service同時指定執行的程式名,這裡並是不只能寫remote程式名,你想要程式名都可以
<service android:name=".service.MyRemoteService" android:process=":remote" /> 複製程式碼
-
新建一個aidl檔案使用者程式間傳遞資料。
AIDL支援的型別:八大基本資料型別、String型別、CharSequence、List、Map、自定義型別。List、Map、自定義型別放到下文講解。
裡面會有一個預設的實現方法,刪除即可,這裡我們新建的檔案如下:
package xxxx;//aidl所在的包名 //interface之前不能有修飾符 interface IProcessInfo { //你想要的通訊用的方法都可以在這裡新增 int getProcessId(); } 複製程式碼
-
實現我們的aidl類
public class IProcessInfoImpl extends IProcessInfo.Stub { @Override public int getProcessId() throws RemoteException { return android.os.Process.myPid(); } } 複製程式碼
-
在Service的onBind()中返回
public class MyRemoteService extends Service { IProcessInfoImpl mProcessInfo = new IProcessInfoImpl(); @Nullable @Override public IBinder onBind(Intent intent) { Log.e("MyRemoteService", "MyRemoteService thread id = " + Thread.currentThread().getId()); return mProcessInfo; } } 複製程式碼
-
繫結Service
mTvRemoteBind.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, MyRemoteService.class); bindService(intent, mRemoteServiceConnection, BIND_AUTO_CREATE); } }); mRemoteServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.e("MainActivity", "MyRemoteService onServiceConnected"); // 通過aidl取出資料 IProcessInfo processInfo = IProcessInfo.Stub.asInterface(service); try { Log.e("MainActivity", "MyRemoteService process id = " + processInfo.getProcessId()); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { Log.e("MainActivity", "MyRemoteService onServiceDisconnected"); } }; 複製程式碼
只要繫結成功就能在有log列印成MyRemoteService所在程式的程式id。這樣我們就完成了跟不同程式的Service通訊的過程。
二、程式碼實操---呼叫其他app的Service
跟調同app下不同程式下的Service相比,呼叫其他的app定義的Service有一些細微的差別
-
由於需要其他app訪問,所以之前的bindService()使用的隱式呼叫不在合適,需要在Service定義時定義action
我們在定義的執行緒的App A 中定義如下Service:
<service android:name=".service.ServerService"> <intent-filter> //這裡的action自定義 <action android:name="com.jxx.server.service.bind" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </service> 複製程式碼
-
我們在需要bindService的App B 中需要做這些處理
-
首先要將A中定義的aidl檔案複製到B中,比如我們在上面定義的IProcessInfo.aidl這個檔案,包括路徑在內需要原封不動的複製過來。
-
在B中呼叫Service通過顯式呼叫
mTvServerBind.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(); intent.setAction("com.jxx.server.service.bind");//Service的action intent.setPackage("com.jxx.server");//App A的包名 bindService(intent, mServerServiceConnection, BIND_AUTO_CREATE); } }); 複製程式碼
-
aidl中自定義物件的傳遞
主要步驟如下:
- 定義自定物件,需要實現Parcelable介面
- 新建自定義物件的aidl檔案
- 在傳遞資料的aidl檔案中引用自定義物件
- 將自定義物件以及aidl檔案拷貝到需要bindService的app中,主要路徑也要原封不動
我們來看一下具體的程式碼:
-
定義自定義物件,並實現Parcelable介面
public class ServerInfo implements Parcelable { public ServerInfo() { } String mPackageName; public String getPackageName() { return mPackageName; } public void setPackageName(String packageName) { mPackageName = packageName; } protected ServerInfo(Parcel in) { mPackageName = in.readString(); } public static final Creator<ServerInfo> CREATOR = new Creator<ServerInfo>() { @Override public ServerInfo createFromParcel(Parcel in) { return new ServerInfo(in); } @Override public ServerInfo[] newArray(int size) { return new ServerInfo[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mPackageName); } //使用out或者inout修飾時需要自己新增這個方法 public void readFromParcel(Parcel dest) { mPackageName = dest.readString(); } } 複製程式碼
-
新建自定義物件的aidl檔案
package com.jxx.server.aidl; //注意parcelable 是小寫的 parcelable ServerInfo; 複製程式碼
-
引用自定義物件
package com.jxx.server.aidl; //就算在同一包下,這裡也要導包 import com.jxx.server.aidl.ServerInfo; interface IServerServiceInfo { ServerInfo getServerInfo(); void setServerInfo(inout ServerInfo serverinfo); } 複製程式碼
注意這裡的set方法,這裡用了inout,一共有3種修飾符
- in:客戶端寫入,服務端的修改不會通知到客戶端 - out:服務端修改同步到客戶端,但是服務端獲取到的物件可能為空 - inout:修改都收同步的 複製程式碼
當使用out和inout時,除了要實現Parcelable外還要手動新增readFromParcel(Parcel dest)
-
拷貝自定義物件以及aidl檔案到在要引用的App中即可。
-
引用
mServerServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { IServerServiceInfo serverServiceInfo = IServerServiceInfo.Stub.asInterface(service); try { ServerInfo serviceInfo = serverServiceInfo.getServerInfo(); Log.e("MainActivity", "ServerService packageName = " + serviceInfo.getPackageName()); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { Log.e("MainActivity", "ServerService onServiceDisconnected"); } }; 複製程式碼
List、Map中引用的物件也應該是符合上面要求的自定義物件,或者其他的幾種資料型別。
使用Messenger實現IPC通訊
步驟是這樣的:
- 在Server端新建一個Messenger物件,用於響應Client端的註冊操作,並在onBind()中傳遞出去
- 在Client端的ServiceConnection中,將Server端傳遞過來的Messenger物件進行儲存
- 同時Client端也新建一個Messenger物件,通過Server傳遞過來的Messenger註冊到Server端,保持通訊用。
- 不管是否進行unbindService()操作,只要Client保有Server端的Messenger物件,仍然能和Server端進行通訊。
一、Server端程式碼
public class MessengerService extends Service {
static final int MSG_REGISTER_CLIENT = 1;
static final int MSG_UNREGISTER_CLIENT = 2;
static final int MSG_SET_VALUE = 3;
//這個是給client端接收引數用的
static final int MSG_CLIENT_SET_VALUE = 4;
static class ServiceHandler extends Handler {
private final List<Messenger> mMessengerList = new ArrayList<>();
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REGISTER_CLIENT:
mMessengerList.add(msg.replyTo);
break;
case MSG_UNREGISTER_CLIENT:
mMessengerList.remove(msg.replyTo);
break;
case MSG_SET_VALUE:
int value = msg.arg1;
for (Messenger messenger : mMessengerList) {
try {
messenger.send(Message.obtain(null, MSG_CLIENT_SET_VALUE, value, 0));
} catch (RemoteException e) {
e.printStackTrace();
}
}
break;
default:
super.handleMessage(msg);
}
}
}
private Messenger mMessenger = new Messenger(new ServiceHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
複製程式碼
二、Client端程式碼
public class MessengerClientActivity extends AppCompatActivity {
//這些型別要和Server端想對應
static final int MSG_REGISTER_CLIENT = 1;
static final int MSG_UNREGISTER_CLIENT = 2;
static final int MSG_SET_VALUE = 3;
static final int MSG_CLIENT_SET_VALUE = 4;
class ClientHandler extends Handler {
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_CLIENT_SET_VALUE) {
mTvValue.setText(msg.arg1 + "");
} else {
super.handleMessage(msg);
}
}
}
TextView mTvServerBind;
TextView mTvServerUnbind;
TextView mTvValue;
TextView mTvSend;
ServiceConnection mServerServiceConnection;
Messenger mServerMessenger;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
mTvServerBind = findViewById(R.id.tv_server_bind);
mTvServerUnbind = findViewById(R.id.tv_server_unbind);
mTvValue = findViewById(R.id.tv_value);
mTvSend = findViewById(R.id.tv_send);
mTvServerBind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setAction("jxx.com.server.service.messenger");
intent.setPackage("jxx.com.server");
bindService(intent, mServerServiceConnection, BIND_AUTO_CREATE);
}
});
mTvServerUnbind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//就算這裡我們unbindService,只要我們還保留有mServerMessenger物件,
//我們就能繼續與Server通訊
unbindService(mServerServiceConnection);
}
});
mTvSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mServerMessenger != null) {
try {
//測試一下能否設定資料
Message test = Message.obtain(null, MSG_SET_VALUE, new Random().nextInt(100), 0);
mServerMessenger.send(test);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
});
mServerServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//服務端的messenger
mServerMessenger = new Messenger(service);
//現在開始構client用來傳遞和接收訊息的messenger
Messenger clientMessenger = new Messenger(new ClientHandler());
try {
//將client註冊到server端
Message register = Message.obtain(null, MSG_REGISTER_CLIENT);
register.replyTo = clientMessenger;//這是註冊的操作,我們可以在上面的Server程式碼看到這個物件被取出
mServerMessenger.send(register);
Toast.makeText(MessengerClientActivity.this, "繫結成功", Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
}
複製程式碼