寫這個(gè)需求踩了很多坑,記憶深刻了,必須要記錄一下了......
push帶圖片的樣式:
|
|
|
| --- | --- |
創(chuàng)建 Notification Service Extension
-
選中File->New->Target,選中NotificationServiceExtension
image(坑一 Xcode bug: 我選中File->New->Target,就崩潰,80%概率崩潰,我也挺崩潰的Xcode版本12.0.1)
-
需要配置NotificationServiceExtension target的Bundle ID,Profile文件(需要在apple開發(fā)者中心配置)。注意team和sign和主target保持一致。
image -
創(chuàng)建
extension之后會(huì)自動(dòng)創(chuàng)建一個(gè)NotificationService文件。注意最好不要自己去修改它。(坑二自己作: 我自己最開始創(chuàng)建的時(shí)候是OC,后來被建議換成Swift文件,我就直接把OC文件給刪除了,但是Swift代碼并沒有生效,應(yīng)該是系統(tǒng)沒有識(shí)別出這個(gè)文件,后來又刪掉extension重新創(chuàng)建的,還是不要瞎折騰的好,折騰的話需要好好研究info.plist里面的NSExtensionPrincipalClass,猜測(cè)。這里我直接用暴力刪除重建的方式解決了,不過感興趣的可以研究)image 代碼,解析的時(shí)候注意自己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
}
//此處回傳一個(gè)description,是為了方便調(diào)試發(fā)生錯(cuò)誤的點(diǎn)在哪,通過修改bestAttemptContent.title = description。不過后來我找到了能走到斷點(diǎn)的方式了
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()
}
}
- 測(cè)試的樣例,和上面的代碼層次匹配。注意必須設(shè)置
mutable-content : 1才會(huì)走到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/"
}
測(cè)試帶圖片的push
-
這個(gè)Mac工具
NWPusher還挺好用的(寶藏),可以發(fā)送push,不用別人配合,通知立馬就到。按照鏈接里面去下載這個(gè)mac工具 https://github.com/noodlewerk/NWPusher,下載下來是這樣的。image 注冊(cè)
didRegisterForRemoteNotificationsWithDeviceToken回調(diào)里面拿到push token。安裝一個(gè)dev環(huán)境下的推送證書(test測(cè)試環(huán)境),然后在這個(gè)工具里選擇這個(gè)證書。
數(shù)據(jù)都填好后,app回到后臺(tái),點(diǎn)擊push即可看到效果。
調(diào)試 Notification Service Extension
- 直接運(yùn)行主app,在extension里面打的斷點(diǎn)是不會(huì)走的。
-
需要選中extension taget,然后點(diǎn)擊運(yùn)行,在彈出的框中選擇主app,點(diǎn)擊run運(yùn)行起來。
image

- 在
NotificationService打上斷點(diǎn) - app退到后臺(tái),用
NWPusher工具發(fā)送一個(gè)圖片的payload。 - 收到通知時(shí)會(huì)進(jìn)入斷點(diǎn)
- 開始以為不能調(diào)試,也不進(jìn)斷點(diǎn),直接在
contentHandler(bestAttemptContent)前修改bestAttemptContent.title,看我修改的push title是否生效了來測(cè)試哪一步出現(xiàn)了問題。
注意點(diǎn)??
- 必須開通通知權(quán)限
- 發(fā)送的
payload必須包含"mutable-content": 1才能進(jìn)入extesnion - code sign和team要注意和主target保持一致,否則報(bào)以下錯(cuò)。
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.
-
下發(fā)的圖片鏈接默認(rèn)只支持
https,若要支持http需要修改extension中的info.plist。image 下載小圖保存的沙盒地址是這樣的(驗(yàn)證app extension和主app是隔離開的,不是同一個(gè)沙盒哦),
file:///var/mobile/Containers/Data/PluginKitPlugin/EEF3E755-E79B-4C7F-A83F-F20642C805C3/Library/Caches/。write的圖片在push成功后會(huì)被系統(tǒng)刪掉,所以不需要管理文件過多的問題。pushExtension 是否能訪問主target的文件:可以
-
將需要訪問的那個(gè)文件,在extension的target上也打上勾勾
image 如果需要在extension中訪問pod,那么也需要在extension target中pod進(jìn)入,然后在
NotificationService.swfit文件中import。
-
發(fā)送多條通知時(shí),
NotificationService會(huì)創(chuàng)建幾個(gè)實(shí)例,還是共用一個(gè):會(huì)創(chuàng)建多個(gè),驗(yàn)證過在NotificationService打印地址,不同的通知地址不一致。image extension的target的支持的iOS的系統(tǒng)和主target保持一致,以免出現(xiàn)部分手機(jī)收不到小圖push問題
天坑:同事review代碼時(shí)想看下我的需求,結(jié)果他手機(jī)沒顯示小圖(他手機(jī)iOS14.3, iPhone X),懷疑我代碼有問題。我把我手機(jī)升級(jí)和他一樣的系統(tǒng),測(cè)試沒問題,又試了好幾個(gè)別的手機(jī)都沒問題,到處查資料,搜索了一天無果。 后來隨機(jī)提到重啟手機(jī)過沒有,因?yàn)椴恢獮樯端謾C(jī)升級(jí)過后系統(tǒng)bug很多,結(jié)果重啟完再push,他收到圖片push啦。想哭......還是重啟大法好啊......
天坑:又一手機(jī),莫名其妙didRegisterForRemoteNotificationsWithDeviceToken和didFailToRegisterForRemoteNotificationsWithError不調(diào)用。那么看看這里
重點(diǎn)是:1. 關(guān)機(jī)重啟 2.或wifi bug,插卡 3.或關(guān)機(jī)插卡
在你崩潰之前,記得重啟手機(jī),說不定很多問題壓根不用解決。
不過經(jīng)歷上件事情,如果沒有重啟,我還是定位不到原因(因?yàn)樗械臈l件都滿足,沒有原因呀),這種情況下,要如何解決問題,不被block需求值得思考,歡迎討論和指導(dǎo)。
參考:
作者:落夏簡(jiǎn)葉
鏈接:http://www.itdecent.cn/p/30ac89ab7797
來源:簡(jiǎn)書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。







