Eight, Android in IPC mechanism (5) of Binder --- Android application layer analysis

Disclaimer: This article is a blogger original article, shall not be reproduced without the bloggers allowed. https://blog.csdn.net/yz_cfm/article/details/90300279

    What is a Binder? By analyzing the previous article, we know that Binder is a cross-process communication of the Android system to provide their own way, the communication is not in Linux. How do we use it to complete the cross-process communication it? This time, they came from Android application layer, Android system provides a Binder class, and this class is the object-side and Client-Server communication media conduct, when bindService time, it will return a Server-side includes Server-side business Binder (IBinder type) object, and the client can Binder object referenced by this call to obtain services or data provided by the Server side, thus completing the inter-process communication.

    In Android development, Binder mainly used in the Service, including AIDL and Messenger, which the general Service of Binder does not involve inter-process communication, it is simple to use, because it can not touch the core Binder, and the underlying Messenger is actually AIDL to achieve, so here we combine AIDL and Binder source (Android Interface Definition Lauguage --- Android Interface Definition language, is one way to achieve Android by IPC Binder mechanism) together to explore how to use the Android system is the mechanism Binder to complete the cross-process communication.

First take a brief look at the application of AIDL:

Server code:

After aidl 1. Create a file (method declarations inside the server implementation, two methods were completed addition and subtraction of two integers), created, Bulid project, generated file open build directory under the following folder will be automatically generated ICalculator.java a file, this is the key code to complete the cross-process communication mechanism by Binder, the latter will be a good analysis.

// ICalculator.aidl
package com.cfm.serverwithaidltest;

interface ICalculator {
    // 两个整型数相加
    int add(int a, int b);
    // 两个整型数相减
    int subtract(int a, int b);
}

2. Create a Service, and then the client and server can be cross-process communication through the Service IBinder type of class.

package com.cfm.serverwithaidltest;

public class MyService extends Service {

    public IBinder mBinder = new ICalculator.Stub() {
        @Override
        public int add(int a, int b) throws RemoteException {
            return (a + b);
        }

        @Override
        public int subtract(int a, int b) throws RemoteException {
            return (a - b);
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        Log.d("cfmtest", "MyService 服务绑定成功!" + "当前进程名: " + getProcessName(MyService.this));
        return mBinder;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("cfmtest", "MyService 服务创建成功!" + "当前进程名: " + getProcessName(MyService.this));
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("cfmtest", "MyService 服务开始!" + "当前进程名: " + getProcessName(MyService.this));
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d("cfmtest", "MyService 服务解绑成功!" + "当前进程名: " + getProcessName(MyService.this));
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        Log.d("cfmtest", "MyService 服务销毁!" + "当前进程名: " + getProcessName(MyService.this));
        super.onDestroy();
    }

    // 获取进程名方法
    public String getProcessName(Context context) {
        int pid = android.os.Process.myPid();
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();

        if (runningApps == null) {
            return null;
        }

        for (ActivityManager.RunningAppProcessInfo procInfo : runningApps) {
            if (procInfo.pid == pid) {
                return procInfo.processName;
            }
        }
        return null;
    }
}

In AndroidManifest.xml Service definition:

<service android:name=".MyService">
    <intent-filter>
        <action android:name="com.cfm.serverwithaidltest.MYSERVICE"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</service>

3. Create a server Activity.

package com.cfm.serverwithaidltest;

public class ServerActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

Here's look at the client implementation code:

1. First copy the file type aidl defined under the server to the client code, be sure to pay attention to the file and can be used after the package name and server remain exactly the same, then Build project AIDL complete cross-process communication.

2. Use AIDL:

// ClientActivity.java
public class ClientActivity extends AppCompatActivity {

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

