android之AIDL跨進程通信詳解

背景

一直以來都只是聽說AIDL是跨進程的,但都由於項目中也沒涉及到,所以也從來都沒徹底去了解過,最近空閒下來去了解插件化開發原理,看着看着正好涉及到Ibinder以及android用到的AIDL,於是乎按圖索驥一條條來探索到底是個怎麼回事兒,按照讀者推薦,我們就先從AIDL使用以及原理開始挖掘。

前言

對於跨進程,我們都知道android底層是linux,所以進程管理也是linux系統的那一套,即進程之間是相互獨立的互不干擾的,數據是獨享的,所以要進行進程間的通信也是通過老掉牙的方案Binder機制去搞,而android的AIDL底層也是基於Binder機制來搞的,只不過封裝的比較好,android開發不需要知道Binder機制也能使用AIDL跨進程開發,只需要遵循一定的流程,一一步步創建就好;由於android的每個app對應一個獨立進程(不是自己開啓新的進程前提),要想跨應用訪問共享數據,那就得掌握進程間的通信。接下來我們一步步教大家如何使用以及對其原理講解。

資料

這個博主的一系列進程通信的講解與比較是比較完美的,推薦想了解的要細讀深究,並手動編寫代碼
http://blog.csdn.net/hitlion2008/article/details/9773251
Binde原理詳解
http://weishu.me/2016/01/12/binder-index-for-newer/

AIDL使用簡述

1、創建client/service的aidl文件(如IRemoteService.aidl),client/service使用的是同一份,分別放置在各自的src目錄下
2、創建完後,重新build項目,會在app\build\generated\source\aidl目錄下自動生成相應的java文件,
想深究的可以對着原理扒扒裏邊的源碼,後邊會簡述
3、創建service,並實現IRemoteService.Stub,通過onBind()返回
4、創建client,並實現ServiceConnection,然後通過以下方式啓動進行綁定,並建立進程間的通道

 Intent intent = new Intent();
 intent.setClassName("包名", "包名+service的類名");
 bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);

5、最後在ServiceConnection的onServiceConnected中通過IRemoteBankService.Stub.asInterface(service)獲取client的代理,通過這個代理就可以和service相互通信了。

AIDL的創建步驟

1、創建所有的aidl文件,包括實現的進程間要通信的實體類也要定義
這裏寫圖片描述
創建aidl文件 如圖所示,名字隨意命名,這裏我命名爲銀行的aidl,名字爲IRemoteBankService
創建完成後就會在src/main/aidl下自動生成和應用包名一致的包名,打開就看到我們創建的aidl文件,打開如下


// IRemoteBankService.aidl
package com.example.zwr.myapplication;

// Declare any non-default types here with import statements

