进程间通信,数据流传递(AIDL、Socket)

2020-05-18  本文已影响0人  沐左

进程间通信

Android 四大组件

Android 进程间通信可以通过Android 四大组件实现。

Activity

使用 Intent

Intent callIntent = new  Intent(Intent.ACTION_CALL, Uri.parse("tel:12345678" );  
startActivity(callIntent);

Content Provider

Content Provider可以跨进程访问其他应用程序中的数据(以Cursor对象形式返回),当然,也可以对其他应用程序的数据进行增、删、改操 作;

Content Provider返回的数据是二维表的形式

Broadcast

广播是一种被动跨进程通讯的方式。当某个程序向系统发送广播时,其他的应用程序只能被动地接收广播数据。

Service

普通的Service并不能实现跨进程操作,我们可以使用


AIDL Service

Android 接口定义语言(AIDL),我们可以利用AIDL定义多个应用都认可的编程接口,方便二者使用进程间通信(IPC)。

在我们定义 AIDL 接口之前,我们需要明确一些事情

1、AIDL 接口的调用是直接的函数调用,如果涉及线程的切换,需要在接口调用方进行处理

2、AIDL 接口的实现必须基于完全的线程安全,调用方要对并发的情况做好处理

谷歌文档

AIDL的具体实现

正式进行开发

1、服务端APP创建.aidl文件

在 src 目录下右键创建 AIDL 文件

// IMyAIDLService.aidl
package com.zuo.aidlservice;


interface IMyAIDLService {
   //获取展示的数据
   String getShowStr();
}

创建完成后build一下,会生成以 .aidl 文件命名的 .java 接口文件。
在 项目 build/generated/aidl_source_output_dir/[debug/release]/compile*Aidl/out/包名/ 下。

生成的接口包含一个名为 Stub 的子类(例如,YourInterface.Stub),该子类是其父接口的抽象实现,并且会声明 .aidl 文件中的所有方法。

补充说明

AIDL 支持的数据类型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5fLk1tIi-1589773353400)(_v_images/20200512120919440_1925.png)]

当我们在接口方法中使用这些类型的时候,需要为各自的类型加入一条 import 语句,才能使用。


2、服务端APP实现aidl文件定义的接口

我们定义一个 Binder 类用来继承 aidl 接口文件的 Stub 子类,或者用匿名内部类的方式实现

    class MyBinder extends IMyAIDLService.Stub {

        @Override
        public String getShowStr() throws RemoteException {
            //todo 实现服务端的逻辑
            return "来自服务端的问好";
        }
    }

现在,binder 是 Stub 类的一个实例(一个 Binder),其定义了服务的远程过程调用 (RPC) 接口。
在下一步中,我们会向客户端公开此实例,以便客户端能与服务进行交互(Binder机制)。

简单理解Binder机制的原理


3、服务端APP向客户端APP公开接口

我们定义一个服务类,实现 onBind() 方法来公开我们的服务,onBind() 方法中返回 IBinder 接口的实现类(继承自 aidl 接口文件的 Stub 子类)

服务类路径为java代码路径 ,而非 aidl 文件路径

/**
 * 向客户端公开 IMyAIDLService 接口
 *
 * @author zuo
 * @date 2020/5/12 14:55
 */
public class MyAIDLService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }

    class MyBinder extends IMyAIDLService.Stub {

        @Override
        public String getShowStr() throws RemoteException {
            //todo 实现服务端的逻辑
            return "来自服务端的问好";
        }
    }
}

现在当客户端APP中的组件(如 Activity)调用 bindService() 以连接此服务的时候,客户端APP的 onServiceConnected() 回调方法就会接收到服务端 onBind() 方法所返回的 binder 实例。

1、服务类需要在清单文件中注册

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

2、客户端必须拥有 IMyAIDLService 接口类的访问权限,才能调用上述服务
因此当客户端和服务不在同一个应用内时,客户端应用也必须包含.aidl 文件的副本。
(该文件会生成 android.os.Binder 接口,进而为客户端提供 AIDL 方法的访问权限)

3、**当客户端在 onServiceConnected() 回调中收到 IBinder 时,必须调用接口服务的asInterface方法,用来把返回的参数转换成 IMyAIDLService 类型,如

 iMyAIDLService= IMyAIDLService.Stub.asInterface(IBinder)

