关于 AIDL & Binder 的一些小事

1,160 阅读5分钟
原文链接: www.diycode.cc

前言

关于AIDL和Binder已经“听说”好多年了,在实际项目中一直没有使用到,只停留在理论的阶段,最近打算对项目进行多进程拆分尝试,于是对Binder的概念重新梳理一下,发现有些概念早已模糊不清,借此机会重新造个轮子,写一下尝试过程中遇到的问题。

先跳过原理,我们来看看怎么实现最简单的ADIL

假如你还不熟悉ADIL和Binder,请参考Binder学习指南.
这篇文章我看了两遍。本文写的思路和它差不多,只不过带上了实践代码和个人理解,基础概念在上文中已描述的非常清楚了。

顺便推荐下Weishu's Notes的文章,主要讲插件化原理的,每一篇个人都觉得写的很棒;

提供一个Service

编写方法和步骤非常简单:

编写ISwordServiceInterface.aidl;定义提供的接口(也就是你这个Service提供给外部的接口);

在这里我们提供了callActivity()和getServerString()的这两个方法;

        // ISwordServiceInterface.aidl
        package hook.sword.com.swordserviceserver;
        import  android.content.Context;
        // Declare any non-default types here with import statements
        interface ISwordServiceInterface {
            /**
            * Demonstrates some basic types that you can use as parameters
            * and return values in AIDL.
            */
            void callActivity();
            String getServerString();
        }

执行gradle build;会在app/build/generated/source/aidl/debug/***/ISwordServiceInterface的java类;

        /*
        * This file is auto-generated.  DO NOT MODIFY.
        * Original file: /Users/mu/sword/aidlgithub/SwordServiceServer/app/src/main/aidl/hook/sword/        com/swordserviceserver/ISwordServiceInterface.aidl
         */
        package hook.sword.com.swordserviceserver;
        // Declare any non-default types here with import statements

        public interface ISwordServiceInterface extends android.os.IInterface {
          /**
           * Local-side IPC implementation stub class.
           */
        public static abstract class Stub extends android.os.Binder implements      hook.sword.com.swordserviceserver.ISwordServiceInterface {
              private static final java.lang.String DESCRIPTOR = "hook.sword.com.swordserviceserver.ISwordServiceInterface";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an hook.sword.com.swordserviceserver.ISwordServiceInterface interface,
         * generating a proxy if needed.
         */
        public static hook.sword.com.swordserviceserver.ISwordServiceInterface asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof hook.sword.com.swordserviceserver.ISwordServiceInterface))) {
                return ((hook.sword.com.swordserviceserver.ISwordServiceInterface) iin);
            }
            return new hook.sword.com.swordserviceserver.ISwordServiceInterface.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_callActivity: {
                    data.enforceInterface(DESCRIPTOR);
                    this.callActivity();
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_getServerString: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _result = this.getServerString();
                    reply.writeNoException();
                    reply.writeString(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements hook.sword.com.swordserviceserver.ISwordServiceInterface {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            /**
             * Demonstrates some basic types that you can use as parameters
             * and return values in AIDL.
             */
            @Override
            public void callActivity() 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_callActivity, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public java.lang.String getServerString() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.lang.String _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getServerString, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_callActivity = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_getServerString = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
         }

        /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
        */
        public void callActivity() throws android.os.RemoteException;

        public java.lang.String getServerString() throws android.os.RemoteException;
        }

这个Java类主要看两个类,一个是Proxy,一个是Stub;

Stub 继承了Binder,具有夸进程的通讯能力(内核赋予的);它是可抽象类,我们需要实现它,然后在Service的onBinder返回它,Service就具有了跨进程通讯能力。

Proxy和它的名字一样,是一个代理类;如果读了Binder学习指南这篇文字,你对这个应该非常熟悉了。简单来说,当使用者通过Binder请求Service服务能力的时候,Binder会返回一个代理对象,使用者调用Proxy的接口,再由Binder通知真正的对象Stub来进行执行。

编写实现类 SwordServiceImpl extends ISwordServiceInterface.Stub;

        package hook.sword.com.swordserviceserver;

        import android.content.Context;
        import android.content.Intent;
        import android.os.RemoteException;

        /**
         * Author: lwh
        * Date: 11/2/16 16:49.
        */

        public class SwordServiceImpl extends ISwordServiceInterface.Stub {
            private Context mContext;
            public SwordServiceImpl(Context context){
                mContext = context;
            }
            @Override
            public void callActivity() throws RemoteException {
                Intent intent = new Intent();
                intent.setClass(mContext,ServerActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                mContext.startActivity(intent);
            }

            @Override
            public String getServerString() throws RemoteException {
                return "Hi,I am from Server!";
            }
        }

编写 SwordService extends Service ;将onBind实现返回 实现类;

        package hook.sword.com.swordserviceserver;

        import android.app.Service;
        import android.content.Intent;
        import android.os.IBinder;

        /**
        * Author: lwh
        * Date: 11/2/16 16:48.
        */

        public class SwordService extends Service {
            private SwordServiceImpl mSwordServiceImpl;

            @Override
            public void onCreate() {
                super.onCreate();
                mSwordServiceImpl = new SwordServiceImpl(getApplicationContext());
                /*Intent intent = new Intent(getApplicationContext(),ServerActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(intent);*/
            }

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

在Mainfest声明action;

        service android:name="hook.sword.com.swordserviceserver.SwordService"
                 android:exported="true"
            android:process=":CoreService"
            >
            
                
            
        

android:exported=true意味着服务对外部是开放的;

android:process=":CoreService"此服务运行在独立进程,进程名叫CoreService;

完成启动服务

        //啟動服務
        Intent intent = new Intent();
        intent.setClass(this,SwordService.class);
        startService(intent);

至此,我们已经编写并完成了一个独立进程Service的运行;它提供了两个接口供外部调用;
接下来我们看外部如何使用。

在这里遇到两个问题比较迷惑一点:

(1)当运行问独立进程的Service之后,在手机的“正在运行”的进程中,只看到一个进程,不知道是什么原理,求解惑~。

(2)当把Service声明为独立进程之后,不适用aidl其实也是可以和本工程的类进行直接相互引用的,感觉上没什么问题,不知道有什么弊端没有。

使用Service

与Service同工程的项目也可以演示调用远程Service,但此时返回的代理对象是本地的,这里为了真正区分夸进程通信,我们建立一个client 工程来调用我们刚才建立的Service,看看是否有效;

拷贝aidl文件

将Server工程的aidl文件夹拷贝到客户端目录一致的地方;然后执行gradle build生成java文件;

这个和server工程的java文件是一模一样的;

启动连接服务

        Bundle args = new Bundle();
        Intent intent = new Intent("hook.sword.com.swordserviceserver.SwordService");
        intent.setPackage("hook.sword.com.swordserviceserver");
        intent.putExtras(args);
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);


        private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.d(TAG,"連接服務成功:");
            mISwordServiceInterface = ISwordServiceInterface.Stub.asInterface(iBinder);
            Toast.makeText(getApplicationContext(),"服務連接成功",Toast.LENGTH_SHORT).show();
            getString();
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.d(TAG,"連接服務失敗:");
        }
        };

