Android平臺必備技能(一)---進程間通信AIDL的應用詳解

一些必備的背景知識,理解以下知識將有助於編寫AIDL通信流程。

1.  AIDL是什麼?

    AIDL:Android Interface Definition Language,即Android接口定義語言。即Android平臺上使用的IDL交互式數據語言,定義了Android平臺IPC的模板。

2.  AIDL的語法?

    2.1)   AIDL文件以 .aidl 爲後綴名;

    2.2)   支持八種基本數據類型:byte、char、short、int、long、float、double、boolean,以及String,CharSequence

    2.3)   List類型:List中的所有元素必須是AIDL支持的類型之一,或者是一個其他AIDL生成的接口,或者是定義的parcelable。List可以使用泛型

    2.4)   Map類型:Map中的所有元素必須是AIDL支持的類型之一,或者是一個其他AIDL生成的接口,或者是定義的parcelable。Map是不支持泛型的

    2.5)   AIDL定向tag:in,out,inout。添加在AIDL接口中表示數據通信的流向。

    in:   client -> server, 數據從客戶端流向服務端

    out: server -> client,數據從服務端流向客戶端

    inout:client <-> server,數據在服務端和客戶端雙向流通。

3.  Android平臺IPC的幾種方式

    3.1)   Socket(套接字)

    作爲更通用的接口,傳輸效率低,主要用於不同機器或跨網絡的通信;

    3.2)   Pipe(管道)

    在創建時分配一個page大小的內存,緩存區大小比較有限;

    3.3   MessageQueue(消息隊列)

    消息隊列:信息複製兩次,額外的CPU消耗;不合適頻繁或信息量大的通信;

    3.4   Anonymous shared memory(匿名共享內存)

    無須複製,共享緩衝區直接付附加到進程虛擬地址空間,速度快;但進程間的同步問題操作系統無法實現,必須各進程利用同步工具解決;

    3.5   Signal(信號量)

    不適用於信息交換,更適用於進程中斷控制,比如非法內存訪問,殺死某個進程等。比如系統log中常見的SIGABRT 6 C 由abort(3)發出的退出指令,SIGKILL 9 AEF Kill信號,SIGSEGV 11 C 無效的內存引用等等。

    3.6  Semaphore(信號量)

    常作爲一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作爲進程間以及同一進程內不同線程之間的同步手段。

    3.7  Binder通信

    (1) 從性能的角度

    數據拷貝次數:Binder數據拷貝只需要一次,而管道、消息隊列、Socket都需要2次,但共享內存方式一次內存拷貝都不需要;從性能角度看,Binder性能僅次於共享內存。

    (2) 從穩定性的角度

    Binder是基於C/S架構的,簡單解釋下C/S架構,是指客戶端(Client)和服務端(Server)組成的架構,Client端有什麼需求,直接發送給Server端去完成,架構清晰明朗,Server端與Client端相對獨立,穩定性較好;而共享內存實現方式複雜,沒有客戶與服務端之別,需要充分考慮到訪問臨界資源的併發同步問題,否則可能會出現死鎖等問題;從這穩定性角度看,Binder架構優越於共享內存。

    (3) 從安全的角度傳統Linux

IPC的接收方無法獲得對方進程可靠的UID/PID,從而無法鑑別對方身份;而Android作爲一個開放的開源體系,擁有非常多的開發平臺,App來源甚廣,因此手機的安全顯得額外重要;對於普通用戶,絕不希望從App商店下載偷窺隱射數據、後臺造成手機耗電等等問題,傳統Linux IPC無任何保護措施,完全由上層協議來確保。

    (4) 語言層面的角度

    Android是基於Java語言(面向對象的語句),而對於Binder恰恰也符合面向對象的思想,將進程間通信轉化爲通過對某個Binder對象的引用調用該對象的方法,而其獨特之處在於Binder對象是一個可以跨進程引用的對象,它的實體位於一個進程中,而它的引用卻遍佈於系統的各個進程之中。相比而言,Binder通信在安全性、性能方面都有比較明顯的優勢。

    Binder通信往往通過ioctl等方法跟內核空間的驅動進行交互:

 

圖1

Binder的架構圖:

 

圖2

C/S架構設計:

 

圖3

