美文网首页AIDLAndroid框架应用与探讨
Android平台必备技能(一)---进程间通信AIDL的应用详

Android平台必备技能(一)---进程间通信AIDL的应用详

作者: 喝拿铁撸铁 | 来源:发表于2019-12-19 21:24 被阅读0次

    一些必备的背景知识,理解以下知识将有助于编写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 String mFileName;

        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 MediaShareService extends 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 MainHandler extends 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://download.csdn.net/download/aqiao58/12042751

    参考链接:

    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

    相关文章

      网友评论

        本文标题:Android平台必备技能(一)---进程间通信AIDL的应用详

        本文链接:https://www.haomeiwen.com/subject/fvzjwqtx.html