Android:远程 Service 和 AIDL 跨进程通信
远程 Service#
Service 是运行在主线程里的,如果直接在 Service 中处理一些耗时的逻辑,就会导致程序 ANR。可以在 Service 中开启线程去执行耗时任务,也可以索性将原来的 Service 转换成一个远程 Service。
将 Service 转换成远程 Service 只需要在注册 Service 的时候,在 AndroidManifest.xml 文件中将 android:process 属性指定成“:remote”。
<service android:name=".MyService"
android:process=":remote">
</service>
AIDL 跨进程通信#
此时 Bind Service 让 Activity 和 Service 建立关联时,由于 Activity 和 Service 运行在两个不同的进程当中,不能再使用传统方式建立关联。
可以使用 AIDL(Android Interface Definition Language)Android 接口定义语言来进行跨进程通信(IPC),它可以让某个 Service 与多个应用程序组件之间进行跨进程通信,帮助 IPC 之间接口的建立,从而实现多个应用程序共享同一个 Service 的功能。使用的步骤如下:
- 定义 AIDL 接口,为 Service 建立接口 IService;
- Client 连接 Service 获得 Stub 对象;
- 在 IService 中具体实现 IService.Stub;
- 直接调用 IService.Stub 里面的方法。
客户端调用远程 Service 时,Android 不是直接返回 Service 对象到客户端,而是通过 onBind() 方法把 Service 的代理对象 IBinder 发送给客户端,所以 AIDL 远程接口的实现类就是 IBinder 的实现类。
远程 Service 样例#
程序需求#
基于 Service 组件技术,编程实现一个可以完成计算任意的两数的加、减、乘运算的远程服务。
功能设计#
首先需要新建一个 aidl,里面写一个接口作为我们要提供的服务,接着写一个该接口的实现类。最后完成 Activity,实现对远程服务的绑定,并且测试提供的加减乘的操作。
代码编写#
IMyAidlInterface.aidl#
// IMyAidlInterface.aidl
package com.example.test5;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
double add(double a, double b);
double subtract(double a, double b);
double multiply(double a, double b);
}
注意我们是把接口写在 .aidl 文件中,此时如果是 eclipse 可以自动生成 IService 文件,但是如果是用 Android studio 的话就需要写完代码后建立一下:
MyService.java#
package com.example.test5;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
public class MyService extends Service {
//定义内部类MyServiceImpl继承AIDL文件自动生成的内部类,并且实现定义的接口方法
private class MyServiceImpl extends IMyAidlInterface.Stub{
@Override
public double add(double a, double b) throws RemoteException {
// TODO Auto-generated method stub
return a + b;
}
@Override
public double subtract(double a, double b) throws RemoteException {
// TODO Auto-generated method stub
return a - b;
}
@Override
public double multiply(double a, double b) throws RemoteException {
// TODO Auto-generated method stub
return a * b;
}
}
@Override
public IBinder onBind(Intent arg0) {
return new MyServiceImpl(); //返回AIDL实现
}
@Override
public void onDestroy(){
super.onDestroy();
}
}
MainActivity#
注意不能在没有绑定服务的时候解绑服务,否则调用方法会导致系统崩溃。此时应该设置一个信号量来确定状态,只有当服务绑定了才可以解绑。
package com.example.test5;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends Activity {
static final String TAG = "IMyAidlInterface";
IMyAidlInterface iService = null;
EditText num1Text, num2Text;
TextView textView;
double num1, num2;
boolean flag = false; //信号量,防止在没有绑定服务的情况下解绑
private ServiceConnection conn = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 返回AIDL接口对象,然后可以调用AIDL方法
iService = IMyAidlInterface.Stub.asInterface(service);
textView.setText("绑定 Service 成功!");
flag = true; //状态设置为已绑定服务
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "释放Service");
}
};
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//定位xml中的2个操作数和输出文本的位置
num1Text = (EditText) findViewById(R.id.etnum1);
num2Text = (EditText) findViewById(R.id.etnum2);
textView = (TextView) findViewById(R.id.textView);
}
public void doClick(View v) {
double result = 0.0; //运算结果
switch (v.getId()) {
// 绑定服务
case R.id.button1:
if(!flag){
Intent bindIntent = new Intent(this, MyService.class);
startService(bindIntent);
bindService(bindIntent, conn, BIND_AUTO_CREATE);
}
break;
// 解绑服务
case R.id.button5:
if(flag) {
unbindService(conn);
textView.setText("解除绑定 Service 成功!");
flag = false;
}
break;
// 运行服务提供的操作
default:
if(getTwoNum()){
try {
switch (v.getId()) {
case R.id.button2:
result = iService.add(num1, num2);
break;
case R.id.button3:
result = iService.subtract(num1, num2);
break;
case R.id.button4:
result = iService.multiply(num1, num2);
break;
}
}
catch (RemoteException e) {
Log.e(TAG,"调用出错!");
e.printStackTrace();
}
textView.setText("" + result);
}
else{
// 提示用户正确输入
textView.setText("非法输入,请重新输入!");
}
break;
}
}
public boolean getTwoNum(){
try{
//获取两个操作数
num1 = Double.parseDouble(num1Text.getText().toString());
num2 = Double.parseDouble(num2Text.getText().toString());
}catch (Exception e) {
//数据非法或者没输入,返回false
return false;
}
return true;
}
}
Activity_main.xml#
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:id="@+id/tvnum1"
android:textSize="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="操作数1:"
android:layout_weight="1"/>
<EditText
android:id="@+id/etnum1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/tvhigh"
android:layout_alignParentRight="true"
android:hint="请输入数字"
android:gravity="center"
android:layout_weight="2"/>
</LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:id="@+id/tvnum2"
android:textSize="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignRight="@+id/tvhigh"
android:layout_below="@+id/tvhigh"
android:text="操作数2:"
android:layout_weight="1"/>
<EditText
android:id="@+id/etnum2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/tvweight"
android:layout_alignLeft="@+id/ethigh"
android:hint="请输入数字"
android:gravity="center"
android:layout_weight="2"/>
</LinearLayout>
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Bind Service"
android:textSize="20dp"
android:onClick="doClick" />
<Button
android:id="@+id/button2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="add"
android:textSize="20dp"
android:onClick="doClick"/>
<Button
android:id="@+id/button3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="subtract"
android:textSize="20dp"
android:onClick="doClick"/>
<Button
android:id="@+id/button4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="multiply"
android:textSize="20dp"
android:onClick="doClick"/>
<Button
android:id="@+id/button5"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Unbind Service"
android:textSize="20dp"
android:onClick="doClick"/>
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:gravity="center"
android:text="result" />
</LinearLayout>
运行效果#
参考资料#
《Android 移动应用开发》,杨谊 主编、喻德旷 副主编,人民邮电出版社
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)