Android系統中Broadcast、ContentProvider、Message都能跨進程通訊,其根本原理都是Binder通信。可見Binder通信並非必須通過AIDL。

4.  序列化和反序列化

Android的進程都有獨立的內存空間,相互間不能訪問。想要在進程間數據通信,就需要將數據做序列化和反序列化。Android中對數據序列化有兩種方法:一種是實現Serializable接口另一種是實現Parcelable接口。Parcelable是android特有的序列化API,它的出現是爲了解決Serializable在序列化的過程中消耗資源嚴重的問題,但是因爲本身使用需要手動處理序列化和反序列化過程,會與具體的代碼綁定,使用較爲繁瑣,一般只獲取內存數據的時候使用。

簡單介紹下Parcelable的兩個過程:序列化、反序列化。

序列化:

@Override

publicvoid writeToParcel(Parcel dest, int flags) {

}

反序列化:

classBean {

}

publicstatic final Creator< Bean > CREATOR = new Creator< Bean >() {

        @Override

        public User createFromParcel(Parcelsource) {

            return null;

        }

        @Override

        public User[] newArray(int size) {

            return new Bean [size];

        }

};

5、AIDL的使用示例

在App 1中分享一個媒體文件(例如MP3)給另一個App 2。App 1中可以播放這個媒體文件,展示上傳文件進度。App 2可以播放傳輸過來的媒體文件。列舉下Server端和Client端需要做的事情:

    a) AIDL通信過程中,Server端需要做什麼?

    創建aidl文件以及數據通信類型,確認數據流向,後臺Service,在Service中埋入樁(Stub),以及在AndroidManifest.xml中聲明該Service。

    b) AIDL通信過程中,Client端需要做什麼?

    創建aidl文件以及數據通信類型(和Server端要保持一致),在activity或者適當位置bindService,bindService中的參數ServiceConnection,調用aidl接口函數傳遞數據。

    值得注意的是,Client端不是必須要有Activity來傳遞數據,很多例子中都以Activity來演示,一個是方便看到數據通信結果,另一個是Activity有完整的生命週期,binder通信可以選擇在onCreate中bindService,在onStop中unbindService,這樣能有效回收資源。

5.1 明確Client端和Server端

   從需求關係看:

    1)  App 1作爲數據提供方,需要源源不斷的將數據傳給App 2;

    2)   App 2作爲頁面(Activity)結果展示,更適合作客戶端。

    定義App 1爲Server端, App 2作爲Client端。分別創建App 1和App 2,以及對應的MainActivity,相關的aidl文件,App 1創建Service,以及對應的樁(Stub)。

(ps:不過,如果反過來App 1作爲客戶端,主動調用AIDL接口獲取App 2傳過來的數據,思路也是通的。不過數據流向需要調整。)

5.2 確定接口以及數據流向

媒體文件的傳遞,確定接口:

// 通知文件傳輸流程開始

void onMediaShareStart(in String name);

//文件分段傳輸數據

void onMediaSharing(in MediaData data);

//通知文件傳輸結束

void onMediaShareFinish();

數據從當前頁面上傳到server端,數據流向爲Client端到Server端,接口參數配置in。

5.3創建AIDL文件

先從Server端開始。創建aidl文件:IMediaShareInterface.aidl、MediaData.aidl和MediaData.java。

//IMediaShareInterface.aidl

package com.cloudyhill.mediashare;

// Declare any non-default types here withimport statements

import com.cloudyhill.mediashare.MediaData;

interface IMediaShareInterface {

    void onMediaShareStart(in String name);

    void onMediaSharing(in MediaData data);

    void onMediaShareFinish();

}

MediaData.aidl:

packagecom.cloudyhill.mediashare;

// Declare anynon-default types here with import statements

parcelable MediaData;

實現序列化的MediaData類,即文件MediaData.java,注意類的包名和類名和MediaData.aidl保持一致,否則會報錯。

MediaData.java:

package com.cloudyhill.mediashare;

import android.os.Parcel;

import android.os.Parcelable;

public class MediaData implements Parcelable {

    private StringmFileName;

    private int mMediaDataSize;

    private byte[]mMediaDataArray;

    public MediaData() {

    }

