iOS拦截H5的<input>标签读取文件

HTML的input标签在 type = "file" 时,即变为文件上传控件,浏览器会去监听这个标签,根据标签的另外一个 accept 字段的内容去调取各个平台的相关系统资源,如图片,视频,声音等,iOS也不例外。通过这个标签,移动端的H5页面就有直接获取系统资源的能力。但是有时候我们并不想让H5拿到原始的文件,或者是希望能够加工一下。比如:文件的压缩,文件格式转换,文件的编辑等。

<form>
    <input type="file" accept="image/gif, image/jpeg"/>
</form>

也许大部分情况下我们会直接采用JS交互的方式。这种方式可定义和可控的程度都比较高,弊端也就是需要交互的地方都要跟H5协商好每个页面去写交互代码。

本文通过拦截的方式,笔者不认为是一种可靠的方案,因为随着iOS系统的升级很可能就变了,不利于项目的稳定,给维护带来麻烦。不过作为另外一种解决问题的思路,感兴趣还是可以看看的。


先以图片的获取为例

1. 寻找切入口

通过Debug View Hierarchy工具查看视图树寻找点击H5标签的弹窗
第一层

第一层

显然这个ActionSheet无法决定最终是哪一张图片,这个切入点不合适,我们再往里面看。
拍照页面

在拍照页面,看到了熟悉的身影,UIImagePickerController.
UIImagePickerController类是获取选择图片和视频的用户接口,我们可以用UIImagePickerController选择我们所需要的图片和视频。

image.png

再看一下相册也是UIImagePickerController,这下比较可以确定就是这个了。

2.尝试hook UIImagePickerControllerDelegate

先把UIImagePickerController的delegate属性的setter方法替换成我们自己的,以便后续修改一些代理方法。

+ (void)hookDelegate {
    if (!isDelegateSetterHooked){
        Method originalMethod = class_getInstanceMethod([UIImagePickerController class], @selector(setDelegate:));
        Method replaceMethod = class_getInstanceMethod([UIImagePickerController class], @selector(new_setDelegate:));
        method_exchangeImplementations(originalMethod, replaceMethod);
        isDelegateSetterHooked = YES;
    }
}

/**
 替换后的delegate setter

 @param delegate delegate
 */
- (void)new_setDelegate:(id<UIImagePickerControllerDelegate>)delegate {
    
    [self new_setDelegate:delegate];//调用原来的方法实现,让UIImagePickerController的代理有值
    
    SEL swizzledSEL = @selector(swizzled_imagePickerController:didFinishPickingMediaWithInfo:);
    SEL originSEL = @selector(imagePickerController:didFinishPickingMediaWithInfo:);
    
    if ([self isKindOfClass:[UIImagePickerController class]]) {
        if (!delegate) {//代理清空时,去掉代理方法的hook
            Class class = NSClassFromString(@"WKFileUploadPanel");
            unHook_delegateMethod(class,swizzledSEL,originSEL);
            return;
        }
        hook_delegateMethod([delegate class], originSEL, [self class], swizzledSEL, swizzledSEL);
    }
}

通过我们自己的setter方法中的断点可以看出,此时的代理对象是WKFileUploadPanel的实例,这个类是WKWebKit的私有类,我们无法直接使用,可以使用字符串加载的方式。

UIImagePickerControllerDelegate的代理对象

熟悉UIImagePickerController的同学应该知道不论是相机还是相册,我们最终拿到图片都是通过这个代理方法:

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey, id> *)info;

把这个代理的实现替换掉

+ (void)hookDelegate {
    SEL swizzledSEL = @selector(swizzled_imagePickerController:didFinishPickingMediaWithInfo:);
    SEL originSEL = @selector(imagePickerController:didFinishPickingMediaWithInfo:);
    
    if (swizzledSEL && originSEL) {
       Class class = NSClassFromString(@"WKFileUploadPanel");
        hook_delegateMethod(class, originSEL, [UIImagePickerController class], swizzledSEL, swizzledSEL);
    }
}

/**
 替换代理方法的实现
 */
static void hook_delegateMethod(Class originalClass, SEL originalSel, Class replacedClass, SEL replacedSel, SEL noneSel)  {
    //原实例方法
    Method originalMethod = class_getInstanceMethod(originalClass, originalSel);
    //替换的实例方法
    Method replacedMethod = class_getInstanceMethod(replacedClass, replacedSel);
    
    if (!originalMethod) {// 如果没有实现 delegate 方法,则手动动态添加
        Method noneMethod = class_getInstanceMethod(replacedClass, noneSel);
        class_addMethod(originalClass, originalSel, method_getImplementation(noneMethod), method_getTypeEncoding(noneMethod));
        return;
    }
    
    // 向实现 delegate 的类中添加新的方法
    class_addMethod(originalClass, replacedSel, method_getImplementation(replacedMethod), method_getTypeEncoding(replacedMethod));
    
    // 重新拿到添加被添加的 method, 因为替换的方法已经添加到原类中了, 应该交换原类中的两个方法
    Method newMethod = class_getInstanceMethod(originalClass, replacedSel);
    if(!isDelegateMethodHooked && originalMethod && newMethod) {
        method_exchangeImplementations(originalMethod, newMethod);// 实现交换
        isDelegateMethodHooked = YES;
    }
}

- (void)swizzled_imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {

}
info数据

这是我们就能拿到原始图像了,想怎么加工就怎么加工。
这个info里面的信息都是什么,这里就不做过多解释了。需要的同学可以查看 官方文档

3. 回传信息给H5

上面我们知道,UIImagePickerController的代理对象是WKFileUploadPanel类的实例,那么该类中必定实现了UIImagePickerControllerDelegate的代理方法。所以我们在加工完数据之后,调用一下原始实现,把我们的加工数据给它,从而实现替换。代码参见上面的:

- (void)swizzled_imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info

其他文件类型的拦截

<input>标签支持上传哪些媒体类型,可以查看MIME类型参考手册

这里给出几个大类,如下表格:

描述
audio/* 接受所有的声音文件。
video/* 接受所有的视频文件。
image/* 接受所有的图像文件。
MIME_type 一个有效的 MIME 类型,不带参数。请参阅 IANA MIME 类型,获得标准 MIME 类型的完整列表。

相应的HTML

<form>
    <input type="file" accept="audio/*"/>
</form>
<form>
    <input type="file" accept="video/*"/>
</form>
<form>
    <input type="file" accept="image/*"/>
</form>
<form>
    <input type="file" accept="MIME_type"/>
</form>

笔者尝试了一下,iOS对audio/*类型的支持似乎不是很友好,这个识别出来跟最后的MIME_type一样能选择所有文件。视频和图片这是只能选择相应类型。其它文件类型的限制和实现就留由读者们自己探索吧。

另外,在实际的需求当中可能只是需要替换H5页面的UIImagePickerControllerDelegate,也不希望影响到其他模块。所以在demo中加了替换和恢复的代码,以及相应时机,具体请看
github


参考文章和文档:

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