先看看效果:

1.项目地址:https://gitee.com/phery/minicapGo

2.实现原理:

手机画面同步:部署 minicap 程序到手机>执行 minicap 程序>绑定 Socket 端口到本地 PC>监听并解析协议内容

调用 adb 模拟点击:获取鼠标按下 x 坐标>获取鼠标松开 x 坐标>计算两个 x 坐标的差值>换算成 adb 长按的时长

3.关键代码

初始化 minicap:

package com.phery.minicap;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;

public class InitDevices
{
    public static int SDK_VERSION;
    public static String ABI;
    public static int PRE;
    public static String REL;
    public static String DIR;
    public static String BIN;

    public static void init()
    {
        SDK_VERSION = Integer.valueOf(excuteCommand("adb shell getprop ro.build.version.sdk")).intValue();
        ABI = excuteCommand("adb shell getprop ro.product.cpu.abi");
        String preSDK = excuteCommand("adb shell getprop ro.build.version.preview_sdk");
        if (!"".equals(preSDK))
            PRE = Integer.valueOf(preSDK).intValue();
        REL = excuteCommand("adb shell getprop ro.build.version.release");
        DIR = "/data/local/tmp/minicap-devel";
        if (PRE > 0)
            SDK_VERSION++;
        if (SDK_VERSION >= 16)
            BIN = "minicap";
        else
            BIN = "minicap-nopie";
        String result;
        result = excuteCommand("adb shell \"mkdir " + DIR + " 2>/dev/null || true\"");
        System.out.println(result);
        result = excuteCommand("adb push resource/libs/" + ABI + "/" + BIN + " " + DIR);
        System.out.println(result);
        result = excuteCommand("adb push resource/aosp/libs/android-" + SDK_VERSION + "/" + ABI + "/minicap.so " + DIR);
        System.out.println(result);
        result = excuteCommand("adb shell \"chmod 777 " + DIR + "/*\"");
        System.out.println(result);
        result = excuteCommand("adb forward tcp:1717 localabstract:minicap");
        System.out.println(result);

        String wmsize = excuteCommand("adb shell wm size");
        String[] value = wmsize.split(" ");
        wmsize = value[2];

        try
        {
            Runtime.getRuntime().exec(
                    "adb shell LD_LIBRARY_PATH=" + DIR + " " + DIR + "/" + BIN + " -Q 100 -P " + wmsize + "@480x800/0");
        } catch (IOException e)
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // System.out.println(result);

    }

    // public static void main(String[] args)
    // {
    // init();
    // }

    public static String excuteCommand(String cmd)
    {
        /* 获取cmd命令 */
        try
        {
            System.out.println("正在执行命令:" + cmd);
            Process pro = Runtime.getRuntime().exec(cmd); // 添加要进行的命令,"cmd
                                                            // /c
                                                            // calc"中calc代表要执行打开计算器,如何设置关机请自己查找cmd命令

            BufferedReader br = new BufferedReader(new InputStreamReader(pro.getInputStream())); // 虽然cmd命令可以直接输出,但是通过IO流技术可以保证对数据进行一个缓冲。
            String msg = null;
            String result = null;
            System.out.println("执行完成!!!");
            while ((msg = br.readLine()) != null)
            {
                System.out.println(msg);
                if (result == null)
                    result = msg;
            }
            System.out.println(result);
            return result;
        } catch (IOException exception)
        {
            exception.printStackTrace();
            return "";
        }

        /*
         * cmd /c dir 是执行完dir命令后关闭命令窗口 cmd /k dir 是执行完dir命令后不关闭命令窗口 cmd /c start
         * dir 会打开一个新窗口后执行dir命令,原窗口会关闭 cmd /k start dir 会打开一个新窗口后执行dir命令,原窗口不会关闭
         * cmd /? 查看帮助信息
         */

    }

}

解析 minicap 协议:

package com.phery.minicap;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.Buffer;

public class MiniCap
{
    private RefreshListener listener;

    public MiniCap(RefreshListener listener)
    {
        this.listener = listener;
    }

    int readBannerBytes = 0;
    int bannerLength = 24;
    int readFrameBytes = 0;
    int frameBodyLength = 0;
    byte[] frameBody = new byte[0];
    String[] banner = new String[24];