    protected MediaData(String name,int size,byte[] data) {

        mFileName = name;

        mMediaDataSize = size;

        mMediaDataArray = data;

    }

    protected MediaData(Parcel in) {

        readFromParcel(in);

    }

    public String getFileName() {

        return mFileName;

    }

    public void setFileName(String name) {

        mFileName = name;

    }

    public int getMediaDataSize() {

        return mMediaDataSize;

    }

    public byte[] getMediaDataArray() {

        return mMediaDataArray;

    }

    public void setMediaDataArray(byte[] array) {

        mMediaDataArray = array;

        mMediaDataSize = array.length;

    }

    public static final CreatorCREATOR = new Creator() {

        @Override

        public MediaData createFromParcel(Parcel in) {

            return new MediaData(in);

        }

        @Override

        public MediaData[] newArray(int size) {

            return new MediaData[size];

        }

    };

    @Override

    public int describeContents() {

        return 0;

    }

    @Override

    public void writeToParcel(Parcel parcel,int flags) {

        parcel.writeString(mFileName);

        parcel.writeInt(mMediaDataSize);

        parcel.writeByteArray(mMediaDataArray);

    }

    public void readFromParcel(Parcel reply) {

        mFileName = reply.readString();

        mMediaDataSize = reply.readInt();

        mMediaDataArray = reply.createByteArray();

    }

}

5.4 Server端

創建Server端後臺運行的MediaShareService,Client端將通過bindService與之建立聯繫。

package com.cloudyhill.mediashareserver;

import android.app.Service;

import android.content.Intent;

import android.os.Environment;

import android.os.IBinder;

import android.os.RemoteException;

import android.util.Log;

import androidx.annotation.Nullable;

import com.cloudyhill.mediashare.IMediaShareInterface;

import com.cloudyhill.mediashare.MediaData;

import com.cloudyhill.mediashareserver.common.Config;

import java.io.File;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

public class MediaShareServiceextends Service {

private static final StringTAG = Config.LOG_TAG +"Srv";

private static MediaShareServicesInstance =null;

private long mMediaFileTotalSize;

private StringmMediaFileName;

private IMediaShareInterface.Stub mIMediaShareMgr = new IMediaShareInterface.Stub() {

        @Override

        public void onMediaShareStart(String name) throws RemoteException {

            Log.d(TAG,"<onMediaShareStart>");

            mMediaFileName = name;

            mMediaFileTotalSize =0;

            Log.d(TAG,"<onMediaShareStart>, mMediaFileName = " +mMediaFileName);

            createMediaFile(mMediaFileName);

        }

        @Override

        public void onMediaSharing(MediaData data) throws RemoteException {

            Log.d(TAG,"<onMediaSharing>");

            int size = data.getMediaDataSize();

            Log.d(TAG,"<onMediaSharing>, size = " + size);

            mMediaFileTotalSize += size;

            writeMediaFile(data.getMediaDataArray(), size);

        }

        @Override

        public void onMediaShareFinish() throws RemoteException {

            Log.d(TAG,"<onMediaShareFinish>, mMediaFileTotalSize = " +mMediaFileTotalSize);

            Intent intent =new Intent();

            intent.setAction(Config.ACTION_MEDIA_SHARE_FINISH);

            intent.putExtra(Config.EXTRA_MEDIA_SHARE_RESULT,mMediaFileTotalSize);

            sendBroadcast(intent);

        }

    };

    public MediaShareService() {

        Log.d(TAG,"<MediaShareService>");

    }

    public MediaShareService getInstance() {

        if (sInstance ==null) {

            synchronized (this)  {

                    if (sInstance == null)  {

                        sInstance = new MediaShareService();

                    }

            }

        }

        return sInstance;

    }

    @Nullable

    @Override

    public IBinder onBind(Intent intent) {

        Log.d(TAG,"<onBind>");

        return mIMediaShareMgr;

    }

    @Override

    public void onCreate() {

        super.onCreate();

        Log.d(TAG,"<onCreate>");

    }

    @Override

    public int onStartCommand(Intent intent,int flags,int startId) {

        Log.d(TAG,"<onStartCommand>");

        if (intent == null) {

            return START_STICKY;

        }

        return START_STICKY;

    }

    @Override

    public void onDestroy() {

        Log.d(TAG,"<onDestroy>");

        super.onDestroy();

    }

