Android双屏驱动Service架构实现

做程序员苦逼的地方就在于,当公司决定做什么的时候,是不会跟你商量的,只会跟你说,xxx,这个可能需要你来实现一下。fuck,想好实现思路了吗?(这是我司的程序员提出,我们来做整理完善的)

Android双屏显示,可能会和别的双屏机制不同,大多数情况下是一个android系统,分主副屏而已。我司的硬件是两个android系统,两个屏幕,内部通过一根usb直连(这根usb连接线很稳定,代工厂和我们讲的,坑~)。双屏运行两个独立的android系统,相互通过一条底层的通道传输数据,主屏通常可以运行业务软件。副屏可以显示宣传图片,视频,购物清单等信息,但不限于这些,实际上副屏也是个可以触摸交互的系统。

在这样的硬件前提下,我们开发需要实现这两个屏幕的通信,涉及到usb的驱动开发(由代工厂搞定),我们只需要调用jni的一些方法即可。上层应用之间的通信,类似于广播,主副屏可以相互发送接收,提供公共的api,可供其他的app调用,使之能实现自己的业务逻辑。

Android双屏驱动Service架构实现_第1张图片

本片文章主要讲底层的Service的实现(也就是驱动的上一层)

思路

双屏通信,主屏会要求副屏显示一些文字(命令),图片(发送文件+命令),图文混合,甚至会发送一些音频,视频等大文件,几百M到几G不等。因为是双向通信,主屏发送指令过后,需要等待副屏的回调。通常如果是命令或者是小文件,毫秒级别内就能被处理掉。但如果是几个G的大文件呢?时间就被延时了,如果这个时候再有命令发送过来了,就会等待(需要维护一个任务列表),这样肯定是不好的。所以我们切割文件,将文件分包,一个一个的发送,最后拼装还原,这样即使中途了命令或者小文件,也能立马被处理掉。

我们有一个任务队列,service不断的去任务队列去轮询,取到任务,根据Task信息,区分是任务类别,做相应的处理。如果是文件的话,我们进行分包处理,这里,我暂定义的最大单个文件包为512kb,然后发送。副屏接收,拼装,还原(每个包有相应的头信息),在给主屏反馈结果,主屏做相应处理。

大致的一个流程图:

Android双屏驱动Service架构实现_第2张图片

协议与机制

其实在整个流程中,我们主要要区分任务的来源,以及之后要反馈的源头,分包与还原包不能错乱,不然会产生脏数据。

那我们定义一下任务的类型:FileTask(文件任务),MemoryTask(内存任务,字符串之类的),ControlTask(控制任务)。对不同的任务类型处理是不同的,有的直接是内存传输,有的是写本地文件。

之前在usb通道还没有连通的时候,我们是用UDP协议来写demo实现的,在usb通来以后,直接改用就可以来。所以说,即使没有这个usb通道,你也可以用udp连接来测试两个手机直接的传输,只是这个速度就依赖于网络了。

具体实现

我们有一个底层service,CoreService,所有的发送、接收,回调都是靠它来实现的。那我们就具体围绕它来展开。

    @Override
    public void onCreate() {
        mSerialPort = new SerialPort();
        connectRunable = new Connect();
        new Thread(connectRunable).start();
    }

SerialPort类里面是一些native的方法,通过jni来调用底层的usb驱动的,这个就略过来,各家的都是不一样的。我们只要知道它有读写的方法即可。

    public native int read(int fd, byte[] data, int offset, int len);
    public native int write(int fd, byte[] data, int offset, int len);

Connect类是连接操作,副屏向主屏发起连接的请求。

class Connect implements Runnable {

