这里记录一下使用Android Studio实现AIDL通信的操作步骤

说明一下:这一篇仅仅记录了初步使用步骤,传输的数据是基本类型。

一·服务端

1.首先创建一个Application,在此Application中创建一个Service,姑且叫RemoteService吧

在Manifest文件中给Service添加一个action,这是为了方便调用方找到这个服务,这两个箭头所指向的内容后面会用到

android 多个module 可以同一个包名吗 android module间通信_客户端

2.定义一个私有方法,返回值为一个String。

public class RemoteService extends Service {
	
	public RemoteService() {
	}

	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

	@Override
	public void onCreate() {
		super.onCreate();		
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		return super.onStartCommand(intent, flags, startId);
	}

	private String getTime() {
		return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss sss").format(new Date());
	}
	@Override
	public void onDestroy() {	
		super.onDestroy();
	}	
}

3.创建.aidl文件,project视图下,选中src-->main,new-->AIDL-->AIDL File,新建一个AIDL文件,取名IRemoteServiceAidl

android 多个module 可以同一个包名吗 android module间通信_ci_02

4.build一下工程,可以看到编译器帮助我们生成了一个对应的AIDL的java文件

android 多个module 可以同一个包名吗 android module间通信_ide_03

4.大体看一下文件内容,Stub是一个抽象类,继承了Binder类,实现了IRemoteServiceAidl接口,也就是说,在服务中,需要继承Stub类,然后实现IRemoteServiceAidl接口方法,这样调用端就可以访问service的方法了

android 多个module 可以同一个包名吗 android module间通信_android_04

android 多个module 可以同一个包名吗 android module间通信_ide_05

 4.service里面添加Binder成员变量,并在onBind回调方法中返回实例对象,此时的Binder类似于中介,可通过它调用service的公有或者私有方法。

public class RemoteService extends Service {
	private RemoteServiceBinder binder;
	public RemoteService() {
	}

	@Override
	public IBinder onBind(Intent intent) {
		return binder;
	}

	@Override
	public void onCreate() {
		super.onCreate();
		binder = new RemoteServiceBinder();
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		return super.onStartCommand(intent, flags, startId);
	}

	private String getTime() {
		return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss sss").format(new Date());
	}
	@Override
	public void onDestroy() {
		System.out.println("remote service has been destory");
		super.onDestroy();
	}

	/**
	 * 实现aidl文件中暴露的接口方法
	 */
	private class RemoteServiceBinder extends IRemoteServiceAidl.Stub {
		@Override
		public String callGetTime() throws RemoteException {
            //调用service中的私有方法
			return getTime();
		}
	}
}

至此,AIDL服务端完成。

二·客户端

1.同样,新建一个application,复制服务端的aidl文件夹到相同层级的目录,然后build一下项目

android 多个module 可以同一个包名吗 android module间通信_ide_06

 2.定义RemoteServiceBinder接口对象,并在serviceConnection回调中获取Binder的实例,代码如下

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

	private Button btnRemoteService;

//定义接口对象
	private IRemoteServiceAidl binder;


	private ServiceConnection serviceConnection = new ServiceConnection() {
		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
            //绑定服务时回调,然后通过返回回来的Binder实例化成员binder
			binder = IRemoteServiceAidl.Stub.asInterface(service);
			
		}
		@Override
		public void onServiceDisconnected(ComponentName name) {
		}
	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		btnRemoteService = findViewById(R.id.btn_remote_service);
		btnRemoteService.setOnClickListener(this);


		Intent intent2 = new Intent();
		intent2.setAction("com.aoto.remote.service");
		intent2.setPackage("com.aoto.remoteservice");


		bindService(intent2,serviceConnection,BIND_AUTO_CREATE);
    }

	

	@Override
	public void onClick(View v) {
		switch (v.getId()) {
			case R.id.btn_remote_service:
				try {
                    //此时按钮点击事件就可以调用远程接口的方法了
					System.out.println("localService: "+binder.callGetTime());
				} catch (RemoteException e) {
					e.printStackTrace();
				}
				break;
			default:
				break;
		}
	}
}

