C#通过adb传输安卓设备数据

来源:互联网 发布:西安 java 软件公司 编辑:程序博客网 时间:2024/06/05 04:27

最近因为项目需要,研究了一下C#调用adb传输和推送数据到安卓设备上。 查了资料发现安卓设备与电脑连接,传输数据有两种方式: 1.通过adb
2.socket。 市面上安卓设备管理工具如:豌豆荚、XX手机助手大多采用socket方式,监听某个端口,通过socket传输数据。socket优点是速度快,不会被语言和编码限制,缺点是开发量大,难懂(至少对于大多数开发者是这样的)。

下面介绍一下adb使用的一些经验和技巧,文章最后附有我写的一个程序。


1.ADB简介及命令

ADB:Android Debug Bridge,安卓调试桥接工具。(连接设备时要保证pc上安装有该设备的驱动)
传输数据文件常用的命令有:

  • adb shell 进入shell界面
  • pull拷贝文件到电脑: pull sdcard/a.jpg d:\a.jpg
  • push拷贝文件到设备:push d:\a.jpg sdcard/a.jpg
  • mkdir创建文件夹:mkdir xxx mkdir –p xxx/xxx(递归创建文件夹)
  • ls 列出当前文件夹下所有文件和文件夹 *
  • cd转到指定文件夹下

    这里写图片描述
    进入shell
    这里写图片描述
    转到目录,列出目录中的文件和文件夹

    这里写图片描述
    pull、push命令不必进入shell

    这里写图片描述
    创建文件夹

*由于cmd字符集的问题,汉字命名的文件在ls时出现乱码,原因是cmd采用gbk,Android系统采用的UTF-8所致,可以更改字符集和展示字体,见此。Ps:在程序中推荐大家使用英文和数字来命名文件和文件夹,免得出现不必要的辛苦,因为通过修改命名方式和修改Android底层adb的源码再次编译相比,真是不要太简单!

2.C#程序

首先说明一下我的开发环境:VS2010 .Net Framework 4.0
这里写图片描述
文件结构

  1. 在程序中最好集成adb.exe,使用起来也很方便
  2. 使用log4net记录数据传输时的返回信息
  3. ProcessHelper类用来监控cmd进程中输入输出流等
    贴一下ProcessHelper类的代码:
using System;using System.Collections.Generic;using System.ComponentModel;using System.Diagnostics;using System.Text;using System.Threading;namespace AndroidDataTransform{    public class ProcessHelper    {        private static Process GetProcess()        {            var mProcess = new Process();            mProcess.StartInfo.CreateNoWindow = true;            mProcess.StartInfo.UseShellExecute = false;            mProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;            mProcess.StartInfo.RedirectStandardInput = true;            mProcess.StartInfo.RedirectStandardError = true;            mProcess.StartInfo.RedirectStandardOutput = true;            mProcess.StartInfo.StandardOutputEncoding = Encoding.UTF8;            return mProcess;        }        private static string ReadStandardOutputLine(Process p)        {            var tmp = new StringBuilder();            //当下一次读取时,Peek可能为-1,但此时缓冲区其实是有数据的。正常的Read一次之后,Peek就又有效了。            if (p.StandardOutput.Peek() == -1)                tmp.Append((char)p.StandardOutput.Read());            while (p.StandardOutput.Peek() > -1)            {                tmp.Append((char)p.StandardOutput.Read());            }            return tmp.ToString();        }        /// <summary>        /// 读取数据的时候等待时间,等待时间过短时,可能导致读取不出正确的数据。        /// </summary>        public static int WaitTime = 50;        /// <summary>        /// 连续运行模式,支持打开某程序后,持续向其输入命令,直到结束。        /// </summary>        /// <param name="exePath"></param>        /// <param name="args"></param>        /// <param name="moreArgs"></param>        /// <returns></returns>        public static RunResult RunAsContinueMode(string exePath, string args, string[] moreArgs)        {            var result = new RunResult();            try            {                using (var p = GetProcess())                {                    p.StartInfo.FileName = exePath;                    p.StartInfo.Arguments = args;                    p.Start();                    //先输出一个换行,以便将程序的第一行输出显示出来。                    //如adb.exe,假如不调用此函数的话,第一行等待的shell@android:/ $必须等待下一个命令输入才会显示。                    p.StandardInput.WriteLine();                    result.OutputString = ReadStandardOutputLine(p);                    result.MoreOutputString = new Dictionary<int, string>();                    for (int i = 0; i < moreArgs.Length; i++)                    {                        p.StandardInput.WriteLine(moreArgs[i] + '\r');                        //必须等待一定时间,让程序运行一会儿,马上读取会读出空的值。                        Thread.Sleep(WaitTime);                        result.MoreOutputString.Add(i, ReadStandardOutputLine(p));                    }                    // Do not wait for the child process to exit before                    // reading to the end of its redirected stream.                    // p.WaitForExit();                    // Read the output stream first and then wait.                    p.WaitForExit();                    result.ExitCode = p.ExitCode;                    result.Success = true;                }            }            catch (Win32Exception ex)            {                result.Success = false;                //System Error Codes (Windows)                //http://msdn.microsoft.com/en-us/library/ms681382(v=vs.85).aspx                result.OutputString = string.Format("{0},{1}", ex.NativeErrorCode, SystemErrorCodes.ToString(ex.NativeErrorCode));            }            catch (Exception ex)            {                result.Success = false;                result.OutputString = ex.ToString();            }            return result;        }        public static RunResult Run(string exePath, string args)        {            var result = new RunResult();            try            {                using (var p = GetProcess())                {                    p.StartInfo.FileName = exePath;                    p.StartInfo.Arguments = args;                    p.Start();                    //获取正常信息                    if (p.StandardOutput.Peek() > -1)                        result.OutputString = p.StandardOutput.ReadToEnd();                    //获取错误信息                    if (p.StandardError.Peek() > -1)                        result.OutputString = p.StandardError.ReadToEnd();                    // Do not wait for the child process to exit before                    // reading to the end of its redirected stream.                    // p.WaitForExit();                    // Read the output stream first and then wait.                    p.WaitForExit();                    result.ExitCode = p.ExitCode;                    result.Success = true;                }            }            catch (Win32Exception ex)            {                result.Success = false;                //System Error Codes (Windows)                //http://msdn.microsoft.com/en-us/library/ms681382(v=vs.85).aspx                result.OutputString = string.Format("{0},{1}", ex.NativeErrorCode, SystemErrorCodes.ToString(ex.NativeErrorCode));            }            catch (Exception ex)            {                result.Success = false;                result.OutputString = ex.ToString();            }            return result;        }        public class RunResult        {            /// <summary>            /// 当执行不成功时,OutputString会输出错误信息。            /// </summary>            public bool Success;            public int ExitCode;            public string OutputString;            /// <summary>            /// 调用RunAsContinueMode时,使用额外参数的顺序作为索引。            /// 如:调用ProcessHelper.RunAsContinueMode(AdbExePath, "shell", new[] { "su", "ls /data/data", "exit", "exit" });            /// 果:MoreOutputString[0] = su执行后的结果字符串;MoreOutputString[1] = ls ...执行后的结果字符串;MoreOutputString[2] = exit执行后的结果字符串            /// </summary>            public Dictionary<int, string> MoreOutputString;            public new string ToString()            {                var str = new StringBuilder();                str.AppendFormat("Success:{0}\nExitCode:{1}\nOutputString:{2}\nMoreOutputString:\n", Success, ExitCode, OutputString);                if (MoreOutputString != null)                    foreach (var v in MoreOutputString)                        str.AppendFormat("{0}:{1}\n", v.Key, v.Value.Replace("\r", "\\Ⓡ").Replace("\n", "\\Ⓝ"));                return str.ToString();            }        }    }}

AdbHelper类中包括了连接设备、列出指定文件夹、文件的传输

  • 将文件拷贝至设备
    若result信息中包含No such file or directory 证明设备上没有该路径。为了保留pc上文件的路径,需要在设备上创建相应的文件夹。
    例如:通过输出字符串:" failed to copy 'F:\padData\image\1.jpg' to 'sdcard/21at/output/image/1.jpg': No such file or directory"
    我们要获得目标文件夹是sdcard/21at/output/image/,具体实现见下:
        /// <summary>        /// 将文件拷贝到设备上(不适用于文件夹)        /// </summary>        /// <param name="deviceNo"></param>        /// <param name="pcPath"></param>        /// <param name="devPath"></param>        /// <returns></returns>        public static bool CopyToDevice(string deviceNo, string pcPath, string devPath)        {            //adb push [-p] <local> <remote>             //- copy file/dir to device            var result = ProcessHelper.Run(AdbExePath, string.Format("-s {0} push {1} {2}", deviceNo, pcPath, devPath));            m_log.Info("推送PAD时结果:" + result.ToString());            if (result.ExitCode != 0                || (result.OutputString.Contains("failed")                && result.OutputString.Contains("No such file or directory")))//若出现设备文件夹不存在的情况,则创建该文件夹            {                //构建拷贝后设备路径                int index1 = result.OutputString.IndexOf("failed to copy ");                //输出字符串:" failed to copy 'F:\padData\image\1.jpg' to 'sdcard/21at/output/image/1.jpg': No such file or directory"                string temp = result.OutputString.Substring(index1);                int index2 = temp.IndexOf(devPath);                int index3 = temp.IndexOf("': No such file or directory");                string devPath2 = temp.Substring(index2, index3 - index2);//设备图片路径                devPath2 = devPath2.Substring(0, devPath2.LastIndexOf('/'));                var moreArgs = new[] { "su", "mkdir -p "+ devPath2, "exit", "exit" };                //shell 方式创建文件夹                result = ProcessHelper.RunAsContinueMode(AdbExePath, string.Format("-s {0} shell", deviceNo), moreArgs);                m_log.Info("创建文件夹:" + devPath2);                m_log.Info("创建文件夹结果:" + result.ToString());                //再次推送该文件                result = ProcessHelper.Run(AdbExePath, string.Format("-s {0} push {1} {2}", deviceNo, pcPath, devPath));                m_log.Info("再次推送PAD结果:" + result.ToString());            }            if (!result.Success                || result.ExitCode != 0                || (result.OutputString != null && result.OutputString.Contains("failed")))            {                return false;                throw new Exception("push 执行返回的结果异常:" + result.OutputString);            }            return true;        }
  • 将文件拷贝至PC
        /// <summary>        /// 拷贝文件到PC上        /// </summary>        /// <param name="deviceNo"></param>        /// <param name="devPath"></param>        /// <param name="pcPath"></param>        /// <returns></returns>        public static bool CopyFromDevice(string deviceNo, string devPath, string pcPath)        {            //使用Pull命令将数据库拷贝到Pc上            //adb pull [-p] [-a] <remote> [<local>]            var result = ProcessHelper.Run(AdbExePath, string.Format("-s {0} pull {1} {2}", deviceNo, devPath, pcPath));            m_log.Info("推送PC时结果:" + result.ToString());            if (!result.Success                || result.ExitCode != 0                || (result.OutputString != null && result.OutputString.Contains("failed")))            {                return false;                throw new Exception("pull 执行返回的结果异常:" + result.OutputString);            }            return true;        } 
  • 列出指定目录
         /// <summary>        /// 获取指定的目录        /// </summary>        /// <param name="deviceNo"></param>        /// <param name="path"></param>        /// <returns></returns>        public static List<string>  ListDataFolder(string deviceNo, string path)        {            var moreArgs = new[] { "su", "ls " + path, "exit", "exit" };            var result = ProcessHelper.RunAsContinueMode(AdbExePath, string.Format("-s {0} shell", deviceNo), moreArgs);            m_log.Info("获取路径结果:" + result.ToString());            var itemsString = result.MoreOutputString[1];            var items = itemsString.Split(new[] { "$", "#", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);            var itemsList = new List<String>();            foreach (var item in items)            {                var tmp = item.Trim();                //移除第一行,输入的命令                if (tmp.Contains(moreArgs[1]))                    continue;                //若有Permission denied证明没有该路径,直接退出                if (tmp.Contains(path + ": Permission denied"))                    break;                //移除空白行                if (string.IsNullOrEmpty(tmp))                    continue;                //移除最后两行的root@android                if (tmp.ToLower().Contains("root@") || tmp.ToLower().Contains("shell@"))                    continue;                if (tmp.Equals("su") || tmp.Contains("su: not found"))//移除su                    continue;                itemsList.Add(path + "/" + tmp);            }            itemsList.Sort();            return itemsList;        }

文件app.config中记录了指定的下载的文件夹,以及当前程序标示是上传还是下载

<appSettings>    <!--文件路径:实际路径是 sdcard/Tencent或extSdCard/Tencent-->    <add key="PadDataPath" value="/Tencent"/>    <!--程序是上传(1)还是下载(0)-->    <add key="type" value="0"/>  </appSettings>

在C#的程序中可通过
System.Configuration.ConfigurationManager.AppSettings["PadDataPath"]方式来获取设置的value值。
这里写图片描述
程序运行图

我写的程序在此。

1 0