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

转自:http://blog.csdn.net/omguare/article/details/49804323

最近因为项目需要,研究了一下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值。
这里写图片描述
程序运行图

我写的程序在此

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值