        final ServiceConnection conn = new ServiceConnection() {

            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                ICalculator calc = ICalculator.Stub.asInterface(service);

                try {
                    int addResult = calc.add(322, 344);
                    int subtractResult = calc.subtract(1000, 112);
                    Log.d("cfmtest", "计算的加法结果: " + addResult + "当前进程名: " + getProcessName(ClientActivity.this));
                    Log.d("cfmtest", "计算的减法结果: " + subtractResult + "当前进程名: " + getProcessName(ClientActivity.this));

                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
        };

        Button startServiceBtn = findViewById(R.id.start_btn);
        Button bindServiceBtn = findViewById(R.id.bind_btn);
        Button unBindServiceBtn = findViewById(R.id.unbind_btn);
        Button stopServiceBtn = findViewById(R.id.stop_btn);

        startServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setAction("com.cfm.serverwithaidltest.MYSERVICE");
                intent.setPackage("com.cfm.serverwithaidltest");
                startService(intent);
            }
        });

        bindServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setAction("com.cfm.serverwithaidltest.MYSERVICE");
                intent.setPackage("com.cfm.serverwithaidltest");
                bindService(intent, conn, Context.BIND_AUTO_CREATE);
            }
        });

        unBindServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                unbindService(conn);
            }
        });

        stopServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setAction("com.cfm.serverwithaidltest.MYSERVICE");
                intent.setPackage("com.cfm.serverwithaidltest");
                stopService(intent);
            }
        });
    }

    // 获取进程名方法
    public static String getProcessName(Context context) {
        int pid = android.os.Process.myPid();
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();

        if (runningApps == null) {
            return null;
        }

        for (ActivityManager.RunningAppProcessInfo procInfo : runningApps) {
            if (procInfo.pid == pid) {
                return procInfo.processName;
            }
        }
        return null;
    }
}

    Finally Log in printing information:

cfmtest: MyService 服务创建成功!当前进程名: com.cfm.serverwithaidltest
cfmtest: MyService 服务开始!当前进程名: com.cfm.serverwithaidltest
cfmtest: MyService 服务绑定成功!当前进程名: com.cfm.serverwithaidltest
cfmtest: 计算的加法结果: 666当前进程名: com.cfm.clientwithaidltest
cfmtest: 计算的减法结果: 888当前进程名: com.cfm.clientwithaidltest
cfmtest: MyService 服务解绑成功!当前进程名: com.cfm.serverwithaidltest
cfmtest: MyService 服务销毁!当前进程名: com.cfm.serverwithaidltest

    AIDL fact, Android system is achieved by providing a mechanism for encapsulating Binder cross-process communication, so we very simple to use, can be seen from the above results, the client request to the server the results of two numbers, then the server after receiving the request at the server computing and finally the result back to the client inter-process communication in order to achieve a C / S model (client-Server) is.

    The key is to realize the underlying AIDL Binder class, let's look at the source code Binder class:

public class Binder implements IBinder {
   ...
}

    Binder realized IBinder interfaces, let's look at the following IBinder Source:

/**
* 远程对象的基本接口,是为高性能而设计的轻量级远程调用机制的核心部分,可以被用于远程调用和进程内调用。这个接口描述了一个用于和远程对象交互的抽象协议。不要直接实现这个接口,而是继承Binder对象。
*/
public interface IBinder {
    ...
}

    We can see through the source code that implements the IBinder interface. The IBinder Android interface is the basic interface provided by the system can be remotely operated one kind of object, which is inherited or object that implements this interface will have the ability to cross-transfer process, so the Binder class object will have the ability to cross-process transfer .

    There are two methods in Binder.java source code:

// 这个方法的作用就是通过键值对的方式,以字符串 descriptor 为 Key,IInterface 接口为 Value 存到 Binder 对象中的一个 Map<String, IInterface> 对象中
public void attachInterface(IInterface owner, String descriptor) {
        ...
    }

// 这个方法的作用就是通过 String 类型的 descriptor 在 Map<String, IInterface> 对象中查询该 Binder 对象所对应的 IInterface 接口对象。
public IInterface queryLocalInterface(String descriptor) {
        ...
    }

    Look again IInterface the interface look like?

package android.os;

