前言
- 最近在学习Android,学到了Serivce组件,趁热打铁写个案例(Server端和Client端之间的进程通信)
- Client发送id给Server,Server查询对应id的学生信息并将学生信息返回给Client
- 使用的是Android Studio
1.界面
- 服务端,不需要界面,其只是做一个数据的处理
- 客户端
<?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"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="bindRemoteService"
android:text="绑定远程service"
android:textAllCaps="false" />
<EditText
android:id="@+id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:hint="请输入学生的id..." />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="invokeRemoteMethod"
android:text="调用远程服务的方法" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="unbindRemoteService"
android:text="解绑远程服务" />
</LinearLayout>
- 长这样👇
2.服务端代码
- (1)首先创建一个服务,该服务需要继承Service【之后还需要对这个类添加代码】
public class MyRemoteService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e("MyRemoteSerivce", "onBind ");
return null;
}
}
- (2)一定要记得注册这个service,点击类名使用快捷键注册,但是自动生成的服务不会自带<intent-filter>,其他应用无法启动,需要自行添加,如下(action的value很关键,client端需要copy):
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.wang.service">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Provider">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".remote.MyRemoteService"
android:exported="true">
<intent-filter>
<action android:name="com.wang.service.remote.MyRemoteService.Action" />
</intent-filter>
</service>
</application>
</manifest>
- (3)准备一个Student实体类,并实现打包Parcelable接口(除了前三行,下面的代码都是使用快捷键自动生成的!)
public class Student implements Parcelable {
private int id;
private String name;
private double price;
public Student(int id, String name, double price) {
this.id = id;
this.name = name;
this.price = price;
}
public Student() {
}
protected Student(Parcel in) {
id = in.readInt();
name = in.readString();
price = in.readDouble();
}
public static final Creator<Student> CREATOR = new Creator<Student>() {
@Override
public Student createFromParcel(Parcel in) {
Log.e("Student.class", "拆包");
return new Student(in);
}
@Override
public Student[] newArray(int size) {
return new Student[size];
}
};
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", price=" + price +
'}';
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
Log.e("Student.class", "打包");
parcel.writeInt(id);
parcel.writeString(name);
parcel.writeDouble(price);
}
}
- (4)开始准备AIDL,也就是自动生成进程间通信代码的语言,模块层次结构如下:
- 注意下面的这些import和package得按照具体的层次结构修改,不然会出错,我这里的是下面这样写的。
- 其中IStudentService.aidl如下:
package com.wang.service;
import com.wang.service.Student;
interface IStudentService {
Student getStudentById(int id);
}
- 还需要一个与实体类对应的Student.aidl
package com.wang.service;
parcelable Student;
- (5)然后点Build -> make,自动生成通信代码
下面是自动生成的代码所在位置
- (6)在自定义的Service类中再增加一个内部类StudentService,继承自动生成接口中的Stub,然后重写之前在AIDL中设定的方法,然后再把这个StudentService作为onBind的返回对象(具体原因看一下自动生成的代码中的Stub代码)
public class MyRemoteService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e("MyRemoteSerivce", "onBind ");
return new StudentService();
}
private class StudentService extends IStudentService.Stub {
@Override
public Student getStudentById(int id) throws RemoteException {
Log.e("MyRemoteSerivce", "getStudentById:" + id);
return new Student(id, "wang", 10000);
}
}
}
3.客户端代码
-
下面是最容易出错的一步!也就是将server端的aidl和student按照server中的层次,原封不动的搬到client中:
-
然后再在client中build中make一下,自动生成client中的通信代码
-
在客户端中的MainActivity中这样写(附带注释):
public class MainActivity extends AppCompatActivity {
private static final String TAG = "client MainActivity";
private EditText editText;
private ServiceConnection conn;
private IStudentService studentService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//对应客户端界面的输入框
editText = findViewById(R.id.edit);
}
//下面这个方法,写在了xml中的onclick属性中进行监听
public void bindRemoteService(View view) {
//使用intent启动远程服务
Intent intent = new Intent();
//下面这个action就是复制过来的
intent.setAction("com.wang.service.remote.MyRemoteService.Action");
//只有action是不行的!会报错!!和安卓版本有关,查阅了资料,解决办法是加上包名!
//不然会报错 Service Intent must be explicit
intent.setPackage("com.wang.service");
//创建ServiceConnection,这是bindService需要的参数
if (conn == null) {
conn = new ServiceConnection() {
//下面这个方法是bind绑定成功后的回调方法,在server服务端的 onBind返回的studentService
//可以在这里通过iBinder进行获取
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.e(TAG, "onServiceConnected: ");
studentService = IStudentService.Stub.asInterface(iBinder);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
//绑定远程服务
bindService(intent, conn, BIND_AUTO_CREATE);
Toast.makeText(this, "绑定远程服务", Toast.LENGTH_SHORT).show();
Log.e(TAG, "bindRemoteService");
} else {
Toast.makeText(this, "重复操作", Toast.LENGTH_SHORT).show();
}
}
//下面这个方法,写在了xml中的onclick属性中进行监听
public void unbindRemoteService(View view) {
if (conn != null) {
unbindService(conn);
conn = null;
Toast.makeText(this, "解除绑定远程服务", Toast.LENGTH_SHORT).show();
}
}
//下面这个方法,写在了xml中的onclick属性中进行监听
public void invokeRemoteMethod(View view) throws RemoteException {
if (studentService != null) {
int id = Integer.parseInt(editText.getText().toString());
Student student = studentService.getStudentById(id);
Toast.makeText(this, student.toString(), Toast.LENGTH_SHORT).show();
Log.e(TAG, "invokeRemoteMethod:" + student);
}
}
}
4.结果展示
5.记录BUG
- 写了个最愚蠢的错误。。。客户端和服务器使用了相同的包名,导致app相互覆盖了,客户端一直在bind回调方法中获取不到studentService。。。经过京东大佬的指导,豁然开朗!冲!