AIDL 客户端与服务端的双向通信

简介: 时隔一年半了,终于写下了这个续篇,我发现我的很多博客有头无尾,都是有前面一点点,后面就没写去了,也正在想办法都补上初涉IPC,了解AIDL的工作原理及使用方法今天聊聊的是客户端和服务端的相互通信,何谓双向通信,事实上,我们在上一篇的博客中,只是讲解了客户端请求服务端的...

时隔一年半了,终于写下了这个续篇,我发现我的很多博客有头无尾,都是有前面一点点,后面就没写去了,也正在想办法都补上

今天聊聊的是客户端和服务端的相互通信,何谓双向通信,事实上,我们在上一篇的博客中,只是讲解了客户端请求服务端的方法,然后服务端返回一个值给我们这样,其实是最简单的用法,但是常常在我们的开发过程中,如果调用了某些方法,比如网络请求,那么就需要等待请求有结果了之后再回调给我们,这个回调的过程就是服务端向客户端通信,作为ipc通信的一种,如果你不会双向通信,那么你可以比较low的用广播,但是我还是建议你直接用一整套的AIDL复用,好的,那么问题来了,我们怎么下手呢?

服务端

我们新建两个工程,一个叫ADILClient,一个叫AIDLService,分别代表的是客户端和服务端

这里写图片描述

我们现在开始编写我们的aidl,这里我需要编写两个AIDL文件,一个是我们对外的方法,一个是我们对外的回调方法,如图

这里写图片描述

这里我做一下讲解,首先我new了一个aidl的文件夹,在main下,和java同级,然后定义了一个公共的包名:com.android.openimpl,最后在里面实现了两个aidl文件,我们来看下具体的文件内容

IMyLifeStyleInterface

// IMyLifeStyleInterface.aidl
package com.android.openimpl;

import com.android.openimpl.IMyLifeStyleListener;

interface IMyLifeStyleInterface {

    //计算
    void sum(int a ,int b);
    //睡觉
    void sleep(boolean isSleep);

    //注册
    void registerCallback(IMyLifeStyleListener il);
    //解绑
    void unregisterCallback(IMyLifeStyleListener il);

}

IMyLifeStyleListener

// IMyLifeStyleListener.aidl
package com.android.openimpl;

//回调接口
interface IMyLifeStyleListener {

   //回调方法
   void OnCallBackSleep(String text);
   void OnCallBackSize(int size);
}

这里我定义了IMyLifeStyleInterface ,里面有两个方法,假设我定义的事一个人,他有基本的两个本能,一个是计算,我传两个数字给他,他进行一系列的处理,那么,问题来, 我不想用返回值,我想通过回调知道,这是一点,另一个是睡觉,而在IMyLifeStyleListener,我也定义了两个对应的回调OnCallBackSleep和OnCallBackSize,好了,现在开始来实现我们的远程Service服务

public class OpenImplService extends Service {

    private IMyLifeStyleListener lifeStyleListener;

    private IBinder mBinder = new IMyLifeStyleInterface.Stub() {
        @Override
        public void sum(int a, int b) throws RemoteException {
            //经过一系列的计算后将值告知客户端
            int c = a * 2 + b;
            if (lifeStyleListener != null) {
                lifeStyleListener.OnCallBackSize(c);
            }
        }

        @Override
        public void sleep(boolean isSleep) throws RemoteException {
            //告诉客户端我已经睡着
            if(isSleep){
                if (lifeStyleListener != null) {
                    lifeStyleListener.OnCallBackSleep("帮我关下灯,谢谢!");
                }
            }
        }

        @Override
        public void registerCallback(IMyLifeStyleListener il) throws RemoteException {
            if (il != null) {
                lifeStyleListener = il;
            }
        }

        @Override
        public void unregisterCallback(IMyLifeStyleListener il) throws RemoteException {
            if (il != null) {
                lifeStyleListener = null;
            }
        }
    };

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

这里,我定义了一个远程的服务OpenImplService,里面我只是new了IMyLifeStyleInterface.Stub并且把Binder对象给了onBind方法,而在Binder内部,我做的操作相信大家都看的明白吧,很简单,Ok,那我们的服务端就已经搞定了,我们来看下服务端的整体结构

这里写图片描述

当然,别忘了在清单文件中注册

