需求如下:
先说下使用场景和需求:
1)登录时版本更新,用户启动app登录或自动登录时,发送请求需要带本应用的当前版本号,服务返回的结果中包含版本更新信息,并且一般立刻弹出提示让用户选择,我们会分为两种,一种是强制更新(左图)一种是非强制更新。
2)在我的→设置→检查更新 模块中进行版本的更新。
思路:
版本更新一般分为:强制更新和非强制性更新。
强制更新的本质是由服务器来判断,需要拿当前版本号(服务器数据库中存储的应用版本号)和应用发送的版本号相比较。
具体实现如下:
我们项目是组件式开发,因此这部分功能的实现,我们放到了Launch模块组件中。
先创建一个服务单例,其中包含(开启第三方、登录成功之后数据初始化、Jpush、Bugly、WX、UM、IQ、RN、配置token相关信息、token刷新无效 则跳转登录)等相关服务。
本篇文章只针对检查版本更新做介绍。
在程序开始启动的时候,增加如下代码
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyWindow];
[[SCLaunchService shareInstance]startSCServices:nil];
[[SCLaunchService shareInstance] openThirdServices:launchOptions block:nil env:SC_Env_Type];
[self showLaunchView];
[[SCLaunchService shareInstance] checkAppVersion:nil];
return YES;
}
其中:
[[SCLaunchService shareInstance] checkAppVersion:nil];
这个功能就是程序一启动就会检查版本情况
#import <Foundation/Foundation.h>
#import "smartSDKHeader.h"
#import "SCEnvParameterConfig.h"
@interface SCLaunchService : NSObject
+ (instancetype)shareInstance;
/**
开启服务(登录成功后数据初始化)
*/
- (void)startSCServices:(VOIDBLOCK)started;
/**
开启三方服务
@param started started description
*/
- (void)openThirdServices:(NSDictionary *)launchOptions block:(VOIDBLOCK)started env:(KEnvType)envType;
@interface SCLaunchService ()<JPUSHRegisterDelegate,JoyRouteProtocol>
@end
@implementation SCLaunchService
//用于登录页面完成之后相关服务的开启
-(void)routeParam:(NSDictionary *)param block:(JoyRouteBlock)block{
if ([[param objectForKey:@"type"] isEqualToString:@"startSCServices"]) {
[self startSCServices:^{
block?block(param,nil):nil;
}];
}
}
//创建一个单例
static SCLaunchService *instance = nil;
+ (instancetype)shareInstance
{
static dispatch_once_t onceToken ;
dispatch_once(&onceToken, ^{
instance = [[SCLaunchService alloc] init] ;
}) ;
return instance ;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [super allocWithZone:zone];
});
return instance;
}
- (instancetype)init{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [super init];
});
return instance;
}
- (id)copyWithZone:(NSZone *)zone{
return instance;
}
- (id)mutableCopyWithZone:(NSZone *)zone{
return instance;
}
#pragma mark 开启服务
- (void)startSCServices:(VOIDBLOCK)started{
[self configTokenInfo];
started?started():nil;
}
#pragma mark 开启三方服务
- (void)openThirdServices:(NSDictionary *)launchOptions block:(VOIDBLOCK)started env:(KEnvType)envType{
[[SCEnvParameterConfig shareInstance] config:envType];
// [LocalCfg config:ENVSTR];
[[SCUser shareInstance]setCacheInfo];
[self addJpushWith:launchOptions];
[self startBuglyService];
[self startWXService];
[self startUMServices];
[self startIQKeyBoard];
[self startRNService];
[[JoyRouter sharedInstance]configScheme:@"scbu"];
started?started():nil;
}
#pragma mark 配置token相关信息
- (void)configTokenInfo{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshTokenInvalid) name:KTOKEN_REFRESH_FAILURED object:nil];
}
#pragma mark token刷新无效 则跳转登录
- (void)refreshTokenInvalid{
[[SCBleService shareInstance] stopService];
[[JoyRouter sharedInstance]routerModule:@"user" path:@"login" actionType:JoyRouteActionTypeSetNavRooter parameter:nil block:nil];
}
#pragma mark 极光推送
- (void)addJpushWith:(NSDictionary *)launchOptions{
#if __has_include("JPUSHService.h")
JPUSHRegisterEntity * entity = [[JPUSHRegisterEntity alloc] init];
UIUserNotificationSettings *settings = [UIUserNotificationSettings
settingsForTypes:UIUserNotificationTypeBadge categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
// [UIApplication sharedApplication].applicationIconBadgeNumber = 0;
entity.types = JPAuthorizationOptionAlert|JPAuthorizationOptionBadge|JPAuthorizationOptionSound;
[JPUSHService registerForRemoteNotificationConfig:entity delegate:self];
NSString *jpushKey = [SCEnvParameterConfig shareInstance].JPUSH_APPKEY;
BOOL isEnvRelease = [SCEnvParameterConfig shareInstance].envType == KEnvTypeRelease;
[JPUSHService setupWithOption:launchOptions appKey:jpushKey
channel:nil
apsForProduction:isEnvRelease
advertisingIdentifier:nil];
[JPUSHService registrationIDCompletionHandler:^(int resCode, NSString *registrationID) {
NSUserDefaults *deaults = [NSUserDefaults standardUserDefaults];
[deaults setObject:registrationID forKey:@"registrationID"];
NSLog(@"resCode : %d,registrationID: %@",resCode,registrationID);
}];
#else
#endif
}
#pragma mark bugly服务
- (void)startBuglyService{
NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
[Bugly startWithAppId:[SCEnvParameterConfig shareInstance].BUGLY_APPID];
BuglyConfig * config = [[BuglyConfig alloc] init];
config.reportLogLevel = BuglyLogLevelWarn;
config.version = version;
[Bugly startWithAppId:[SCEnvParameterConfig shareInstance].BUGLY_APPID config:config];
}
#pragma mark 微信服务
- (void)startWXService{
#if __has_include("WXApi.h")
[WXApi registerApp:[SCEnvParameterConfig shareInstance].WEIXIN_APPID];
#else
#endif
}
#pragma mark 开启友盟服务
- (void)startUMServices{
NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
[MobClick startWithConfigure:UMConfigInstance];
[MobClick setAppVersion:version];
NSDictionary *config = @{};
[GLLogging setupWithConfiguration:config];
}
- (void)startIQKeyBoard{
//默认为NO
IQKeyboardManager *manager = [IQKeyboardManager sharedManager]; // 获取类库的单例变量
manager.enable = YES; // 控制整个功能是否启用
manager.shouldResignOnTouchOutside = YES;//控制点击背景是否收起键盘
// manager.toolbarManageBehaviour = IQAutoToolbarBySubviews; // 有多个输入框时,可以通过点击Toolbar 上的“前一个”“后一个”按钮来实现移动到不同的输入框
manager.enableAutoToolbar = NO; // 控制是否显示键盘上的工具条
// manager.shouldShowTextFieldPlaceholder = NO; // 是否显示占位文字
// manager.placeholderFont = [UIFont systemFontOfSize:17]; // 设置占位文字的字体
manager.keyboardDistanceFromTextField = 10.0f; //// 输入框距离键盘的距离
}
#pragma mark jpush delegate
- (void)jpushNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(NSInteger))completionHandler {
#if __has_include("JPUSHService.h")
// Required
NSDictionary * userInfo = notification.request.content.userInfo;
NSDictionary *data = [NSDictionary dictionaryWithJsonString:userInfo[@"data"]];
if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
[JPUSHService handleRemoteNotification:userInfo];
}
if ([userInfo[@"msgType"] isEqualToString:@"BppPayCallbackMessage"]) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"PayBcak" object:self userInfo:@{@"orderId":data[@"orderId"],@"status":data[@"status"],@"payType":data[@"payType"]}];
return;
}
if (@available(iOS 10.0, *)) {
if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]] && [userInfo[@"msgType"] isEqualToString:@"message_center_detail"]) {
NSDictionary *aps = [userInfo objectForKey:@"aps"];
NSString *title = nil;
if ([aps isKindOfClass:NSDictionary.class]){
title = [aps objectForKey:@"alert"];
}
/// iOS10处理远程推送
[JPUSHService handleRemoteNotification:userInfo];
/// 应用处于前台收到推送的时候转成本地通知 ===========================
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.alertBody =title?:@"告警通知";
notification.userInfo = userInfo;
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
return;
}
}
completionHandler(UNNotificationPresentationOptionAlert); // 需要执行这个方法,选择是否提醒用户,有Badge、Sound、Alert三种类型可以选择设置
#else
#endif
}
- (void)jpushNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler {
NSDictionary * userInfo = response.notification.request.content.userInfo;
NSDictionary *data = [NSDictionary dictionaryWithJsonString:userInfo[@"data"]];
//相关页面跳转在这个地方实现...
if([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
#if __has_include("JPUSHService.h")
[JPUSHService handleRemoteNotification:userInfo];
#else
#endif
}
completionHandler(); // 系统要求执行这个方法
}
@end
以上是使用应用启动前这个服务的大致内容。
为了避免这个页面业务太多,针对不同的服务,我们通过分类的形式进行扩展。
比如AppService(检查版本更新服务)
#import "SCLaunchService.h"
typedef void (^checkVersionBlock)(NSInteger isNewst); //0不是最新版本 1:是最新版本 3:接口异常
@interface SCLaunchService (AppService)
//版本更新
- (void)checkAppVersion:(checkVersionBlock)block;
@end
#import "SCLaunchService+AppService.h"
#import "SCBUApiRequest+SCAppVersion.h"
#import "SCVersionUpdateView.h"
@implementation SCLaunchService (AppService)
#pragma mark 检测版本
- (void)checkAppVersion:(checkVersionBlock)block{
//网络请求调服务端接口 检测是否需要强更
[SCBUApiRequest checkAppVersion:@{} Success:^(SCBUApiResponse *response) {
NSDictionary *result = response.responseObject;
//获取本地版本号
NSString *curVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
//获取服务端版本号
NSString *serviceVersion = result[@"required_version"];
int currVersionInt = [self convertVersionStrToInt:curVersion];
int serviceVersionInt = [self convertVersionStrToInt:serviceVersion];
if (currVersionInt<serviceVersionInt) {
//required_version 版本号 type更新类型(如果是force 那是就强更、非得话是暂不更新和立即更新) result[@"update_content"] 更新说明
SCVersionUpdateView *tipView = [[SCVersionUpdateView alloc] initWithVersion:result[@"required_version"] updateType:result[@"type"] updateCon:result[@"update_content"] updateUrl:result[@"update_url"]];
[tipView show];
block?block(0):nil;
}else{
block?block(1):nil;
}
} Failure:^(NSError *error) {
block?block(2):nil;
}];
}
//版本号转换成整形 把版本号转换成数值
- (int)convertVersionStrToInt:(NSString *)versionStr{
__block int version = 0;
[[versionStr componentsSeparatedByString:@"."] enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (idx == 0) {
version+= [obj intValue]*1000;
}else if (idx==1){
version+= [obj intValue]*100;
}else if (idx==2){
version+= [obj intValue];
}
}];
return version;
}
@end
接口调用形式
@implementation SCBUApiRequest (SCAppVersion)
+ (void)checkAppVersion:(NSDictionary *)dic Success:(ApiSuccessBlock)success Failure:(ApiFailureBlock)failure{
NSString *versionCheckUrl = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"sc_version_check"];
if (versionCheckUrl) {
NSString *url = [NSString stringWithFormat:@"%@%@",[SCEnvParameterConfig shareInstance].API_HOST_URL,versionCheckUrl];
[SCBUApiRequest getJsonWithUrl:url param:nil Success:success failure:failure app:SCAppRequestTypeTSP];
}
}
@end