iOS rich push 遠程通知帶圖片NotificationServiceExtension

寫這個需求踩了很多坑,記憶深刻了,必須要記錄一下了......

push帶圖片的樣式:

push小圖.PNG
push小圖長按詳情.PNG

創建 Notification Service Extension

  1. 選中File->New->Target,選中NotificationServiceExtension


    image.png

    (坑一 Xcode bug: 我選中File->New->Target,就崩潰,80%概率崩潰,我也挺崩潰的Xcode版本12.0.1)

  2. 需要配置NotificationServiceExtension target的Bundle ID,Profile文件(需要在apple開發者中心配置)。注意team和sign和主target保持一致。


    image.png
  3. 創建extension之后會自動創建一個NotificationService文件。注意最好不要自己去修改它。(坑二自己作: 我自己最開始創建的時候是OC,后來被建議換成Swift文件,我就直接把OC文件給刪除了,但是Swift代碼并沒有生效,應該是系統沒有識別出這個文件,后來又刪掉extension重新創建的,還是不要瞎折騰的好,折騰的話需要好好研究info.plist里面的NSExtensionPrincipalClass,猜測。這里我直接用暴力刪除重建的方式解決了,不過感興趣的可以研究)

    image.png

  4. 代碼,解析的時候注意自己url的字典層次結構,自行修改,這里的代碼和下面我發的樣例匹配

import UserNotifications
import CommonCrypto

class NotificationService: UNNotificationServiceExtension {
    static let notificationServiceImageAttachmentIdentifier = "com.notificationservice.imagedownloaded"
    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        let imagekey = "smallImage"
        let dataKey = "data"
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

        guard let bestAttemptContent = bestAttemptContent else {
            return
        }
        
        //download image
        let userInfo = request.content.userInfo
        guard let data = userInfo[dataKey] as? [String: Any],
              let image = data[imagekey] as? String, !image.isEmpty,
              let imageURL = URL(string: image) else {
            contentHandler(bestAttemptContent)
            return
        }
        //此處回傳一個description,是為了方便調試發生錯誤的點在哪,通過修改bestAttemptContent.title = description。不過后來我找到了能走到斷點的方式了
        downloadAndSave(url: imageURL) { (localURL, description)  in
            guard let localURL = localURL, let attachment = try? UNNotificationAttachment(identifier: NotificationService.notificationServiceImageAttachmentIdentifier, url: localURL, options: nil) else {
                contentHandler(bestAttemptContent)
                return
            }
            bestAttemptContent.attachments = [attachment]
            contentHandler(bestAttemptContent)
        }
    }

    
    override func serviceExtensionTimeWillExpire() {
        // Called just before the extension will be terminated by the system.
        // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
        if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {
            contentHandler(bestAttemptContent)
        }
    }

    private func downloadAndSave(url: URL, handler: @escaping (_ localURL: URL?, _ des: String) -> Void) {
        let task = URLSession.shared.dataTask(with: url) { (data, res, error) in
            var localURL: URL? = nil
            guard let data = data else {
                handler(nil, "data is null")
                return
            }
            let ext = (url.absoluteString as NSString).pathExtension
            let cacheURL = FileManager.cacheDir()
            let url = cacheURL.appendingPathComponent(url.absoluteString.md5).appendingPathExtension(ext)

            guard let _ = try? data.write(to: url) else {
                handler(nil, "data write error")
                return
            }
            localURL = url
            handler(localURL, "success")
        }

        task.resume()
    }



}

extension FileManager {
    class func cacheDir() -> URL {
        let dirPaths = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)
        let cacheDir = dirPaths[0] as String
        return URL(fileURLWithPath: cacheDir)
    }
}