       <service
            android:name=".service.OpenImplService"
            android:enabled="true"
            android:exported="true" />

客户端

好的,现在就开始来实现我们的客户端,客户端也需要同样的AIDL文件,所以我可以直接复制过去,但是要注意的是包名一定要相同,如图

这里写图片描述

这里,我的客户端就是app包下的东西,那么我们来实现UI上的逻辑

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="10dp">

    <EditText
        android:id="@+id/et_numer_1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入a" />

    <EditText
        android:id="@+id/et_numer_2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入b" />

    <Button
        android:id="@+id/btn_sum"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="计算"
        android:textAllCaps="false" />

    <Button
        android:id="@+id/btn_sleep"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="睡觉"
        android:textAllCaps="false" />

</LinearLayout>

这里,我定义了两个输入框和有个按钮,对应的计算和睡觉,好的,我们run一下

这里写图片描述

我们来开始实现具体的逻辑了,这也是我们客户端经常要干的事情首先,我们initService来初始化了服务,在android5.0之后绑定远程服务都需要完整的包名了,如下

    private void initService() {
        Intent i = new Intent();
        //Android 5.0 之后需要直接定义包名
        i.setComponent(new ComponentName("com.liuguilin.aidlservice", "com.liuguilin.aidlservice.service.OpenImplService"));
        bindService(i, mConnection, Context.BIND_AUTO_CREATE);
    }

这里需要传一个mConnection,这是一个对应的关系,如果现在bind,你销毁的时候需要unBind

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mConnection);
    }

好的,我们具体来看下mConnection的内容吧

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.i(TAG, "onServiceConnected");
            //获取AIDL对象
            iMyLifeStyleInterface = IMyLifeStyleInterface.Stub.asInterface(iBinder);
            //注册接口
            try {
                iMyLifeStyleInterface.registerCallback(new OpenImpleListener());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            isBind = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.i(TAG, "onServiceDisconnected");
            try {
                iMyLifeStyleInterface.unregisterCallback(new OpenImpleListener());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            iMyLifeStyleInterface = null;
            isBind = false;
        }
    };

他一共会重写两个方法,服务绑定onServiceConnected和服务解绑onServiceDisconnected,所以这里我们用了一个标志位isBind来标记绑定状态,然后通过AIDL的asInterface方法来实例化对应的AIDL对象,然后就是注册这个接口回调了

    class OpenImpleListener extends IMyLifeStyleListener.Stub {

        @Override
        public void OnCallBackSleep(String text) throws RemoteException {
            Toast.makeText(MainActivity.this, "text:" + text, Toast.LENGTH_SHORT).show();
        }

        @Override
        public void OnCallBackSize(int size) throws RemoteException {
            Toast.makeText(MainActivity.this, "size:" + size, Toast.LENGTH_SHORT).show();
        }
    }