interface IRemoteBankService{
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

這樣我們就創建完畢!

2、在aidl文件中編寫自定想要的接口。
3、編輯實體類.aidl,刪除除了包名外的所有內容,寫下parcelable 類名;(如parcelable User;)parcelable 中的p必須是小寫的;
4、將IRemoteBankService.aidl定義的接口中所使用到的實體類,將對應的包名+類名導入IRemoteBankService.aidl;
(如:
import com.example.zwr.myapplication.User;
import com.example.zwr.myapplication.RemoteClientCallBack;

5、創建自己實現的實體類,這個類必須要是實現Parcelable
6、重新編譯:即重新build就完畢了
注意:
自定義的接口方法的返回類型以及參數類型:類型有一定的限制
(1) 支持java的基本類型:float/int/String…..
(2)支持實現Parcelable的實體類
(3)支持Stub/aidl接口
基本類型不用說,看看實體類是怎麼玩的
一般來說我們會定義自己傳遞的實體類,但在定義之前,我們要先在aidl/包名/創建實體類同名的aidl(如這裏是User.aidl),然後在創建java/包名/實體類的.java,否則如果先創建類,有可能無法創建aidl,這是as工具的問題

AIDL的Demo詳解

1、創建aidl
這裏我的demo中是創建了3個aidl,所以先將這3個aidl文件創建
這裏寫圖片描述

<1>、IRemoteBankService.aidl

創建完後得到一個類似java的接口,同樣我們可以在裏邊自定義自己要通信的接口;比如我定義如下
這裏寫圖片描述
這裏創建了4個方法:
//—-以下方法都是客戶端直接調用,將數據傳給服務端,順帶從服務端拿取數據—–類似,客戶端發送請求,拿取數據
/*客戶端註冊回調接口,用以服務端主動通過調用回調方法,向客戶端發送消息—類似服務端主動推送數據給客戶端/
/*存錢 存款/
boolean despoistMoney(int money);
/*取款/
int drawMoney(int money);
/*當前存取款用戶/
User getUser();
//—-以上方法都是客戶端直接調用,將數據傳給服務端,順帶從服務端拿取數據—–類似,客戶端發送請求,拿取數據

/*客戶端註冊回調接口,用以服務端主動通過調用回調方法,向客戶端發送消息—類似服務端主動推送數據給客戶端/
void registerClientOberser(RemoteClientCallBack clientCallBack);

正如註釋寫的,我們存取款以及獲取用戶信息都是通過定義的前3個方法獲取,前3個方法相當於client向service發送請求,然後獲取或者發送信息,主動方是在client,而最後一個方法是client首先向service註冊一個觀察者,然後當service有什麼更新的狀態想要通知client時都是通過這個觀察者來通知更新,即主動權在service,由service主動推送消息,這樣就達到了信息互通

以上3個aidl中,IRemoteBankService.aidl中聲明的接口方法中用到了User和RemoteClientCallBack,所以需要導包:
import com.example.zwr.myapplication.User;
import com.example.zwr.myapplication.RemoteClientCallBack;

<2>、User.aidl

接下來看看User.aidl做了什麼:
// User.aidl
package com.example.zwr.myapplication;

// Declare any non-default types here with import statements

parcelable User;

其實創建aidl時會生成和之前一模一樣的接口類型,這裏我們把它都給刪除,然後寫下parcelable 類名;(這裏是parcelable User;)
這樣就算聲明完了。

<3>RemoteClientCallBack.aidl

// RemoteClientCallBack.aidl
package com.example.zwr.myapplication;

// Declare any non-default types here with import statements

interface RemoteClientCallBack {

void transferToClientByServer(String transferData);
}

這裏聲明瞭接口方法,但是參數是普通類型,不需要導入任何包;這個方法就是service主動將消息推送後,客戶端回調的方法

以上就是aidl的玩法。

<4>、創建aidl使用參數的實體類

接下來我們要在java/aidl文件同包名/創建aidl相應的實體類User,如本demo:java\com\example\zwr\myapplication\User.java
如下所示

package com.example.zwr.myapplication;

import android.os.Parcel;
import android.os.Parcelable;

/**
 * author : zhongwr on 2017/2/22
 */
public class User implements Parcelable {
    private String name;
    private String id;
    /**進程Id*/
    private String pId;

    public User(String name, String id, String pId) {
        this.name = name;
        this.id = id;
        this.pId = pId;
    }

    public User() {
    }

    @Override
    public String toString() {
        return "name = " + name + " id = " + id + " pid = " + pId;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.name);
        dest.writeString(this.id);
        dest.writeString(this.pId);
    }

    protected User(Parcel in) {
        this.name = in.readString();
        this.id = in.readString();
        this.pId = in.readString();
    }

    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel source) {
            return new User(source);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };
}

看着要生成這麼多東東,其實我們不用自己手寫,直接使用as自帶的工具:
打開當前文件右鍵-》generate-》Parcelable
這樣就會自動生成
這裏我們要注意如下:
實現的Parcelable 類(User) 必須放在src/main/java目錄下,包名與aidl的包名一致(這裏是com.example.zwr.myapplication)
如圖:
這裏寫圖片描述

這樣前提準備工作就基本完事,最後一步,重新build項目,然後不出什麼意外的話會在pp\build\generated\source\aidl目錄下自動生成相應的java文件
這裏寫圖片描述
裏邊的內容先不關心,後續會講解

2、服務端:創建RemoteBankService.java

這裏就跟平時創建的service一樣,繼承service之後 主要是這個方法返回的IBinder

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