4、进程间传递对象

我们可以通过上述的 IPC 接口,在进程间传递实体对象,该实体对象需要支持 Parcelable 接口。

备注
如果需要创建 Parcelable 类的 .aidl 文件,请参考Rect.aidl 文件所示步骤

private final IRectInsideBundle.Stub binder = new IRectInsideBundle.Stub() {
    public void saveRect(Bundle bundle){
        bundle.setClassLoader(getClass().getClassLoader());
        Rect rect = bundle.getParcelable("rect");
        process(rect); // Do more with the parcelable.
    }
};

5、客户端调用IPC方法和服务端通信

客户端APP调用 aidl 接口实现和服务端APP的进程间通信。

我这里是直接将服务端APP的aidl 文件拷贝过来使用的

// IMyAIDLService.aidl
package com.zuo.aidlservice;


interface IMyAIDLService {
   //获取展示的数据
   String getShowStr();
}

同样是将服务端APP的文件拷贝过来使用,区别在于客户端只拷贝了 Binder 类,没有拷贝 Service 类。
** Binder 类必须要,没有则无法访问到服务端APP的 getShowStr() 方法。**

/**
 * IMyAIDLService 接口
 *
 * @author zuo
 * @date 2020/5/12 14:55
 */
public class MyBinder extends IMyAIDLService.Stub {

    @Override
    public String getShowStr() throws RemoteException {
        return "来自客户端的问好";
    }
}

在需要调用的地方,如 Activitty 实现ServiceConnection

    /**
     * 实现 ServiceConnection。
     */
    private ServiceConnection conn = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

