android aidl通讯详解


一,aidl进程通讯介绍

Android 是进程间内存是分离的,因此需要将对象分解成操作系统可理解的元数据,并将此打包让操作系统帮忙传递对象到另一个进程。这个过程是十分复杂繁重的,因此 Google 定义了 AIDL(Android Interface Definition Language)帮助开发者简化工作。


二,aidl通讯的实现

实现步骤:

1,创建.aidl文件-该文件(YourInterface.aidl)定义了客户端可用的方法和数据的接口。

2,在服务端和客户端makefile文件中都加入.aidl文件——Eclipse中的ADT插件自动将aidl文件编译成java代码生成在gen文件夹下

3,Implement your interface methods - The AIDL compiler creates an interface in the Java programming language from your AIDL interface. This interface has an inner abstract class named Stub that inherits the interface (and implements a few additional methods necessary for the IPC call). You must create a class that extends YourInterface.Stub and implements the methods you declared in your .aidl file. 
实现你定义aidl接口中的内部抽象类Stub。(这一步骤建议联系下面的代码理解会容易一些)

class Stub extends Binder implements binder.aidl.AIDLService (这里的代码在gen文件夹下)

4,服务端继承Service并且重载Service.onBind(Intent)以返回实现了接口的对象实例


具体实现

IMyService.aidl:

package com.myapp.aidl;
interface IMyService {
    public void playMusic(int id);
    int getName(int id);
}

这里的设置意味着:客户端可以向服务器发送指令和获取数据。

其中aidl中支持的参数类型为:基本类型(int、long、char、boolean等),String,CharSequence,List,Map。若想要自定义对象传输,后面会详细讲到。

定义好aidl文件后,请在服务端和客户端粘贴一份一模一样的aidl文件。

Eclipse在编译的时候会自动在gen下面为我们生成了一个代理类Stub,这个Stub类就是一个普通的Binder,只不过它实现了我们定义的aidl接口。通过它的静态方法asInterface我们就可以在客户端中得到IMyService的实例,进而通过实例来调用其他方法。

客户端:

Intent startIntent = new Intent();
		ComponentName componentName = new ComponentName(
				PACKAGE_REMOTE_SERVICE,
				NAME_REMOTE_SERVICE);
		startIntent.setComponent(componentName);
		bindService(startIntent, mConnection, Context.BIND_AUTO_CREATE);

这里的PACKAGE_REMOTE_SERVICE,NAME_REMOTE_SERVICE就是指定的service。

IMyService mService;  
private ServiceConnection mConnection = new ServiceConnection() {  
    public void onServiceConnected(ComponentName className, IBinder service) {  
        Log("connect service");  
        mService = IMyService.Stub.asInterface(service);  
        try {  
            int name = mService.getName(1);  
        } catch (RemoteException e) {  
  
        }  
    }  
  
  
    public void onServiceDisconnected(ComponentName className) {  
        Log("disconnect service");  
        mService = null;  
    }  
};  


服务端:

public class TestService extends Service {

	@Override
	public IBinder onBind(Intent arg0) {
		System.out.println("Service onBind");
		return mBinder;
	}

	private final IMyService.Stub mBinder = new IMyService.Stub() {
            @Override
	    String getName(int id){
	    	String name = findBy(id);//findBy()这是伪代码
	        return name;
	    }
            @Override 
            public void playMusic(int id){ 
                playMusic(int id);//playMusic()也是伪代码;
            }
        };
}

Manifest.xml中记得配置service,注意:exported="true"要配置为true,不然客户端是没有权限访问服务的。

 <service android:name="com.marttinli.service.MusicService"  android:exported="true">  
	    </service>  


如此一个基本的aidl通讯就完成了。

原理分析

那么接下来我们开始分析在gen目录下自动生成的文件到底是什么?

这里有几个重要的类和方法:asInterface(IBinder obj) , Proxy ,transact(int code, Parcel data, Parcel reply, int flags) ,onTransact(int code, Parcel data,Parcel reply, int flags)

这里涉及到Parcel序列化的一些技术,晚些会专门开一章来讲,这里你先明白Parcel对象是用来在进程间传递的数据对象,其他的基本类型和对象最终都要序列化程Parcel数据才能在进程间传递。

还记得客户端调用的IMyService.Stub.asInterface(service)吧,就在这里

public static com.marttinli.aidl.IMysService asInterface(
				android.os.IBinder obj) {
			if ((obj == null)) {
				return null;
			}
			android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
			if (((iin != null) && (iin instanceof com.marttinli.aidl.IMysService))) {
				return ((com.marttinli.aidl.IMysService) iin);
			}
			return new com.marttinli.aidl.IMysService.Stub.Proxy(
					obj);
		}

这里返回了一个Proxy,并把service作为输入。而Proxy正是完成了对我们在aidl接口中定义接口的实现。这里Client把参数转换成Parcel(_data)传递到了Service,而在Server端又会把返回数据保存到_reply中,这就形成了一次交互。