/**
* Binder接口的基类。当定义一个新的接口,你必须从IInterface中继承。
*/
public interface IInterface
{
    /**
     * 取得与这个接口有联系的Binder对象。
     * 你必须使用它而不是普通的类型转换,这样代理对象才可以返回正确的结果.
     */
    public IBinder asBinder();
}

    It can be seen that there is only a asBinder method, the effect of the method is to return the current interface object associated Binder (Binder because the class implements the interface IBinder, so the return type is IBinder type). This interface is a common interface Android system as defined in the inter-process communication, all of which can be transmitted in the Binder interfaces need to inherit from this interface. So when we use a custom mechanism Binder Binder class, it must inherit Binder class provided by Android system, and then implement IInterface interface type interface. Here we analyze the use of the above specific AIDL generated java file (in fact, the equivalent of AIDL can help us to automatically generate a class Binder interprocess communication):

package com.cfm.serverwithaidltest;

/**
* 这个是 AIDL 自动为我们生成的继承了 IInterface 接口的 ICalculator 接口,这样该接口就可以在 Binder 中传输了。
* 注:所有可以在 Binder 中传输的接口都必须继承 IInterface 接口,所有为了实现跨进程通信而自定义的 Binder 类都必须实现该接口。
*/
public interface ICalculator extends android.os.IInterface
{
    /**
     * 这个 Stub 类就是继承了 Binder 类并实现了 IInterface 接口的抽象 Binder 类,所以该类就是实现 Binder 机制的跨进程通信的关键。
     * 该类的作用就是: 当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的 transact 过程,直接使用 Stub 类中的 onTransact() 完成方法调用;
     * 而当两者位于不同进程时,方法调用需要走 transact 过程,这个逻辑由 Stub 类的内部代理类 Proxy 来完成。
     */
    public static abstract class Stub extends android.os.Binder implements com.cfm.serverwithaidltest.ICalculator
    {
        /**
         * 该 Binder 类的唯一标识,一般用当前 Binder 的类名表示
         */
        private static final java.lang.String DESCRIPTOR = "com.cfm.serverwithaidltest.ICalculator";

        /**
         *  该 Binder 类实现了 IInterface 接口,所以也是 IInterface 类型,通过 attachInterface() 将该 IInterface 类型对象与
         *  DESCRIPTOR 描述符以类似 <key, value> 形式存储,后面就可以通过 queryLocalInterface(DESCRIPTOR) 查找到该 IInterface 类型对象。
         */
        public Stub()
        {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * 用于将服务端的 Binder 对象转换成客户端所需的 AIDL 类型的对象。这样客户端就可以调用服务端 Binder 类中实现的方法了。
         * 注:这种转换过程是区分进程的,如果客户端和服务端位于同一进程中,那么此方法返回的就是服务端的 Stub 对象本身;否则返回的是系统封装后的
         * Stub.proxy 对象。
         */
        public static com.cfm.serverwithaidltest.ICalculator asInterface(android.os.IBinder obj)
        {
            // 这个 IBinder 类型的对象 obj,有可能来自服务端进程,也有可能来自客户端进程。如果来自服务端,那就不需要
            // 进行跨进程通信了,只需要返回 Stub 对象,最后调用 Stub 对象本身中的 onTransact 方法即可;否则就需返回 Stub.Proxy 对象,
            // 最后调用 Stub.Proxy 对象中的 transact 方法。
            if ((obj==null)) {
                return null;
            }

            // 判断 obj 对象是否是和服务端的 Binder 对象是同一个,如果是,就是同进程通信;否则就是跨进程通信。
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);

            if (((iin!=null)&&(iin instanceof com.cfm.serverwithaidltest.ICalculator))) {
                // 返回当前 Stub 对象本身
                return ((com.cfm.serverwithaidltest.ICalculator)iin);
            }

            // 返回 Stub 对象的内部代理对象 Stub.Proxy
            return new com.cfm.serverwithaidltest.ICalculator.Stub.Proxy(obj);
        }

        /**
         * 实现 IInterface 接口中的方法,返回该接口所关联的 Binder 类,也就是 Stub 类本身
         */
        @Override
        public android.os.IBinder asBinder()
        {
            return this;
        }

