美文网首页
iOS通过本地server创建桌面快捷方式

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

作者: 野码道人 | 来源:发表于2021-07-10 18:07 被阅读0次

    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:@"<html><head><meta content=\"yes\" name=\"apple-mobile-web-app-capable\" /><meta content=\"text/html; charset=UTF-8\" http-equiv=\"Content-Type\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\" /><title>%@</title></head><body bgcolor=\"#ffffff\">",title];
        NSString *htmlUrlScheme = [NSString stringWithFormat:@"<a href=\"%@",urlScheme];
        NSString *dataUrlStr = [NSString stringWithFormat:@"%@=%@&%@=%@\" id=\"qbt\" style=\"display: none;\"></a>",@"10000",moduleID,@"1",@(1)];
        
        UIImage *image = [UIImage imageNamed:imageName];
        NSData *imageData = UIImagePNGRepresentation(image);
        
        NSString *base6ImageStr = [imageData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithCarriageReturn];
        NSString *imageUrlStr = [NSString stringWithFormat:@"<span id=\"msg\"></span></body><script>if (window.navigator.standalone == true) {    var lnk = document.getElementById(\"qbt\");    var evt = document.createEvent('MouseEvent');    evt.initMouseEvent('click');    lnk.dispatchEvent(evt);}else{    var addObj=document.createElement(\"link\");    addObj.setAttribute('rel','apple-touch-icon-precomposed');    addObj.setAttribute('href','data:image/png;base64,%@');",base6ImageStr];
        
        UIImage *bubbleImage = [UIImage imageNamed:@"bubble"];
        NSData *bubbleImageData = UIImagePNGRepresentation(bubbleImage);
        NSString *base6BubbleImageStr = [bubbleImageData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithCarriageReturn];
        
        NSString *orientationchangeJS = @"<script>window.addEventListener('orientationchange',function () {if (window.orientation == 0) {document.getElementById('img-div').setAttribute('style', 'width:267px;position:fixed;bottom:8px;left:50vw;margin-left:-133.5px;');document.getElementById('img-bottom').setAttribute('style', 'width:267px;');} else {document.getElementById('img-div').setAttribute('style', 'width:160px;position:fixed;bottom:8px;left:50vw;margin-left:-80px;');document.getElementById('img-bottom').setAttribute('style', 'width:160px;');}},false);</script>";
        
        NSString *lastHtmlStr = [NSString stringWithFormat:@"document.getElementsByTagName(\"head\")[0].appendChild(addObj);    document.getElementById(\"msg\").innerHTML='<div style=\"font-size:14px;position:fixed;width:100vw;top: 30px;text-align:center;left:0;\"> <div style=\"width:75px;margin: 0 auto;border-radius:12px;margin-bottom:10px;overflow:hidden;box-shadow: 0 6px 14px 0 rgba(9,40,71,0.2);\"><img id=\"i\" src=\"data:image/png;base64,%@\" style=\"width:75px;\"></div> 添加快捷方式到主屏幕 </div><div  id=\"img-div\" style=\"width:267px;position:fixed;bottom:8px;left:50vw;margin-left:-133.5px;\"><img id=\"img-bottom\" src=\"data:image/png;base64,%@\" style=\"width:267px;\"></div>';}</script>%@</html>", 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创建桌面快捷方式

          本文链接:https://www.haomeiwen.com/subject/mincpltx.html