mRemote是远程对象,transact方法会执行onTransact方法

@Override
			public void playMusic(int id)
					throws android.os.RemoteException {
				android.os.Parcel _data = android.os.Parcel.obtain();
				android.os.Parcel _reply = android.os.Parcel.obtain();
				try {
					_data.writeInterfaceToken(DESCRIPTOR);
					_data.writeStrongBinder((((id != null)) ? (id
							.asBinder()) : (null)));
					mRemote.transact(Stub.TRANSACTION_playMusic, _data,
							_reply, 0);
					_reply.readException();
				} finally {
					_reply.recycle();
					_data.recycle();
				}
			}


@Override
		public boolean onTransact(int code, android.os.Parcel data,
				android.os.Parcel reply, int flags)
				throws android.os.RemoteException {
			switch (code) {
			case INTERFACE_TRANSACTION: {
				reply.writeString(DESCRIPTOR);
				return true;
			}
			case TRANSACTION_playMusic: {
				data.enforceInterface(DESCRIPTOR);
				int _arg0;
				_arg0 = data.readInt();
				this.playMusic(_arg0);
				reply.writeNoException();
				return true;
			}
		
			}
			return super.onTransact(code, data, reply, flags);
		}
而到了onTransact()中,已经是开始和service进行交互了,_data最终转化成了他原来的数据格式(这里是int),然后调用playMusic(),service端实现了playMusic()。


三,aidl通讯传递对象数据

aidl的数据传递本来也就是把基本数据类型转化成Parelable作为跨进称数据传输的格式。

所以一个对象想要传输,也只需要把对象转成Parelable 就可以跨进称传输。

我还专门为此写了个demo,确保可行。

服务端3个文件,我假定我需要传输的对象为Music:Music.java,Music.aidl,RemoteMusic.aidl

Music.aidl

parcelable Music;

RemoteMusic.aidl

package com.marttinli.aidl;
import com.marttinli.aidl.Music;
interface RemoteMusic{
	Music getMusic();
	void uploadMusic(in Music music);
}

Music.java

public class Music implements Parcelable {

	int id;
	String name;
	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;
	}

	@Override
	public int describeContents() {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public void writeToParcel(Parcel dest, int flags) {
		// TODO Auto-generated method stub
		dest.writeInt(id);
		dest.writeString(name);
	}
	
	/** 
     * 在想要进行序列号传递的实体类内部一定要声明该常量。常量名只能是CREATOR,类型也必须是 
     * Parcelable.Creator<T> 
     */  
    public static final Parcelable.Creator<Music> CREATOR = new Creator<Music>() {  
          
        /** 
         * 创建一个要序列号的实体类的数组,数组中存储的都设置为null 
         */  
        @Override  
        public Music[] newArray(int size) {  
            return new Music[size];  
        }  
          
        /*** 
         * 根据序列号的Parcel对象,反序列号为原本的实体对象 
         * 读出顺序要和writeToParcel的写入顺序相同 
         */  
        @Override  
        public Music createFromParcel(Parcel source) {  
        	int id = source.readInt();  
            String name = source.readString();  
            Music music = new Music();  
            music.setId(id);  
            music.setName(name);  
            return music;  
        }  
    };  

}

MusiceService.java

public class MusicService extends Service {

	MyBinder mBinder = new MyBinder();
	
	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return mBinder;
	}
	
	
	private class MyBinder extends RemoteMusic.Stub{  
		  
        public Music getMusic() throws RemoteException {  
              
        	Music music = new Music();  
        	music.setId(1)  ;
            music.setName("沉默是金")  ;             
            return music;  
        }

		@Override
		public void uploadMusic(Music music) throws RemoteException {
			// TODO Auto-generated method stub
			Log.d("marttinli", "uploadMusic music:"+music.getName());
		}}  
}


客户端。客户端的Music.java,Music.aidl,RemoteMusic.aidl,这三个文件请从服务器端copy过来,连外面的包也一起copy过来,确保一模一样。

客户端绑定服务和发送请求的代码就不贴了,第三点就已经说过了。

这里有几个点要注意的:

1,Music.java中注释也写了,在序列化时,想要进行序列号传递的实体类内部一定要声明CREATOR 常量。常量名只能是CREATOR,类型也必须是 Parcelable.Creator<T> 

2,Music.java在序列化时,根据序列号的Parcel对象,反序列号为原本的实体对象 读出顺序要和writeToParcel的写入顺序相同 

3,标识符in,out,inout,类似c语言里的用法,in表示输入参数,这个很好理解。out表示该参数做为输入,也是作为输出;比如change(int value),输入value=1,传到service后,service重新复制value=2,那么在客户端这个value==2.

4,Music.java,Music.aidl也要名字一样,后缀不一样而已。


四,aidl实现数据监听