        /**
         * 这个方法运行在服务端的 Binder 线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。
         *
         * @param code  服务端通过 code 可以确定客户端所请求目标方法是什么
         * @param data  从该参数可以取出目标方法所需要的参数(如果目标方法有参数的话)
         * @param reply 当目标方法执行完毕后,就向该参数写入返回值(如果目标方法由返回值的话)
         * @param flags 参数flags只有 0 和 FLAG_ONEWAY 两种。默认的跨进程操作是同步的,所以transact()方法的执行会阻塞,调用以同步的形式传递到远程的transact(),
         *              等待远端的transact()返回后继续执行——最好理解的方式就是把两端的transact()看作一个方法,
         *              Binder机制的目标也就是这样。指定FLAG_ONEWAY时,表示Client的transact()是单向调用,
         *              执行后立即返回,无需等待Server端transact()返回。
         * @return 如果此方法返回 false,那么客户端的请求会失败。因此我们可以通过这个特性来做权限验证,准许客户端请求就返回 true,否则就返回 false。
         */
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
        {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code)
            {
                case INTERFACE_TRANSACTION:
                {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_add:
                {
                    data.enforceInterface(descriptor);
                    int _arg0;
                    _arg0 = data.readInt();
                    int _arg1;
                    _arg1 = data.readInt();
                    int _result = this.add(_arg0, _arg1);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
                case TRANSACTION_subtract:
                {
                    data.enforceInterface(descriptor);
                    int _arg0;
                    _arg0 = data.readInt();
                    int _arg1;
                    _arg1 = data.readInt();
                    int _result = this.subtract(_arg0, _arg1);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
                default:
                {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        /**
         * 当客户端和服务端不位于同一个进程时,客户端就会通过这个 Proxy 代理类,然后通过 transact 方法完成所需方法调用。
         */
        private static class Proxy implements com.cfm.serverwithaidltest.ICalculator
        {
            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;
            }

            /**
             * 该方法运行在客户端
             */
            @Override
            public int add(int a, int b) throws android.os.RemoteException
            {
                // 创建输入型 Parcel 对象 _data
                android.os.Parcel _data = android.os.Parcel.obtain();
                // 创建输出型 Parcel 对象 _reply
                android.os.Parcel _reply = android.os.Parcel.obtain();
                // 创建返回值 _result
                int _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(a);
                    _data.writeInt(b);

                    //调用 transact 方法发起 RPC(远程过程调用)请求,同时当前线程挂起,然后服务端的 onTransact 方法会被调用,
                    // 直到 RPC 过程返回后,当前线程继续执行,并从 _reply 中取出 RPC 过程的返回结果
                    mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                }
                finally {
                    // 释放资源
                    _reply.recycle();
                    _data.recycle();
                }

                // 返回结果
                return _result;
            }

            /**
             * 同上,只是该方法声明中没有返回值,所以最后没有结果返回。
             */
            @Override
            public int subtract(int a, int b) throws android.os.RemoteException
            {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(a);
                    _data.writeInt(b);
                    mRemote.transact(Stub.TRANSACTION_subtract, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                }
                finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        // 声明两个整型的 id 分别用于标识我们声明的那两个方法。作用是用于标识在 transact 过程中客户端所请求的到底是哪个方法
        static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_subtract = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    // 声明方法。这两个是我们在服务端要实现的方法。
    public int add(int a, int b) throws android.os.RemoteException;
    public int subtract(int a, int b) throws android.os.RemoteException;
}

    Above is the inter-process communication AIDL complete and corresponding source code for analysis, we also need the following additional note two points:

    1. When the client initiates the remote request, the current thread will be suspended until the server processes to return data, so if a remote method is very time-consuming, it can not initiate this request in a remote UI thread.

    Binder 2. The method of operation of the server thread pool in Binder, Binder so regardless of whether the time-consuming method should be adopted to achieve a synchronized manner, because it is already running in a thread of.

Binder brother just working mechanism provided is as follows:

    In summary we can see, AIDL document is not a necessity to achieve Binder, if you do not want to use AIDL completed Binder mechanisms of inter-process communication, you can refer to the above AIDL automatically generated class that Binder own custom class to a Binder.

    Next, look at Binder in two very important ways: linkToDeath () and unlinkToDeath () method. We know, Binder running on the server process, if the server process terminates abnormally for some reason, this time we connect to the service side of the fracture Binder (Binder called death), will lead to our remote call failed. More critical it is, if the client does not know Binder connection has been broken, then its function will be affected, which is why the above two methods arise. The following look at their prototypes:

// 这两个方法都是在 IBinder 接口中声明,然后在 Binder 类中实现的
public void linkToDeath(DeathRecipient recipient, int flags) throws RemoteException;

public boolean unlinkToDeath(DeathRecipient recipient, int flags);
public interface DeathRecipient {
    public void binderDied();
}

    When a client requests the server service, we can () to set up a Binder death by proxy linkToDeath, when Binder death, we will be notified, this time we can re-initiate the connection request to restore the connection. Here we look at how to use (or AIDL of the example above, only the client code to change it):

// ClientActivity.java
package com.cfm.clientwithaidltest;

public class ClientActivity extends AppCompatActivity {
    private ICalculator calc;

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            calc = ICalculator.Stub.asInterface(service);
            try {
                /* 2. 远程服务绑定成功后,为 Binder 设置死亡代理 */
                service.linkToDeath(mDeathRecipient, 0);

                // 向服务端请求服务
                int addResult = calc.add(322, 344);
                int subtractResult = calc.subtract(1000, 112);

                Log.d("cfmtest", "计算的加法结果: " + addResult + "当前进程名: " + getProcessName(ClientActivity.this));
                Log.d("cfmtest", "计算的减法结果: " + subtractResult + "当前进程名: " + getProcessName(ClientActivity.this));
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    /*
     * 1. 声明一个 DeathRecipient 对象。从上面可以看到 DeathRecipient 是一个接口,
     * 其内部只有一个 binderDied 方法,当服务端 Binder 死亡时,系统会回调 binderDied 方法,
     * 然后我们就可以通过 unlinkToDeath 方法移除之前绑定的 binder 代理并重新绑定远程服务。
     * */
    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            if(calc == null) return;
            calc.asBinder().unlinkToDeath(mDeathRecipient, 0);
            calc = null;

            // 重新绑定远程 Service
            Intent intent = new Intent();
            intent.setAction("com.cfm.serverwithaidltest.MYSERVICE");
            intent.setPackage("com.cfm.serverwithaidltest");
            bindService(intent, conn, Context.BIND_AUTO_CREATE);
        }
    };

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

        Button startServiceBtn = findViewById(R.id.start_btn);
        Button bindServiceBtn = findViewById(R.id.bind_btn);
        Button unBindServiceBtn = findViewById(R.id.unbind_btn);
        Button stopServiceBtn = findViewById(R.id.stop_btn);

        startServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setAction("com.cfm.serverwithaidltest.MYSERVICE");
                intent.setPackage("com.cfm.serverwithaidltest");
                startService(intent);
            }
        });

        bindServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setAction("com.cfm.serverwithaidltest.MYSERVICE");
                intent.setPackage("com.cfm.serverwithaidltest");
                bindService(intent, conn, Context.BIND_AUTO_CREATE);
            }
        });

        unBindServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                unbindService(conn);
            }
        });

        stopServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setAction("com.cfm.serverwithaidltest.MYSERVICE");
                intent.setPackage("com.cfm.serverwithaidltest");
                stopService(intent);
            }
        });
    }

    // 获取进程名方法
    public static String getProcessName(Context context) {
        int pid = android.os.Process.myPid();
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();

        if (runningApps == null) {
            return null;
        }

        for (ActivityManager.RunningAppProcessInfo procInfo : runningApps) {
            if (procInfo.pid == pid) {
                return procInfo.processName;
            }
        }
        return null;
    }
}

    Through the above code to achieve, we can give our Binder death proxy settings, when Binder death we can be notified, and then we re-initiate binding service request. In addition, the method by isBinderAlive Binder Binder can also determine whether the death.

 

Guess you like

Origin blog.csdn.net/yz_cfm/article/details/90300279