这里要注意的事需要继承的是AIDL的Stub,最后就是我们的点击事件了

    @Override
    public void onClick(View v) {
        if (isBind) {
            switch (v.getId()) {
                case R.id.btn_sum:
                    String a = et_numer_1.getText().toString().trim();
                    String b = et_numer_2.getText().toString().trim();
                    try {
                        iMyLifeStyleInterface.sum(Integer.parseInt(a),Integer.parseInt(b));
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                case R.id.btn_sleep:
                    try {
                        iMyLifeStyleInterface.sleep(true);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
            }
        } else {
            Toast.makeText(this, "服务未连接", Toast.LENGTH_SHORT).show();
        }
    }

这里算是比较简单的了,传值调用即可,好的,下面贴上全部的代码

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = "AIDL";

    private EditText et_numer_1;
    private EditText et_numer_2;
    private Button btn_sum;
    private Button btn_sleep;

    //绑定状态
    private static boolean isBind = false;

    private IMyLifeStyleInterface iMyLifeStyleInterface;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.i(TAG, "onServiceConnected");
            //获取AIDL对象
            iMyLifeStyleInterface = IMyLifeStyleInterface.Stub.asInterface(iBinder);
            //注册接口
            try {
                iMyLifeStyleInterface.registerCallback(new OpenImpleListener());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            isBind = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.i(TAG, "onServiceDisconnected");
            try {
                iMyLifeStyleInterface.unregisterCallback(new OpenImpleListener());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            iMyLifeStyleInterface = null;
            isBind = false;
        }
    };

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

        initService();
        initView();
    }

    private void initService() {
        Intent i = new Intent();
        //Android 5.0 之后需要直接定义包名
        i.setComponent(new ComponentName("com.liuguilin.aidlservice", "com.liuguilin.aidlservice.service.OpenImplService"));
        bindService(i, mConnection, Context.BIND_AUTO_CREATE);
    }

    private void initView() {
        et_numer_1 = (EditText) findViewById(R.id.et_numer_1);
        et_numer_2 = (EditText) findViewById(R.id.et_numer_2);
        btn_sum = (Button) findViewById(R.id.btn_sum);
        btn_sleep = (Button) findViewById(R.id.btn_sleep);

        btn_sum.setOnClickListener(this);
        btn_sleep.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (isBind) {
            switch (v.getId()) {
                case R.id.btn_sum:
                    String a = et_numer_1.getText().toString().trim();
                    String b = et_numer_2.getText().toString().trim();
                    try {
                        iMyLifeStyleInterface.sum(Integer.parseInt(a),Integer.parseInt(b));
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                case R.id.btn_sleep:
                    try {
                        iMyLifeStyleInterface.sleep(true);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
            }
        } else {
            Toast.makeText(this, "服务未连接", Toast.LENGTH_SHORT).show();
        }
    }

    class OpenImpleListener extends IMyLifeStyleListener.Stub {

        @Override
        public void OnCallBackSleep(String text) throws RemoteException {
            Toast.makeText(MainActivity.this, "text:" + text, Toast.LENGTH_SHORT).show();
        }

        @Override
        public void OnCallBackSize(int size) throws RemoteException {
            Toast.makeText(MainActivity.this, "size:" + size, Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mConnection);
    }
}

可以发现,代码很是简洁,希望大家能够学会,最后我们运行一遍

这里写图片描述

这里可以看到,点击睡觉,服务端会叫我们关下灯,而计算的话,我传的是2和3,服务端计算了一下 2 * 2 + 3 = 7 并且返回回来了。

Demo下载:点击下载

我的公众号,期待你的关注

weixin

目录
相关文章
|
14天前
|
网络协议 算法 Linux
服务端如何准备连接
【4月更文挑战第1天】服务端创建套接字涉及`socket()`函数。
|
3月前
|
Java Maven
【Netty 网络通信】启动通信服务端
【1月更文挑战第9天】【Netty 网络通信】启动通信服务端
|
3月前
|
前端开发 Java Maven
【Netty 网络通信】启动客户端连接服务端实现通信
【1月更文挑战第9天】【Netty 网络通信】启动客户端连接服务端实现通信
|
4月前
|
网络协议 安全 Python
socket客户端和服务端,文件的传输
socket 实现,客户端和服务端,文件的传输
41 1
|
4月前
|
网络协议 Cloud Native
为什么服务端会有那么多的 TimeWait ?
为什么服务端会有那么多的 TimeWait ?
|
6月前
|
NoSQL 数据建模 API
UCX-UCT统一通信传输层3-服务端和客户端调用栈详解(及相关)_源码解读
主流程(服务端或客户端): 1. 主函数中解析命令行参数(parse_cmd), 设置默认服务端口 2. 初始化上下文(ucs_async_context_create, 异步事件上下文用于管理定时器和FD通知), 在其中, 初始化多生产者/多消费者队列(ucs_mpmc_queue_init), 初始化非阻塞异步轮询器(ucs_async_poll_init), 初始化可重入自旋锁上下文等 3. 创建工人(uct_worker_create), 工人代表着 progress 的引擎。 可以在应用程序中创建多个进度引擎,例如供多个线程使用 4. 根据入参查找期望的传输层(dev_tl_loo
170 1
|
6月前
|
Windows
WCF服务端调用客户端.
WCF服务端调用客户端.
|
7月前
使用WebSocket实现服务端和客户端的通信
使用WebSocket实现服务端和客户端的通信
60 0
|
运维 Java 数据库连接
排除法,先找客户端问题,再找服务端问题
先找客户端问题,再找服务端问题
55 0
|
自然语言处理 前端开发 大数据
小黄鸡的客户端和服务器端通讯 | 学习笔记
快速学习小黄鸡的客户端和服务器端通讯
130 0