iOS中正确的截屏姿势

时间 2014-12-22 09:20:48CocoaChina

原文http://www.cocoachina.com/ios/20141222/10713.html

主题iOS开发

昨天写了个用到截屏功能的插件,结果问题不断,今天终于解决好了,把debug过程中所有尝试过的截屏方法都贴出来吧~

第一种

这是iOS 3时代开始就被使用的方法,它被废止于iOS 7。iOS的私有方法,效率很高。

#importextern"C"CGImageRefUIGetScreenImage();UIImage* screenshot(void)NS_DEPRECATED_IOS(3_0,7_0);UIImage* screenshot(){UIImage*image = [UIImageimageWithCGImage:UIGetScreenImage()];returnimage;}

第二种

这是在比较常见的截图方法,不过不支持Retina屏幕。

UIImage* screenshot(UIView*);UIImage* screenshot(UIView*view){UIGraphicsBeginImageContext(view.frame.size);[view.layer renderInContext:UIGraphicsGetCurrentContext()];UIImage*image =UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();returnimage;}

第三种

从iPhone 4、iPod Touch 4开始,Apple逐渐采用Retina屏幕,于是在iOS 4的SDK中我们有了,上面的截图方法也自然变成了这样。

UIImage* screenshot(UIView*)NS_AVAILABLE_IOS(4_0);UIImage* screenshot(UIView*view){if(UIGraphicsBeginImageContextWithOptions!=NULL){UIGraphicsBeginImageContextWithOptions(view.frame.size,NO,0.0);}else{UIGraphicsBeginImageContext(view.frame.size);}[view.layer renderInContext:UIGraphicsGetCurrentContext()];UIImage*image =UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();returnimage;}

第四种

或许你会说有时Hook的是一个按钮的方法,用第三个方法的话,根本找不到view来传值,不过还好,iOS 7又提供了一些UIScreen的API。

UIImage* screenshot(void)NS_AVAILABLE_IOS(7_0);UIImage* screenshot(){UIView* view = [[UIScreenmainScreen] snapshotViewAfterScreenUpdates:YES];if(UIGraphicsBeginImageContextWithOptions!=NULL){UIGraphicsBeginImageContextWithOptions(view.frame.size,NO,0.0);}else{UIGraphicsBeginImageContext(view.frame.size);}[view.layer renderInContext:UIGraphicsGetCurrentContext()];UIImage*image =UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();returnimage;}

第五种

@interfaceSBScreenShotter: NSObject+ (id)sharedInstance;-(void)saveScreenshot:(_Bool)arg1;@end

然后直接

[[SBScreenShotter sharedInstance] saveScreenshot:YES];

一道白光之后,咱们就模拟了用户截屏的动作,不过这个方法在只需要截屏时比较好,如果要对屏幕录像(其实就是不断截图)的话,那不得闪瞎了。。而且我们也拿不到UIImage的实例去拼成一个视频呀。即使通过Hook别的类拿到UIImage的实例,这个私有API的效率大概也是达不到30FPS的视频要求的。

那么现在我们有5种方法了,第一种是私有API,私有API通常效率和质量都比Documented API的好,可是它在iOS 7以后就被废除了啊,就没有别的了吗?

答案当然是————有的!用Private Framework来完成这项任务!直接走底层拿屏幕的缓冲数据,然后生成UIImage的实例。

第六种

