IOS app进行自动更新(不用App Store)
在APP没有登录到app store的前提下,如果app需要更新,如何通知用户呢?
实现思想:
app运行时,如果发现最新的版本号和现在app内的版本号不同时,在桌面的图标的右上角显示一个红色的圆圈1,蹦出一个提示框提示升级或者取消,点击提示升级时,应启动默认的浏览器跳转到能下载到最新版本app的页面。
实现流程:
1.在每一次程序开始运行的时候,都要进行一次版本对比。
2.对比的双方是,从app内部取得的版本号和从服务器端取得的需要安装的最新的版本号
3.客户端要有能取到最新版本号的接口(这里采用的方案是,在服务器端放一个.json的文件,里面只存最新的版本号)
实现代码:
//在appdelegate.m中的applicationDidBecomeActive方法里,填入以下代码
//appdelegate.m中的applicationDidBecomeActive方法会在每次程序激活时运行
- (void)applicationDidBecomeActive:(UIApplication *)application
{
//UpdateManager是github上的一个开源插件,专门用来进行这种版本更新的操作
//https://github.com/slmcmahon/UpdateManager
UpdateManager *manager = [UpdateManagersharedManager];
//设定服务器端用来取得最新app版本号的json文件(xxxx代表根据各个具体项目自己设置的文件夹名)
[manager setVersionUrl:@"/xxxx/appVersion.json"];
//提示用户有新版本后,跳转到下载页面的URL
[manager setPListUrl:@"/xxx/xxxxxxx.html"];
//进行版本号检测并更新
[manager checkForUpdates];
}
//UpdateManager.h
//
// UpdateManager.h
// ReactiveLearning
//
// Created by Stephen L. McMahon on 8/4/13.
//
// implmementation: https://gist.github.com/slmcmahon/6152160
#import <Foundation/Foundation.h>
@interface UpdateManager : NSObject
@property (nonatomic, copy) NSString *pListUrl;
@property (nonatomic, copy) NSString *versionUrl;
@property (nonatomic, copy) NSString *currentServerVersion;
+ (UpdateManager *)sharedManager;
- (void)checkForUpdates;
- (void)performUpdate:(NSString *)pageUrl;
@end
//UpdateManager.m
//具体连接的地方,需要自己完善了
//
// UpdateManager.m
// ReactiveLearning
//
// Created by Stephen L. McMahon on 8/4/13.
//
// interface: https://gist.github.com/slmcmahon/6152156
static NSString *const kPreferenceAskUpdate = @"pref_ask_update";
#import "UpdateManager.h"
#import "AFNetworking.h"
#import "UIAlertView+Blocks.h"
#import "RIButtonItem.h"
#import "OMPromise.h"
#import "OMDeferred.h"
@implementation UpdateManager
+ (UpdateManager *)sharedManager {
static UpdateManager *sharedManager = nil;
if (!sharedManager)
{
sharedManager = [[super allocWithZone:nil] init];
}
return sharedManager;
}
+ (id)allocWithZone:(NSZone *)zone {
return [selfsharedManager];
}
// This will return the version by combining both the version and build fields in
// the iOS Application Target found in the summary section of the current build target
- (NSString *)appVersion {
NSDictionary *info = [[NSBundlemainBundle] infoDictionary];
NSString *version = [info objectForKey:@"CFBundleShortVersionString"];
NSString *build = [info objectForKey:@"CFBundleVersion"];
if ([build isEqualToString:@""]) {
return [NSString stringWithFormat:@"%@", version];
} else {
return [NSString stringWithFormat:@"%@.%@", version, build];
}
}
- (BOOL)shouldAskForUpdate {
NSUserDefaults *prefs = [NSUserDefaultsstandardUserDefaults];
if ([prefs valueForKey:kPreferenceAskUpdate] == nil) {
return YES;
}
return [prefs boolForKey:kPreferenceAskUpdate];
}
// this is exposed as a public method in case you would like to create another view, an App Update
// view perhaps, that explains that there is an update for the application and allow the user to
// manually update in case they opted NOT to when initially prompted.
- (void)disableAskUpdate {
NSUserDefaults *prefs = [NSUserDefaultsstandardUserDefaults];
[prefs setBool:NOforKey:kPreferenceAskUpdate];
[prefs synchronize];
}
- (void)performUpdate:(NSString *)pageUrl {
// in case there's a network issue or some other type of failure, we go
// ahead and reset the preference so that the user will be prompted again
// to update on future sessions.
NSUserDefaults *prefs = [NSUserDefaultsstandardUserDefaults];
[prefs setBool:YESforKey:kPreferenceAskUpdate];
NSLog(@"%@", pageUrl);
NSURL *url = [NSURL URLWithString:pageUrl];
UIApplication *thisApp = [UIApplicationsharedApplication];
// turn off the badge
[thisApp setApplicationIconBadgeNumber:0];
// launch Mobile Safari, which will immediately attempt to install the application
// from the URL that was specified.
[thisApp openURL:url];
}
- (void)checkForUpdates {
NSURL *defaultSettingsFile = [[NSBundle mainBundle] URLForResource:@"DefaultSettings" withExtension:@"plist"];
NSDictionary *defaultSettings = [NSDictionary dictionaryWithContentsOfURL:defaultSettingsFile];
NSString *baseUrlString = [defaultSettings objectForKey:@"login_host_pref"];
NSString *currentVersion = [self appVersion];
OMPromise *chain = [self GET:_versionUrl parameters:nil baseUrl:baseUrlString];
[chain fulfilled:^(id result) {
NSLog(@"%@", result);
UIApplication *thisApp = [UIApplicationsharedApplication];
// assumes that the server will be responding with a JSON object containing at least:
// { CurrentVersion: "1.2.3.4" }
NSString *serverVersion = [result valueForKeyPath:@"CurrentVersion"];
if ([self compareVersion:serverVersion toVersion:currentVersion] <= 0) {
// make sure that we don't have a badge showing since there are no updates.
[thisApp setApplicationIconBadgeNumber:0];
_currentServerVersion = currentVersion;
NSLog(@"The application is up to date.");
return;
}
// we have determined that there is an update. We are going to ask the user if they would like to
// update immediately, but they may choose not to, so we will set a badge here to remind them later
// that there are pending updates.
[thisApp setApplicationIconBadgeNumber:1];
_currentServerVersion = serverVersion;
// if we have previously asked the user if they wanted to update and they refused, then we don't
// want to continue to bother them about it.
//if (![self shouldAskForUpdate]) {
//NSLog(@"There is a new version, but the user has opted to update manually later.");
//return;
//}
// this action will be performed if the user selects "OK" in the upcoming alert view. If the
// user selects "OK" then we will attempt to perform the update.
RIButtonItem *okButton = [RIButtonItem itemWithLabel:@"アップデート" action:^{
NSString *updatePageUrl = [baseUrlString stringByAppendingString:_pListUrl];
[self performUpdate:updatePageUrl];
}];
// if the user cancels the update, then we will set a persistent preference value so that it
// will not ask them on subsequent runs of the application.
RIButtonItem *cancelButton = [RIButtonItem itemWithLabel:@"キャンセル" action:^{
//[self disableAskUpdate];
}];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"XXXX からの通知(アップデート)"
message:@"新しいバージョンがあります。アップデートしますか?"
cancelButtonItem:cancelButton
otherButtonItems:okButton, nil];
[alert show];
}];
[chain failed:^(NSError *error) {
if (error) {
NSLog(@"Update error: %@", [error localizedDescription]);
}
[self setCurrentServerVersion:currentVersion];
}];
}
- (OMPromise*)GET:(NSString*)path parameters:(id)parameters baseUrl:(NSString*)baseUrl{
OMDeferred *deferred = [OMDeferred deferred];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManagermanager];
manager.requestSerializer = [AFHTTPRequestSerializerserializer];
manager.responseSerializer = [AFJSONResponseSerializerserializer];
NSString *urlString = [baseUrl stringByAppendingString:path];
DDLogVerbose(@"Connecting to %@", urlString);
[manager GET:urlString parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
[deferred fulfil:responseObject];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[deferred fail:error];
}];
return deferred.promise;
}
// compares all of the bits in the version identifier starting from the left and
// returns as soon as it finds a difference. same = 0, l > r = 1, r > l = -1
- (int)compareVersion:(NSString *)firstVersion toVersion:(NSString *)secondVersion {
NSMutableArray *fvArray = [self splitVersionString:firstVersion];
NSMutableArray *svArray = [self splitVersionString:secondVersion];
while ([fvArray count] < [svArray count]) {
[fvArray addObject:[NSNumber numberWithInt:0]];
}
while ([svArray count] < [fvArray count]) {
[svArray addObject:[NSNumber numberWithInt:0]];
}
for (int i = 0; i < [fvArray count]; i++) {
int a = [[fvArray objectAtIndex:i] intValue];
int b = [[svArray objectAtIndex:i] intValue];
if (a > b) {
return 1;
}
if (b > a) {
return -1;
}
}
return 0;
}
- (NSMutableArray *)splitVersionString:(NSString *)version {
return [NSMutableArrayarrayWithArray:[version componentsSeparatedByString:@"."]];
}
@end
//xxxx.json文件的内容
{ "CurrentVersion": "3.0.1.0" }