    public void tryRead()
    {
        Socket socket;
        InputStream stream = null;
        try
        {
            socket = new Socket("127.0.0.1", 1717);
            stream = socket.getInputStream();
        } catch (UnknownHostException e1)
        {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } catch (IOException e1)
        {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        while (true)
        {
            try
            {
                DataInputStream input = new DataInputStream(stream);
                int len = input.available();// 获取读到的字节长度
                byte[] chunk = new byte[len];
                input.read(chunk);// 将读取的字节保存到chunk
                // System.out.println("拉取到长度:" + len);
                for (int cursor = 0; cursor < len;)
                {
                    if (readBannerBytes < bannerLength)
                    {
                        switch (readBannerBytes)
                        {
                        case 0:
                            // version
                            banner[0] = chunk[cursor] + "";
                            System.out.println("Banner Version:" + banner[0]);
                            break;
                        case 1:
                            // length
                            bannerLength = chunk[cursor];
                            banner[1] = String.valueOf(bannerLength);
                            System.out.println("Banner bannerLength:" + banner[1]);
                            break;
                        // case 2:
                        // case 3:
                        // case 4:
                        case 5:

                            // byte[] temp = new byte[4];
                            // System.arraycopy(chunk, 2, temp, 0, 4);
                            banner[5] = bytesToLong(chunk, 2) + "";
                            // pid
                            // banner[5] += (chunk[cursor] << ((readBannerBytes
                            // - 2) * 8)) >>> 0;
                            System.out.println("Banner pid:" + banner[5]);
                            break;
                        // case 6:
                        // case 7:
                        // case 8:
                        case 9:
                            // real width
                            banner[9] = bytesToLong(chunk, 6) + "";
                            // banner[9] += (chunk[cursor] << ((readBannerBytes
                            // - 6) * 8)) >>> 0;
                            System.out.println("Banner real width:" + banner[9]);
                            break;
                        // case 10:
                        // case 11:
                        // case 12:
                        case 13:
                            // real height
                            banner[13] = bytesToLong(chunk, 10) + "";
                            // banner[13] += (chunk[cursor] << ((readBannerBytes
                            // - 10) * 8)) >>> 0;
                            System.out.println("Banner real height:" + banner[13]);
                            break;
                        // case 14:
                        // case 15:
                        // case 16:
                        case 17:
                            // virtual width
                            banner[17] = bytesToLong(chunk, 14) + "";
                            // banner[17] += (chunk[cursor] << ((readBannerBytes
                            // - 14) * 8)) >>> 0;
                            System.out.println("Banner virtual width:" + banner[17]);
                            break;
                        // case 18:
                        // case 19:
                        // case 20:
                        case 21:
                            // virtual height
                            banner[21] = bytesToLong(chunk, 18) + "";
                            // banner[21] += (chunk[cursor] << ((readBannerBytes
                            // - 18) * 8)) >>> 0;
                            System.out.println("Banner virtual height:" + banner[21]);
                            break;
                        case 22:
                            // orientation
                            banner[22] += chunk[cursor] * 90;
                            System.out.println("Banner orientation:" + banner[22]);
                            break;
                        case 23:
                            // quirks
                            banner[23] = chunk[cursor] + "";
                            System.out.println("Banner quirks:" + banner[23]);
                            break;
                        }

                        cursor += 1;
                        readBannerBytes += 1;

                        if (readBannerBytes == bannerLength)
                        {
                            System.out.println("banner读取完毕!");
                            // console.log('banner', banner)
                        }
                    } else if (readFrameBytes < 4)
                    {

                        frameBodyLength = (int) bytesToLong(chunk, 0);
                        // System.out.println("获取字节长度:" + frameBodyLength);
                        // frameBodyLength += (chunk[cursor] << (readFrameBytes
                        // * 8)) >>> 0;
                        if (frameBodyLength < 0 || frameBodyLength > 999999)
                        {

                            cursor += frameBodyLength;
                            frameBodyLength = readFrameBytes = 0;
                            frameBody = new byte[0];
                            continue;
                        }
                        cursor += 4;
                        readFrameBytes += 4;
                    } else
                    {
                        if (len - cursor >= frameBodyLength)
                        {
                            // console.info('bodyfin(len=%d,cursor=%d)',
                            // frameBodyLength, cursor)
                            byte[] temp = new byte[cursor + frameBodyLength];
                            System.arraycopy(chunk, cursor, temp, 0, cursor + frameBodyLength);
                            frameBody = addBytes(frameBody, temp);

                            // Sanity check for JPG header, only here for
                            // debugging purposes.
                            // System.out.println("frameBody[0]:" + frameBody[0]
                            // + " frameBody[1]" + frameBody[1]
                            // + " 0xFF:" + 0xFF + " 0xD8:" + 0xD8);
                            // if (frameBody[0] != -1 || frameBody[1] != -40)
                            // {

                            // cursor += frameBodyLength;
                            // frameBodyLength = readFrameBytes = 0;
                            // frameBody = new byte[0];
                            // console.error(
                            // 'Frame body does not start with JPG header',
                            // frameBody)
                            // continue;
                            // }
                            // System.out.println("读取完毕:" + frameBody.length);
                            listener.refresh(frameBody);

                            cursor += frameBodyLength;
                            frameBodyLength = readFrameBytes = 0;
                            frameBody = new byte[0];
                        } else
                        {
                            // console.info('body(len=%d)', len - cursor)

                            byte[] temp = new byte[len - cursor];
                            System.arraycopy(chunk, cursor, temp, 0, len - cursor);
                            frameBody = addBytes(frameBody, temp);
                            // System.out.println("body:" + (len - cursor));
                            // System.out.println("frameBodyLength:" +
                            // frameBodyLength);

                            frameBodyLength -= len - cursor;
                            readFrameBytes += len - cursor;
                            cursor = len;
                        }
                    }
                }
            } catch (Exception e)
            {
                e.printStackTrace();
                System.out.println("报错了");
            }
        }
    }

    /**
     * byte数组中取int数值,本方法适用于(低位在前,高位在后)的顺序,和和intToBytes()配套使用
     * 
     * @param src
     *            byte数组
     * @param offset
     *            从数组的第offset位开始
     * @return int数值
     */
    public static long bytesToLong(byte[] src, int offset)
    {
        long value;
        value = (long) ((src[offset] & 0xFF) | ((src[offset + 1] & 0xFF) << 8) | ((src[offset + 2] & 0xFF) << 16)
                | ((src[offset + 3] & 0xFF) << 24));
        return value;
    }

    /**
     * 
     * @param data1
     * @param data2
     * @return data1 与 data2拼接的结果
     */
    public static byte[] addBytes(byte[] data1, byte[] data2)
    {
        byte[] data3 = new byte[data1.length + data2.length];
        System.arraycopy(data1, 0, data3, 0, data1.length);
        System.arraycopy(data2, 0, data3, data1.length, data2.length);
        return data3;

    }
}


↙↙↙阅读原文可查看相关链接,并与作者交流