如何在Android中利用AIDL添加service
AIDL(Android Interface Definition Language,Android接口描述语言)用于定义Client与service进行跨进程通信的编程接口。AIDL简化了IPC通信时数据交换的流程,而程序员只需关心接口的实现即可。
注意:只有你需要其他应用程序可以通过IPC接口调用service或者需要service可以处理多线程的情况时,才去使用AIDL。若非如此,(1)不需要处理跨进程的并发访问,可通过实现 Binder 接口;(2)需要使用IPC,但无需处理多线程的情况,可以选择使用 Messager
使用AIDL进行IPC通信时,需要注意:
- 当前进程调用时,service与调用者在同一线程执行(通常这种情况,一般不使用AIDL进程IPC,而是通过使用 Binder 接口进行调用)
- 如果是其他进程进行IPC调用(系统维护的一个当前进程的Thread Pool),这时可能同时出现多个IPC调用,因此必须确保AIDL接口是支持多线程(thread-safe)
- oneway(异步,不会阻塞当前进程)关键字用于修改远程调用的具体行为。使用 oneway 时,远程调用不会阻塞,只是发送交易数据(transaction data),然后立即返回,而实现该接口的service最终会将其当作一个来自 Binder 线程池(Thread pool)普通的调用(regular call);如果oneway用于本地调用,则不会有任何影响,该调用始终是同步的
接下来,通过一个具体的示例说明如何用AIDL定义一个service,如何绑定该service,如何进行IPC的调用.
定义AIDL接口
.aidl接口的语法跟JAVA语言一样,其定义的方式与JAVA中的interface基本一致,主要区别是AIDL只能定义methods,不能定义数据或者static的methods。每个.aidl文件只能定义一个接口(interface)。AIDL支持以下数据类型:
- 所有JAVA中的基本数据类型(primitive types),例如int,long,char,boolean
- String/CharSequence
- List
- Map
- android.os.parcelable 用于自定义数据类型
以下示例均基于Android Studio
在AS,右击项目,new –> ADIL,对其进行命名,可以看到在 src 目录下,生成了一个AIDL的目录,里面包含了刚才新建的.aidl文件:
package com.jason.test.systemserviceaddingtest; // Declare any non-default types here with import statements interface IMathService { /** * get current process PID */ int getPID(); /** * add two value */ int add(int x, int y); /** * mutiple two value */ int multiply(int x, int y); }*
实现AIDL接口
编译时,Android SDK工具会产生一个与 .aidl 同名的 .java 接口文件,该接口文件包含了一个名为 stub abstract class,其声明了 .aidl 文件里所有的methods,同时也定义一个 asInterface 的方法,它用 IBinder(作为参数传递给调用者的 onServiceConnected() 回调函数)作为参数,并返回一个stub接口的实例。我们可以通过扩展 .Stub 类,定义一个service:
package com.jason.test.systemserviceaddingtest.service; import android.os.*; import android.os.Process; import com.jason.test.systemserviceaddingtest.IMathService; /** * Created by Jason on 2016/7/11. */ public class MathServiceImpl extends IMathService.Stub { public static final String TAG = MathServiceImpl.class.getSimpleName(); @Override public int getPID() throws RemoteException { return Process.myPid(); } @Override public int add(int x, int y) throws RemoteException { return x + y; } @Override public int multiply(int x, int y) throws RemoteException { return x * y; } }
MathServiceImpl为 service 定义了一个RPC(Remote Process Call),接下来只需要将该实例传递给调用者,即可实现与service的远程通信。
使用AIDL的注意事项
- 调用不一定在Main Thread中执行,因此开始就要考虑如何处理多线程的情况
- RPC调用默认支持同步。如果一个service花比较长的时间完成一个request,不要在主线程(UI thread)中调用,以免发生ANR(Application Not Responding),而是应该在另一个线程中进行调用
- 在service中抛出的任何异常都不会返回给调用者client
将接口提供给Clients
将上述实现了的接口提供给clients,以便clients进行绑定:
package com.jason.test.systemserviceaddingtest.service; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log; /** * Created by Jason on 2016/7/11. */ public class MathService extends Service{ public static final String TAG = MathService.class.getSimpleName(); private final MathServiceImpl mServiceBinder = new MathServiceImpl(); @Override public void onCreate(){ Log.v(TAG,"onCreate()"); super.onCreate(); } @Override public IBinder onBind(Intent intent) { Log.v(TAG,"onBind()"); return mServiceBinder; } }
当一个client通过调用 bindService() 连接这个service时,client对应的onServiceConnected() 的回调函数会接收到传自该service onBind() 函数的 mBinder 的实例(如果client与service在不同的应用中,则client所属的应用必须要有之前定义的.aidl文件的拷贝)。
调用IPC接口
调用service的IPC接口前,首先需要通过 ServiceConnection 将client与service进行绑定,具体参考下列代码:首先通过mServiceConnection将client(一个新的HandlerThread)与service进行绑定;作为示例,为避免APP出现ANR,创建了一个新的线程去执行service的调用。
package com.jason.test.systemserviceaddingtest; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.support.v4.app.Fragment; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.Toast; import com.jason.test.systemserviceaddingtest.service.MathService; /** * A placeholder fragment containing a simple view. */ public class MainActivityFragment extends Fragment { public static final String TAG = MainActivityFragment.class.getSimpleName(); public static final int EVENT_START_SERVICE = 0x01; public static final int EVENT_STOP_SERVICE = 0x02; public static final int EVENT_BIND_SUCCESS = 0x03; private Context mContext; private IMathService mMathService; private Handler mHandler; private Boolean mIsServiceBound = false; private Button mStartBtn; private Button mStopBtn; public MainActivityFragment() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.v(TAG,"onCreateView()"); mContext = getContext(); View fragment = inflater.inflate(R.layout.fragment_main, container, false); mStartBtn = (Button)fragment.findViewById(R.id.start_service); mStopBtn = (Button)fragment.findViewById(R.id.stop_service); mStartBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // start service thread mServiceThread.start(); Message msg = mHandler.obtainMessage(EVENT_START_SERVICE); mHandler.sendMessage(msg); } }); mStopBtn.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v){ Message msg = mHandler.obtainMessage(EVENT_STOP_SERVICE); mHandler.sendMessage(msg); // stop the service thread if(!mIsServiceBound) { mServiceThread.quitSafely(); } } }); return fragment; } // 连接service后,回调函数会将IBinder传回给client private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mMathService = IMathService.Stub.asInterface(service); Log.d(TAG,"onServiceConnected(): success"); } @Override public void onServiceDisconnected(ComponentName name) { mMathService = null; Log.d(TAG,"onServiceDisconnected(): success"); } }; private void getMathOperations(){ int pid = 0; int[] val = {203,230}; try{ pid = mMathService.getPID(); val[0] = mMathService.add(val[0],val[1]); val[1] = mMathService.multiply(val[0],val[1]); }catch (RemoteException e){ Log.e(TAG,"remote calling exception"); } Toast.makeText(mContext,"pid = " + pid + " result is " + val[0] + "," + val[1], Toast.LENGTH_LONG).show(); } // 为防止APP出现ANR,启动新的线程调用service接口 private HandlerThread mServiceThread = new HandlerThread("ProcessInfoGettingService"){ @Override public void start(){ //super.start(); mHandler = new Handler(Looper.myLooper()){ @Override public void handleMessage(Message msg){ switch (msg.what){ case EVENT_START_SERVICE: bindService(); break; case EVENT_STOP_SERVICE: unbindService(); break; case EVENT_BIND_SUCCESS: getMathOperations(); default: break; } } }; } @Override public void run(){ Log.d(TAG,"MathService is running"); } }; // 绑定service private void bindService(){ Log.v(TAG, "bindService()"); Intent service = new Intent(getContext(), MathService.class); getContext().bindService(service, mServiceConnection, Context.BIND_AUTO_CREATE); mIsServiceBound = true; Toast.makeText(getContext(),"service is bound successfully",Toast.LENGTH_LONG).show(); // notify to calling Message msg = mHandler.obtainMessage(EVENT_BIND_SUCCESS, Integer.valueOf(mIsServiceBound ? 1 : 0)); mHandler.sendMessageDelayed(msg,1*1000); } // 解绑service private void unbindService(){ if(mIsServiceBound) { mContext.unbindService(mServiceConnection); mIsServiceBound = false; Toast.makeText(getContext(),"service is unbound successfully",Toast.LENGTH_LONG).show(); } }}
更多关于利用IPC进行数据传递的介绍请参考[2];
如何添加如 Telephony/Alarm/Window Manager 系统服务,请参考[1]
参考文献
- http://processors.wiki.ti.com/index.php/Android-Adding_SystemService
- https://developer.android.com/guide/components/aidl.html
更多相关文章
- Android(安卓)C++多线程-创建子线程
- android spinner修改 样式
- Android自定义控件进阶-打造Android自定义的下拉列表框控件
- Android(安卓)数据存储(一) Preference的使用
- 断点下载
- Android(安卓)面试题(5):谈谈 Handler 机制和原理?
- android fence sync
- Android开发艺术探索知识回顾——第2章 IPC机制:1、IPC 的基础知
- Android(安卓)使用Parcelable序列化对象