#import #import #import #import #import extern"C"IOReturn IOSurfaceLock(IOSurfaceRef buffer, uint32_t options, uint32_t *seed);extern"C"IOReturn IOSurfaceUnlock(IOSurfaceRef buffer, uint32_t options, uint32_t *seed);extern"C"size_t IOSurfaceGetWidth(IOSurfaceRef buffer);extern"C"size_t IOSurfaceGetHeight(IOSurfaceRef buffer);extern"C"IOSurfaceRef IOSurfaceCreate(CFDictionaryRefproperties);extern"C"void*IOSurfaceGetBaseAddress(IOSurfaceRef buffer);extern"C"size_t IOSurfaceGetBytesPerRow(IOSurfaceRef buffer);externconstCFStringRefkIOSurfaceAllocSize;externconstCFStringRefkIOSurfaceWidth;externconstCFStringRefkIOSurfaceHeight;externconstCFStringRefkIOSurfaceIsGlobal;externconstCFStringRefkIOSurfaceBytesPerRow;externconstCFStringRefkIOSurfaceBytesPerElement;externconstCFStringRefkIOSurfacePixelFormat;enum{kIOSurfaceLockReadOnly  =0x00000001,kIOSurfaceLockAvoidSync =0x00000002};UIImage* screenshot(void);UIImage* screenshot(){IOMobileFramebufferConnection connect;kern_return_t result;CoreSurfaceBufferRef screenSurface =NULL;io_service_t framebufferService = IOServiceGetMatchingService(kIOMasterPortDefault,IOServiceMatching("AppleH1CLCD"));if(!framebufferService)framebufferService = IOServiceGetMatchingService(kIOMasterPortDefault,IOServiceMatching("AppleM2CLCD"));if(!framebufferService)framebufferService = IOServiceGetMatchingService(kIOMasterPortDefault,IOServiceMatching("AppleCLCD"));result = IOMobileFramebufferOpen(framebufferService, mach_task_self(),0, &connect);result = IOMobileFramebufferGetLayerDefaultSurface(connect,0, &screenSurface);uint32_t aseed;IOSurfaceLock((IOSurfaceRef)screenSurface,0x00000001, &aseed);size_t width = IOSurfaceGetWidth((IOSurfaceRef)screenSurface);size_t height = IOSurfaceGetHeight((IOSurfaceRef)screenSurface);CFMutableDictionaryRefdict;size_t pitch = width*4, size = width*height*4;intbPE=4;charpixelFormat[4] = {'A','R','G','B'};dict =CFDictionaryCreateMutable(kCFAllocatorDefault,0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);CFDictionarySetValue(dict, kIOSurfaceIsGlobal, kCFBooleanTrue);CFDictionarySetValue(dict, kIOSurfaceBytesPerRow,CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt32Type, &pitch));CFDictionarySetValue(dict, kIOSurfaceBytesPerElement,CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt32Type, &bPE));CFDictionarySetValue(dict, kIOSurfaceWidth,CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt32Type, &width));CFDictionarySetValue(dict, kIOSurfaceHeight,CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt32Type, &height));CFDictionarySetValue(dict, kIOSurfacePixelFormat,CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt32Type, pixelFormat));CFDictionarySetValue(dict, kIOSurfaceAllocSize,CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt32Type, &size));IOSurfaceRef destSurf = IOSurfaceCreate(dict);IOSurfaceAcceleratorRef outAcc;IOSurfaceAcceleratorCreate(NULL,0, &outAcc);IOSurfaceAcceleratorTransferSurface(outAcc, (IOSurfaceRef)screenSurface, destSurf, dict,NULL);IOSurfaceUnlock((IOSurfaceRef)screenSurface, kIOSurfaceLockReadOnly, &aseed);CFRelease(outAcc);CGDataProviderRefprovider =CGDataProviderCreateWithData(NULL,  IOSurfaceGetBaseAddress(destSurf), (width * height *4),NULL);CGImageRefcgImage =CGImageCreate(width, height,8,8*4, IOSurfaceGetBytesPerRow(destSurf),CGColorSpaceCreateDeviceRGB(), kCGImageAlphaNoneSkipFirst |kCGBitmapByteOrder32Little,provider,NULL,YES, kCGRenderingIntentDefault);UIImage*image = [UIImageimageWithCGImage:cgImage];returnimage;}

需要注意的是,第五种方法需要修改一下IOMobileFramebuffer的头文件。

typedefvoid* IOMobileFramebufferConnection;

In the reversed header, IOMobileFramebufferConnection is typedef'd to io_connect_t, which is typedef'd to io_object_t, which is mach_port_t, which is __darwin_mach_port_t, which is __darwin_mach_port_name_t, which is __darwin_natural_t, which is unsigned int! Int just happens to be pointer-sized on 32-bit, but is not under 64-bit。

——StackoverFlow

修改好的头文件顺便也丢上来吧,解压后放在Project的根目录下。

如果你使用的是theos的话,记得在Makefile里写上,

YOUR_TWEAK_NAME_PRIVATE_FRAMEWORKS = IOSurface IOKit IOMobileFramebuffer

YOUR_TWEAK_NAME_CFLAGS = -I./headers/ -I./headers/IOSurface

如果是XCode上的Logos Tweak的话,在Build Settings -> Search Paths -> Header Search Paths里面添加一项:$(PROJECT_DIR)/YOUR_PROJECT_NAME/headers, 搜索方式为recursive. 最后在Build Phases里Link上IOSurface IOKit IOMobileFramebuffer这三个私有Framework。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容