Activity如果想要调用Service中的方法就需要绑定服务,然后才能获取服务的代理对象。进一步调用服务中的方法。类似这样的代码:
首先去创建一个服务:
public class MyService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
class MyBinder extends Binder implements IService {
@Override
public void showToast() {
Show();
}
}
private void Show() {
Toast.makeText(this, "我是服务中的方法", Toast.LENGTH_SHORT).show();
}
}复制代码
这里Iservice是为了解耦去定义的接口:
interface IService {
void showToast();
}复制代码
我们在Activity中需要去获取binder对象,然后调用服务中的方法:
package remote.zz.remoteservice;
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.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import java.util.List;
/**
* Created by Administrator on 2018/6/27.
*/
public class MainActivity extends AppCompatActivity {
private MyServiceConnection conn;
private IService ser;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//绑定服务
Intent service = new Intent(this, MyService.class);
conn = new MyServiceConnection();
bindService(service, conn, Context.BIND_AUTO_CREATE);
//点击对象
findViewById(R.id.tv).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//调用服务中的方法
ser.showToast();
}
});
}
//内部类
class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//获取服务中代理对象
ser = (IService) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
@Override
protected void onDestroy() {
//取消绑定,防止报错
unbindService(conn);
super.onDestroy();
}
}复制代码
上边的代码是同一个应用中Activity调用Service的基本代码。
假如有这样一个需求,A应用绑定B应用的服务,并互相之间传递数据,这样的需求呢?这个时候你首先会想到用intent的bundle,共享文件或者sp对象(并发会有问题,不建议使用),Messenger(它是封装aidl的,不好用)或者广播,当然这些手段是可以的。但是除了这些之外,我们还可以使用aidl实现跨应用之间的数据传递和调用。为了方便叙述,A应用称为本地应用,B应用成为远程服务应用。跨应用之间通讯其实内部也是使用Binder去做代理对象。只不过这个binder是系统帮我们生成的代码,但是我们需要按照步骤去创建需要的文件:
步骤:
1.先在远程服务应用B工程的main目录下建立一个名为aidl的文件夹,再右键该文件夹,在该文件夹下面新建一个名字与AndroidManifest.xml中的package相同的包,再右键该包,新建你所需的AIDL文件;
创建一个aidl文件,其实就是一个接口:
IRemoteService.aidl
package remote.zz.remoteservice;
//注意这里需要自己导入包
import remote.zz.remoteservice.Book;
interface IRemoteService {
List<Book> getData();
//注意这里的in必须写上,表示传递的方向
void setData(in Book book);
}复制代码
还需要创建一个Book数据类型,并且实现序列化:
package remote.zz.remoteservice;
import android.os.Parcel;
import android.os.Parcelable;
//这个写到java文件夹下即可
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(bookId);
out.writeString(bookName);
}
public static final Parcelable.Creator<Book> CREATOR = new Parcelable.
Creator<Book>() {
public Book createFromParcel(Parcel in) {
return new Book(in);
}
public Book[] newArray(int size) {
return new Book[size];
}
};
private Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
@Override
public String toString() {
return "Book{" +
"bookId=" + bookId +
", bookName='" + bookName + '\'' +
'}';
}
}复制代码
同时需要创建一个Book.aidl文件
package remote.zz.remoteservice;//这里的包名就是book的包名
parcelable Book;//被序列化的数据类
复制代码
2.当你进入你的AIDL文件并编写好了之后,点击AS上方菜单栏中的Build->Make Project,之后便可以在当前工程的app/build/generated/source/aidl/debug中找到系统为我们生成的.java文件了。这个对用生成的java文件就是系统为你创建binder类。
对应的服务我们应该这样写:
package remote.zz.remoteservice;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Administrator on 2018/6/27.
*/
public class RemoveService extends Service {
private ArrayList arr;
@Override
public void onCreate() {
super.onCreate();
arr = new ArrayList<Book>();
arr.add(new Book(1001, "我与地坛"));
arr.add(new Book(1002, "钢铁是怎样练成的"));
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
//系统为我们生成的binder,名字叫Stub, 可点进去查看源码
class MyBinder extends IRemoteService.Stub {
@Override
public List<Book> getData() throws RemoteException {
return arr;
}
@Override
public void setData(Book book) throws RemoteException {
arr.add(book);
}
}
}复制代码
在清单文件别忘了配置过滤器,让其他应用可以开启服务
<!--提供远程服务的服务,5.0之后必须写 android:exported="true"-->
<service
android:name=".RemoveService"
android:exported="true">
<intent-filter>
<action android:name="zz.remove.service" />
</intent-filter>
</service>复制代码
那么本地应用A怎么调用呢?
同样先复制远程应用aidl文件夹到本地应用的mian下,然后在mian下创建同远程应用Book类一样的文件夹,把Book类复制到这个文件夹下边,然后去Build->Make Project,同样也会生成一个binder类,这个时候我们就可以调用远程的应用获取服务中的数据:
package local.zz.localapp;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import java.util.List;
import remote.zz.remoteservice.Book;
import remote.zz.remoteservice.IRemoteService;
public class MainActivity extends AppCompatActivity {
private MyConnection conn;
private IRemoteService remote;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//隐私启动服务,5.0之后需要设置包名,否则报错
Intent service = new Intent();
service.setAction("zz.remove.service");
service.setPackage("remote.zz.remoteservice");
conn = new MyConnection();
bindService(service, conn, BIND_AUTO_CREATE);
findViewById(R.id.tv).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
//获取远程服务数据
List<Book> data = remote.getData();
for (Book book : data) {
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
findViewById(R.id.tv2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
//调用远程方法传递数据
remote.setData(new Book(1003, "让子弹飞"));
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
class MyConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//获取代理对象
remote = IRemoteService.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
@Override
protected void onDestroy() {
unbindService(conn);
super.onDestroy();
}
}
复制代码
到此,aidl的使用完毕!
aidl可以传递aidl声明的接口,所以我们可以做一个观察者模式,动态去监听图书的变化,然后通知注册的观察者。
写这个demo时候遇到一个问题:如果本app的服务没有启动,另外app去绑定获取不到代理对象,还不知道原因是什么!