如何在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]

參考文獻

  1. http://processors.wiki.ti.com/index.php/Android-Adding_SystemService
  2. https://developer.android.com/guide/components/aidl.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章