        @Override
        public void run() {
            id = 0;
            // 在建立连接之前,断开之前所有的任务
            if (mapSend != null) {
                for (Integer i : mapSend.keySet()) {
                    for (SendTask sendTask : mapSend.get(i).tasksFile) {
                        try {
                            sendTask.setTaskState(TaskState.error_sendData);
                            sendTask.getCallback().sendError(sendTask.getID(), sendTask.getTaskState().get_code(),
                                    sendTask.getTaskState().get_message());
                        } catch (Exception e) {
                        }
                    }
                    for (SendTask sendTask : mapSend.get(i).tasksMemory) {
                        try {
                            sendTask.setTaskState(TaskState.error_sendData);
                            sendTask.getCallback().sendError(sendTask.getID(), sendTask.getTaskState().get_code(),
                                    sendTask.getTaskState().get_message());
                        } catch (Exception e) {
                        }
                    }
                }
            }

            // 重新初始化参数
            mapSend = new ConcurrentHashMap();
            mapRecv = new ConcurrentHashMap();
            controlQueue = new ConcurrentLinkedQueue();
            finshAndWait = new HashMap();
            errorSendTaskID = new HashSet();
            errorRecvTaskID = new HashSet();
            // 对象锁
            lock = new Object();
            // 线程控制锁
            controlThreadLock = new Object();
            controlProcess = new SendProcess(errorSendTaskID, controlThread, finshAndWait);
            mapSend.put(-1, controlProcess);
            controlThread = new ControlThread(controlQueue, controlProcess, lock, controlThreadLock, finshAndWait,
                    errorSendTaskID);
            controlProcess.setControlThread(controlThread);
            controlThread.start();

            while (true) {
                // usb端口打开
                if (mSerialPort.tryOpen(isMain)) {
                    // isMain true:表示主屏 false:表示副屏
                    if (isMain) {
                        byte[] temp = new byte[1];
                        boolean flag = false;
                        do {
                            // 读取建立请求连接的数据 -1
                            if (mSerialPort.read(mSerialPort.mFd, temp, 0, 1) <= 0) {
                                flag = true;
                                break;
                            } else if (temp[0] != -1 && temp[0] != -2) {
                                flag = true;
                                break;
                            } else if (temp[0] == -2) {
                                flag = false;
                                break;
                            }
                            // 向副屏发送建立请求连接的数据 -1
                            if (mSerialPort.write(mSerialPort.mFd, temp, 0, 1) <= 0) {
                                flag = true;
                                break;
                            }
                        } while (temp[0] == -1);
                        if (flag) {
                            continue;
                        }
                    } else {
                        // 向主屏发送建立请求连接的数据 -1
                        sendsyn = new sendSYN();
                        sendsynThread = new Thread(sendsyn);
                        sendsynThread.start();
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        byte[] temp = new byte[1];
                        mSerialPort.clear(mSerialPort.mFd);
                        if (mSerialPort.read(mSerialPort.mFd, temp, 0, 1) <= 0) {
                            sendsyn.gh = false;
                            continue;
                        } else if (temp[0] != -1) {
                            sendsyn.gh = false;
                            continue;
                        }
                        sendsyn.gh = false;
                        try {
                            sendsynThread.join();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        temp[0] = -2;
                        // 读取建立请求连接的数据 -1
                        if (mSerialPort.write(mSerialPort.mFd, temp, 0, 1) <= 0) {
                            sendsyn.gh = false;
                            continue;
                        }
                    }
                    // 发送处理
                    transferSend = new TransferSend(lock, mapSend, mSerialPort, handler);
                    // 接收处理
                    transferRecv = new TransferRecv(mapRecv, mSerialPort, controlThread, errorRecvTaskID, handler);
                    transferRecv.start();
                    transferSend.start();
                    // 发送handler表示连接成功
                    handler.sendMessage(handler.obtainMessage(-2));
                    break;
                } else {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

            isConnecting = false;
        }

    }

代码有点多,来分析一下初始化的整个连接过程。

    // 发送task的map列表
    private ConcurrentHashMap mapSend;
    // 接收task的map列表
    private ConcurrentHashMap mapRecv;

1.首先,在建立连接之前,断开之前所有的任务,Task的TaskState.error_sendData就是表示usb数据通道断开,取消所有发送接收的任务。

2.初始化参数。包括发送,接收的列表,锁对象,任务id等,ControlThread类是一个线程控制类,来处理task的。SendProcess类是发送task的包装类,把它丢到ControlThread类去处理,然后开启Thread,让它不停的去轮询任务队列。

3.开始建立发送与接收的连接。isMain这个字段,true表示主屏,false表示副屏。先来看副屏的代码,初始化一个sendSYN类。它向主屏发送一个为-1字节的数据,然后如果主屏收到一个为-1的数据,就表示这是副屏发起的连接请求(正常的数据请求是不可能为-1 的)。主屏收到以后,也向副屏发送一个-1字节的数据,如果副屏也收到来这个数据,表示双屏建立连接成功。

连接通信成功,就可以相互发送数据了。来看下定义的Task这个类。

Android双屏驱动Service架构实现_第3张图片

这里面是一些数据信息,hasSendedLength,hasRecvLength,fileLength用来分包,还原包的,sender表示发送者,成功或失败要反馈发送者…

TaskType是一个枚举

这里写图片描述

分为文件任务,内存任务,控制任务三种。

那我们是如何向副屏发送任务的呢?是通过服务的aidl来发送的和接收回调信息的。

// SendService.aidl
interface SendService {

    int sendFileToFile(in String recvPackageName,in String path,boolean isReport, long userFlag,in SendServiceCallback callback);

    int sendByteToMemory(in String recvPackageName,in byte [] data,in SendServiceCallback callback);

}

在CoreService的onBind()方法中,返回了此对象。现在来看下SendService的具体实现。

    @Override
    public IBinder onBind(Intent intent) {
        if (callback == null) {
            callback = new CallBack();
        }
        return callback;
    }

    private class CallBack extends SendService.Stub {
        @Override
        public int sendFileToFile(String recvPackageName, String path, boolean isReport, long userFlag,
                SendServiceCallback callback) throws RemoteException {
            SendProcess process = mapSend.get(Binder.getCallingUid());
            if (process == null) {
                process = new SendProcess(errorSendTaskID, controlThread, finshAndWait);
                mapSend.put(Binder.getCallingUid(), process);
            }
            int tem_id = getID();
            synchronized (lock) {
                process.addTask(new FileTask(tem_id,
                        getApplicationContext().getPackageManager().getNameForUid(Binder.getCallingUid()),
                        recvPackageName, path, isReport, userFlag, callback));
                lock.notify();
            }
            return tem_id;
        }

        @Override
        public int sendByteToMemory(String recvPackageName, byte[] data, SendServiceCallback callback)
                throws RemoteException {
            SendProcess process = mapSend.get(Binder.getCallingUid());
            if (process == null) {
                process = new SendProcess(errorSendTaskID, controlThread, finshAndWait);
                mapSend.put(Binder.getCallingUid(), process);
            }
            int tem_id = getID();
            synchronized (lock) {
                process.addTask(new MemoryTask(tem_id,
                        getApplicationContext().getPackageManager().getNameForUid(Binder.getCallingUid()),
                        recvPackageName, data, callback));
                lock.notify();
            }
            return tem_id;
        }
    }

主要是从map队列中取出客户端任务,放进任务列表,唤醒处理线程,执行任务。主要发送任务是SendProcess类,其中我们定义了sendM()和sendF()方法,来发送字符串命令和文件。

Android双屏驱动Service架构实现_第4张图片

Android双屏驱动Service架构实现_第5张图片

最终都通过task的send()方法来发送,其实也就是前面所说的SerialPort中的write()这个jni方法。

Android双屏驱动Service架构实现_第6张图片

其中fillData()方法,也就是前面所有的给Task填充数据,包括请求头,大小,描述等。

下面我们再来看一下接收的方法,也是前面所说的SerialPort的read()来读取发送过来的数据,通过aidl回调主屏。其中的回调callback在任务创建的时候传入的,在控制线程和发送线程中对其作出相应的回调处理。

Android双屏驱动Service架构实现_第7张图片

// SendServiceCallback.aidl
interface SendServiceCallback {

    oneway void sendSuccess(int id);

    oneway void sendError(int id,int  errorId, String errorInfo);

    oneway void sendProcess(int id, long totle, long sended);

}

获取到数据,进行拼装还原,取出任务的携带信息,进行分类处理。

            byte flag = data[4];
            int taskId = ByteUtil.bytes2Int(data, 5);
            fileTaskRecv = (RecvTask) mapRecv.get(taskId);
            int sendPackNameLength = ByteUtil.bytes2Int(data, 9);
            int recvPackNameLength = ByteUtil.bytes2Int(data, 13 + sendPackNameLength);
            if (fileTaskRecv == null) {
                if (errorTaskID.contains(taskId)) {
                    return;
                }
                String sendPackName = new String(data, 13, sendPackNameLength, "utf-8");
                String recvPackName = new String(data, 17 + sendPackNameLength, recvPackNameLength, "utf-8");
                int timeOut = ByteUtil.bytes2Int(data, 17 + recvPackNameLength + sendPackNameLength);
                long fileLength = ByteUtil.bytes2long(data, 21 + recvPackNameLength + sendPackNameLength);
                long userFlag = ByteUtil.bytes2long(data, 29 + recvPackNameLength + sendPackNameLength);

最后通过Service中的Handler来回调处理,其实是通过广播发送出去的,所以需要接收双屏通信信息的app都要注册该广播。

Android双屏驱动Service架构实现_第8张图片

存在问题与改进空间

这样,双屏通信的大致流程就已经说完了,其中有一些需要补充和完善的地方,因为项目紧急,所以第一版上线的也比较粗糙,后续会陆续改进的。但我想,不管是怎样改,分包,发送,接收,回调,原包这样逻辑应该是通用的。

问题:1.接收到的文件没有处理,因为sdcard的内存是有限的。
2.如果连接断开(像强行关机之类),失败的任务是不能复原的。在初始化的时候,我们把所有以前的任务都当作失败任务放弃掉了。这里其实可以做一些缓存处理,在拿出未完成的任务,在重连的时候继续处理。

还有一些东西可能还没有考虑到,在后面无尽的需求中,也许会增加上去。

Beating Heart !

你可能感兴趣的:(Android,开发记录)