iOS通过本地server创建桌面快捷方式

iOS14苹果添加本地网络权限

使用苹果的bonjour技术在iOS14开始被检测,并且会弹窗访问权限弹窗,很鸡肋,即便点击拒绝也是可以正常访问本地网络的,一旦苹果检测到NSNetService相关api,就会在app的设置界面出现一个选项叫做本地网络,很难受,GCDWebServer使用套接字实现,直接访问CFNetwork层,可以跳过这个权限的检测
下载链接

原理

GCDWebServer负责启动本地server,地址:@"http://127.0.0.1:6123",端口号随意写一个,只要避开主流协议端口如443、8080就行,避免端口被占用导致的server启动失败,然后通过openURL的方式访问本地服务,会跳转到浏览器,此时浏览器地址栏中的地址就是上面的地址,会访问到我们写的html,书签存储的就是地址栏中的内容,因此需要重定向修改地址栏中的内容,iOS14系统添加了本地网络权限的同时Safari禁止了以h5标签的形式重定向,呵呵,怎么办,只有由服务器直接重定向了,以@"data:text/html;charset=UTF-8"开头,后面拼接html存储到沙盒,然后重定向访问fileURL,这样地址栏中的地址就变成我们需要的地址了

流程

  • 创建服务目录
- (instancetype)init {
    if (self = [super init]) {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsPath = paths[0];
    self.webRootDir = [documentsPath stringByAppendingPathComponent:@"server/web"];
    BOOL isDirectory = YES;
    BOOL exsit = [[NSFileManager defaultManager] fileExistsAtPath:_webRootDir isDirectory:&isDirectory];
    if(!exsit){
        [[NSFileManager defaultManager] createDirectoryAtPath:_webRootDir withIntermediateDirectories:YES attributes:nil error:nil];
    }
    return self;
}

+ (NSString *)webRedirectPath {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsPath = paths[0];
    NSString *webRootDir = [documentsPath stringByAppendingPathComponent:@"server/web"];
    BOOL isDirectory = YES;
    BOOL exsit = [[NSFileManager defaultManager] fileExistsAtPath:webRootDir isDirectory:&isDirectory];
    if(!exsit){
        [[NSFileManager defaultManager] createDirectoryAtPath:webRootDir withIntermediateDirectories:YES attributes:nil error:nil];
    }
    return [NSString stringWithFormat:@"%@/server/web/redirectPath",documentsPath];
}
  • 把index.html写入目录

这里有个注意点显示的html与写入文件html有一点不同,写入到本地的html需要在显示的内容前面拼接@"data:text/html;charset=UTF-8"

+ (NSString *)createRedirectHtmlWithModel:(BookmarkModel *)model {
    NSString *title = model.title;
    NSString *urlScheme = model.urlScheme;
    NSString *moduleID = model.moduleID;
    NSString *imageName = model.imageName;
    
    NSMutableString *taragerUrl = [NSMutableString stringWithFormat:@"%@",title];
    NSString *htmlUrlScheme = [NSString stringWithFormat:@"",@"10000",moduleID,@"1",@(1)];
    
    UIImage *image = [UIImage imageNamed:imageName];
    NSData *imageData = UIImagePNGRepresentation(image);
    
    NSString *base6ImageStr = [imageData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithCarriageReturn];
    NSString *imageUrlStr = [NSString stringWithFormat:@"";
    
    NSString *lastHtmlStr = [NSString stringWithFormat:@"document.getElementsByTagName(\"head\")[0].appendChild(addObj);    document.getElementById(\"msg\").innerHTML='
添加快捷方式到主屏幕
';}%@", base6ImageStr, base6BubbleImageStr,orientationchangeJS]; [taragerUrl appendString:htmlUrlScheme]; [taragerUrl appendString:dataUrlStr]; NSString *dataUrlEncode = [[NSString stringWithFormat:@"data:text/html;charset=UTF-8,%@", taragerUrl] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; NSString *imageUrlEncode = [imageUrlStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; NSString *lastHtmlStrEncode = [lastHtmlStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; NSData *data = [[NSString stringWithFormat:@"%@%@%@",dataUrlEncode,imageUrlEncode,lastHtmlStrEncode] dataUsingEncoding:NSUTF8StringEncoding]; [data writeToFile:[DesktopBookmark webRedirectPath] atomically:YES]; NSString *finalHtml = [NSString stringWithFormat:@"%@%@%@",taragerUrl,imageUrlStr,lastHtmlStr]; return finalHtml; }
  • 前后台事件监听
    GCDWebServer这里有个问题,默认内部监听了前后台事件,而且后台直接关闭了服务,可以配置options参数,保持服务后台运行,如果想自己控制,需要把内部的通知移除,[[NSNotificationCenter defaultCenter] removeObserver:_webServer],然后自己控制,如下,后台延时两秒关闭保证服务能被访问到,进入前台如果服务还在运行,直接关闭
- (void)applicationDidBecomeActive {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        if (self.webServer.isRunning) {
            [self.webServer stop];
            self.webServer = nil;
        }
    });
}

- (void)applicationDidEnterBackground {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(2);
        [self.webServer stop];
        self.webServer = nil;
    });
}
  • 服务生命周期
    由于服务需要持续运行,_webServer对象直到服务关闭才能允许被销毁,否则会异常,因此可以写一个单例来管理server
+ (instancetype)sharedInstance {
    static DesktopBookmark *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}
  • 重写server的response,把我们写好的html以字符串的形式配置进去
    NSString *html = [DesktopBookmark createRedirectHtmlWithModel:model];
    self.webServer = [[GCDWebServer alloc] init];
    [_webServer addDefaultHandlerForMethod:@"GET"
                              requestClass:[GCDWebServerRequest class]
                              processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {
        return [GCDWebServerDataResponse responseWithHTML:html];
    }];
  • 配置response的重定向地址
    [_webServer addHandlerForMethod:@"GET" path:@"/" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) {
        NSError *error;
        NSString *redirectPath = [[NSString alloc] initWithContentsOfFile:[DesktopBookmark webRedirectPath] encoding:NSUTF8StringEncoding error:&error];
        NSURL *url = [NSURL URLWithString:redirectPath];
        return [GCDWebServerResponse responseWithRedirect:url permanent:NO];
    }];
  • 启动server
    注意这里要用固定的端口号和bonjourName为nil,以成功绕过苹果的本地网络权限检测
[_webServer startWithPort:6123 bonjourName:nil];
  • 跳转到浏览器
NSString *urlStrWithPort = @"http://127.0.0.1:6123";
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlStrWithPort]];

你可能感兴趣的:(iOS通过本地server创建桌面快捷方式)