已越狱的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).

This site uses Akismet to reduce spam. Learn how your comment data is processed.