转载请注明出处:https://blog.csdn.net/xiangxinzijiwonen/article/details/83547250(来自ZhongGuanYao的csdn博客)
目录
前言
从事游戏SDK接入和打包相关工作时,经常对要APK文件进行信息检查、打包测试、问题排查等等,难免要敲一些反编译、重签名等繁琐的命令行,为方便和提高工作效率,制作一款适合自己工作需要的APKINFO小工具是有必要的。
APKINFO工具下载:https://download.csdn.net/download/xiangxinzijiwonen/10766262
github源码:https://github.com/zhongguanyao/APKINFO
注意:无法在CSDN资源下载时,可到github下载源码工程,直接使用bin/Debug目录即可
先展示张图片看下效果:
使用语言:C#
涉及到的工具:aapt.exe、jarsigner.exe、zipalign.exe、apktool、java.exe、keytool.exe,第三方jadx等
APKINFO的功能是通过C#语言对这些工具的命令行进一步封装来实现的,你会发现APKINFO工具为什么有一百多M那么大,是因为它引用了JDK和ADT的部分工具。
项目结构如下:
一、查看APK信息
工具:aapt.exe
命令行:aapt dump badging apkFilePath
示例截图:
编程思路:C#调用命令行后获得APK信息,然后将应用名、包名、版本号、图标、权限等信息整理出来,最后展示到窗体的控件上。
注意点:
- C#在调用命令行时,需要设置使用Shell执行,输出重定向,可以屏蔽Shell窗体的显示。
- 截图上的中文应用名出现乱码,是因为Shell输出编码默认使用了gb2312,将标准输出编码设置为UTF-8即可。
- 不同版本的aapt.exe,查看APK信息的速度不一样,输出来的信息结构也稍有不同,一般低版本的aapt执行速度比较快。
- 图标读取是先在APK信息中找到图标路径,然后根据路径解压图标文件的,项目中是引入Ionic.Zip.dll解压文件。
- 另外,增加了查看APK的assets目录下的配置,至于查看哪些个配置文件里的参数,由用户设置决定,这里只能读取内容格式为key=value的配置文件。
C#关键代码如下:
BrowseApkInfoBLL类:
/// <summary>
/// 读取APK信息
/// </summary>
/// <param name="apkFilePath"></param>
/// <returns></returns>
public static ApkInfo ReadApkInfo(string apkFilePath)
{
if (String.IsNullOrEmpty(apkFilePath))
return null;
string aaptPath = PathUtils.GetPath(Constants.PATH_AAPT);
var startInfo = new ProcessStartInfo(aaptPath);
string args = string.Format("dump badging \"{0}\"", apkFilePath);
startInfo.Arguments = args;
startInfo.UseShellExecute = false;
startInfo.RedirectStandardOutput = true;
startInfo.CreateNoWindow = true;
startInfo.StandardOutputEncoding = Encoding.UTF8;
// 读取命令行返回的所有txt文本和字符编码
List<string> lineLst = new List<string>();
using (var process = Process.Start(startInfo))
{
var sr = process.StandardOutput;
while (!sr.EndOfStream)
{
lineLst.Add(sr.ReadLine());
}
process.WaitForExit();
}
ApkInfo apkInfo = new ApkInfo();
apkInfo.ApkFilePath = apkFilePath;
apkInfo.CurrentFileName = Path.GetFileName(apkFilePath);
apkInfo.AllInfoList = lineLst;
// 解析相关的配置参数
ParseTxtFile(ref apkInfo);
ParseIcon(ref apkInfo);
ParseAssetsConfig(ref apkInfo);
return apkInfo;
}
/// <summary>
/// 解压并读取icon图片
/// </summary>
/// <param name="apkInfo"></param>
private static void ParseIcon(ref ApkInfo apkInfo)
{
if (apkInfo == null
|| string.IsNullOrEmpty(apkInfo.ApkFilePath)
|| apkInfo.IconDic == null
|| apkInfo.IconDic.Count <= 0) return;
string icon = null;
int i = 0;
// 在多张尺寸的ICON里选择尺寸为240
foreach (KeyValuePair<string, string> kv in apkInfo.IconDic)
{
if (kv.Key.Contains("240"))
{
icon = kv.Value;
apkInfo.IconName = Path.GetFileName(icon);
break;
}
if (i == apkInfo.IconDic.Count - 1)
{
icon = kv.Value;
apkInfo.IconName = Path.GetFileName(icon);
break;
}
i++;
}
// 指定解压目录
string unZipPath = PathUtils.GetPath(Constants.DIR_TEMP_ICON);
// 清空解压目录
FileUtils.ClearDir(unZipPath);
// 解压指定的icon图片文件
ZipUtils.UnZip(apkInfo.ApkFilePath, unZipPath, icon);
// 读取解压出来的图片文件
string iconPath = PathUtils.JoinPath(unZipPath, icon);
apkInfo.Icon = new Bitmap(iconPath);
}
二、查看APK源代码
工具:jadx-0.7.1
命令行:jadx-gui.bat apkFilePath 或jarFilePath 或dexFilePath
之前使用过jd-gui.exe查看源码,但它只能打开jar文件,dex和apk文件需要转换成jar文件才能打开,并不好用;这里推荐使用jadx工具,jar、dex和apk文件它都能直接打开。
编程思路:程序判断用户传递的文件类型,如果为jar或dex文件,则直接通过命令行调用jadx-0.7.1打开,如果为apk文件,则展示APK信息,让用户通过点击《查看源码》按钮打开,当jadx-0.7.1打开后,关闭程序。
C#关键代码如下:
BrowseSourceBLL类:
/// <summary>
/// 用jadx-gui.bat打开文件
/// </summary>
/// <param name="filePath">以.jar/.dex/.apk为后缀的文件路径</param>
public static void OpenJadxGUI(string filePath) {
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) return;
string jadxPath = PathUtils.GetPath(Constants.PATH_JADXGUI);
if (!File.Exists(jadxPath))
{
LogUtils.WriteLine(jadxPath + "不存在!");
return;
}
string cmdArgs = jadxPath + " " + filePath;
CmdUtils.ExecCmd(cmdArgs);
}
CmdUtils类:
/// <summary>
/// 执行CMD命令,但不等待执行结果
/// </summary>
/// <param name="args"></param>
public static void ExecCmd(string args)
{
try
{
Process proc = new Process();
proc.StartInfo.WorkingDirectory = System.Windows.Forms.Application.StartupPath;
proc.StartInfo.FileName = "cmd.exe";
proc.StartInfo.Arguments = "/C " + args;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.CreateNoWindow = true;//不创建窗口
proc.Start();
}
catch (Exception ex)
{
LogUtils.WriteLine("CMD :" + args);
LogUtils.WriteLine("CMD Exception Occurred :" + ex.Message + ", " + ex.StackTrace.ToString());
Console.WriteLine("Exception Occurred :{0},{1}", ex.Message, ex.StackTrace.ToString());
}
}
三、查看签名信息
工具:keytool.exe
命令行:keytool -printcert -file apk中META-INF目录下的.RSA签名文件路径 // 查看APK签名信息
keytool -list -v -keystore certFilePath -storepass password // 查看.jks或.keystore签名库信息
示例截图:
编程思路:如果是查看APK签名信息,需要将APK中META-INF目录下的.RSA签名文件解压到临时目录下,调用命令行读取该.RSA文件的签名信息;如果是查看签名库文件(.jks或.keystore文件),需要用户拖放签名库文件至APKINFO.exe,获得签名库文件路径,再弹框让用户输入密码,最后调用上面第二个命令行即可。
C#关键代码如下:
BrowseCertificateBLL类:
/// <summary>
/// 查看APK签名信息
/// </summary>
/// <param name="apkFilePath"></param>
/// <param name="logView"></param>
/// <returns></returns>
public static string BrowseCert(string apkFilePath, ILog logView) {
if (string.IsNullOrEmpty(apkFilePath) || !File.Exists(apkFilePath)) {
logView.Log("Apk文件不存在!");
return null;
}
// 解压文件存放的临时目录
string tempDir = PathUtils.GetPath(Constants.DIR_CERT);
if (!Directory.Exists(tempDir))
{
Directory.CreateDirectory(tempDir);
}
FileUtils.ClearDir(tempDir);
// 解压APK文件中META-INF目录下的.RSA签名文件
ZipUtils.UnZipApkCertRSAFile(apkFilePath, tempDir, logView);
tempDir = PathUtils.JoinPath(tempDir, "META-INF");
if (!Directory.Exists(tempDir)) {
logView.Log(">>>>>>查看签名信息失败: 解压APK文件中META-INF目录下的.RSA签名文件失败,META-INF目录未解压出来!");
return null;
}
string[] files = Directory.GetFiles(tempDir);
if (files == null || files.Length != 1 || !".RSA".Equals(Path.GetExtension(files[0])))
{
logView.Log(">>>>>>查看签名信息失败: 解压APK文件中META-INF目录下的.RSA签名文件失败!");
return null;
}
string keyToolPath = PathUtils.GetPath(Constants.PATH_KEYTOOL);
if (!File.Exists(keyToolPath)) {
logView.Log(">>>>>>重新签名失败: " + keyToolPath + "不存在!");
return null;
}
var startInfo = new ProcessStartInfo(keyToolPath);
string args = "-printcert -file " + files[0];
startInfo.Arguments = args;
startInfo.UseShellExecute = false;
startInfo.RedirectStandardOutput = true;
startInfo.CreateNoWindow = true;
StringBuilder sb = new StringBuilder();
using (var process = Process.Start(startInfo))
{
var sr = process.StandardOutput;
while (!sr.EndOfStream)
{
sb.AppendLine(sr.ReadLine());
}
process.WaitForExit();
}
return sb.ToString();
}
ZipUtils类:
/// <summary>
/// 解压APK文件中META-INF目录下的.RSA签名文件
/// </summary>
/// <param name="zipPath"></param>
/// <param name="outPath"></param>
/// <param name="logView"></param>
public static void UnZipApkCertRSAFile(string zipPath, string outPath, ILog logView)
{
try
{
using (ZipFile zip = ZipFile.Read(zipPath))
{
List<ZipEntry> entries = new List<ZipEntry>();
foreach (ZipEntry entry in zip.Entries)
{
if (string.IsNullOrEmpty(entry.FileName))
continue;
if (!entry.FileName.StartsWith("META-INF/"))
continue;
if (entry.FileName.EndsWith(".RSA"))
{
entry.Extract(outPath, ExtractExistingFileAction.OverwriteSilently);
break;
}
}
}
}
catch (Exception ex)
{
logView.Log("UnZipApkCertRSAFile Exception : " + ex.Message);
LogUtils.WriteLine("UnZipApkCertRSAFile Exception : " + ex.Message);
Console.WriteLine("Exception Occurred :{0},{1}", ex.Message, ex.StackTrace.ToString());
Console.ReadKey();
}
}
// <summary>
/// 查看签名库文件信息
/// </summary>
/// <param name="certFilePath">.keystore或.jks签名库文件</param>
/// <param name="password">签名库密码</param>
/// <param name="logView"></param>
/// <returns></returns>
public static string BrowseCert(string certFilePath, string password, ILog logView)
{
if (string.IsNullOrEmpty(certFilePath) || !File.Exists(certFilePath))
{
logView.Log(">>>>>>查看签名信息失败:签名库文件不存在!");
return null;
}
if (string.IsNullOrEmpty(password))
{
logView.Log(">>>>>>查看签名信息失败:密码为空!");
return null;
}
string keyToolPath = PathUtils.GetPath(Constants.PATH_KEYTOOL);
if (!File.Exists(keyToolPath))
{
logView.Log(">>>>>>查看签名信息失败: " + keyToolPath + "不存在!");
return null;
}
var startInfo = new ProcessStartInfo(keyToolPath);
string args = " -list -v -keystore " + certFilePath + " -storepass " + password;
startInfo.Arguments = args;
startInfo.UseShellExecute = false;
startInfo.RedirectStandardOutput = true;
startInfo.CreateNoWindow = true;
StringBuilder sb = new StringBuilder();
using (var process = Process.Start(startInfo))
{
var sr = process.StandardOutput;
while (!sr.EndOfStream)
{
sb.AppendLine(sr.ReadLine());
}
process.WaitForExit();
}
return sb.ToString();
}
四、反编译APK
工具:java.exe 、apktool.jar、apktool.bat
命令行:apktool.bat d apkFilePath -o 输出目录
示例截图:
编程思路:由于反编译过程比较耗时,需要开启线程执行命令行操作,反编译结束后关闭程序,并打开输出目录。
注意: 输出目录不能是已存在的目录。
C#关键代码如下:
DecompileBLL类:
/// <summary>
/// 反编译
/// </summary>
/// <param name="apkFilePath"></param>
/// <returns>返回保存反编译信息的目录,为空表示反编译失败</returns>
public static string DecompileApk(string apkFilePath)
{
if (string.IsNullOrEmpty(apkFilePath)
|| !File.Exists(apkFilePath)) return null;
string apktoolPath = PathUtils.GetPath(Constants.PATH_APKTOOL);
if (!File.Exists(apktoolPath))
{
LogUtils.WriteLine(apktoolPath + "不存在!");
return null;
}
string decompileDir = PathUtils.GetPath(Constants.DIR_TEMP_DECOMPLIE);
if (!Directory.Exists(decompileDir))
{
Directory.CreateDirectory(decompileDir);
}
FileUtils.ClearDir(decompileDir);
decompileDir = PathUtils.JoinPath(decompileDir, DateTime.Now.ToString("yyyyMMdd_HHmmss"));
string cmdArgs = apktoolPath + " d " + apkFilePath + " -o " + decompileDir;// -o后面的目录不能已存在
string[] res = CmdUtils.ExecCmdAndWaitForExit(cmdArgs);
if (res == null || res.Length < 2 || !string.IsNullOrEmpty(res[1]))
return null;
return decompileDir;
}
五、重签名
工具:jarsigner.exe、zipalign.exe
命令行:
- jarsigner -digestalg SHA1 -sigalg SHA1withRSA -keystore keystoreFilePath -storepass password -keypass aliasPwd apkFilePath aliasKey
- zipalign -f 4 apkFilePath newApkFilePath
示例截图:
编程思路:由于重签名也比较耗时,需要开启线程执行命令行操作,因为是对APK文件进行重签名,所以在重签名之前需要删掉META-INF目录下旧的相关签名文件(以.MF、.SF、.RSA、.DSA后缀结尾的文件),重签名之后,使用zipalign.exe工具优化APK。
C#关键代码如下:
ResignApkBLL类:
/// <summary>
/// 重签名
/// </summary>
/// <param name="apkFilePath"></param>
/// <param name="newApkPath"></param>
/// <param name="config"></param>
/// <param name="logView"></param>
/// <returns></returns>
public static bool ResignApk(string apkFilePath, string newApkPath, KeystoreConfig config, ILog logView)
{
if (string.IsNullOrEmpty(apkFilePath) || !File.Exists(apkFilePath))
{
logView.Log(">>>>>>重新签名失败: " + apkFilePath + "不存在!");
return false;
}
string tempDir = PathUtils.GetPath(Constants.DIR_TEMP_RESIGNAPK);
if (!Directory.Exists(tempDir))
{
Directory.CreateDirectory(tempDir);
}
FileUtils.ClearDir(tempDir);
string tempApkFilePath = PathUtils.JoinPath(tempDir, "temp.apk");
// 拷贝一份APK作为临时文件,在该临时文件上操作
FileInfo fi = new FileInfo(apkFilePath);
if (fi.Exists)
{
fi.CopyTo(tempApkFilePath, true);
}
logView.BlankLine();
logView.Log("删除APK文件中META-INF目录下的签名文件");
ZipUtils.RemoveApkCertFile(tempApkFilePath, logView);
logView.BlankLine();
logView.Log("↓↓↓↓↓↓ 重签名-开始 ↓↓↓↓↓↓");
string jarsignerPath = PathUtils.GetPath(Constants.PATH_JARSIGNER);
string args = jarsignerPath + " -digestalg SHA1 -sigalg SHA1withRSA -keystore " + config.KeystoreFilePath + " -storepass " + config.Password + " -keypass " + config.Aliaspwd + " " + tempApkFilePath + " " + config.Aliaskey;
string[] ret = CmdUtils.ExecCmdAndWaitForExit(args, logView);
if (ret == null || ret.Length < 2 || !string.IsNullOrEmpty(ret[1]))
{
logView.Log(">>>>>>重新签名失败: 执行CMD失败");
return false;
}
logView.Log("↑↑↑↑↑↑ 重签名-结束 ↑↑↑↑↑↑");
logView.BlankLine();
logView.Log("↓↓↓↓↓↓ 对齐优化-开始 ↓↓↓↓↓↓");
string zipalignPath = PathUtils.GetPath(Constants.PATH_ZIPALIGN);
args = zipalignPath + " -f 4 " + tempApkFilePath + " " + newApkPath;
ret = CmdUtils.ExecCmdAndWaitForExit(args, logView);
if (ret == null || ret.Length < 2 || !string.IsNullOrEmpty(ret[1]))
{
logView.Log(">>>>>>对齐优化失败: 执行CMD失败");
return false;
}
logView.Log("↑↑↑↑↑↑ 对齐优化-结束 ↑↑↑↑↑↑");
logView.BlankLine();
if (!File.Exists(newApkPath)) {
logView.Log(">>>>>>重新签名失败: " + newApkPath + "导出的新apk文件不存在!");
return false;
}
logView.Log("导出apk为: " + newApkPath);
return true;
}
ZipUtils类:
/// <summary>
/// 删除APK文件中META-INF目录下的签名文件
/// </summary>
/// <param name="zipPath"></param>
/// <param name="logView"></param>
public static void RemoveApkCertFile(string zipPath, ILog logView)
{
try
{
using (ZipFile zip = ZipFile.Read(zipPath))
{
List<ZipEntry> entries = new List<ZipEntry>();
foreach (ZipEntry entry in zip.Entries)
{
if (string.IsNullOrEmpty(entry.FileName))
continue;
if (!entry.FileName.StartsWith("META-INF/"))
continue;
if (entry.FileName.EndsWith(".MF")
|| entry.FileName.EndsWith(".SF")
|| entry.FileName.EndsWith(".RSA")
|| entry.FileName.EndsWith(".DSA"))
{
entries.Add(entry);
logView.Log("删除->" + entry.FileName);
}
}
if (entries.Count > 0)
{
zip.RemoveEntries(entries);
}
zip.Save();
}
}
catch (Exception ex)
{
logView.Log("RemoveApkCertFile Exception : " + ex.Message);
LogUtils.WriteLine("RemoveApkCertFile Exception : " + ex.Message);
Console.WriteLine("Exception Occurred :{0},{1}", ex.Message, ex.StackTrace.ToString());
Console.ReadKey();
}
}
六、打入Jar包代码
工具:java.exe 、apktool.jar、apktool.bat、dx.jar、baksmali.jar、android.jar、jarsigner.exe、zipalign.exe
命令行:
- apktool.bat d apkFilePath -o 输出目录decompileDir //反编译
- java -jar -Xms1024m -Xmx1024m dxJarFilePath --dex --output=dexFilePath jarFilePath //将jar包转换成dex文件
- java -jar baksmaliJarFilePath -o targetDir dexFilePath //将dex文件转换成smali文件
- apktool.bat b decompileDir -o apkFilePath //回编译生成apk
- jarsigner -digestalg SHA1 -sigalg SHA1withRSA -keystore keystoreFilePath -storepass password -keypass aliasPwd apkFilePath aliasKey //签名
- zipalign -f 4 apkFilePath newApkFilePath //对齐优化
示例截图:
编程思路:上面的命令行就是将Jar打入到Apk文件里需要操作的步骤,先是反编译Apk文件,生成反编译目录,里面有个包含.smali文件的smali目录,就是Apk的源码;我们要将jar包文件也转换成.smali文件(中间要经过转换为.dex文件),转换后的smali目录路径设置为Apk的smali目录路径,目的是将它里面的.smali文件覆盖掉Apk的smali目录下的文件,以实现源码替换;然后将反编译目录回编译生成新的Apk文件,最后给新的Apk文件签名、对齐优化。
注意点:
- 反编译时,指定的输出目录必须是新的目录,不能是已存在的目录。
- jar文件转换成dex文件时,如果是多个jar文件,可通过空格隔开。
- dex文件转换成smali文件时,将输出的目标目录设置为Apk反编译目录下的smali目录,以实现smali文件替换。
- 这里给Apk签名不是重新签名,不需要进行删掉META-INF目录下旧的相关签名文件的操作。
- 整个操作流程比较耗时,要开线程跑。
C#关键代码如下:
MergeJarBLL类:
/// <summary>
/// 反编译
/// </summary>
/// <param name="apkFilePath"></param>
/// <param name="logView"></param>
/// <returns></returns>
public static string DecompileApk(string apkFilePath, ILog logView)
{
logView.Log("↓↓↓↓↓↓ 反编译-开始 ↓↓↓↓↓↓");
string res = DecompileBLL.DecompileApk(apkFilePath);
if (res == null)
{
logView.Log(">>>>>>反编译失败");
return res;
} else {
logView.Log(">>>>>>反编译成功:" + res);
}
logView.Log("↑↑↑↑↑↑ 反编译-结束 ↑↑↑↑↑↑");
logView.BlankLine();
return res;
}
/// <summary>
/// Jar文件转换成Dex文件
/// </summary>
/// <param name="jarPath">Jar文件或目录</param>
/// <param name="logView"></param>
/// <returns>返回生成dex文件的路径,为null时表示转换dex文件失败</returns>
public static string Jar2Dex(string jarPath, ILog logView)
{
string javaPath = PathUtils.GetPath(Constants.PATH_JAVA);// java.exe
string dexToolPath = PathUtils.GetPath(Constants.PATH_DX);// dx.jar
if (!File.Exists(javaPath))
{
logView.Log(">>>>>>Jar文件转换成Dex文件失败: " + javaPath + "文件未生成!");
return null;
}
if (!File.Exists(dexToolPath))
{
logView.Log(">>>>>>Jar文件转换成Dex文件失败: " + dexToolPath + "文件未生成!");
return null;
}
string dexFilePath = PathUtils.GetPath(Constants.DIR_DEX);
if (!Directory.Exists(dexFilePath)) {
Directory.CreateDirectory(dexFilePath);
}
FileUtils.ClearDir(dexFilePath);
// 生成的dex文件路径
dexFilePath = PathUtils.GetPath(Constants.PATH_CLASSDEX);
string args = javaPath + " -jar -Xms1024m -Xmx1024m " + dexToolPath + " --dex --output=" + dexFilePath;
logView.Log("↓↓↓↓↓↓ Jar文件转换成Dex文件-开始 ↓↓↓↓↓↓");
if (Directory.Exists(jarPath))
{
string[] files = Directory.GetFiles(jarPath);
if (files != null && files.Length > 0)
{
for (int i = 0; i < files.Length; i++)
{
if (files[i].EndsWith(".jar"))
{
args += " " + files[i];
}
}
}
} else if (File.Exists(jarPath) && jarPath.EndsWith(".jar"))
{
args += " " + jarPath;
}
string[] ret = CmdUtils.ExecCmdAndWaitForExit(args, logView);
if (ret == null || ret.Length < 2 || !string.IsNullOrEmpty(ret[1])) {
logView.Log(">>>>>>Jar文件转换成Dex文件失败: 执行CMD失败" );
return null;
}
logView.Log("↑↑↑↑↑↑ Jar文件转换成Dex文件-结束 ↑↑↑↑↑↑");
logView.BlankLine();
if (File.Exists(dexFilePath))
{
logView.Log("已生成dex文件:" + dexFilePath);
} else {
logView.Log("没生成dex文件:" + dexFilePath);
}
logView.BlankLine();
return dexFilePath;
}
/// <summary>
/// 将Dex文件转换为Smali文件
/// </summary>
/// <param name="decompileDir"></param>
/// <param name="logView"></param>
/// <returns></returns>
public static bool Dex2Smali(string decompileDir, ILog logView)
{
if (string.IsNullOrEmpty(decompileDir) || !Directory.Exists(decompileDir))
{
logView.Log(">>>>>>将Dex文件转换为Smali文件失败: " + decompileDir + "目录不存在!");
return false;
}
string dexFilePath = PathUtils.GetPath(Constants.PATH_CLASSDEX);
if (!File.Exists(dexFilePath))
{
logView.Log(">>>>>>将Dex文件转换为Smali文件失败: " + dexFilePath + "文件未生成!");
return false;
}
string javaPath = PathUtils.GetPath(Constants.PATH_JAVA);// java.exe
string smaliTool = PathUtils.GetPath(Constants.PATH_BAKSMALI);// baksmali.jar
if (!File.Exists(javaPath))
{
logView.Log(">>>>>>将Dex文件转换为Smali文件失败: " + javaPath + "不存在!");
return false;
}
if (!File.Exists(smaliTool))
{
logView.Log(">>>>>>将Dex文件转换为Smali文件失败: " + smaliTool + "不存在!");
return false;
}
// 生成smali位置,是Apk反编译目录下的smali目录,用于覆盖apk的smali文件
string targetDir = PathUtils.JoinPath(decompileDir, Constants.DIR_SMALI);
logView.Log("↓↓↓↓↓↓ 将Dex文件转换为Smali文件-开始 ↓↓↓↓↓↓");
string args = javaPath + " -jar " + smaliTool + " -o " + targetDir + " " + dexFilePath;
string[] ret = CmdUtils.ExecCmdAndWaitForExit(args, logView);
if (ret == null || ret.Length < 2 || !string.IsNullOrEmpty(ret[1]))
{
logView.Log(">>>>>>将Dex文件转换为Smali文件失败: 执行CMD失败");
return false;
}
logView.Log("↑↑↑↑↑↑ 将Dex文件转换为Smali文件-结束 ↑↑↑↑↑↑");
logView.BlankLine();
return true;
}
/// <summary>
/// 回编译
/// </summary>
/// <param name="decompileDir"></param>
/// <param name="logView"></param>
/// <returns></returns>
public static bool RecompileApk(string decompileDir, ILog logView)
{
if (string.IsNullOrEmpty(decompileDir) || !Directory.Exists(decompileDir))
{
logView.Log(">>>>>>回编译失败: " + decompileDir + "目录不存在!");
return false;
}
string apktoolPath = PathUtils.GetPath(Constants.PATH_APKTOOL);
if (!File.Exists(apktoolPath))
{
LogUtils.WriteLine(">>>>>>回编译失败: " + apktoolPath + "不存在!");
return false;
}
string tempApkPath = PathUtils.GetPath(Constants.PATH_TEMPAPK);
if (File.Exists(tempApkPath))
{
File.Delete(tempApkPath);
}
logView.Log("↓↓↓↓↓↓ 回编译-开始 ↓↓↓↓↓↓");
string args = apktoolPath + " b " + decompileDir + " -o " + tempApkPath;
string[] ret = CmdUtils.ExecCmdAndWaitForExit(args, logView);
if (ret == null || ret.Length < 2 || !string.IsNullOrEmpty(ret[1]))
{
logView.Log(">>>>>>回编译失败: 执行CMD失败");
return false;
}
logView.Log("↑↑↑↑↑↑ 回编译-结束 ↑↑↑↑↑↑");
logView.BlankLine();
return true;
}
/// <summary>
/// 签名
/// </summary>
/// <param name="config"></param>
/// <param name="logView"></param>
/// <returns></returns>
public static bool SignApk(KeystoreConfig config, ILog logView)
{
string tempApkPath = PathUtils.GetPath(Constants.PATH_TEMPAPK);
if (!File.Exists(tempApkPath))
{
logView.Log(">>>>>>签名失败: " + tempApkPath + "未生成!");
return false;
}
string jarsignerPath = PathUtils.GetPath(Constants.PATH_JARSIGNER);
if (!File.Exists(jarsignerPath))
{
logView.Log(">>>>>>签名失败: " + jarsignerPath + "不存在!");
return false;
}
logView.Log("↓↓↓↓↓↓ 签名-开始 ↓↓↓↓↓↓");
string args = jarsignerPath + " -digestalg SHA1 -sigalg SHA1withRSA -keystore " + config.KeystoreFilePath + " -storepass " + config.Password + " -keypass " + config.Aliaspwd + " " + tempApkPath + " " + config.Aliaskey;
string[] ret = CmdUtils.ExecCmdAndWaitForExit(args, logView);
if (ret == null || ret.Length < 2 || !string.IsNullOrEmpty(ret[1]))
{
logView.Log(">>>>>>签名失败: 执行CMD失败");
return false;
}
logView.Log("↑↑↑↑↑↑ 签名-结束 ↑↑↑↑↑↑");
logView.BlankLine();
return true;
}
/// <summary>
/// 对齐优化
/// </summary>
/// <param name="newApkPath"></param>
/// <param name="logView"></param>
/// <returns></returns>
public static bool AlignApk(string newApkPath, ILog logView)
{
string tempApkPath = PathUtils.GetPath(Constants.PATH_TEMPAPK);
if (!File.Exists(tempApkPath))
{
logView.Log(">>>>>>对齐优化失败: " + tempApkPath + "未生成!");
return false;
}
string zipalignPath = PathUtils.GetPath(Constants.PATH_ZIPALIGN);
if (!File.Exists(zipalignPath))
{
logView.Log(">>>>>>对齐优化失败: " + zipalignPath + "不存在!");
return false;
}
logView.Log("↓↓↓↓↓↓ 对齐优化-开始 ↓↓↓↓↓↓");
string args = zipalignPath + " -f 4 " + tempApkPath + " " + newApkPath;
string[] ret = CmdUtils.ExecCmdAndWaitForExit(args, logView);
if (ret == null || ret.Length < 2 || !string.IsNullOrEmpty(ret[1]))
{
logView.Log(">>>>>>对齐优化失败: 执行CMD失败");
return false;
}
logView.Log("↑↑↑↑↑↑ 对齐优化-结束 ↑↑↑↑↑↑");
logView.BlankLine();
logView.Log("导出apk为: " + newApkPath);
logView.BlankLine();
return true;
}