已越狱的iOS上查看app是否已安装

想在MoeApps里显示该app是否已经安装。如果已安装则是直接显示“已安装”而不是程序的价格。
首先是看到了iHasApp这个库。实际测试发现这个只能看到app被他们收录而且是带有URL Scheme的才能够显示,也就是说并不能显示全部的app。不过这个倒是能通过审核上架。

但是咱的要求不是上架而是要显示完全部的app。
网上查了下,找到了这个
不过如果说知道BundleID(比如com.apple.mobilesafari)的话倒是能直接用,可惜MoeApps只知道纯数字的AppID,不过倒是可以通过先用AppID从iTuneslookupAPI获取到BundleID再进行判断是否已安装。
于是整个代码如下:

CheckAppInstalled.h

#import
@class CheckAppInstalled;
@protocol CheckAppInstalledDelegate
@optional
- (void)CheckApp:(CheckAppInstalled *)app isInstalled:(BOOL)installed;
@end
@interface CheckAppInstalled : NSObject
-(CheckAppInstalled *)initWithdelegate:(NSObject *)delegate;
-(void)checkappinstalledWithAppID:(NSString *)appid;
-(void)checkappinstalledWithBundleID:(NSString *)bundleid;
-(void)cancelRequest;
@end

CheckAppInstalled.m

#import "CheckAppInstalled.h"
@interface CheckAppInstalled ()
@property (assign, nonatomic) NSObject *delegate;
@property (retain, nonatomic) NSURLConnection *connection;
@end
@implementation CheckAppInstalled
-(CheckAppInstalled *)initWithdelegate:(NSObject *)delegate{
self = [super init];
self.delegate = delegate;
return self;
}
-(void)checkappinstalledWithAppID:(NSString *)appid{
NSString *path = [NSString stringWithFormat:@iTunesSearshBaseURL,appid,[[NSLocale currentLocale] objectForKey:NSLocaleLanguageCode],[[NSLocale currentLocale] objectForKey:NSLocaleCountryCode]];
NSURL *url = [NSURL URLWithString:path];
NSURLRequest *urlrequest = [NSURLRequest requestWithURL:url];
self.connection = [[NSURLConnection alloc] initWithRequest:urlrequest delegate:self];
[self.connection start];
}
-(void)checkappinstalledWithBundleID:(NSString *)bundleid{
if (APCheckIfAppInstalled(bundleid)){
NSLog(@"App installed: %@", bundleid);
if([self.delegate respondsToSelector:@selector(CheckApp:isInstalled:)]){
[self.delegate CheckApp:self isInstalled:YES];
}
}else{
NSLog(@"App not installed: %@", bundleid);
if([self.delegate respondsToSelector:@selector(CheckApp:isInstalled:)]){
[self.delegate CheckApp:self isInstalled:NO];
}
}
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSLog(@"%@",response);
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(@"%@,%@",error.localizedDescription,connection);
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions
error:NULL];
if ([[json objectForKey:@"resultCount"] integerValue] >= 1) {
NSString *identifier = [[[json objectForKey:@"results"] objectAtIndex:0] valueForKey:@"bundleId"];
if (APCheckIfAppInstalled(identifier)){
NSLog(@"App installed: %@", identifier);
if([self.delegate respondsToSelector:@selector(CheckApp:isInstalled:)]){
[self.delegate CheckApp:self isInstalled:YES];
}
}else{
NSLog(@"App not installed: %@", identifier);
if([self.delegate respondsToSelector:@selector(CheckApp:isInstalled:)]){
[self.delegate CheckApp:self isInstalled:NO];
}
}
}
}
-(void)cancelRequest{
[self.connection cancel];
self.connection = nil;
}
// Declaration
BOOL APCheckIfAppInstalled(NSString *bundleIdentifier); // Bundle identifier (eg. com.apple.mobilesafari) used to track apps
// Implementation
BOOL APCheckIfAppInstalled(NSString *bundleIdentifier){
static NSString *const cacheFileName = @"com.apple.mobile.installation.plist";
NSString *relativeCachePath = [[@"Library" stringByAppendingPathComponent: @"Caches"] stringByAppendingPathComponent: cacheFileName];
NSDictionary *cacheDict = nil;
NSString *path = nil;
// Loop through all possible paths the cache could be in
for (short i = 0; 1; i++)
{
switch (i) {
case 0: // Jailbroken apps will find the cache here; their home directory is /var/mobile
path = [NSHomeDirectory() stringByAppendingPathComponent: relativeCachePath];
break;
case 1: // App Store apps and Simulator will find the cache here; home (/var/mobile/) is 2 directories above sandbox folder
path = [[NSHomeDirectory() stringByAppendingPathComponent: @"../.."] stringByAppendingPathComponent: relativeCachePath];
break;
case 2: // If the app is anywhere else, default to hardcoded /var/mobile/
path = [@"/var/mobile" stringByAppendingPathComponent: relativeCachePath];
break;
default: // Cache not found (loop not broken)
return NO;
break; }
BOOL isDir = NO;
if ([[NSFileManager defaultManager] fileExistsAtPath: path isDirectory: &isDir] && !isDir) // Ensure that file exists
cacheDict = [NSDictionary dictionaryWithContentsOfFile: path];
if (cacheDict) // If cache is loaded, then break the loop. If the loop is not "broken," it will return NO later (default: case)
break;
}
NSDictionary *system = [cacheDict objectForKey: @"System"]; // First check all system (jailbroken) apps
if ([system objectForKey: bundleIdentifier]) return YES;
NSDictionary *user = [cacheDict objectForKey: @"User"]; // Then all the user (App Store /var/mobile/Applications) apps
if ([user objectForKey: bundleIdentifier]) return YES;
// If nothing returned YES already, we'll return NO now
return NO;
}
@end

使用方法就是初始化:

CheckAppInstalled *checkapp = [[CheckAppInstalled alloc] initWithdelegate:self];//不要忘记引用CheckAppInstalledDelegate
[checkapp checkappinstalledWithAppID:self.appid];

然后返回的时候如果是已经安装了的程序那么将按钮显示成APP INSTALLED:

-(void)CheckApp:(CheckAppInstalled *)app isInstalled:(BOOL)installed{
if (installed == YES) {
[self.storebtn setTitle:@"APP INSTALLED" forState:UIControlStateNormal];
}
}

不过别忘了当viewWillDisappear的时候记得:
[checkapp cancelRequest];
不然会导致未加载完就切换其他页面导致崩溃喔~

效果就是加载完app详细页面之后,如果app已经安装过那么稍等一会就能看到已安装的提示了。
不过因为需要访问一次iTunes的API所以速度较慢,如果说本身就已经知道了BundleID那么直接就可以显示该程序是否已经安装了。

另外关于app内开启其他app,这个似乎需要root权限也就是说app必须不能sandbox而且是在/Applications下才行(一般ipa包安装的app都是存在/var/mobile/Applications/下的,而且都是用UUID来区分。/Applicaitons下的一般都为系统级别程序,如iTunes、AppStore、Safari、Settings等,Cydia也是一样)。而越狱机器要这么装的话只能依靠Cydia…便利程度自然没有直接用ipa那么好。所以也就没有继续往这方面考虑。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

:b1 :b2 :b3 :b4 :b5 :b6 more »

Note: Commenter is allowed to use '@User+blank' to automatically notify your reply to other commenter. e.g, if ABC is one of commenter of this post, then write '@ABC '(exclude ') will automatically send your comment to ABC. Using '@all ' to notify all previous commenters. Be sure that the value of User should exactly match with commenter's name (case sensitive).