    private void createMediaFile(String name) {

        Log.d(TAG,"<createMediaFile>, name = " + name);

        File sdcard = Environment.getExternalStorageDirectory();

        File file = new File(sdcard, name);

        Log.d(TAG,"<createMediaFile>, file = " + file);

        mMediaFileName = file.getAbsolutePath();

        if (file.exists()) {

            Log.d(TAG,"<createMediaFile>, file exist, delete");

            file.delete();

        }

        try {

            Log.d(TAG,"<createMediaFile>, create new file.");

            file.createNewFile();

        } catch (IOException e) {

            Log.e(TAG,"<createMediaFile>, e = " + e);

        }

    }

    private void writeMediaFile(byte[] array,int size) {

        Log.d(TAG,"<writeMediaFile>");

        File file =new File(mMediaFileName);

        if (!file.exists()) {

            try {

                Log.d(TAG,"<writeMediaFile>, create new file.");

                file.createNewFile();

            } catch (IOException e) {

                Log.e(TAG,"<writeMediaFile>, e = " + e);

            }

        }

        try {

            // append write the file

            FileOutputStream fos =new FileOutputStream(file,true);

            fos.write(array);

            fos.flush();

            fos.close();

        } catch (FileNotFoundException e) {

            e.printStackTrace();

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

}

5.5 Client端

將之前的aidl文件和序列化的MediaData.java複製過去,注意包名以及位置。編寫用於上傳數據的MediaShareClientActivity

package com.cloudyhill.mediashareclient;

import android.app.Activity;

import android.content.ComponentName;

import android.content.Context;

import android.content.Intent;

import android.content.ServiceConnection;

import android.media.MediaPlayer;

import android.os.Bundle;

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.util.Log;

import android.view.View;

import android.widget.Button;

import android.widget.TextView;

import com.cloudyhill.mediashare.IMediaShareInterface;

import com.cloudyhill.mediashare.MediaData;

import com.cloudyhill.mediashareclient.common.Config;

import java.io.BufferedInputStream;

import java.io.IOException;

import java.io.InputStream;

import static com.cloudyhill.mediashareclient.common.Config.MSG_MEDIA_FILE_SHARE;

import static com.cloudyhill.mediashareclient.common.Config.MSG_UPDATE_PROGRESS;

public class MediaShareClientActivity extends Activity implements View.OnClickListener {

    private static final StringTAG = Config.LOG_TAG +"Activity";

    private ContextmContext;

    private MediaPlayermMediaPlayer;

    private StringmMediaFileName = Config.FILE_NAME;

    private long mMediaFileTotalSize;

    private boolean mIsPlaying;

    private TextViewmMediaFileText;

    private TextViewmMediaProgressText;

    private ButtonmPlayBtn;

    private ButtonmShareBtn;

    private IMediaShareInterfacemIMediaShare;

    private HandlerThreadmHandlerThread;

    private MessageHandlermMsgHandler;

    private MainHandlermMainHandler;

    private ServiceConnection mServiceConnection = new ServiceConnection() {

        @Override

        public void onServiceConnected(ComponentName componentName, IBinder service) {

            Log.d(TAG,"<onServiceConnected>");

            if (service !=null) {

                try {

                    service.linkToDeath(mDeathRecipient,0);

                }catch (RemoteException e) {

                    e.printStackTrace();

                }

            }

            // step 2, notify media share start

            mMediaFileTotalSize = 0;

            // 關鍵點,獲取Server端接口類型

            mIMediaShare = IMediaShareInterface.Stub.asInterface(service);

            if (mIMediaShare !=null) {

                try {

                    // 調用Server端接口

                    mIMediaShare.onMediaShareStart(Config.FILE_NAME);

                } catch (RemoteException e) {

                    e.printStackTrace();

                }

            }

            // step 3, send message

            mMsgHandler.sendEmptyMessage(MSG_MEDIA_FILE_SHARE);

        }

        @Override

        public void onServiceDisconnected(ComponentName componentName) {

            Log.d(TAG,"<onServiceDisconnected>");

        }

    };

    IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {

        @Override

        public void binderDied() {

            Log.d(TAG,"<binderDied>");

            attemptBindService();

        }

    };

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        mContext = getApplicationContext();

        initViews();

        initData();

    }

    @Override

    protected void onStart() {

        super.onStart();

        Log.d(TAG,"<onStart>");

    }

    @Override

    protected void onStop() {

        super.onStop();

        Log.d(TAG,"<onStart>");

        attemptUnbindService();

    }

    @Override

    public void onClick(View view) {

        Log.d(TAG,"<onClick>");

        int resId = view.getId();

        switch (resId) {

        case R.id.play:

            if (mIsPlaying) {

                pausePlayMusic();

            } else {

                startPlayMusic();

            }

            break;

        case R.id.share:

            handleShare();

            break;

        default:

            break;

        }

    }

    private void initViews() {

        Log.d(TAG,"<initViews>");

        mMediaFileText = (TextView) findViewById(R.id.share_name);

        mMediaFileText.setText(mMediaFileName);

        Log.d(TAG,"<initViews>, media file name = " +mMediaFileName);

        mMediaProgressText = (TextView) findViewById(R.id.progress_text);

        mPlayBtn = (Button) findViewById(R.id.play);

        mPlayBtn.setOnClickListener(this);

        mShareBtn = (Button) findViewById(R.id.share);

        mShareBtn.setOnClickListener(this);

    }

    private void initData() {

        Log.d(TAG,"<initData>");

        mHandlerThread =new HandlerThread("MediaShareThread");

        mHandlerThread.start();

        mMsgHandler = new MessageHandler(mHandlerThread.getLooper());

        mMainHandler = new MainHandler(Looper.getMainLooper());

    }

    private void updateMediaShareProgress() {

        Log.d(TAG,"<updateMediaShareProgress>");

        if (mMediaProgressText != null) {

            mMediaProgressText.setText(String.valueOf(getMediaShareProgress()));

        }

    }

    private void startPlayMusic() {

        Log.d(TAG,"<pausePlayMusic>");

        mMediaPlayer = MediaPlayer.create(mContext, R.raw.music_demo);

        mMediaPlayer.start();

        mPlayBtn.setText(R.string.pause);

        mIsPlaying =true;

    }

    private void pausePlayMusic() {

        Log.d(TAG,"<pausePlayMusic>");

        if (mMediaPlayer != null) {

            mMediaPlayer.pause();

        }

        mPlayBtn.setText(R.string.play);

        mIsPlaying =false;

    }

    private synchronized long getMediaShareProgress() {

        return mMediaFileTotalSize;

    }

    private synchronized void setMediaShareProgress(int size) {

        mMediaFileTotalSize += size;

    }

    private void handleShare() {

        Log.d(TAG,"<handleShare>");

        // step 1

        attemptBindService();

    }

    private void shareMediaFile() {

        Log.d(TAG,"<shareMediaFile>");

        boolean result =false;

        InputStream is = getResources().openRawResource(R.raw.music_demo);

        try {

            result = readMediaFile(is);

        }catch (IOException err) {

            Log.e(TAG,"<shareMediaFile>, err = " + err);

        }

        Log.d(TAG,"<shareMediaFile>, result = " + result);

    }

    private boolean readMediaFile(InputStream is)throws IOException {

        boolean result = false;

        BufferedInputStream bis = new BufferedInputStream(is);

        MediaData mediaData =new MediaData();

        byte[] data =new byte[256*1024];

        int size;

        while ((size = bis.read(data)) != -1) {

            Log.d(TAG,"<readMediaFile>, size = " + size);

            setMediaShareProgress(size);

            mMainHandler.sendEmptyMessage(MSG_UPDATE_PROGRESS);

            mediaData.setMediaDataArray(data);

            try {

                if (mIMediaShare !=null) {

                    mIMediaShare.onMediaSharing(mediaData);

                }

            }catch (RemoteException e) {

                e.printStackTrace();

            }

        }

        Log.d(TAG,"<readMediaFile>, break while, size = " + size);

        // step 4, notify media share finished.

        try {

            mIMediaShare.onMediaShareFinish();

        }catch (RemoteException e) {

            e.printStackTrace();

        }

        return result;

    }

    private void attemptBindService() {

        Log.d(TAG,"<attemptBindService>");

        Intent intent =new Intent();

        intent.setAction("com.cloudyhill.action.MEDIA_SHARE");

        intent.setPackage("com.cloudyhill.mediashareserver");

        bindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE);

    }

