通过appt获取apk文件的基本信息
E: application
节点下的android:icon
信息./aapt dump xmltree /Users/mac123/Desktop/携程旅行.apk --file AndroidManifest.xml
部分输出结果如下:
E: application (line=128)
A: http://schemas.android.com/apk/res/android:theme(0x01010000)=@0x7f0c01c5
A: http://schemas.android.com/apk/res/android:label(0x01010001)="携程旅行" (Raw: "携程旅行")
A: http://schemas.android.com/apk/res/android:icon(0x01010002)=@0x7f030000
A: http://schemas.android.com/apk/res/android:name(0x01010003)="ctrip.base.component.CtripBaseApplication" (Raw: "ctrip.base.component.CtripBaseApplication")
A: http://schemas.android.com/apk/res/android:allowBackup(0x01010280)=false
A: http://schemas.android.com/apk/res/android:largeHeap(0x0101035a)=true
A: http://schemas.android.com/apk/res/android:supportsRtl(0x010103af)=false
A: http://schemas.android.com/apk/res/android:resizeableActivity(0x010104f6)=false
A: http://schemas.android.com/apk/res/android:networkSecurityConfig(0x01010527)=@0x7f070006
上述结果中0x7f030000
就是我们需要的Id信息,根据此Id在resource资源文件总获取缩略图。
Print the contents of the resource table from the APK.
./aapt dump resources /Users/mac123/Desktop/携程旅行.apk
部分输出结果如下:
type mipmap id=03 entryCount=3
resource 0x7f030000 mipmap/common_ic_launcher
(ldpi-v4) (file) res/mipmap-ldpi-v4/common_ic_launcher.png type=PNG
(mdpi-v4) (file) res/mipmap-mdpi-v4/common_ic_launcher.png type=PNG
(hdpi-v4) (file) res/mipmap-hdpi-v4/common_ic_launcher.png type=PNG
(xhdpi-v4) (file) res/mipmap-xhdpi-v4/common_ic_launcher.png type=PNG
(xxhdpi-v4) (file) res/mipmap-xxhdpi-v4/common_ic_launcher.png type=PNG
(anydpi-v26) (file) res/mipmap-anydpi-v26/common_ic_launcher.xml type=XML
resource 0x7f030001 mipmap/ic_launcher
(mdpi-v4) (file) res/mipmap-mdpi-v4/ic_launcher.png type=PNG
(hdpi-v4) (file) res/mipmap-hdpi-v4/ic_launcher.png type=PNG
(xhdpi-v4) (file) res/mipmap-xhdpi-v4/ic_launcher.png type=PNG
(xxhdpi-v4) (file) res/mipmap-xxhdpi-v4/ic_launcher.png type=PNG
(xxxhdpi-v4) (file) res/mipmap-xxxhdpi-v4/ic_launcher.png type=PNG
resource 0x7f030002 mipmap/leak_canary_icon
(mdpi-v4) (file) res/mipmap-mdpi-v4/leak_canary_icon.png type=PNG
(hdpi-v4) (file) res/mipmap-hdpi-v4/leak_canary_icon.png type=PNG
(xhdpi-v4) (file) res/mipmap-xhdpi-v4/leak_canary_icon.png type=PNG
(xxhdpi-v4) (file) res/mipmap-xxhdpi-v4/leak_canary_icon.png type=PNG
(xxxhdpi-v4) (file) res/mipmap-xxxhdpi-v4/leak_canary_icon.png type=PNG
(anydpi-v26) (file) res/mipmap-anydpi-v26/leak_canary_icon.xml type=XML
我们需要的信息就在type mipmap 或者type drawable中,一般情况下,上述输出结果中只有其一,因此代码中需要分支判断,单独处理。
根据Id信息,定位到resource 0x7f030000
之后,解析其内部信息,即可得到各个分辨率的缩略图路径。
res/mipmap-ldpi-v4/common_ic_launcher.png
res/mipmap-mdpi-v4/common_ic_launcher.png
res/mipmap-hdpi-v4/common_ic_launcher.png
res/mipmap-xhdpi-v4/common_ic_launcher.png
res/mipmap-xxhdpi-v4/common_ic_launcher.png
由于考虑性能及效率,只拉取分辨率最高的一张(xxhdpi-v4),然后通过命令从.apk文件中解压出即可。
解压命令如下:
unzip -j /Users/mac123/Desktop/携程旅行.apk "res/mipmap-xxhdpi-v4/common_ic_launcher.png" -d /Users/mac123/Desktop/getAppIconDecode
//获取bundleID
./aapt dump packagename 蘑菇街.apk
//获取版本号:
./aapt dump badging my.apk | grep “VersionName” | sed -n "s/.*versionName='\([^']*\).*/\1/p"
//获取versionCode
./aapt dump badging /path/test.apk | grep -o ‘versionCode=[^,]*’ | cut -d’=’ -f 2 | cut -d ‘ ‘ -f 1 | tr -d "'"
//获取icon
./aapt dump badging /Users/mac123/Desktop/美团.apk | sed -n "/^application: /s/.*icon='\([^']*\).*/\1/p"
//获取apk文件的信息
- (NSDictionary *)getApkBaseInfoWithPath:(NSString *)apkPath {
NSMutableDictionary *baseInfoDic = [[NSMutableDictionary alloc] init];
//获取包名
NSArray *packageNameArgArr = @[@"dump", @"packagename", apkPath];
NSString *packageName = [self runAAPTCommondWithArguments:packageNameArgArr];
if (packageName != nil && packageName.length > 0) {
packageName = [[packageName componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] componentsJoinedByString:@""];
[baseInfoDic setValue:packageName forKey:@"name"];
}
NSArray *allPropertyArr = [NSArray arrayWithObjects:@"dump",@"badging",apkPath, nil];
NSString *allProperty = [self runAAPTCommondWithArguments:allPropertyArr];
NSArray *outputArr = [allProperty componentsSeparatedByString:@"\n"];
//获取版本号
NSArray *packageArr = [outputArr filteredArrayUsingPredicate: [NSPredicate predicateWithFormat: @"SELF contains[c] 'versionName'"]];
NSArray *packageStrArr = [[packageArr firstObject] componentsSeparatedByString:@" "];
for (NSString *propertyStr in packageStrArr) {
if ([propertyStr rangeOfString:@"="].location == NSNotFound) {
continue;
}
NSArray *inlineArr = [propertyStr componentsSeparatedByString:@"="];
NSString *keyName = [inlineArr firstObject];
NSString *value = [inlineArr lastObject];
value = [value stringByReplacingOccurrencesOfString:@"'" withString:@""];
if (keyName.length > 0 && value.length > 0) {
[baseInfoDic setValue:value forKey:keyName];
}
}
//缩略图路径
NSArray *containsApplicationArr = [outputArr filteredArrayUsingPredicate: [NSPredicate predicateWithFormat: @"SELF contains[c] 'application'"]];
NSArray *applicationArr = [[containsApplicationArr lastObject] componentsSeparatedByString:@" "];
for (NSString *propertyStr in applicationArr) {
if ([propertyStr rangeOfString:@"="].location == NSNotFound) {
continue;
}
NSArray *inlineArr = [propertyStr componentsSeparatedByString:@"="];
NSString *keyName = [inlineArr firstObject];
NSString *value = [inlineArr lastObject];
value = [value stringByReplacingOccurrencesOfString:@"'" withString:@""];
if (keyName.length > 0 && value.length > 0) {
if ([keyName isEqualToString:@"icon"]) {
if ([Help isImageType:value.pathExtension]) {
[baseInfoDic setValue:value forKey:keyName];
}else {
NSString *iconPath = [self getApkIconFromMinmapFileWithApkPath:apkPath];
[baseInfoDic setValue:iconPath forKey:keyName];
}
}else {
[baseInfoDic setValue:value forKey:keyName];
}
}
}
NSLog(@"app baseInfoDic = %@", baseInfoDic);
return baseInfoDic;
}
//从minmap文件中获取apk缩略图路径
- (NSString *)getApkIconFromMinmapFileWithApkPath:(NSString *)apkPath {
NSString *resultString = @"";
NSArray *manifesetXMLParams = [NSArray arrayWithObjects:@"dump", @"xmltree", apkPath, @"--file", @"AndroidManifest.xml", nil];
NSString *manifestAll = [self runAAPTCommondWithArguments:manifesetXMLParams];
NSArray *outputArr = [manifestAll componentsSeparatedByString:@"\n"];
NSArray *applicationArr = [outputArr filteredArrayUsingPredicate: [NSPredicate predicateWithFormat: @"SELF contains[c] 'E: application'"]];
NSInteger applicationIndex = [outputArr indexOfObject:[applicationArr firstObject]];
outputArr = [outputArr subarrayWithRange:NSMakeRange(applicationIndex+1, 10)];
NSArray *containIconArr = [outputArr filteredArrayUsingPredicate: [NSPredicate predicateWithFormat: @"SELF contains[c] 'android:icon'"]];
if (containIconArr.count > 0) {
NSString *propertyStr = containIconArr.firstObject;
if ([propertyStr rangeOfString:@"="].location != NSNotFound) {
NSArray *inlineArr = [propertyStr componentsSeparatedByString:@"="];
NSString *value = [inlineArr lastObject];
value = [value stringByReplacingOccurrencesOfString:@"'" withString:@""];
value = [value stringByReplacingOccurrencesOfString:@"@" withString:@""];
if (value.length > 0) {
resultString = [[self getAppIconPathsArrWithId:value apkPath:apkPath] lastObject];
}
}
}
return resultString;
}
//根据id查找appicon,从mimap或者drawable中截取路径
- (NSArray *)getAppIconPathsArrWithId:(NSString *)IdStr apkPath:(NSString *)apkPath {
NSMutableArray *resultArr = [[NSMutableArray alloc] init];
NSArray *minmapParams = [NSArray arrayWithObjects:@"dump", @"resources", apkPath, nil];
NSString *resourcesArr = [self runAAPTCommondWithArguments:minmapParams];
NSArray *outputArr = [resourcesArr componentsSeparatedByString:@"\n"];
NSPredicate *mipmapPredicate = [NSPredicate predicateWithFormat: @"SELF contains[c] 'type mipmap'"];
NSPredicate *drawablePredicate = [NSPredicate predicateWithFormat: @"SELF contains[c] 'type drawable'"];
if ([outputArr filteredArrayUsingPredicate:mipmapPredicate].count > 0) {
NSArray * containMipmapArr = [outputArr filteredArrayUsingPredicate:mipmapPredicate];
NSString *typeOfMinmap = [containMipmapArr firstObject];
NSInteger index = [outputArr indexOfObject:typeOfMinmap];
if (index != NSNotFound) {
NSArray *minmapNodeArr = [outputArr subarrayWithRange:NSMakeRange(index+1, 50)];
NSArray *iconsArr = [minmapNodeArr filteredArrayUsingPredicate: [NSPredicate predicateWithFormat: @"SELF contains[c] %@", IdStr]];
if (iconsArr.count > 0) {
NSInteger iconArrIndex = [minmapNodeArr indexOfObject:[iconsArr firstObject]];
if (iconArrIndex != NSNotFound) {
NSArray *images = [minmapNodeArr subarrayWithRange:NSMakeRange(iconArrIndex+1, 10)];
for (NSString *subStr in images) {
if ([subStr rangeOfString:@"resource"].location != NSNotFound) {
break;
}
NSArray *allIconsArr = [subStr componentsSeparatedByString:@" "];
if([subStr rangeOfString:@"type="].location != NSNotFound) {
NSString *iconPath = [allIconsArr objectAtIndex:allIconsArr.count-2];
iconPath = [iconPath stringByReplacingOccurrencesOfString:@"\"" withString:@""];
iconPath = [[iconPath componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] componentsJoinedByString:@""];
if ([Help isImageType:iconPath.pathExtension]) {
[resultArr addObject:iconPath];
}
}else {
NSString *iconPath = [allIconsArr lastObject];
iconPath = [iconPath stringByReplacingOccurrencesOfString:@"\"" withString:@""];
iconPath = [[iconPath componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] componentsJoinedByString:@""];
if ([Help isImageType:iconPath.pathExtension]) {
[resultArr addObject:iconPath];
}
}
}
}
}
}
}else if([outputArr filteredArrayUsingPredicate:drawablePredicate].count > 0) {
NSArray * containDrawableArr = [outputArr filteredArrayUsingPredicate:drawablePredicate];
NSString *typeOfDrawble = [containDrawableArr firstObject];
NSInteger index = [outputArr indexOfObject:typeOfDrawble];
if (index != NSNotFound) {
NSArray *drawableNodeArr = [outputArr subarrayWithRange:NSMakeRange(index+1, 50)];
NSArray *iconsArr = [drawableNodeArr filteredArrayUsingPredicate: [NSPredicate predicateWithFormat: @"SELF contains[c] %@", IdStr]];
if (iconsArr.count > 0) {
NSInteger iconArrIndex = [drawableNodeArr indexOfObject:[iconsArr firstObject]];
if (iconArrIndex != NSNotFound) {
NSArray *images = [drawableNodeArr subarrayWithRange:NSMakeRange(iconArrIndex+1, 10)];
for (NSString *subStr in images) {
if ([subStr rangeOfString:@"resource"].location != NSNotFound) {
break;
}
NSArray *tempStrArr = [subStr componentsSeparatedByString:@" "];
NSString *typeStr = [tempStrArr lastObject];
NSArray *typeArr = [typeStr componentsSeparatedByString:@"="];
if (typeArr.count != 2 || ![typeArr.firstObject isEqualToString:@"type"]) {
continue;
}
if (![Help isImageType:[typeArr lastObject]]) {
continue;
}
NSString *iconPath = [tempStrArr objectAtIndex:tempStrArr.count - 2];
iconPath = [iconPath stringByReplacingOccurrencesOfString:@"\"" withString:@""];
iconPath = [[iconPath componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] componentsJoinedByString:@""];
if ([Help isImageType:iconPath.pathExtension]) {
[resultArr addObject:iconPath];
}
}
}
}
}
}
return resultArr;
}
//运行appt
- (NSString *)runAAPTCommondWithArguments:(NSArray *)arguments {
NSTask *zipTask = [[NSTask alloc] init];
NSString *launchPath = [[NSBundle mainBundle] pathForResource:@"aapt" ofType:@""];
if (![[DMFileManager defaultManager] fileExistsAtPath:launchPath]) {
return nil;
}
zipTask.launchPath = launchPath;
zipTask.arguments = arguments;
NSPipe *pipe;
pipe = [NSPipe pipe];
[zipTask setStandardOutput: pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[zipTask launch];
NSData *data;
data = [file readDataToEndOfFile];
NSString *output;
output = [[NSString alloc] initWithData: data
encoding: NSUTF8StringEncoding];
[file closeFile];
[zipTask waitUntilExit];
// NSLog(@"output = %@", output);
if (output.length <= 0) {
return nil;
}
return output;
}