這篇我們講下如何使用service實現進程間通信 。通常講到這裏大家都會想到aidl,其實不然,aidl只是其中的一種方式,並且使用起來,我個人感覺太不靈活了,相對Messenger更加靈活。
整個過程我們還是藉助於 Activity 跨進程通信 中使用的項目
1.AIDL 跨進程通訊
aidl 是個簡寫,全拼android interface difine language(android自定義接口語言)
服務端:
1.1先從新建開始,選中main 右鍵選擇aidl file 新建,會在main 下生成aidl文件夾,我的新建默認名字叫
IMyAidlInterface,也就是IMyAidlInterface.aidl文件。這個就是我們要定義的接口類,默認會有
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString);接口,也就是默認支持六種基本數據類型
先寫兩個基本數據類型,再自定義一個自定義接口
package com.example.intentmode;
import com.example.intentmode.Person;
// Declare any non-default types here with import statements
interface IRemoteService {
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
int getPid();
String getName(int id);
Person getPerson(int id);
}
先彆着急,
1.2 Person getPerson(int id);是會報錯的。現在定義Person類,在和IRemoteService類平級目錄下新建Person類,並且必須實現Parcelable接口,爲啥Serializable 接口不行呢,在android 中都使用Parcelable接口因爲Serializable的序列化效率相對較低。
Person 如下:
public class Person implements Parcelable {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.name);
dest.writeInt(this.age);
}
public Person() {
}
protected Person(Parcel in) {
this.name = in.readString();
this.age = in.readInt();
}
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel source) {
return new Person(source);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
}
1.3 新建Person.aidl類 :
// Person.aidl
package com.example.intentmode;
// Declare any non-default types here with import statements
parcelable Person;
1.4 新建服務類 ServiceAIDL ,繼承自Service
public class ServiceAIDL extends Service {
private String [] names={"呂布","關羽","趙子龍","張飛"};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private final IMyAidlInterface.Stub mBinder=new IMyAidlInterface.Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
System.out.println("Thread: " + Thread.currentThread().getName());
System.out.println("basicTypes aDouble: " + aDouble +" anInt: " + anInt+" aBoolean " + aBoolean+" aString " + aString);
}
@Override
public int getPid() throws RemoteException {
System.out.println("Thread: " + Thread.currentThread().getName());
System.out.println("RemoteService getPid ");
return android.os.Process.myPid();
}
@Override
public String getName(int id) throws RemoteException {
return names[id];
}
@Override
public Person getPerson(int id) throws RemoteException {
Person person=new Person();
try {
person.setAge(20);
person.setName(names[id]);
} catch (Exception e) {
e.printStackTrace();
}
return person;
}
};
}
在這裏我們定義了接口的返回數據,用於客戶端調用時返回想要的數據,其實叫他服務端另一端客戶端是有問題的,他們是可以相互調用的,我們暫且這麼叫吧。這裏有的朋友可能會報attempting to use incompatible return type ,這是因爲使用自定義類型要在接口類中導入包名,
package com.example.csdnactivity;
// Declare any non-default types here with import statements
//這個要手動導入下
import com.example.csdnactivity.Person;
interface IMyAidlInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
int getPid();
String getName(int id);
Person getPerson(int id);
}
1.5 註冊service 並且設置標籤 action
<service android:name=".ServiceAIDL">
<intent-filter>
<action android:name="com.example.intentmode"/>
</intent-filter>
</service>
需要注意的是,aidl寫完後需要重新構建下,會在build --> generated--> source --> aidl --> debug--> 接口類
如果沒有,有可能是代碼有錯誤了。
服務端結束,下面看客戶端
客戶端:
1.1 將服務端的aidl文件夾粘貼到客戶端的main文件下。
1.2 綁定客戶端service
//服務端設置的action
intent.setAction("com.example.intentmode");
//服務端的包名
intent.setPackage("com.example.csdnactivity");
bindService(intent, conn, Context.BIND_AUTO_CREATE);
package com.example.csdnactivity1;
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.widget.Button;
import android.widget.Toast;
import com.example.csdnactivity.IMyAidlInterface;
import com.example.csdnactivity.Person;
/**
* Created by LCT
* Time:2018/12/24 13:49.
* Annotation:
*/
public class AIDLActivity extends Activity implements View.OnClickListener {
private static final String TAG = "AIDLActivity";
/**
* 獲取遠aidl程數據
*/
private Button mGetData;
IMyAidlInterface iMyAidlInterface;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aidl_activity);
initView();
bindService();
}
private void bindService() {
Intent intent = new Intent();
//服務端設置的action
intent.setAction("com.example.intentmode");
//服務端的包名
intent.setPackage("com.example.csdnactivity");
bindService(intent, conn, Context.BIND_AUTO_CREATE);
}
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
/**
* IBinder 轉爲IRemoteService接口
*/
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
private void initView() {
mGetData = (Button) findViewById(R.id.get_Data);
mGetData.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
default:
break;
case R.id.get_Data:
/**
* 通過iMyAidlInterface 獲取數據
*/
try {
if (iMyAidlInterface != null) {
String name = iMyAidlInterface.getName(0);
iMyAidlInterface.basicTypes(12, 1223, true, 12.2f, 12.3, "有夢就要去追,加油!");
Toast.makeText(this, name, Toast.LENGTH_SHORT).show();
Log.d(TAG, "onClick: name:"+ name);
Person person = iMyAidlInterface.getPerson(0);
String str=person.getName() + "_" +person.getAge();
Log.d(TAG, "onClick: person:"+ str);
}
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
}
}
客戶端代碼較少,現在可以運行服務端,如果你使用的是studio 使用了默人新建的build.gradle 文件,那麼現在項目可能運行不起來,會提示你:錯誤:找不到符號符號:類 Person 我用的3.2的編輯器,即使這樣還是不太智能需要手動寫點東西
在build.gradle 添加如下代碼
sourceSets {
main {
manifest.srcFile 'src/main/AndroidManifest.xml'
java.srcDirs = ['src/main/java', 'src/main/aidl']
resources.srcDirs = ['src/main/java', 'src/main/aidl']
aidl.srcDirs = ['src/main/aidl']
res.srcDirs = ['src/main/res']
assets.srcDirs = ['src/main/assets']
}
}
這樣問題就解決了。
現在可以運行服務端,然後啓動客戶端,獲取數據了。
1.Messenger跨進程通訊
原理與實現
這個相對aidl相對比較簡單,並不是代碼實現少,而是比較好理解,首先在服務端會有一個Messenger 它綁定了一個Hander ,客戶端同樣有一個Messenger 同樣綁定了一個Hander ,然後客戶端綁定服務通過service onBind返回服務端的Messenger對象,然後將客戶端的Messenger Message.replyTo=cMessenger
到服務端的messenger中,然後發送消息,服務端在Hander收到消息,得到客戶端的Messenger ,這樣就各自持有對方messenger對象,互相發送消息了。接着我們看看具體實現。
服務端
1.1 新建Messenger對象
Messenger有兩個構造函數分別是
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
handler用於綁定Messenger,接收消息。
IBinder用於將服務端的IBinder構造爲Messenger對象
@Override
public void onCreate() {
super.onCreate();
sMessenger=new Messenger(handler);
}
1.2獲取Messenger 的IBinder通過onBind返回
public IBinder onBind(Intent intent) {
return sMessenger.getBinder();
}
1.3Handler 接收數據處理,將收到的客戶端Messenger保存,並處理相關邏輯
Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg!=null) {
Log.d(TAG, "handleMessage: 收到客戶端發來的消息");
/**
* 保存客戶端Messenger,用於向客戶端發送消息
*/
if (msg.replyTo!=null) {
cMessenger=msg.replyTo;
}
Bundle bundle=msg.getData();
String ms;
if (bundle !=null) {
ms=bundle.getString("serviceData");
}else {
Log.d(TAG, "handleMessage: null bundle");
return;
}
if (ms==null) {
Toast.makeText(ServiceMessenger.this,"客戶端收到空消息" ,Toast.LENGTH_LONG).show();
return;
}
/**
* 如果收到消息1 就往客戶端發送一條消息
*/
if (ms.equals("1")) {
Message message=Message.obtain();
Bundle bundle1=new Bundle();
bundle1.putString("clientData","2");
message.setData(bundle1);
try {
cMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}else {
Toast.makeText(ServiceMessenger.this,"收到客戶端發來的消息"+ms,Toast.LENGTH_LONG).show();
}
}
}
};
服務端的完整代碼
public class ServiceMessenger extends Service {
private static final String TAG = "ServiceMessenger";
/**
* 服務端Messenger
*/
Messenger sMessenger;
/**
* 客戶端 Messenger
*/
Messenger cMessenger=null;
@Override
public void onCreate() {
super.onCreate();
sMessenger=new Messenger(handler);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return sMessenger.getBinder();
}
Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg!=null) {
Log.d(TAG, "handleMessage: 收到客戶端發來的消息");
/**
* 保存客戶端Messenger,用於向客戶端發送消息
*/
if (msg.replyTo!=null) {
cMessenger=msg.replyTo;
}
Bundle bundle=msg.getData();
String ms;
if (bundle !=null) {
ms=bundle.getString("serviceData");
}else {
Log.d(TAG, "handleMessage: null bundle");
return;
}
if (ms==null) {
Toast.makeText(ServiceMessenger.this,"客戶端收到空消息" ,Toast.LENGTH_LONG).show();
return;
}
/**
* 如果收到消息1 就往客戶端發送一條消息
*/
if (ms.equals("1")) {
Message message=Message.obtain();
Bundle bundle1=new Bundle();
bundle1.putString("clientData","2");
message.setData(bundle1);
try {
cMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}else {
Toast.makeText(ServiceMessenger.this,"收到客戶端發來的消息"+ms,Toast.LENGTH_LONG).show();
}
}
}
};
}
1.4.註冊service 並設置action,action用來客戶端綁定服務時使用
<service android:name=".ServiceMessenger">
<intent-filter>
<action android:name="com.android.serviceMessenger.action" />
</intent-filter>
</service>
服務端結束。
客戶端
1.1 創建客戶端Messenger ,綁定handler,用於接收消息
cMessenger = new Messenger(handler);
1.2 綁定服務並監聽綁定狀態,獲取服務端的Messenger對象。
private void bindMessengerService() {
Intent intent = new Intent();
//服務端設置的action
intent.setAction("com.android.serviceMessenger.action");
//服務端的包名
intent.setPackage("com.example.csdnactivity");
bindService(intent, connMessenger, Context.BIND_AUTO_CREATE);
}
獲取服務端Messenger,並將客戶端的Messenger發送到服務端
private ServiceConnection connMessenger = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
/**
* 得到服務端的messenger
*/
sMessenger = new Messenger(service);
if (sMessenger == null) {
Log.d(TAG, "onServiceConnected: messenger is null");
return;
}
/**
* 發送客戶端的messenger到服務端
*/
Message message = Message.obtain();
message.replyTo = cMessenger;
try {
sMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
1.3 發送消息到服務端
String str = mSendDataToServiceEdit.getText().toString();
if (str != null) {
Message message = Message.obtain();
Bundle bundle = new Bundle();
bundle.putString("serviceData",str);
message.setData(bundle);
try {
sMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
完整代碼類
public class MessengerActivity extends Activity implements View.OnClickListener {
private static final String TAG = "MessengerActivity";
/**
* 輸入發送的信息 通過Messenger 發送
*/
private EditText mSendDataToService;
/**
* SENd
*/
private Button mSendData;
/**
* 服務端 Messenger
*/
Messenger sMessenger;
/**
* 客戶端 Messenger
*/
Messenger cMessenger;
/**
* messenger
*/
private Button mMessengerButton;
/**
* 輸入發送的信息 通過Messenger 發送
*/
private EditText mSendDataToServiceEdit;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.messenger_activity);
initView();
initMessenger();
}
private void initMessenger() {
cMessenger = new Messenger(handler);
bindMessengerService();
}
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Bundle bundle = msg.getData();
if (bundle != null) {
String ms = bundle.getString("clientData");
if (ms.equals("2")) {
Toast.makeText(MessengerActivity.this, "收到服務端發來的數據了" + ms + "", Toast.LENGTH_LONG).show();
}
}
}
;
};
private void bindMessengerService() {
Intent intent = new Intent();
//服務端設置的action
intent.setAction("com.android.serviceMessenger.action");
//服務端的包名
intent.setPackage("com.example.csdnactivity");
bindService(intent, connMessenger, Context.BIND_AUTO_CREATE);
}
private ServiceConnection connMessenger = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
/**
* 得到服務端的messenger
*/
sMessenger = new Messenger(service);
if (sMessenger == null) {
Log.d(TAG, "onServiceConnected: messenger is null");
return;
}
/**
* 發送客戶端的messenger到服務端
*/
Message message = Message.obtain();
message.replyTo = cMessenger;
try {
sMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
private void initView() {
mSendDataToServiceEdit = (EditText) findViewById(R.id.send_data_to_service_edit);
mSendDataToServiceEdit.setOnClickListener(this);
mSendData = (Button) findViewById(R.id.send_data);
mSendData.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
default:
break;
case R.id.send_data:
String str = mSendDataToServiceEdit.getText().toString();
if (str != null) {
Message message = Message.obtain();
Bundle bundle = new Bundle();
bundle.putString("serviceData",str);
message.setData(bundle);
try {
sMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
break;
}
}
}
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/send_data_to_service_edit"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_toLeftOf="@+id/send_data"
android:hint="輸入發送的信息 通過Messenger 發送"
android:textSize="14sp" />
<Button
android:id="@+id/send_data"
android:layout_width="100dp"
android:layout_height="50dp"
android:layout_alignParentRight="true"
android:text="SENd" />
</RelativeLayout>
</LinearLayout>
客戶端完畢。這裏注意一點,在調試時記得將兩端都啓動起來,客戶端可以向服務端通過輸入框發送消息,服務端收到後會toast出來,如果發送的數據是1,那麼服務端會給客戶端發送一條消息內容爲 2,客戶端會toast出來,整個service 跨進程通信就這麼多了。
service 跨進程通信源碼