这里需要注意,Android5.0以后绑定启动Service考虑到安全原因,不允许隐式意图的方式启动,也就是说要给出一个明确的组件Service。
intent.setPackage(String packageName)或者intent.setComponent(ComponentName componentName)都可以显示设置组件处理意图。

    /**
     * 绑定服务,设置绑定后自动开启服务
     *
     * @return
     */
    private void bindService() {
        Intent intent = new Intent();
        intent.setAction("com.zuo.aidlservice.MyAIDLService");
        //待使用远程Service所属应用的包名
        intent.setPackage("com.zuo.aidlservice");
        try {
            bindService(intent, conn, BIND_AUTO_CREATE);
            isBound = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 实现 ServiceConnection。
     */
    private ServiceConnection conn = new ServiceConnection() {

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

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

我们需要在调用方法的时候捕获 DeadObjectException 异常,该异常是系统在连接中断时抛出的。
我们还需要捕获 SecurityException 异常,这个异常是 IPC 方法调用中两个进程的 AIDL 定义发生冲突时,系统抛出的异常。

    /**
     * 实现 ServiceConnection。
     */
    private ServiceConnection conn = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IMyAIDLService iMyAIDLService = IMyAIDLService.Stub.asInterface(service);
            try {
                String showStr = iMyAIDLService.getShowStr();
                binding.text.setText(TextUtils.isEmpty(showStr) ? "返回错误!" : showStr);
            } catch (Exception e) {
                Log.i(TAG, "onServiceConnected: " + e.getMessage());
            }

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
    @Override
    protected void onPause() {
        super.onPause();
        //解绑服务
        if (isBound) {
            try {
                unbindService(conn);
                isBound = false;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

效果
先启动 AIDL_SERVICE APP ,然后启动 AIDL_Client APP,
点击AIDL_Client界面展示的 “Hello Worlf!”从AIDL_SERVICE获取展示内容

在这里插入图片描述

项目代码结构

12

LocalSocket & LocalServerSocket

LocalSocket

本地 socket 是在unix 域名空间创建一个套接字(非服务器)。

构造函数

可以创建的类型
SOCKET_DGRAM -- 数据报,数据报是通过网络传输的数据的基本单元,包含一个报头(header)和数据本身,类似于 UDP
SOCKET_STREAM -- 流,类似于 TCP
SOCKET_SEQPACKET -- 顺序数据包


公共方法

1、bind(LocalSocketAddress bindpoint)

绑定套接字到本地地址上,该方法只能调用一次,如果已绑定的套接字实例继续调用该方法会报IOException("already bound")异常。

我们可以通过 isBound()方法来判断当前实例是否已经绑定。

2、close()

关闭当前的套接字

3、connect()

连接套接字到本地地址上,该方法有两个重载方法 connect(LocalSocketAddress endpoint)connect(LocalSocketAddress endpoint, int timeout)
区别在于一个可以设置连接超时时间。

同样的,如果已经绑定的套接字实例继续调用该方法会报IOException("already connected")异常

我们可以通过 isConnected()方法来判断当前实例是否已经绑定。

另外,如果套接字处于无效状态或者连接的地址不存在。也会报IOException异常

4、getAncillaryFileDescriptors() 、setFileDescriptorsForSend(FileDescriptor[] fds)

set 方法,发送一组文件描述,将在普通数据下一次写入时发送,并以单个辅助信息的方式到达。
get方法,获取一组文件描述,通过辅助信息返回的一组文件描述,FileDescriptor[] 。

文件描述只能和常规数据一起传递,因此此方法只能在读取操作后返回非null。

5、getFileDescriptor()

返回文件描述符;如果尚未打开/已经关闭,则返回null

6、getInputStream()

返回套接字实例的输入流,InputStream

7、getOutputStream()

返回套接字实例的输出流,OutputStream

8、getLocalSocketAddress()

返回套接字绑定的地址,可能为 null 。LocalSocketAddress

9、getPeerCredentials()

返回套接字的证书,包含 pid 、uid 、gid 。已 root 的设备可能被篡改。

10、其他方法


相关概念

1、LocalSocketAddress

两个构造函数,LocalSocketAddress(String name)LocalSocketAddress(String name, Namespace namespace)

区别在于是否指定命名空间,不指定时默认为:ABSTRACT

可选择的命名空间类型
ABSTRACT -- Linux 中抽象的命名空间
RESERVED -- Android保留命名空间,位于/ dev / socket中。 只有init进程可以在此处创建套接字。
FILESYSTEM -- 以普通文件系统路径命名的套接字。

2、pid 、uid 、gid

Android中UID、GID和PID的讲解

Linux中的概念

除了UID和GID外,还包括其扩展的有效的用户、组(euid、egid)、文件系统的用户、组(fsuid、fsgid)和保存的设置用户、组(suid、sgid)等。

Android 中的概念

在Android中一个UID的对应的就是一个可执行的程序,对于普通的程序其UID就是对应与GID,程序在Android系统留存期间,其UID不变。
PID 同样是进程的 ID。

3、FileDescriptor

文件描述符,用来表示打开的文件、打开的套接字或者其他流。
主要用途是创建一个输入流或者输出流,FileInputStream or FileOutputStream。


LocalServerSocket

在Linux抽象命名空间中创建一个 在 UNIX域名 边界内的套接字

构造函数

    public LocalServerSocket(String name) throws IOException
    {
        impl = new LocalSocketImpl();

        impl.create(LocalSocket.SOCKET_STREAM);

        localAddress = new LocalSocketAddress(name);
        impl.bind(localAddress);

        impl.listen(LISTEN_BACKLOG);
    }
    public LocalServerSocket(FileDescriptor fd) throws IOException
    {
        impl = new LocalSocketImpl(fd);
        impl.listen(LISTEN_BACKLOG);
        localAddress = impl.getSockAddress();
    }

公共方法

1、accept()

接收一个新的socket连接,阻塞直到这个新的连接到达。

返回一个 新连接的套接字,LocalSocket。

2、close()

关闭服务器套接字

3、getFileDescriptor()

返回文件描述符;如果尚未打开/已经关闭,则返回null

4、getLocalSocketAddress()

获取套接字的本地地址


LocalSocket 使用示例

服务端APP

LocalServerSocket实现类

/**
 * 和客户端进行数据收发
 * <p>
 * 传递的数据为 二进制数组 byte[]
 *
 * @author zuo
 * @date 2020/5/14 15:08
 */
public class SocketServerImpl implements Runnable {
    private static final String TAG = "SocketServerImpl";
    private String localSocketAddress = "com.zuo.service";
    private BufferedOutputStream os;
    private BufferedInputStream is;
    public static final int bufferSizeOutput = 1024 * 1024;
    LocalServerSocket server;
    LocalSocket client;
    Handler handler;

    public SocketServerImpl(Handler handler) {
        this.handler = handler;
    }

    @Override
    public void run() {
        Log.i(TAG, "Server isOpen");
        try {
            if (null == server) {
                server = new LocalServerSocket(localSocketAddress);
            }
            if (null == client) {
                client = server.accept();
                Log.i(TAG, "Client Connected");
            }
            Credentials cre = client.getPeerCredentials();
            Log.i(TAG, "ClientID:" + cre.getUid());
            os = new BufferedOutputStream(client.getOutputStream(), bufferSizeOutput);
            is = new BufferedInputStream(client.getInputStream(), bufferSizeOutput);
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        while (null != is) {
            try {
                if (is.available() <= 0) continue;
                Message msg = handler.obtainMessage();
                msg.obj = is;
                msg.arg1 = 1;
                handler.sendMessage(msg);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 发送数据
     *
     * @param data
     */
    public void send(byte[] data) throws Exception {
        if (null != os) {
            os.write(data);
            os.flush();
        }
    }

    /**
     * 关闭监听
     */
    public void close() {
        try {
            if (null != os) {
                os.close();
                os = null;
            }
            if (null != is) {
                is.close();
                is = null;
            }
            if (null != client) {
                client.close();
                client = null;
            }
            if (null != server) {
                server.close();
                server = null;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

服务端活动界面

/**
 * @author zuo
 * @date 2020/5/18 11:01
 */
public class MainActivity extends AppCompatActivity {
    
    private SocketServerImpl socketServer;
    private ActivityMainBinding binding;
    private List<Integer> data;
    @IntRange(from = 0, to = 3)
    private int index = 0;

    //持续接收客户端反馈信息
    private StringBuilder buffer = new StringBuilder();
    Handler handler = new Handler(new Handler.Callback() {

        @Override
        public boolean handleMessage(Message msg) {
            if (msg.arg1 == 1) {
                SocketParseBean bean = null;
                try {
                    bean = SendDataUtils.parseSendData((BufferedInputStream) msg.obj);
                    if (null == bean || TextUtils.isEmpty(bean.getInfo())) return false;
                    showImg();
                } catch (Exception e) {
                    return false;
                }
                buffer.append(bean.getInfo());
                buffer.append("\r\n");
                showSocketMsg();
            }
            return false;
        }
    });

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setPresenter(new Presenter());
        initData();
        startSocketServer();
    }

    private void showSocketMsg() {
        if (null != binding) {
            binding.backMsgShow.setText("客户端消息:" + buffer.toString());
        }
    }

    private void startSocketServer() {
        socketServer = new SocketServerImpl(handler);
        new Thread(socketServer).start();
    }

    private void initData() {
        data = new ArrayList<>();
        data.add(R.drawable.kb890);
        data.add(R.drawable.kb618);
        data.add(R.drawable.kb224);
    }

    private void showImg() {
        Bitmap bmp = BitmapFactory.decodeResource(getResources(), data.get(index));
        binding.imgShow.setImageBitmap(bmp);
        binding.indexShow.setText((index + 1) + "/" + data.size());
        String hint = "服务端正在展示第 " + (index + 1) + " 张照片";
        sendData(hint, bmp);
    }

    public void sendData(final String hint, final Bitmap bmp) {
        if (null != socketServer) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] array = null;
            try {
                if (null != bmp) {
                    bmp.compress(Bitmap.CompressFormat.PNG, 100, baos);
                    array = baos.toByteArray();
                }
                byte[] bytes = SendDataUtils.makeSendData(hint, array);
                socketServer.send(bytes);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (null != socketServer) {
            socketServer.close();
        }
    }

    public class Presenter {

        public void last(View view) {
            if (index <= 0) {
                Toast.makeText(MainActivity.this, "没有上一张了!", Toast.LENGTH_SHORT).show();
                return;
            }
            index--;
            showImg();
        }

        public void next(View view) {
            if (index >= 2) {
                Toast.makeText(MainActivity.this, "没有下一张了!", Toast.LENGTH_SHORT).show();
                return;
            }
            index++;
            showImg();
        }
    }

}

客户端APP

LocalSocket实现类

/**
 * 和服务端进行数据收发
 *
 * @author zuo
 * @date 2020/5/14 15:08
 */
public class SocketClientImpl implements Runnable {
    private static final String TAG = "SocketClientImpl";
    private String localSocketAddress = "com.zuo.service";
    private BufferedOutputStream os;
    private BufferedInputStream is;
    private int timeout = 30000;
    public static final int bufferSizeOutput = 1024 * 1024;
    private LocalSocket client;
    private Handler handler;

    public SocketClientImpl(Handler handler) {
        this.handler = handler;
    }

    @Override
    public void run() {
        Log.i(TAG, "Client isOpen");
        try {
            if (null == client) {
                client = new LocalSocket();
                client.connect(new LocalSocketAddress(localSocketAddress));
                client.setSoTimeout(timeout);
                Log.i(TAG, "Server Connected");
            }
            os = new BufferedOutputStream(client.getOutputStream(), bufferSizeOutput);
            is = new BufferedInputStream(client.getInputStream(), bufferSizeOutput);
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        //将接收到的数据发送出去
        while (null != is) {
            try {
                if (is.available() <= 0) continue;
                Message msg = handler.obtainMessage();
                msg.obj = is;
                msg.arg1 = 1;
                handler.sendMessage(msg);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 发送数据
     *
     * @param data
     */
    public void send(byte[] data) throws Exception {
        if (null != os) {
            os.write(data);
            os.flush();
        }
    }

    /**
     * 关闭监听
     */
    public void close() {
        try {
            if (null != os) {
                os.close();
                os = null;
            }
            if (null != is) {
                is.close();
                is = null;
            }
            if (null != client) {
                client.close();
                client = null;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

客户端活动界面

/**
 * @author zuo
 * @date 2020/5/18 11:29
 */
public class MainActivity extends AppCompatActivity {

    private SocketClientImpl socketClient;
    private ActivityMainBinding binding;

    //持续接收服务端反馈信息
    private StringBuilder buffer = new StringBuilder();
    Handler handler = new Handler(new Handler.Callback() {

        @Override
        public boolean handleMessage(Message msg) {
            if (msg.arg1 == 1) {
                SocketParseBean bean = null;
                try {
                    bean = SendDataUtils.parseSendData((BufferedInputStream) msg.obj);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (null == bean || TextUtils.isEmpty(bean.getInfo())) return false;
                buffer.append(bean.getInfo());
                buffer.append("\r\n");
                showSocketMsg(bean.getData());
            }
            return true;
        }
    });

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setPresenter(new Presenter());
        startSocketClient();
    }

    private void showSocketMsg(final byte[] data) {
        if (null != binding) {
            binding.backMsgShow.setText(buffer.toString());
        }
        showImg(data);
    }

    private void startSocketClient() {
        socketClient = new SocketClientImpl(handler);
        new Thread(socketClient).start();
    }

    private void showImg(byte[] data) {
        if (null == data) return;
        Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
        binding.imgShow.setImageBitmap(bitmap);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (null != socketClient) {
            socketClient.close();
        }
    }

    public void sendData2Server(final String hint, final Bitmap bmp) throws Exception {
        if (null != socketClient) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] array = null;
            if (null != bmp) {
                bmp.compress(Bitmap.CompressFormat.PNG, 100, baos);
                array = baos.toByteArray();
            }
            byte[] bytes = SendDataUtils.makeSendData(hint, array);
            socketClient.send(bytes);
        }
    }

    public class Presenter {

        public void sendData(View view) {
            String text = binding.clientInput.getText().toString().trim();
            if (TextUtils.isEmpty(text)) {
                Toast.makeText(MainActivity.this, "消息内容不能为空!", Toast.LENGTH_SHORT).show();
                return;
            }
            try {
                sendData2Server(text, null);
            } catch (Exception e) {
                Toast.makeText(MainActivity.this, "消息发送失败!", Toast.LENGTH_SHORT).show();
            }
        }

    }
}

共用工具类

封装、解析流数据

/**
 * LocalSocket 传输数据(封装、解析)工具类
 * <p>
 * 数据传输规则:
 * [0,7)  -- infoSize
 * [7,14) -- dataSize
 * [14,14+infoSize) -- info
 * [14+infoSize,14+infoSize+dataSize)  -- data
 *
 * @author zuo
 * @date 2020/5/14 19:20
 */
public class SendDataUtils {
    /**
     * 对应数据的 size ,7 位 (9.5M)
     */
    private static final int infoSize = 7;
    private static final int dataSize = 7;

    /**
     * 封装 LocalSocket 发送的数据
     *
     * @param info -- 需要发送的字符串数据
     * @param data -- 需要发送的字节流数据
     * @return 封装后的字节流数据
     */
    public static byte[] makeSendData(@NonNull String info, byte[] data) throws Exception {
        //文本信息
        Charset charset_utf8 = Charset.forName("utf-8");
        ByteBuffer buff = charset_utf8.encode(info);
        byte[] infoBytes = buff.array();
        int infoLength = infoBytes.length;
        byte[] headSizeBytes = String.valueOf(infoLength).getBytes();
        int dataLength = data == null ? 0 : data.length;
        byte[] dataSizeBytes = String.valueOf(dataLength).getBytes();
        int totalSize = infoSize + dataSize + infoLength + dataLength;
        byte[] output = new byte[totalSize];
        //1、头部信息(info size)
        System.arraycopy(headSizeBytes, 0, output, 0, headSizeBytes.length);
        //2、头部信息(data size)
        System.arraycopy(dataSizeBytes, 0, output, infoSize, dataSizeBytes.length);
        //2、info 信息
        System.arraycopy(infoBytes, 0, output, infoSize + dataSize, infoLength);
        if (dataLength > 0) {
            //拷贝 data 信息
            System.arraycopy(data, 0, output, infoSize + dataSize + infoLength, dataLength);
        }
        return output;
    }

    /**
     * 解析 LocalSocket 接收到的数据
     *
     * @param is -- 待解析的输入流
     * @return 解析后的数据
     * @throws Exception
     */
    public static SocketParseBean parseSendData(BufferedInputStream is) throws Exception {
        if (null == is || is.available() <= 0) return null;
        //拿到info信息的size
        byte[] infoSizeByte = new byte[infoSize];
        is.read(infoSizeByte);
        String infoLength = new String(infoSizeByte);
        String infoSizeStr = infoLength.trim();
        Integer infoSize = Integer.valueOf(infoSizeStr);
        //拿到data的size
        byte[] dataSizeByte = new byte[dataSize];
        is.read(dataSizeByte);
        String dataLength = new String(dataSizeByte);
        String dataSizeStr = dataLength.trim();
        Integer dataSize = Integer.valueOf(dataSizeStr);
        //数据读取
        SocketParseBean parseBean = new SocketParseBean();
        if (infoSize <= 0 && dataSize <= 0) {
            return parseBean;
        }
        //读取info
        byte[] infoByte = new byte[infoSize];
        is.read(infoByte, 0, infoSize);
        String s = new String(infoByte, "utf-8");
        parseBean.setInfo(s.trim());
        //读取data
        if (dataSize > 0) {
            byte[] buffer = new byte[dataSize];
            is.read(buffer, 0, dataSize);
            parseBean.setData(buffer);
        }
        return parseBean;
    }

}

解析数据封装实体

/**
 * 解析socket服务传递的数据
 *
 * @author zuo
 * @date 2020/5/15 17:03
 */
public class SocketParseBean {

    private String info;
    private byte[] data;

    public SocketParseBean() {
    }

    public SocketParseBean(String info, byte[] data) {
        this.info = info;
        this.data = data;
    }

    public String getInfo() {
        return info;
    }

    public void setInfo(String info) {
        this.info = info;
    }

    public byte[] getData() {
        return data;
    }

    public void setData(byte[] data) {
        this.data = data;
    }
}

项目结构

在这里插入图片描述

LocalSocket交互效果展示

在这里插入图片描述

使用Socket

LocalSocket 在某些设备上出现 权限拒绝等错误,将上述demo中的 LocalSocket 替换为 Socket

SocketClientImpl

替换后的代码

            if (null == client) {
//                client = new LocalSocket();
                client = new Socket("localhost", 8080);
//                client.connect(new LocalSocketAddress(localSocketAddress));
                client.setSoTimeout(timeout);
                Log.i(TAG, "Server Connected");
            }

SocketServerImpl

替换后的代码

            if (null == server) {
//                server = new LocalServerSocket(localSocketAddress);
                server = new ServerSocket(8080);
            }

流里面取每一帧的策略

//25 Kb 的缓冲区
int bufferSizeOutput = 1024 * 25;
os = new BufferedOutputStream(client.getOutputStream(), bufferSizeOutput);
is = new BufferedInputStream(client.getInputStream(), bufferSizeOutput);
//yuv data的长度 = 视频帧width*height*1.5
int srcWidth = 480, srcHeight = 320;
int totalSize = srcWidth * srcHeight * 3 / 2;
int tmpSize = 0;
byte[] buffer = new byte[bufferSizeOutput];
while (client.isConnected()) {
    if (is.read() == 0xA0) {
        ByteArrayOutputStream tempStream = new ByteArrayOutputStream();
        while (tmpSize < totalSize) {
            int len = is.read(buffer);
            tmpSize += len;
            tempStream.write(buffer, 0, len);
        }
        Frame frame = new Frame(tempStream.toByteArray(), srcWidth, srcHeight);
        LiveStreamRepository.getInstance().addFrame(frame);
        tmpSize = 0;
        tempStream.close();
        Log.e(TAG, "receive " + frame.toString());
    }
}

使用 DatagramSocket

使用数据报套接字实现进程间通信,客户端和服务端应用各自监听自己的端口。实现类SocketTextImpl

客户端和服务端使用同一个类,区别在于监听和发送数据包的端口不同

/**
 * 采用数据包的方式发送文本类型的数据
 * 本实例区别于 SocketClientImpl ,仅用作于文本信息的传递,采用 UDP 协议
 *
 * @author zuo
 * @date 2020/5/14 15:08
 */
public class SocketTextImpl implements Runnable {
    private static final String TAG = "SocketClientTextImpl";
    public static final int bufferSize = 1024 * 1024;
    private DatagramSocket socket;
    private final int SERVER_PORT = 8090;
    private final int CLIENT_PORT = 8091;

    public SocketTextImpl() {
    }

    @Override
    public void run() {
        Log.i(TAG, "Client isOpen");
        try {
            if (null == socket) {
                //监听对应端口
                socket = new DatagramSocket(SERVER_PORT, InetAddress.getLocalHost());
            }
        } catch (IOException e1) {
            Log.i(TAG, e1.getMessage());
            e1.printStackTrace();
        }
        //接收信息
        while (true) {
            byte[] buffer = new byte[bufferSize];
            DatagramPacket recDp = new DatagramPacket(buffer, buffer.length);
            try {
                //定义1M的文本消息缓存,如果消息大于1M,会被截断
                socket.receive(recDp);
                String recMsg = new String(buffer, 0, recDp.getLength());
                LiveStreamRepository.getInstance().addData(recMsg);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 发送给客户端的数据,使用客户端监听的端口
     *
     * @param data
     */
    public void send(String data) throws Exception {
        if (null != socket) {
            byte[] bytes = data.getBytes();
            DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), CLIENT_PORT);
            socket.send(packet);
        }
    }

    /**
     * 关闭监听
     */
    public void close() {
        try {
            if (null != socket) {
                socket.close();
                socket = null;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

其他

可用端口范围

一个有效的端口整数值:0 --65535

在Socket使用时,可以用1024~65535的端口号


辅助类,数据存储队列

/**
 * 无人机互联,数据存储队列
 *
 * @author zuo
 * @date 2020/5/19 14:13
 */
public class LiveStreamRepository {
    //队列,可存储20帧数据
    private int mQueueSize = 10;
    private int mBufferSize = 5;

    private ArrayBlockingQueue<String> mQueue = new ArrayBlockingQueue<>(mQueueSize);


    private LiveStreamRepository() {
    }

    private final static class UavVideoInfoInstanceHolder {
        private static final LiveStreamRepository ins = new LiveStreamRepository();
    }

    public static LiveStreamRepository getInstance() {
        return UavVideoInfoInstanceHolder.ins;
    }

    public String getData() {
        return mQueue.poll();
    }

    public boolean addData(String data) {
        //如果插入失败(),移除前5帧
        if (mQueue.size() == mQueueSize) {
            for (int i = 0; i < mBufferSize; i++) {
                mQueue.remove(i);
            }
        }
        return mQueue.offer(data);
    }
}
上一篇 下一篇

猜你喜欢

热点阅读