iOS rich push 遠(yuǎn)程通知帶圖片NotificationServiceExtension

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

push帶圖片的樣式:

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

創(chuàng)建 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開發(fā)者中心配置)忿项。注意team和sign和主target保持一致蓉冈。


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

    image.png

  4. 代碼蔓腐,解析的時候注意自己url的字典層次結(jié)構(gòu),自行修改龄句,這里的代碼和下面我發(fā)的樣例匹配

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回论,是為了方便調(diào)試發(fā)生錯誤的點在哪,通過修改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. 測試的樣例傀蓉,和上面的代碼層次匹配。注意必須設(shè)置mutable-content : 1才會走到extension里面來卿樱,當(dāng)然也要注意開啟了允許通知的權(quán)限僚害。
{
  "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還挺好用的(寶藏),可以發(fā)送push繁调,不用別人配合,通知立馬就到靶草。按照鏈接里面去下載這個mac工具 https://github.com/noodlewerk/NWPusher蹄胰,下載下來是這樣的。

    image.png

  2. 注冊didRegisterForRemoteNotificationsWithDeviceToken回調(diào)里面拿到push token奕翔。

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

  4. 數(shù)據(jù)都填好后派继,app回到后臺宾袜,點擊push即可看到效果。

調(diào)試 Notification Service Extension

  1. 直接運行主app驾窟,在extension里面打的斷點是不會走的庆猫。
  • 需要選中extension taget,然后點擊運行绅络,在彈出的框中選擇主app月培,點擊run運行起來嘁字。


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

注意點??

  1. 必須開通通知權(quán)限
  2. 發(fā)送的payload必須包含"mutable-content": 1才能進(jìn)入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. 下發(fā)的圖片鏈接默認(rèn)只支持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成功后會被系統(tǒng)刪掉举娩,所以不需要管理文件過多的問題析校。
  3. pushExtension 是否能訪問主target的文件:可以
  • 將需要訪問的那個文件,在extension的target上也打上勾勾


    打上勾勾
  • 如果需要在extension中訪問pod铜涉,那么也需要在extension target中pod進(jìn)入智玻,然后在NotificationService.swfit文件中import
  1. 發(fā)送多條通知時芙代,NotificationService會創(chuàng)建幾個實例吊奢,還是共用一個:會創(chuàng)建多個,驗證過在NotificationService打印地址纹烹,不同的通知地址不一致页滚。
    image.png
  2. extension的target的支持的iOS的系統(tǒng)和主target保持一致,以免出現(xiàn)部分手機收不到小圖push問題

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

天坑:又一手機掘宪,莫名其妙didRegisterForRemoteNotificationsWithDeviceTokendidFailToRegisterForRemoteNotificationsWithError不調(diào)用杖虾。那么看看這里
重點是:1. 關(guān)機重啟 2.或wifi bug梢莽,插卡 3.或關(guān)機插卡

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

不過經(jīng)歷上件事情鼠次,如果沒有重啟更哄,我還是定位不到原因(因為所有的條件都滿足,沒有原因呀)腥寇,這種情況下成翩,要如何解決問題,不被block需求值得思考赦役,歡迎討論和指導(dǎo)麻敌。

參考:

  1. https://onevcat.com/2016/08/notification/
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市掂摔,隨后出現(xiàn)的幾起案子术羔,更是在濱河造成了極大的恐慌,老刑警劉巖乙漓,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件级历,死亡現(xiàn)場離奇詭異,居然都是意外死亡叭披,警方通過查閱死者的電腦和手機寥殖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涩蜘,“玉大人嚼贡,你說我怎么就攤上這事≈逄常” “怎么了编曼?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長剩辟。 經(jīng)常有香客問我,道長往扔,這世上最難降的妖魔是什么贩猎? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮萍膛,結(jié)果婚禮上吭服,老公的妹妹穿的比我還像新娘。我一直安慰自己蝗罗,他們只是感情好艇棕,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布蝌戒。 她就那樣靜靜地躺著,像睡著了一般沼琉。 火紅的嫁衣襯著肌膚如雪北苟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天打瘪,我揣著相機與錄音友鼻,去河邊找鬼。 笑死闺骚,一個胖子當(dāng)著我的面吹牛彩扔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播僻爽,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼虫碉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了胸梆?” 一聲冷哼從身側(cè)響起敦捧,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎乳绕,沒想到半個月后绞惦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡洋措,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年济蝉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片菠发。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡王滤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出滓鸠,到底是詐尸還是另有隱情雁乡,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布糜俗,位于F島的核電站踱稍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏悠抹。R本人自食惡果不足惜珠月,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望楔敌。 院中可真熱鬧啤挎,春花似錦、人聲如沸卵凑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至伙判,卻和暖如春象对,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背澳腹。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工织盼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人酱塔。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓沥邻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親羊娃。 傳聞我的和親對象是個殘疾皇子唐全,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容