這裏實現的是我們創建的aidl自動生成的java文件IRemoteBankService中的IRemoteBankService.Stub

private IRemoteBankService.Stub mRemoteBind = new IRemoteBankService.Stub() {
        /**存錢*/
        @Override
        public boolean despoistMoney(int money) throws RemoteException {
            Log.d(TAG, "despoistMoney pid = " + android.os.Process.myPid());
            if (money > 0) {
                return true;
            }
            return false;
        }

        /**取錢*/
        @Override
        public int drawMoney(int money) throws RemoteException {
            Log.d(TAG, "drawMoney pid = " + android.os.Process.myPid());
            clientCallBackInstance.transferToClientByServer("當前用戶存錢成功
             餘額 :"+money+"當前進程Id = "+android.os.Process.myPid());
            return money;
        }

        /**
         * 用戶信息
         * @return
         * @throws RemoteException
         */
        @Override
        public User getUser() throws RemoteException {
            Log.d(TAG, "getUser pid = " + android.os.Process.myPid());
            return new User("張三", "" + System.currentTimeMillis(),
             "" + android.os.Process.myPid());
        }

        /**
         * 註冊客戶端的觀察者,用以服務端更新後主動通知其更新
         * @param clientCallBack
         * @throws RemoteException
         */
        @Override
        public void registerClientOberser(RemoteClientCallBack clientCallBack) 
        throws RemoteException {
            clientCallBackInstance =clientCallBack;
            Message msg = Message.obtain();
            msg.obj = clientCallBack;
            timeHandler.sendMessageDelayed(msg, 10000);
        }
    };
    private RemoteClientCallBack clientCallBackInstance;

看到這個實現的Stub中實現了我們在aidl定義的接口方法,我們就是通過這些方法進行通信的

由於前三個是client主動調用的方法,而最後registerxxx()是client註冊後,service主動推送消息,所以我們這裏使用handler延時模擬推送。

service的完整代碼如下

     package com.example.zwr.myapplication;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.*;
import android.os.Process;
import android.util.Log;

/**
 * 獨立進程運行的服務
 */
public class RemoteBankService extends Service {
    private static final String TAG = "RemoteBankService";

    public static void bindService(Context context, ServiceConnection connection) {
        Log.d(TAG, "bindService pid = " + android.os.Process.myPid());
        Intent intent = new Intent(context, RemoteBankService.class);
//        intent.setClassName("com.example.zwr.myapplication","com.example.zwr.myapplication.RemoteBankService");
        context.bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

    public static void doUnbindService(Context context, ServiceConnection connection) {
        Log.d(TAG, "doUnbindService pid = " + android.os.Process.myPid());
        if (connection != null) {
            context.unbindService(connection);
            context.stopService(new Intent(context, RemoteBankService.class));
        }
    }

    public RemoteBankService() {
    }

    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate pid = " + android.os.Process.myPid());
        super.onCreate();
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy pid = " + android.os.Process.myPid());
        super.onDestroy();
        Process.killProcess(Process.myPid());
    }

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

    private IRemoteBankService.Stub mRemoteBind = new IRemoteBankService.Stub() {
        /**存錢*/
        @Override
        public boolean despoistMoney(int money) throws RemoteException {
            Log.d(TAG, "despoistMoney pid = " + android.os.Process.myPid());
            if (money > 0) {
                return true;
            }
            return false;
        }

        /**取錢*/
        @Override
        public int drawMoney(int money) throws RemoteException {
            Log.d(TAG, "drawMoney pid = " + android.os.Process.myPid());
            clientCallBackInstance.transferToClientByServer("當前用戶存錢成功 
            餘額 :"+money+"當前進程Id = "+android.os.Process.myPid());
            return money;
        }

        /**
         * 用戶信息
         * @return
         * @throws RemoteException
         */
        @Override
        public User getUser() throws RemoteException {
            Log.d(TAG, "getUser pid = " + android.os.Process.myPid());
            return new User("張三", "" + System.currentTimeMillis(),
             "" + android.os.Process.myPid());
        }

        /**
         * 註冊客戶端的觀察者,用以服務端更新後主動通知其更新
         * @param clientCallBack
         * @throws RemoteException
         */
        @Override
        public void registerClientOberser(RemoteClientCallBack clientCallBack) 
        throws RemoteException {
            clientCallBackInstance =clientCallBack;
            Message msg = Message.obtain();
            msg.obj = clientCallBack;
            timeHandler.sendMessageDelayed(msg, 10000);
        }
    };
    private RemoteClientCallBack clientCallBackInstance;

    private TimeHander timeHandler = new TimeHander();
    static class TimeHander extends Handler{
        public TimeHander(){
            Looper looper = Looper.myLooper();
            if(null == looper){
                Looper.prepare();
                Looper.loop();
            }
        }
        @Override
        public void handleMessage(Message msg) {
            if(null!=msg.obj){
                RemoteClientCallBack clientCallBackInstance 
                = (RemoteClientCallBack)msg.obj;
                try {
                    clientCallBackInstance.transferToClientByServer(
                    "已延期10s後發送 當前進程Id = "+ Process.myPid());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

這樣service工作已完成99%了,最後一步就是要註冊,這裏我們起一個獨立進程來跑;
AndroidManifest.xml:

<service
     ndroid:name=".RemoteBankService"
    android:enabled="true"
    android:exported="true"
    android:process=":remoteservice">
</service>

3、本應用的客戶端Client:創建RemoteActivity
實現ServiceConnection:建立連接

  private IRemoteBankService iRemoteBankService;
    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {//建立通信鏈接成功
            Log.d(TAG, "onServiceConnected pid = " + android.os.Process.myPid());
            iRemoteBankService = IRemoteBankService.Stub.asInterface(service);//跨進程的處理方式
//            iRemoteBankService = (IRemoteBankService)service;//統一進程的處理方式
            try {
                iRemoteBankService.registerClientOberser(remoteClientCallBack);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {//斷開鏈接
            Log.d(TAG, "onServiceDisconnected pid = " + android.os.Process.myPid());
            iRemoteBankService = null;
        }
    };

當建立連接之後會回調onServiceConnected();在這裏我們獲取遠程的IBinder,由於這個是服務端的IBinder,這裏又是不同的進程所以不能直接強轉成IRemoteBankService,否則會拋出類型轉換異常所以要使用
iRemoteBankService = IRemoteBankService.Stub.asInterface(service);來獲取客戶端的Binder代理;

獲取到iRemoteBankService 後我們將客戶端的觀察者註冊到service中:
iRemoteBankService.registerClientOberser(remoteClientCallBack);
再來看看remoteClientCallBack是何方妖物:

private RemoteClientCallBack.Stub remoteClientCallBack = new RemoteClientCallBack.Stub() {
        @Override
        public void transferToClientByServer(final String transferData) throws RemoteException {
            Log.d(TAG, "transferData = " + transferData + 
            " client Pid = " + android.os.Process.myPid());
            //如果是service通過handler調用的這個的,由於service的進程調用,所以這個回調不是在
            //主線程而是工作線程中,直接更新或toast會拋出如下異常,所以定要在主線程中更新
            //Uncaught remote exception! (Exceptions are not yet supported across processes.)
            runOnUiThread(new Runnable() {

                @Override
                public void run() {
                    Toast.makeText(RemoteActivity.this, transferData, Toast.LENGTH_SHORT).show();
                }
            });

        }
    };

這裏實現的就是我們定義的RemoteClientCallBack.aidl文件自動生成的RemoteClientCallBack.java文件中的Stub;到了這裏其實這裏註冊觀察者,可以理解爲把client當作service,service當作client互換角色吧。
然後通過回調就可以互相通信互相拿到消息了。
這裏你可能注意到了在回調方法中使用runOnUiThread()來Toast,這是因爲,回調後不是主線程,而是從系統線程池中獲取到一個空閒工作線程來傳遞消息的,直接Toast則報異常(Exceptions are not yet supported across processes.)

ok!主要代碼都寫完了,這下需要綁定啓動服務然後操縱看看是不是能夠通信了;完整代碼如下:package com.example.zwr.myapplication;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

public class RemoteActivity extends AppCompatActivity implements View.OnClickListener {

    public static void startInstance(Context context){
        context.startActivity(new Intent(context,RemoteActivity.class));
    }
    private static final String TAG = "RemoteActivity";

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

    private void initView() {
        findViewById(R.id.tv_bindService).setOnClickListener(this);
        findViewById(R.id.tv_despoistMoney).setOnClickListener(this);
        findViewById(R.id.tv_drawMoney).setOnClickListener(this);
        findViewById(R.id.tv_getuser).setOnClickListener(this);
        Log.d(TAG, "initView pid = " + android.os.Process.myPid());
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.tv_bindService:
                RemoteBankService.bindService(this,serviceConnection);
                break;
            case R.id.tv_despoistMoney:
                try {
                   boolean  isDesMoney = iRemoteBankService.despoistMoney(5);
                    Log.d(TAG,"isDesMoney1 = "+isDesMoney);
                     isDesMoney = iRemoteBankService.despoistMoney(-5);
                    Log.d(TAG,"isDesMoney2 = "+isDesMoney);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            case R.id.tv_drawMoney:
                try {
                    int  drawMoney = iRemoteBankService.drawMoney(5);
                    Log.d(TAG,"drawMoney = "+drawMoney);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            case R.id.tv_getuser:
                try {
                    User user = iRemoteBankService.getUser();
                    Log.d(TAG,"user = "+user.toString());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;

        }
    }

    private IRemoteBankService iRemoteBankService;
    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {//建立通信鏈接成功
            Log.d(TAG, "onServiceConnected pid = " + android.os.Process.myPid());
            iRemoteBankService = IRemoteBankService.Stub.asInterface(service);//跨進程的處理方式
//            iRemoteBankService = (IRemoteBankService)service;//統一進程的處理方式
            try {
                iRemoteBankService.registerClientOberser(remoteClientCallBack);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {//斷開鏈接
            Log.d(TAG, "onServiceDisconnected pid = " + android.os.Process.myPid());
            iRemoteBankService = null;
        }
    };

    private RemoteClientCallBack.Stub remoteClientCallBack = new RemoteClientCallBack.Stub() {
        @Override
        public void transferToClientByServer(final String transferData) throws RemoteException {
            Log.d(TAG, "transferData = " + transferData + " client Pid = " 
            + android.os.Process.myPid());
            //如果是service通過handler調用的這個的,由於service的進程調用,所以這個回調不是
            //在主線  程而是工作線程中,直接更新或toast會拋出如下異常,所以定要在主線程中更新
            //Uncaught remote exception! (Exceptions are not yet supported across processes.)
            runOnUiThread(new Runnable() {

                @Override
                public void run() {
                    Toast.makeText(RemoteActivity.this, transferData, Toast.LENGTH_SHORT).show();
                }
            });

        }
    };
    @Override
    protected void onDestroy() {
        super.onDestroy();
        RemoteBankService.doUnbindService(this,serviceConnection);
    }
}

來看看運行結果: 先綁定,在操作其它選項
client:
這裏寫圖片描述

service:
這裏寫圖片描述
通過日誌可以發現,這兩個是在獨立進程中,而且是可以正常通信的
以上是同一個應用中進行的,下邊我們看看在不同應用中的通信

4、新創建應用爲ClientAIDLDEMO :爲client
先創建完後,將創建的所有aidl文件以及相關的類全部複製到這個項目中
注意:包名也是要一致的
如圖:
這裏寫圖片描述
然後將RemoteActivity也一同複製過來,唯一要修改的只有綁定和解綁,修改爲:通過包名隱式意圖啓動進行綁定:如下

    Intent intent = new Intent();
    intent.setClassName(
   "com.example.zwr.myapplication",  "com.example.zwr.myapplication.RemoteBankService");
    bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);

    Intent intent = new Intent();
    intent.setClassName(
    "com.example.zwr.myapplication", "com.example.zwr.myapplication.RemoteBankService");
     unbindService(serviceConnection);
     stopService(intent);       

然後運行:點擊綁定,結果如同上邊,就不再重複!
跨進程通信講解完畢!,有不對的地方請不吝指正!

demo:
同一個應用demo
不同應用的demo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章