转自: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
文件结构
- 在程序中最好集成adb.exe,使用起来也很方便
- 使用log4net记录数据传输时的返回信息
- 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值。
程序运行图
我写的程序在此。