    private void attemptUnbindService() {

        Log.d(TAG,"<attemptUnbindService>");

        unbindService(mServiceConnection);

    }

    private class MessageHandler extends Handler {

        private MessageHandler(Looper looper) {

            super(looper);

        }

        @Override

        public void handleMessage(Message msg) {

            Log.d(TAG,"<handleMessage>, msg.what = " + msg.what);

            switch (msg.what) {

            case MSG_MEDIA_FILE_SHARE:

                shareMediaFile();

                break;

            default:

                break;

            }

        }

    }

    private class MainHandlerextends Handler {

        private MainHandler(Looper looper) {

            super(looper);

        }

        public void handleMessage(Message msg) {

            Log.d(TAG,"<handleMessage>, MainHandler, msg.what = " + msg.what);

            switch (msg.what) {

            case MSG_UPDATE_PROGRESS:

                updateMediaShareProgress();

                break;

            default:

                break;

            }

        }

    }

}

5.6 簡單看下byte[]的通信流程

從Client端的onMediaSharing入手,mIMediaShare是aidl編譯生成的IMediaShareInterface.java類。

BufferedInputStream bis =new BufferedInputStream(is);

MediaData mediaData =new MediaData();

byte[] data =new byte[256*1024];

int size;

while ((size = bis.read(data)) != -1) {

    Log.d(TAG,"<readMediaFile>, size = " + size);

    setMediaShareProgress(size);

    mMainHandler.sendEmptyMessage(MSG_UPDATE_PROGRESS);

    mediaData.setMediaDataArray(data);

    try {

        if (mIMediaShare !=null) {

            // 調用aidl定義接口

            mIMediaShare.onMediaSharing(mediaData);

        }

    } catch (RemoteException e) {

        e.printStackTrace();

    }

}

