一篇好的文章势必是可以用简短的文字就可以讲透一个知识点,所以我一般写文章都是把最本质的原理体现出来,如果你要阅读哪些详细的说明可以百度这个相关的技术点。跟着我的步骤操作我可以用最少的时间和精力让你用起这个技术点,看到效果,只有自己操作一遍之后看到效果了才可以学好一个技术点,我会把技术点的精华和本质给大家说明,同学们可以根据自己已有的知识去体会,融汇到以前学的知识中去,看看你现在学的知识点和以前的知识点有什么联系和不同,只有多思考这些才可以熟练的使用学到的知识点。
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:下载