有个问题就是,目前我们已经实现了客户端对服务端发送指令和获取数据,但有一种情况:当你发送指令后,服务在处理数据要执行耗时操作才能返回数据。对,这个时候你应该很容易可以想到再定义一个接收数据的接口,只要服务端处理完数据调用该接口方法,在客户端实现的方法就可以获取到对应数据。

实现步骤(这里接着上面的代码写):

1,再定义一个RemoteMusicCallback.aidl,顾名思义,这个是服务端执行完耗时操作后对客户端的callback。这里的标识符一定是in,至于原因,稍后会讲到。

interface RemoteMusicCallback{
	void onChanged(in Music music);
}
2,既然有了Callback,那么必须要声明注册监听和反注册的方法,这里放在原来的RemoteMusic.aidl中是最合适的。那么RemoteMusic.adil变为:

interface RemoteMusic{
	Music getMusic();
	void uploadMusic(in Music music);
	void registerMusic(in RemoteMusicCallback callback);
	void unRegisterMusic(in RemoteMusicCallback callback);
}

3,然后把Music.java,Music.aidl,RemoteMusic.aidl,RemoteMusicCallback.aidl,连同包一起复制到客户端和服务端。

客户端:

加入注册music变化的callback:

	RemoteMusicCallback mRemoteMusicCallback = new RemoteMusicCallback.Stub() {
		
		@Override
		public void onChanged(Music music) throws RemoteException {
			// TODO Auto-generated method stub
			System.out.println("music:"+music.getName());
		}
	};

	@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			// TODO Auto-generated method stub
			Log.d("marttinli","connected successful");
			mService = RemoteMusic.Stub.asInterface(service); 
			try {
				mService.registerMusic(mRemoteMusicCallback);
			} catch (RemoteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

在取消ServiceConnection绑定的时候先取消注册:

private void unBindService(){
		
		if (mService!=null) {
			if (mRemoteMusicCallback!=null&& mRemoteMusicCallback.asBinder().isBinderAlive()) {
				try {
					mService.unRegisterMusic(mRemoteMusicCallback);
				} catch (RemoteException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			if (mConnection!=null) {
				unbindService(mConnection);
			}
		}
	}


服务端:

这里有一点要注意的,我们是提供service的,谁注册监听我的数据变化,我就要给谁提供服务。那么必然存在一种可能,很多应用同时监听我的服务,那么这时我就需要把他们都按顺序存放在一个队列里,等到他们监听的服务变化时,我就给他们一一推送过去。这里我用ArrayList来存放队列,在MyBinder下实现队列的添加和删除:

private class MyBinder extends RemoteMusic.Stub{  
		  
        public Music getMusic() throws RemoteException {  
              
        	Music music = new Music();  
        	music.setId(1)  ;
            music.setName("沉默是金")  ;             
            return music;  
        }

		@Override
		public void uploadMusic(Music music) throws RemoteException {
			// TODO Auto-generated method stub
			Log.d("marttinli", "uploadMusic music:"+music.getName());
		}

		@Override
		public void registerMusic(RemoteMusicCallback callback)
				throws RemoteException {
			// TODO Auto-generated method stub
			mCallbacks.add(callback);
                        notifyListeners();//这是一个段伪代码
	}

		@Override
		public void unRegisterMusic(RemoteMusicCallback callback)
				throws RemoteException {
			// TODO Auto-generated method stub
			mCallbacks.remove(callback);
		}}  

notifyListeners模拟数据发送:

private void notifyListeners() throws RemoteException{
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				int i = 0;
				for (int j = 0; j < 10; j++) {
					for (RemoteMusicCallback callback : mCallbacks) {
						Music music = new Music();
						music.setId(i);
						music.setName("沉默是金 "+i);
						try {
							callback.onChanged(music);
						} catch (RemoteException e1) {
							// TODO Auto-generated catch block
							e1.printStackTrace();
						}
						try {
							Thread.sleep(3000);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
					i++;
				}

			}
		}).start();
		
		
	}

有没有发现,这样就实现了对服务端数据的监听。

在实际开发中实现对service的数据监听经常有这个需求,service通常用来处理一类数据,你通过service获取你想要的数据,也可以监听service数据,一旦某条你监听的数据发生变化,service就会根据注册监听的队列对象一一推送该数据过去。而你只要在监听实现中获取数据。

不知道你有没有发现,aidl里面定义的都是interface,顾名思义,aidl就是定义的接口,一个供不同进程连接的接口。他们会在gen目录下生成对应的.java文件,也是接口,是继承IInterface的接口;因为需要通过Binder跨进程传输的接口都是需要继承IInterface的。


五,aidl与Messager在实现进程通讯上的区别

一般,你只有在客户端需要访问另一进程的 Service ,且需要 Service 多纯种处理客户端请求时才有必要使用 AIDL;如果只需要在进程内和 Service 通信,只需要实现 Binder 通过 onBind() 返回对象;如果需要进程间通信但不需要并发处理请求,可考虑使用 Messenger,Messenger 底层实现和 AIDL 类似,上层采用 handler-message 方式通信,更为简单易用(具体请参考上文)。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页