本次我想讲一下在AIDL的使用方法,目前它还是最强大的进程间通信方法,支持一对多并发通信,支持实时通信。
1.传输基础类型
我使用一个简单例子简述整个AIDL的搭建过程
首先创建一个AIDLServer工程,包的路径为com.example.aidlserver,我们添加一个AIDL文件
package com.example.aidlserver;
interface IRemote
{
int multi(int a, int b);
}
然后再添加一个Service类作为该aidl接口函数的具体实现
public class ArithmeticService extends Service{
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return mBinder;
}
private final IRemote.Stub mBinder = new IRemote.Stub() {
@Override
public int multi(int a, int b) throws RemoteException {
// TODO Auto-generated method stub
return (a * b);
}
};
}
然后再注册文件里加
<service android:name=".ArithmeticService"
android:process=":remote">
<intent-filter >
<action android:name="com.remote.service.CALCULATOR"/>
</intent-filter>
</service>
接下来在建立一个AIDLClient的工程,包的路径com.example.aidlclient,
我们还要在com.example下再创建一个aidlserver目录,这是必须和之前工程的AIDL文件的目录一样。然后在com.example.aidlserver目录下创建AIDL文件,其代码和之前的AIDL文件一样。
然后我们对MainActivity和对应的布局文件做出改动
public class MainActivity extends Activity implements OnClickListener {
EditText mFirst, mSecond;
Button mAdd, mSubtract, mClear;
TextView mResultText;
protected IRemote mService;
ServiceConnection mServiceConnection;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mFirst = (EditText) findViewById(R.id.firstValue);
mSecond = (EditText) findViewById(R.id.secondValue);
mResultText = (TextView) findViewById(R.id.resultText);
mAdd = (Button) findViewById(R.id.add);
mAdd.setOnClickListener(this);
initConnection();
}
void initConnection() {
mServiceConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
mService = null;
Toast.makeText(getApplicationContext(), "no", Toast.LENGTH_SHORT).show();
Log.d("IRemote", "Binding - Service disconnected");
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
mService = IRemote.Stub.asInterface((IBinder) service);
Toast.makeText(getApplicationContext(), "yes", Toast.LENGTH_SHORT).show();
Log.d("IRemote", "Binding is done - Service connected");
}
};
if (mService == null) {
Intent it = new Intent();
it.setAction("com.remote.service.CALCULATOR");
// binding to remote service
bindService(it, mServiceConnection, Service.BIND_AUTO_CREATE);
}
}
protected void onDestroy() {
super.onDestroy();
unbindService(mServiceConnection);
};
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.add: {
int a = Integer.parseInt(mFirst.getText().toString());
int b = Integer.parseInt(mSecond.getText().toString());
try {
mResultText.setText("Result -> multi ->" + mService.multi(a, b));
Log.d("IRemote", "Binding - Add operation");
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
break;
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/resultText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<EditText
android:id="@+id/firstValue"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="Enter a numeric value"/>
<EditText android:id="@+id/secondValue"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="Enter a numeric value"/>
<LinearLayout android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:weightSum="3">
<Button android:id="@+id/add"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="multi" android:layout_weight="1"/>
</LinearLayout>
</LinearLayout>
好了运行两个工程到手机上,结果如下
然后我对AIDLServer的注册文件作出改动
<service android:name=".ArithmeticService">
<intent-filter >
<action android:name="com.remote.service.CALCULATOR"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
程序正常运行,但是如果去掉那个默认category就不行
我个人认为这是android:process=”:remote” 这个不仅将Service放在一个独立的进程,还因为这个进程只有它一个所以系统默认为它加了默认category
那我接下来继续做实验研究
<service android:name=".ArithmeticService"
android:process="com.example.aidlserver.remote">
<intent-filter >
<action android:name="com.remote.service.CALCULATOR"/>
</intent-filter>
</service>
运行正常
但我再添加一个activity,然后再注册文件里加代码
<activity android:name=".TwoActivity"
android:process="com.example.aidlserver.remote"/>
这就运行不了了,看来如果一个进程只有一个activity或者一个service,系统就会为其添加一个默认category
2.传输对象
然后就是说AIDL如何传输一个实体类对象去传递数据。但我们必须使用Parcelable将类序列化。
首先我们创建一个MyServer,包名为com.lypeer.ipcclient。因为aidl文件中要使用Parcelable对象那就必须创建一个同名的AIDL文件。所以首先我们创建一个Book.aidl,不能先创建Book.java然后再创建Book.aidl,我想这是因为Book通过接入Parcelable接口实现序列化导致系统认为它是接口,然后aidl文件也是一个接口,所以在创建aidl文件时系统认为已经有同名的接口已被创建。代码如下:
package com.lypeer.ipcserver;
public class AIDLService extends Service {
public final String TAG = this.getClass().getSimpleName();
//由AIDL文件生成的BookManager
private final BookManager.Stub mBookManager = new BookManager.Stub() {
@Override
public void changeBook(Book book) throws RemoteException {
synchronized (this) {
if (book == null) {
Log.e(TAG, "Book is null in In");
book = new Book();
}
//尝试修改book的参数,主要是为了观察其到客户端的反馈
book.setPrice(2333);
}
}
};
@Override
public void onCreate() {
super.onCreate();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e(getClass().getSimpleName(), String.format("on bind,intent = %s", intent.toString()));
return mBookManager;
}
}
对于序列化,我们要在引入Parcelable接口添加默认函数后,必须自己添加readFromParcel为了读取数据。
package com.lypeer.ipcclient;
public class Book implements Parcelable{
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
private String name;
private int price;
public Book(){}
protected Book(Parcel in) {
name = in.readString();
price = in.readInt();
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(price);
}
/**
* 参数是一个Parcel,用它来存储与传输数据
* @param dest
*/
public void readFromParcel(Parcel dest) {
//注意,此处的读值顺序应当是和writeToParcel()方法中一致的
name = dest.readString();
price = dest.readInt();
}
//方便打印数据
@Override
public String toString() {
return "name : " + name + " , price : " + price;
}
}
package com.lypeer.ipcclient;
parcelable Book;
package com.lypeer.ipcclient;
import com.lypeer.ipcclient.Book;
interface BookManager {
void changeBook(inout Book book);
}
<service
android:name=".AIDLService">
<intent-filter>
<action android:name="com.lypeer.aidl"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
接下来我们创建MyClient
package com.lypeer.ipcclient;
public class AIDLActivity extends AppCompatActivity {
//由AIDL文件生成的Java类
private BookManager mBookManager = null;
//标志当前与服务端连接状况的布尔值,false为未连接,true为连接中
private boolean mBound = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_aidl);
}
/**
* 按钮的点击事件,点击之后调用服务端的changeBook方法
*
* @param view
*/
public void changeBook(View view) {
//如果与服务端的连接处于未连接状态,则尝试连接
if (!mBound) {
attemptToBindService();
Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show();
return;
}
if (mBookManager == null) return;
Book book = new Book();
book.setName("APP研发录In");
book.setPrice(30);
try {
mBookManager.changeBook(book);
Log.e(getLocalClassName(), book.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
/**
* 尝试与服务端建立连接
*/
private void attemptToBindService() {
Intent intent = new Intent();
intent.setAction("com.lypeer.aidl");
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStart() {
super.onStart();
if (!mBound) {
attemptToBindService();
}
}
@Override
protected void onStop() {
super.onStop();
if (mBound) {
unbindService(mServiceConnection);
mBound = false;
}
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(getLocalClassName(), "service connected");
mBookManager = BookManager.Stub.asInterface(service);
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(getLocalClassName(), "service disconnected");
mBound = false;
}
};
}
然后还有Book.java,Book.aidl,BookManger.aidl那和之前一样
3.in、out、inout
AIDL代码之中的in代表输入型参数,out代表输出型参数,inout代表输入输出型参数。
那我这里就好好讲讲这啥意思。
当使用没有返回值的函数时,in 指客户端能够传输序列化的实例过去,这个实例如果被服务端改变,客户端再去使用这个实例,但这个实例的值没有改变。
out我个人觉得很坑,客户端传输实例过去,这个实例却变成空的,它没有传输数据的能力,最重要的是这个实例只是数据的索引,这个索引是空的这个就服务端即便赋值了这个实例也返回不了客户端。
inout指的是你能够传输实例过去,并且在服务端改变后,这个改变在客户端生效。
使用的时候最好用in,其他的方式占资源。
下一次说AIDL生成了什么样的java代码,与IBinder有何关系,如何实现进程通讯。