extension String {
    var md5: String {
        let data = Data(self.utf8)
        let hash = data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) -> [UInt8] in
            var hash = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
            CC_MD5(bytes.baseAddress, CC_LONG(data.count), &hash)
            return hash
        }
        return hash.map { String(format: "%02x", $0) }.joined()
    }
}

  1. 測試的樣例,和上面的代碼層次匹配。注意必須設置mutable-content : 1才會走到extension里面來,當然也要注意開啟了允許通知的權限。
{
  "data": {
        "smallImage": "https://onevcat.com/assets/images/background-cover.jpg",
    },
    "aps": {
        "badge": 6,
        "alert": {
            "subtitle": "sub title",
            "body": "Hello Moto!",
            "title": "Hi i ii i I I"
        },
        "sound": "default",
        "mutable-content": 1
    },
    "uri": "https://www.baidu.com/"
}

測試帶圖片的push

  1. 這個Mac工具NWPusher還挺好用的(寶藏),可以發送push,不用別人配合,通知立馬就到。按照鏈接里面去下載這個mac工具 https://github.com/noodlewerk/NWPusher,下載下來是這樣的。

    image.png

  2. 注冊didRegisterForRemoteNotificationsWithDeviceToken回調里面拿到push token。

  3. 安裝一個dev環境下的推送證書(test測試環境),然后在這個工具里選擇這個證書。

  4. 數據都填好后,app回到后臺,點擊push即可看到效果。

調試 Notification Service Extension

  1. 直接運行主app,在extension里面打的斷點是不會走的。
  • 需要選中extension taget,然后點擊運行,在彈出的框中選擇主app,點擊run運行起來。


    image.png
image.png
  • NotificationService 打上斷點
  • app退到后臺,用NWPusher工具發送一個圖片的payload。
  • 收到通知時會進入斷點
  1. 開始以為不能調試,也不進斷點,直接在
    contentHandler(bestAttemptContent) 前修改bestAttemptContent.title,看我修改的push title是否生效了來測試哪一步出現了問題。

注意點??

  1. 必須開通通知權限
  2. 發送的payload必須包含"mutable-content": 1才能進入extesnion
  3. code sign和team要注意和主target保持一致,否則報以下錯。

Embedded binary is not signed with the same certificate as the parent app. Verify the embedded binary target's code sign settings match the parent app's.

  1. 下發的圖片鏈接默認只支持https,若要支持http需要修改extension中的info.plist。
    image.png
  2. 下載小圖保存的沙盒地址是這樣的(驗證app extension和主app是隔離開的,不是同一個沙盒哦),file:///var/mobile/Containers/Data/PluginKitPlugin/EEF3E755-E79B-4C7F-A83F-F20642C805C3/Library/Caches/。write的圖片在push成功后會被系統刪掉,所以不需要管理文件過多的問題。
  3. pushExtension 是否能訪問主target的文件:可以
  • 將需要訪問的那個文件,在extension的target上也打上勾勾


    打上勾勾
  • 如果需要在extension中訪問pod,那么也需要在extension target中pod進入,然后在NotificationService.swfit文件中import。
  1. 發送多條通知時,NotificationService會創建幾個實例,還是共用一個:會創建多個,驗證過在NotificationService打印地址,不同的通知地址不一致。
    image.png
  2. extension的target的支持的iOS的系統和主target保持一致,以免出現部分手機收不到小圖push問題

天坑:同事review代碼時想看下我的需求,結果他手機沒顯示小圖(他手機iOS14.3, iPhone X),懷疑我代碼有問題。我把我手機升級和他一樣的系統,測試沒問題,又試了好幾個別的手機都沒問題,到處查資料,搜索了一天無果。 后來隨機提到重啟手機過沒有,因為不知為啥他手機升級過后系統bug很多,結果重啟完再push,他收到圖片push啦。想哭......還是重啟大法好啊......

天坑:又一手機,莫名其妙didRegisterForRemoteNotificationsWithDeviceTokendidFailToRegisterForRemoteNotificationsWithError不調用。那么看看這里
重點是:1. 關機重啟 2.或wifi bug,插卡 3.或關機插卡

在你崩潰之前,記得重啟手機,說不定很多問題壓根不用解決。

不過經歷上件事情,如果沒有重啟,我還是定位不到原因(因為所有的條件都滿足,沒有原因呀),這種情況下,要如何解決問題,不被block需求值得思考,歡迎討論和指導。

參考:

  1. https://onevcat.com/2016/08/notification/
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內容