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




  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值