iOS攔截H5的標籤讀取文件

HTML的input標籤在 type = "file" 時,即變爲文件上傳控件,瀏覽器會去監聽這個標籤,根據標籤的另一個 accept 字段的內容去調取各個平臺的相關係統資源,如圖片,視頻,聲音等,iOS也不例外。經過這個標籤,移動端的H5頁面就有直接獲取系統資源的能力。可是有時候咱們並不想讓H5拿到原始的文件,或者是但願可以加工一下。好比:文件的壓縮,文件格式轉換,文件的編輯等。git

<form>
    <input type="file" accept="image/gif, image/jpeg"/>
</form>
複製代碼

也許大部分狀況下咱們會直接採用JS交互的方式。這種方式可定義和可控的程度都比較高,弊端也就是須要交互的地方都要跟H5協商好每一個頁面去寫交互代碼。github

本文經過攔截的方式,筆者不認爲是一種可靠的方案,由於隨着iOS系統的升級極可能就變了,不利於項目的穩定,給維護帶來麻煩。不過做爲另一種解決問題的思路,感興趣仍是能夠看看的。瀏覽器


先以圖片的獲取爲例

1. 尋找切入口

經過Debug View Hierarchy工具查看視圖樹尋找點擊H5標籤的彈窗 第一層 bash

第一層
顯然這個ActionSheet沒法決定最終是哪一張圖片,這個切入點不合適,咱們再往裏面看。
拍照頁面

在拍照頁面,看到了熟悉的身影,UIImagePickerController. UIImagePickerController類是獲取選擇圖片和視頻的用戶接口,咱們能夠用UIImagePickerController選擇咱們所須要的圖片和視頻。微信

image.png
再看一下相冊也是 UIImagePickerController,這下比較能夠肯定就是這個了。

2.嘗試hook UIImagePickerControllerDelegate

先把UIImagePickerController的delegate屬性的setter方法替換成咱們本身的,以便後續修改一些代理方法。app

+ (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的私有類,咱們沒法直接使用,可使用字符串加載的方式。ide

UIImagePickerControllerDelegate的代理對象

熟悉UIImagePickerController的同窗應該知道不管是相機仍是相冊,咱們最終拿到圖片都是經過這個代理方法:工具

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

把這個代理的實現替換掉ui

+ (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裏面的信息都是什麼,這裏就不作過多解釋了。須要的同窗能夠查看 官方文檔spa

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. www.jianshu.com/p/626f663e9…
  2. www.w3school.com.cn/media/media…

筆者和朋友作了一個小副業,微信公衆號,替你省錢,分享還能賺點小錢; 幫忙關注,支持一下,權當請我喝咖啡,謝謝。 若是很差用,能夠取消。

微信公衆號
相關文章
相關標籤/搜索