一、项目介绍
【知识准备】
①Android Interface definition language(aidl,android接口定义语言),其目的实现跨进程的调用。进程是程序在os中执行的载体,一个程序对应一个进程,不同进程就是指不同程序,aidl实现不同程序之间的调用。
②主线程与子线程通信使用handler,handler可以在子线程中发出消息,在主线程处理消息,从而完成线程之间的通信,即使有多个线程,仍然是一个程序。
③不同程序之间需要通过aidl通信,通信方式可以有多种,aidl是其中一种。实现的结果就像自己的程序调用自己的其他方法一样,感觉就像一个程序。
④业务场景:例如购物app需要支付,购物app是淘宝,支付app是支付宝。所以就需要不同的程序进行通信。
二、首先介绍一个App之间的Service和Activity之间的通信
【项目结构】
【MyService】
【提示】
①创建Service
②如果不是通过上述方法创建,一定要记得注册
1 <service 2 android:name=".MyService"
3 android:enabled="true"
4 android:exported="true"></service>
【代码】
1 public class MyService extends Service { 2 public MyService() { 3 } 4
5 @Override 6 public IBinder onBind(Intent intent) { 7 return new MyBinder();//return MyBinder通过ServiceConnection在activity中拿到MyBinder
8 } 9
10 @Override 11 public int onStartCommand(Intent intent, int flags, int startId) { 12
13 return super.onStartCommand(intent, flags, startId); 14 } 15
16 public void payService(){ 17 Log.i("MyService", "payService: --------"); 18 } 19
20 class MyBinder extends Binder{ 21
22 public void pay(){ 23 payService(); 24 }//通过Binder实例将service中的方法暴露出去
25 } 26 }
【layout_main】
添加按钮,点击便于调用
1 <Button 2 android:id="@+id/btn_paly"
3 android:text="Pay"
4 android:layout_width="wrap_content"
5 android:layout_height="wrap_content" />
【MainActivity】
1 public class MainActivity extends AppCompatActivity { 2
3 MyService.MyBinder binder = null; 4 ServiceConnection conn; 5
6 @Override 7 protected void onCreate(Bundle savedInstanceState) { 8 super.onCreate(savedInstanceState); 9 setContentView(R.layout.activity_main); 10
11 Button btnPlay = (Button) findViewById(R.id.btn_paly); 12 conn = new ServiceConnection() { 13 @Override 14 public void onServiceConnected(ComponentName componentName, IBinder iBinder) { 15 binder = (MyService.MyBinder) iBinder; 16 } 17
18 @Override 19 public void onServiceDisconnected(ComponentName componentName) { 20
21 } 22 }; 23
24 Intent intent = new Intent(MainActivity.this,MyService.class); 25 bindService(intent,conn,BIND_AUTO_CREATE);//开启服务
26
27 btnPlay.setOnClickListener(new View.OnClickListener() { 28 @Override 29 public void onClick(View view) { 30 if (binder!=null){ 31 binder.play(); 32 } 33 } 34 }); 35 } 36 }
【效果】
点击后输出service中pay方法中的内容
三、两个App之间的Service通信
【项目结构】
【步骤】
①在AppPayProvider中创建MyService
代码同上
【注册】
Ⅰ、注册时(android:enabled="true" android:exported="true")设置为true,将Service暴露出去,另一个App才能访问到它
Ⅱ、添加『<intent-filter>』。由于不是同一个App,通过intent-filter对Intent进行过滤,让另一个app通过action开启服务
1 <service 2 android:name=".MyService"
3 android:enabled="true"
4 android:exported="true">
5 <!--enable:ture设置可用 6 exported:ture对外暴露 -->
7 <intent-filter>
8 <action android:name="com.xqz.apppayprovider.MyService" />
9 </intent-filter>
10 </service>
②MainActivity和layout_main保留创建时不作任何修改,但也不要删掉,因为安装程序必须提供起始页面,否则将会出错
③在AppPayProvider中添加AIDL
【代码】
【提示】接口中定义中方法要和Service中的MyBinder中的方法一致
④再创建好AIDL,添加完方法后,android studio需要对这个aidl进行编译,会自动按aidl规范生成一个Binder子类的代码。
⑤对MyService中的MyBinder进行修改
【提示】继承IPay.Stub。在这之前必须Make Project,否则将没有只能联想
⑥创建AppPayUser对AppPayProvider中的MyService进行操作
【layout-main】
1 <Button 2 android:id="@+id/btnPay"
3 android:text="pay"
4 android:layout_width="wrap_content"
5 android:layout_height="wrap_content" />
⑦将AppPayProvider中AIDL拷贝到AppPayUser中
【提示】Ⅰ、包名要相同,按目录位置复制,通过下述方法,直接在文件夹进行复制。『此处可以查看项目结构,可以看到包名是相同的』
Ⅱ、同样拷贝过来后需要Make Project
⑧【AppPayUser-MainActivity】
1 public class MainActivity extends AppCompatActivity { 2
3 Button btnPay; 4 private IPay myBinder;//定义AIDL
5
6 ServiceConnection conn = new ServiceConnection() { 7 @Override 8 public void onServiceConnected(ComponentName componentName, IBinder iBinder) { 9
10 myBinder = IPay.Stub.asInterface(iBinder); 11 } 12
13 @Override 14 public void onServiceDisconnected(ComponentName componentName) { 15
16 } 17 }; 18
19 @Override 20 protected void onCreate(Bundle savedInstanceState) { 21 super.onCreate(savedInstanceState); 22 setContentView(R.layout.activity_main); 23
24 Intent intent = new Intent(); 25 intent.setAction("com.xqz.apppayprovider.MyService"); 26 //表示按照什么进行过滤,启动意图
27 /*android5.0之后,如果servicer不在同一个App的包中, 28 需要设置service所在程序的包名 29 (包名可以到App的清单文件AndroidManifest中查看)*/
30 intent.setPackage("com.xqz.apppayprovider"); 31 bindService(intent,conn,BIND_AUTO_CREATE);//开启Service
32
33 btnPay = (Button) findViewById(R.id.btnPay); 34
35 btnPay.setOnClickListener(new View.OnClickListener() { 36 @Override 37 public void onClick(View view) { 38 try { 39 myBinder.pay(); 40 } catch (RemoteException e) { 41 //因为是跨程序调用服务,可能会出现远程异常
42 e.printStackTrace(); 43 } 44 } 45 }); 46 } 47 }
【安装】
先安装AppPayProvider再安装AppPayUser。
【效果】
将run中的 视图调到AppPayProvider,点击模拟器AppPayUser中的pay按钮,将会执行AppPayProvider中MyService中pay方法中的内容。
四、总结
【跨App和同App之间的区别】
①跨App开启服务是提供服务的App需要设置intent-filter过滤器,控制服务的App需要通过。setAction和setPackage方法进行设置action和包名,才能开启服务。而同App只需要指定启动的service就可。
②跨App的MyBinder实例要通过AIDL获取,两个应用定义同样的接口的方法,通过对应的AIDL名称.Stub.asInterface方法得到binder实例,然后就和同App的myBinder使用么有区别了。
③跨App的MyBinder对象的使用必须捕获异常,而同App不需要。
④可以根据上方简单的例子实现很多类似的功能。
五、园友实践问题
1、Attempt to invoke interface method 'void com.example.aidl_.IPsy.pay()' on a null object
错误原因:提供远程服务的应用和调用远程服务的应用不是同一个应用,两个应用使用的applicationId 相同。要想在同一个手机上安装,他们得设置不同的 applicationId (buidle.gradle)
有关ApplicationId:applicationId默认为应用的包名,可以修改,也有相应的命名规则。在bindService intent 设置 setPackage 这里不是字面上的应用包名,而是应用的 ApplicationId,这也会导致 bindService 返回 false。 更多请参考博客:Gradle学习之设置applicationId( https://www.jianshu.com/p/4653c8a38771)
2、api 30 Android 11 bindservice 返回 false,在 API 28 (Android 9)的模拟器上是可以的 bindService 返回 true,但是运行到 API 30(Android 11)上就返回 false。
我刚开始解决问题的方向一直放在代码上面,是为了兼容需要在代码上做什么处理,看了官网并没有得到什么信息。
园友说将 targetSDKVersion 设置为 28就可以了。
targetSDKVersion 设置应用的目标版本,当Android版本低于APP的目标的API时,将使用Android手机版本API即28,当APP目标版本低于Android手机版本,将以APP目标版本API在Android手机上以目标版本的行为运行,也就是 目标版本设置为 28 在api30 的手机上,以api 28行为运行,这也就是APP的向上兼容。