此时客户端完成 

关于非基本数据类型的数据传输会在后续博客中继续,文章中有误的地方欢迎您提供宝贵意见们也可以留言讨论,谢谢。

注意事项说一下吧(主要是我自己在写的时候遇到的坑):

1.bindService()绑定到远程服务后报错:

Service Intent must be explicit

这是由于安全机制的问题,不能使用隐式Intent启动服务,必须明确指定到具体的service组件 

解决方案:

①.使用 Intent.setPackage("**.**.remoteservice");设置服务所在的应用的包名。就是在第一张图中的箭头所指的内容。

②.通过PackageManager获取符合action的具体的Intent和ComponentName,将隐式Intent转换为显示Intent,方法如下:

/**
	 *get an explicit intent in order to start or bind service
	 * @param context the context that need to start or bind a service component
	 * @param implicitIntent the intent that not so explicit,such as an intent created from an non-parameter constructor
	 *                          or with an extra action but no idea which package it belongs to
	 * @return Intent that can start or bind an service component directly
	 */
	public static Intent getExplicitIntent(Context context, Intent implicitIntent) {
		// Retrieve all services that can match the given intent
		PackageManager pm = context.getPackageManager();
		List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);
		// Make sure only one match was found
		if (resolveInfo == null || resolveInfo.size() != 1) {
			return null;
		}
		// Get component info and create ComponentName
		ResolveInfo serviceInfo = resolveInfo.get(0);
		String packageName = serviceInfo.serviceInfo.packageName;
		String className = serviceInfo.serviceInfo.name;
		ComponentName component = new ComponentName(packageName, className);
		// Create a new intent. Use the old one for extras and such reuse
		Intent explicitIntent = new Intent(implicitIntent);
		// Set the component to be explicit
		explicitIntent.setComponent(component);
		return explicitIntent;
	}

2.调用Binder对象的方法时报空指针异常 ( 不走onServiceConnected()回调方法 )

在出发按钮点击事件或其他时间调用binder对象的接口方法时,binder为空,经调试发现没有回调onServiceConnected(),查阅了一番文档没有找到令人信服的解释,有的网友说在执行bindService()后,并没有立即执行onServiceConnected()回调并返回binder对象,所以执行时报错,官方文档还没有提到时间差的问题,所以,仅在这里记录一下解决方案。

将一下代码放在onCreate方法中执行,而不是事件回调方法中执行即可解决

Intent intent2 = new Intent();
intent2.setAction("com.aoto.remote.service");
intent2.setPackage("com.aoto.remoteservice");
bindService(intent2,serviceConnection,BIND_AUTO_CREATE);

3.AIDL支持的基本数据类型不包括short

无论以何种形式,返回值、或者参数类型,都会编译报错,之所以不支持short类型,是因为Parcel没有办法对short进行序列化,也就没办法通过aidl将short类型在客户端与服务端进行传递

Process 'command 'E:\android\sdk\build-tools\28.0.3\aidl.exe'' finished with non-zero exit value 1

4.服务端和客户端的.aidl文件里的接口方法个数和顺序要保持一致

之所以发现这个问题是因为服务端的aidl接口方法有的时候会过时废弃,这个时候可能会考虑删除客户端的aidl接口方法,well,don't do that.因为客户端的方法的个数和顺序必须完全一致,经测试发现,如果两个接口方法调换了位置,除了这两个方法之外,其他的方法工作正常,若是客户端删除了一个过时弃用的接口方法,那么从下一个方法到最后一个方法都无法正常调用了。什么原因还没有研究出来。。。但是这个坑我已经踩过了,在此谨记用于提醒我自己。

5.aidl接口不支持方法重载

也就是说方法名必须不同。即使参数个数个类型不同,方法名相同也无法使用