@Override 

public void onMediaSharing(com.cloudyhill.mediashare.MediaData data) throws android.os.RemoteException {

    // 獲取一個Parcel對象

    android.os.Parcel _data = android.os.Parcel.obtain();

    android.os.Parcel _reply = android.os.Parcel.obtain();

    try {

        _data.writeInterfaceToken(DESCRIPTOR);

        if ((data!=null)) {

            _data.writeInt(1);

            // 調用自定義Parcelable類的writeToParcel,data即MediaData

            data.writeToParcel(_data,0);

        } else {

            _data.writeInt(0);

        }

        mRemote.transact(Stub.TRANSACTION_onMediaSharing, _data, _reply,0);

        _reply.readException();

    } finally {

        _reply.recycle();

        _data.recycle();

    }

}

先是調用MediaData的writeToParcel,再調用mRemote(即Server端的IMediaShareInterface)發消息執行TRANSACTION_onMediaSharing。

查看MediaData的writeToParcel接口:

@Override

public void writeToParcel(Parcel parcel,int flags) {

    parcel.writeString(mFileName);

    parcel.writeInt(mMediaDataSize);

    parcel.writeByteArray(mMediaDataArray);

}

數組寫入方式是調用writeByteArray寫入數據。在onTransact中有處理TRANSACTION_onMediaSharing:

case TRANSACTION_onMediaSharing:

{

    data.enforceInterface(descriptor);

    com.cloudyhill.mediashare.MediaData _arg0;

    if ((0!=data.readInt())) {

        // 這裏的data就是之前要往裏寫入的數據_data

        _arg0 = com.cloudyhill.mediashare.MediaData.CREATOR.createFromParcel(data);

    } else {

        _arg0 =null;

    }

    this.onMediaSharing(_arg0);

    reply.writeNoException();

    return true;

}

從MediaData的用data數據即寫入的數據,調用MediaData的createFromParcel創建一個MediaData對象:

public static final CreatorCREATOR =new Creator() {

    @Override

    public MediaData createFromParcel(Parcel in) {

        return new MediaData(in);

    }

};

protected MediaData(Parcel in) {

    readFromParcel(in);

}

public void readFromParcel(Parcel reply) {

    mFileName = reply.readString();

    mMediaDataSize = reply.readInt();

    mMediaDataArray = reply.createByteArray();

}

