Android AIDL开发
Introduction
在Android中, 每个应用程序都运行在自己的进程中,拥有独立的内存空间。但是有些时候我们的应用程序需要跟其它的应用程序进行通信,这个时候该怎么办呢?显然, Java中不允许跨进程内存共享.无法直接交换数据。Android中可以采用AIDL的方式实现进程间通信(interprocess communication(IPC))。
Android Developer原文介绍如下:AIDL (Android Interface Definition Language) is similar to other IDLs you might have worked with. It allows you to define the programming interface that both the client and service agree upon in order to communicate with each other using interprocess communication (IPC). On Android, one process cannot normally access the memory of another process. So to talk, they need to decompose their objects into primitives that the operating system can understand, and marshall the objects across that boundary for you. The code to do that marshalling is tedious to write, so Android handles it for you with AIDL.
Scenario (应用场景)
当且仅当你需要让来自不同应用程序的客户端通过你的Service实现IPC并且希望在你的Service中处理多线程问题,你才需要使用AIDL。
Android Developer原文介绍如下:
Note: Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service. If you do not need to perform concurrent IPC across different
applications, you should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger. Regardless, be sure that you understand Bound Services before implementing
an AIDL.
线程阻塞与安全问题
Before you begin designing your AIDL interface, be aware that calls to an AIDL interface are direct function calls. You should not make assumptions about the thread in which the call occurs. What happens is different depending
on whether the call is from a thread in the local process or a remote process. Specifically:
- Calls made from the local process are executed in the same thread that is making the call. If this is your main UI thread, that thread continues to execute in the AIDL interface. If it is another thread, that is the one that executes your code in the service. Thus, if only local threads are accessing the service, you can completely control which threads are executing in it (but if that is the case, then you shouldn‘t be using AIDL at all, but should instead create the interface by implementing a Binder).
- Calls from a remote process are dispatched from a thread pool the platform maintains inside of your own process. You must be prepared for incoming calls from unknown threads, with multiple calls happening at the same time. In other words, an implementation of an AIDL interface must be completely thread-safe.
- The oneway keyword modifies the behavior of remote calls. When used, a remote call does not block; it simply sends the transaction data and immediately returns. The implementation of the interface eventually receives this as a regular call from the Binder thread pool as a normal remote call. If oneway is used with a local call, there is no impact and the call is still synchronous.
Defining an AIDL Interface
你需要按照Java编程语言的语法来定义你的AIDL接口并将其保存到后缀名为 .aidl 的文件中。.aidl 的文件必须保存在 主应用和Client应用的src目录下。当你的应用程序中包含 .aidl时,Android SDK tools会根据 .aidl的定义在应用程序的gen/ 目录下自动产生IBinder接口。主应用程序中的Service需要实现IBinder接口,然后Client应用就可以绑定到此Service并通过IBinder对象实现IPC。
AIDL实战
充分了解上述知识之后,就可以开始动手实现AIDL接口并实现进程间通信了。
为了让大家更好的理解先展示一下整个工程的目录结构
1.创建 .aidl 文件
你必须按照Java的语法结构来构建.aidl 文件,每一个.aidl 文件只能定义一个接口。
AIDL默认支持一下数据类型:
- All primitive types in the Java programming language (such as int, long, char, boolean, and so on)
- String
- CharSequence
- List All elements in the List must be one of the supported data types in this list or one of the other AIDL-generated interfaces or parcelables you‘ve declared. A List may optionally be used as a "generic" class (for example, List<String>). The actual concrete class that the other side receives is always an ArrayList, although the method is generated to use the List interface.
- Map All elements in the Map must be one of the supported data types in this list or one of the other AIDL-generated interfaces or parcelables you‘ve declared. Generic maps, (such as those of the form Map<String,Integer> are not supported. The actual concrete class that the other side receives is always a HashMap, although the method is generated to use the Map interface.
如果你要使用除了上述的其它类型对象,你必须通过 import语句来导入,即使它和你在.aidl 文件中定义的包名一致。
下面是.aidl 示例文件
// IRemoteService.aidl package com.ricky.android.aidl.protocal; import com.ricky.android.aidl.model.Student; import com.ricky.android.aidl.model.Address; // Declare any non-default types here with import statements /** Example service interface */ interface IRemoteService { /** Request the process ID of this service, to do evil things with it. */ int getPid(); /** Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ int sum(int number, String process); void say(int time, String message); Student getStudent(int id); List<Address> getAddress(int id); }
AIDL接口需要传递Student对象和Address对象,二者都实现了Parcelable接口,代码分别如下:
Student.java
package com.ricky.android.aidl.model; import java.util.List; import android.os.Parcel; import android.os.Parcelable; public class Student implements Parcelable { private int id; private String name; private List<String> hobbies; private Address addr; public static final Parcelable.Creator<Student> CREATOR = new Parcelable.Creator<Student>() { @Override public Student createFromParcel(Parcel in) { // TODO Auto-generated method stub return new Student(in); } @Override public Student[] newArray(int size) { // TODO Auto-generated method stub return new Student[size]; } }; public Student(){ } protected Student(Parcel in){ readFromParcel(in); } 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 List<String> getHobbies() { return hobbies; } public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; } public Address getAddr() { return addr; } public void setAddr(Address addr) { this.addr = addr; } @Override public int describeContents() { // TODO Auto-generated method stub return 0; } @Override public void writeToParcel(Parcel out, int flags) { out.writeInt(id); out.writeString(name); out.writeList(hobbies); out.writeParcelable(addr, 0); } @SuppressWarnings("unchecked") public void readFromParcel(Parcel in) { id = in.readInt(); name = in.readString(); hobbies = in.readArrayList(getClass().getClassLoader()); addr = in.readParcelable(getClass().getClassLoader()); } @Override public String toString() { return "Student [id=" + id + ", name=" + name + ", hobbies=" + hobbies + ", addr=" + addr + "]"; } }
Address.java
package com.ricky.android.aidl.model; import android.os.Parcel; import android.os.Parcelable; public class Address implements Parcelable { private String province; private String city; private String district; private String street ; private int postcode; public static final Parcelable.Creator<Address> CREATOR = new Parcelable.Creator<Address>() { @Override public Address createFromParcel(Parcel in) { return new Address(in); } @Override public Address[] newArray(int size) { return new Address[size]; } }; public Address(){ } public Address(Parcel in){ readFromParcel(in); } public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getDistrict() { return district; } public void setDistrict(String district) { this.district = district; } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public int getPostcode() { return postcode; } public void setPostcode(int postcode) { this.postcode = postcode; } @Override public String toString() { return "Address [province=" + province + ", city=" + city + ", district=" + district + ", street=" + street + ", postcode=" + postcode + "]"; } @Override public int describeContents() { // TODO Auto-generated method stub return 0; } @Override public void writeToParcel(Parcel out, int flags) { out.writeString(province); out.writeString(city); out.writeString(district); out.writeString(street); out.writeInt(postcode); } public void readFromParcel(Parcel in) { province = in.readString(); city = in.readString(); district = in.readString(); street = in.readString(); postcode = in.readInt(); } }
注意:.aidl 文件必须存放在.aidl 文件中定义的包名目录下,例如本例中package com.ricky.android.aidl.protocal;
2.实现IBinder接口
package com.ricky.android.aidl.protocal; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import com.ricky.android.aidl.model.Address; import com.ricky.android.aidl.model.Student; import com.ricky.android.aidl.util.Logger; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; public class MyService extends Service { private static final String TAG = MyService.class.getSimpleName(); @Override public void onCreate() { Logger.i(TAG, "onCreate"); super.onCreate(); } @Override public IBinder onBind(Intent intent) { Logger.i(TAG, "onBind"); return mBinder; } private final IRemoteService.Stub mBinder = new IRemoteService.Stub() { @Override public int getPid() throws RemoteException { return Process.myPid(); } @Override public int sum(int number, String process) throws RemoteException { Logger.i(TAG, "call sum,number="+number+"**process="+process); int sum = 0; for(int i=0;i<number;i++){ sum += i; } return sum; } @Override public void say(int time, String message) throws RemoteException { Logger.i(TAG, "call say,time="+time+"**message="+message); Logger.i(TAG, "say "+message+ " from "+time); } @Override public Student getStudent(int id) throws RemoteException { Student stu = new Student(); stu.setId(id); stu.setName("Ricky"); stu.setHobbies(Arrays.asList("篮球","code","Music")); Address addr = new Address(); addr.setProvince("北京"); addr.setCity("北京市"); addr.setDistrict("朝阳区"); addr.setStreet("静安中心"); addr.setPostcode(100010); stu.setAddr(addr); return stu; } @Override public List<Address> getAddress(int id) throws RemoteException { List<Address> list = new ArrayList<Address>(); Address addr = new Address(); addr.setProvince("北京"); addr.setCity("北京市"); addr.setDistrict("朝阳区"); addr.setStreet("香河园"); addr.setPostcode(100010); list.add(addr); return list; } }; }
在Manifest清单文件中注册Service
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.ricky.android.aidl" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="17" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.ricky.android.aidl.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name="com.ricky.android.aidl.protocal.MyService"> <intent-filter > <action android:name="com.ricky.android.aidl"/> </intent-filter> </service> </application> </manifest>
到这里AIDL的Server端已经完成了,Client通过绑定MyService获取到IBinder对象就可以实现 IPC了。
There are a few rules you should be aware of when implementing your AIDL interface:
- Incoming calls are not guaranteed to be executed on the main thread, so you need to think about multithreading from the start and properly build your service to be thread-safe.
- By default, RPC calls are synchronous. If you know that the service takes more than a few milliseconds to complete a request, you should not call it from the activity‘s main thread, because it might hang the application (Android might display an "Application is Not Responding" dialog)—you should usually call them from a separate thread in the client.
- No exceptions that you throw are sent back to the caller.
3.暴露接口给Client调用
3.1.新建客户端Application,结构如下:
3.2 引入AIDL文件以及相关的类,直接从服务端里代码copy过来就OK。
3.3 绑定远程Service
package com.ricky.android.aidlclient; import java.util.List; import com.ricky.android.aidl.model.Address; import com.ricky.android.aidl.model.Student; import com.ricky.android.aidl.protocal.IRemoteService; import com.ricky.android.aidlclient.util.Logger; 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.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity implements OnClickListener { protected static final String TAG = null; private Button bt_bind; private IRemoteService mIRemoteService; private Button bt_unbind; private TextView tv_status; private boolean isBind; private Button bt_call; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(); setListener(); processLogic(); } private void findViewById() { bt_bind = (Button) findViewById(R.id.bt_bind); bt_unbind = (Button) findViewById(R.id.bt_unbind); bt_call = (Button) findViewById(R.id.bt_call); tv_status = (TextView) findViewById(R.id.tv_status); } private void setListener() { bt_bind.setOnClickListener(this); bt_unbind.setOnClickListener(this); bt_call.setOnClickListener(this); } private void processLogic() { tv_status.setText("Unbinding"); } private ServiceConnection mConnection = new ServiceConnection() { // Called when the connection with the service is established @Override public void onServiceConnected(ComponentName name, IBinder service) { Logger.i(TAG, "onServiceConnected"); // Following the example above for an AIDL interface, // this gets an instance of the IRemoteInterface, which we can use to call on the service mIRemoteService = IRemoteService.Stub.asInterface(service); } // Called when the connection with the service disconnects unexpectedly @Override public void onServiceDisconnected(ComponentName name) { Logger.i(TAG, "onServiceDisconnected"); mIRemoteService = null; } }; @Override public void onClick(View v) { switch (v.getId()) { case R.id.bt_bind: bindService(new Intent("com.ricky.android.aidl"), mConnection, Context.BIND_AUTO_CREATE); isBind = true; tv_status.setText("Binding"); break; case R.id.bt_unbind: if (isBind) { try { unbindService(mConnection); isBind = false; tv_status.setText("Unbinding"); } catch (Exception e) { e.printStackTrace(); } } break; case R.id.bt_call: if (isBind) { try { int pid = mIRemoteService.getPid(); Logger.i(TAG, "call mIRemoteService's getPid pid="+pid); mIRemoteService.say(1, "hello world"); int sum = mIRemoteService.sum(100, "ricky"); Logger.i(TAG, "call mIRemoteService's sum result="+sum); Student stu = mIRemoteService.getStudent(3); Logger.i(TAG, "call mIRemoteService's getStudent stu="+stu); List<Address> list = mIRemoteService.getAddress(2); Logger.i(TAG, "call mIRemoteService's getAddress list="+list); } catch (RemoteException e) { e.printStackTrace(); } }else{ Logger.i(TAG, "not bind remote service"); Toast.makeText(getApplicationContext(), "not bind remote service",Toast.LENGTH_SHORT).show(); } break; default: break; } } @Override protected void onDestroy() { if (isBind) { try { unbindService(mConnection); isBind = false; tv_status.setText("Unbinding"); } catch (Exception e) { e.printStackTrace(); } } super.onDestroy(); } }
通过 Context.bindService(Intent service, ServiceConnection conn, int flags);方法绑定远程Service,然后通过 IRemoteService com.ricky.android.aidl.protocal.IRemoteService.Stub.asInterface(IBinder obj);方法获取到 IRemoteService ,此时Client 就可以通过获取到的IRemoteService 对象进行IPC了。
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="${relativePackage}.${activityClass}" android:orientation="vertical" > <TextView android:id="@+id/tv_status" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16sp" /> <Button android:id="@+id/bt_bind" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/bind" /> <Button android:id="@+id/bt_unbind" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/unbind" /> <Button android:id="@+id/bt_call" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/call" /> </LinearLayout>
OK,到这里已经完成了AIDL完整流程了,如有写的不对的地方还请多多指教,谢谢!
参考资料:
Android Developer AIDL:http://developer.android.com/guide/components/aidl.html
源码下载:http://download.csdn.net/detail/fx_sky/8233633
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。