在这里有一行代码mISwordServiceInterface = ISwordServiceInterface.Stub.asInterface(iBinder)需要解释一下原理:

iBinder是內核传递过来的;

我们来看ISwordServiceInterface.Stub.asInterface这个方法,在生成的那个Java类里:

        public static hook.sword.com.swordserviceserver.ISwordServiceInterface asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof hook.sword.com.swordserviceserver.ISwordServiceInterface))) {
                return ((hook.sword.com.swordserviceserver.ISwordServiceInterface) iin);
            }
            return new hook.sword.com.swordserviceserver.ISwordServiceInterface.Stub.Proxy(obj);
        }
    它的逻辑是告诉Binder找一下本地有没有叫DESCRIPTOR的本地服务,如果有的话,就返回;
    如果没有的话,就创建一个代理对象返回;而这个代理对象的接口跟真正代理的接口伪装的一模一样,让使用者用起来感觉在使用真正的服务一样,他们内部通过Binder的transact来传输和交互;

调用服务提供的方法:

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(mISwordServiceInterface!=null){
                    getString();
                }
            }
        });
        findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(mISwordServiceInterface!=null){
                    callActivity();
                }
            }
        });
         private void getString(){
        try {
            String serverString = mISwordServiceInterface.getServerString();
            Toast.makeText(getApplicationContext(),"獲取服務端字符串:"+serverString,Toast.LENGTH_SHORT).show();
            Log.d(TAG,"獲取服務端字符串:"+serverString);
        }catch (Exception ex){
            ex.printStackTrace();
        }
        }

        private void callActivity(){
        try{
            mISwordServiceInterface.callActivity();
        }catch (Exception ex){
            ex.printStackTrace();
        }
        }

Server和Client代码已提交github,欢迎下载


QQ:452825089

mail:452825089@qq.com

wechat:ice3897315

blog:iceAnson.github.io