這樣就把Parcelable數據從Client端傳給Server端,這個時候Server端的onMediaSharing中調用onMediaSharing,就能得到Client端的數據了。

上面還留了一個坑,爲什麼說mRemote即Server端的IMediaShareInterface?我們注意到Server端的Service有個接口:

public IBinder onBind(Intent intent) {

Log.d(TAG,"<onBind>");

return mIMediaShareMgr;

}

然後,看Client端獲取IMediaDataInterface的方法,下面這個service就是bindService建立連接以後返回的Server端的mIMediaShareMgr,跟蹤一下它的走向:

mIMediaShare = IMediaShareInterface.Stub.asInterface(service);

public static com.cloudyhill.mediashare.IMediaShareInterface asInterface(android.os.IBinder obj)

{

    if ((obj==null)) {

        return null;

    }

    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);

    if (((iin!=null)&&(iininstanceof com.cloudyhill.mediashare.IMediaShareInterface))) {

        return ((com.cloudyhill.mediashare.IMediaShareInterface)iin);

    }

    return new com.cloudyhill.mediashare.IMediaShareInterface.Stub.Proxy(obj);

}

Proxy(android.os.IBinder remote) {

    mRemote = remote;

}

最終傳給了mRemote,即mRemote就是Server端的mIMediaShareMgr。

6、踩坑記錄

6.1 錯誤提示:ProcessException: Error while executing process D:\android\sdk\build-tools\28.0.3\aidl.exe with arguments

基本數據類型的數組(例如byte[])不能直接作爲aidl接口參數傳遞,要用實現Parcelable的數據類型來封裝數組。也就是說,aidl支持參數傳遞常用的數據類型byte、int等,但是並不直接支持byte[]、int[]等數組類型。也就是需要用Parcelable來序列化數據。

接口中傳遞的封裝數據類型,需要添加一個aidl文件,以parcelable來聲明該類型,在引用該類型的aidl文件中加import引用,並實現該類型的實現。詳情可參考5.1部分。

6.2 aidl文件的package name和Parcelable實現類的package name不一致,也會提示6.1的錯誤,應該是找不到實現類,所以報錯。

6.3 aidl文件中聲明數據流向是out或者inout,但是server端沒有實現readFromParcel。看aidl編譯生成的java文件,和數據流向有關的函數接口實現:

@Override public void onMediaSharing(com.cloudyhill.mediashare.MediaData data) throws android.os.RemoteException {

    android.os.Parcel _data = android.os.Parcel.obtain();

    android.os.Parcel _reply = android.os.Parcel.obtain();

    try {

        _data.writeInterfaceToken(DESCRIPTOR);

        mRemote.transact(Stub.TRANSACTION_onMediaSharing, _data, _reply, 0);

        _reply.readException();

        if ((0!=_reply.readInt())) {

            data.readFromParcel(_reply);

        }

    } finally {

        _reply.recycle();

        _data.recycle();

    }

}

從這裏看,數據流向聲明爲out、inout需要複寫readFromParcel。

6.4 Binder通信大小限制

Binder通信大小限制爲1M – 8KB,除非定製化Binder通信大小。在android源碼中ProcessState.cpp中有配置信息:

#define BINDER_VM_SIZE((1*1024*1024) - (4096 *2))

但是我設置爲512*1024KB,同樣會報TransactionTooLargeException的錯誤,因此,本例中設置byte[] data = new byte[256*1024],一次256KB。原因可以參考以下知乎上的回答:

“Binder的線程池數量默認是15個,由15個線程共享這1MB-8KB的內存空間,所以實際傳輸大小並沒有那麼大。”

抱着嚴謹的態度,這種說法正確與否還有待驗證。

 

源碼請另外參考附件demo程序,Client App和Server App。

參考鏈接:

https://www.zhihu.com/question/39440766/answer/89210950

https://www.jianshu.com/p/533de5fa6e4c

http://gityuan.com/2015/10/31/binder-prepare/

https://blog.csdn.net/luoyanglizi/article/details/51980630

https://blog.csdn.net/luoyanglizi/article/details/51958091

https://www.jianshu.com/p/ea4fc6aefaa8

https://www.zhihu.com/question/264164505

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