本文主要講解了一下幾個(gè)方面:
(1)bitmap.copy()發(fā)生np的原因及Config 為何為null;
(2)不同系統(tǒng)setMetadata的實(shí)現(xiàn)機(jī)制及其差異性對(duì)比;
(3)不同版本support 庫MediaSessioCompat::setMetadata實(shí)現(xiàn)差異枫疆。
一潭兽、深坑背景?
既然是深坑聚请,必然是難以發(fā)現(xiàn)(偶現(xiàn)問題),大家可能在想MediaSessionCompat::setMetadata()一個(gè)方法調(diào)用還未出現(xiàn)什么問題,正因我也是這么想拇派,所以問題發(fā)生了 拍屑。
問題現(xiàn)場 ?:在RDM問題列表中發(fā)現(xiàn)了一個(gè)np問題 (只有幾例途戒,上報(bào)機(jī)型均為小米系統(tǒng),api19)僵驰,來自bitmap.copy() 喷斋,對(duì)應(yīng)到項(xiàng)目調(diào)用處 mMediaSessionCompat.setMetadata() 如下圖所示 ,這里是專門針對(duì)小米系統(tǒng)鎖屏進(jìn)行的相關(guān)處理蒜茴。
一看便知 星爪,傳入的congfig為null導(dǎo)致 ,為什么config為null粉私,我們后面再講顽腾,先來看看為什么setMetadata會(huì)導(dǎo)致bitmap.copy ,帶著疑問去看看setMetadata的調(diào)用過程诺核。
二抄肖、MediaSessioCompat::setMetadata 調(diào)用過程
? ? ? 在講解MediaSessioCompat::setMetadata前,先了解一下RemoteControlClient窖杀、MediaSession等多媒體控制相關(guān)類漓摩。
? ? ? ?RemoteControlClient 主要用于鎖屏狀態(tài)控制音樂播放,鎖屏界面由系統(tǒng)提供入客,在Android 4.0 (API 14)提供管毙,目前已經(jīng)被標(biāo)記為deprecated(目前已推薦使用MediaSession)腿椎。 MediaSession? 是一個(gè)專門解決媒體播放時(shí)界面和服務(wù)通訊問題的框架,在Android 5.0才被引進(jìn)夭咬,可應(yīng)用在鎖屏啃炸、耳機(jī)線控、藍(lán)牙耳機(jī)卓舵。
? ? ? 而這里的MediaSessioCompat 是support包提供的一個(gè)兼容性類 南用,從命名就可以看出來,setMetadata方法作用主要是更新數(shù)據(jù)(包括歌曲名边器、歌手训枢、背景圖、字幕等信息)忘巧,因此對(duì)于音頻類系統(tǒng)鎖屏是相當(dāng)重要的恒界。
MediaSessioCompat::setMetadata()內(nèi)部實(shí)踐上調(diào)用的是 MediaSessionImpl::setMetadata,既然是兼容類 砚嘴,對(duì)應(yīng)的MediaSessionImpl 肯定在不同版本有著不同實(shí)現(xiàn) (api 21 作為區(qū)分節(jié)點(diǎn))十酣,如下:
(1)MediaSessionImplBase::setMetadata? (api <21)
? ? ? ?在MediaSessionImplBase內(nèi)部 主要進(jìn)行了兩大操作,先對(duì)metadata數(shù)據(jù)中的 bitmaps數(shù)據(jù)進(jìn)行拷貝 (發(fā)現(xiàn)bitmap相關(guān)操作了)际长,然后又對(duì)api 19 和 api 14 分別進(jìn)行了區(qū)分(后面解釋為什么會(huì)進(jìn)行區(qū)分)耸采,調(diào)用各自的setMetadata。
沒錯(cuò)工育,bitmap.copy()的np問題正是發(fā)生在這里虾宇,通過Bitmap相關(guān)api注釋知,bitmap.getConfig()可能返回為null如绸,奈何這竟然是一個(gè)系統(tǒng)bug嘱朽,迭代了這么多版本google竟未修復(fù)。
這里的Config其實(shí)指的是 bitmap的類型 :可以看到確實(shí)有兩類返回的config為null
? ? ? 具體什么情況為null了 怔接,我在Bitmapcreate Bitmap(Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter) 實(shí)現(xiàn)中找到了蛛絲馬跡搪泳,如下圖中注釋:gif 文件會(huì)產(chǎn)出 null configs,但是對(duì)于我們項(xiàng)目來說扼脐,專輯封面采用的都不是gif格式岸军,因此對(duì)于bitmap.getConfig()返回為null還是為弄清(那位大神知道請(qǐng)告訴一下,謝謝)瓦侮。
? ? ? ?雖然這個(gè)np問題可以輕而易舉的解決艰赞,單本人不甘停留如此,還有有個(gè)疑問為什么要clone bitmap相關(guān)數(shù)據(jù)(注釋已經(jīng)說了肚吏,防止recycle)猖毫,那在什么時(shí)候會(huì)recycle 。
(2)在那里進(jìn)行recycle操作
在(1)中已經(jīng)說過须喂,MediaSessionImplBase里面會(huì)進(jìn)行兩部分操作吁断,第二部分操作是setMetadata,其內(nèi)部實(shí)現(xiàn)如下:
創(chuàng)建editor 坞生、put 數(shù)據(jù)仔役、最后apply。 ?這里分別使用了RemoteControlClient.MetadataEditor和MediaMetadataEditor (api 19 才提供)是己。
? ? ? ? apply()才是關(guān)鍵 又兵,內(nèi)部實(shí)現(xiàn)如下:其中mOriginalArtWork就是專門用來緩存封面的bitmap ,可能考慮到鎖屏比較頻繁卒废,防止頻繁調(diào)用吧沛厨,但是bitmap.recycle() 不是在2.3.3以后就不推薦使用嗎,真不知道為何在此還要如此實(shí)現(xiàn)摔认。
? ? ? 細(xì)心一點(diǎn)的同學(xué)可能發(fā)現(xiàn)逆皮,在這個(gè)里面竟然調(diào)用了MediaSession::setMetadata()根據(jù),前面對(duì)MediaSession(5.0 引進(jìn))介紹知参袱,在5.0以下實(shí)現(xiàn)方式肯定不一致:
另外 电谣,在高版本中又是如何處理相關(guān)問題的了,帶著這些疑問我們直接分析MediaSession::setMetadata()方法即可抹蚀。
三剿牺、MediaSession::setMetadata()
? ? ? ?在第二部分,主要講解了MediaSessioCompat::setMetadata api 21以下的調(diào)用环壤,在api >=21時(shí)晒来,其內(nèi)部調(diào)用的是 MediaSession::setMetadata(),具體如下:
這里也是分為兩步 :更新metadata 數(shù)據(jù)郑现,執(zhí)行更新 湃崩。
相比api <21 情況,這里并未clone 封面背景bitmap 懂酱,保證了一定性能竹习。內(nèi)部使用的是scaleBitmap 最后實(shí)際調(diào)用Bitmapcreate Bitmap(Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter) ,整個(gè)過程簡單明了列牺。
而對(duì)于執(zhí)行更新整陌,這里采用的IPC 方式,上面的mBinder 其實(shí)是 ISession.aidl 對(duì)象瞎领,效率相比之前的提高不少泌辫。(這里具體就不展開。)
四九默、不同版本support 庫MediaSessioCompat::setMetadata實(shí)現(xiàn)差異
? ? ? ?前面講的MediaSessioCompat 是基于support-v4-23.4.0 版本震放,可以看到MediaSessioCompat為了配合RemoteContorlClient::apply() 方法中的 bitmap.recyle() , 在setMetadta內(nèi)部對(duì)進(jìn)行了Clone。
? ? ? 而在support-v4-22.2.1 中發(fā)現(xiàn)驼修,并未對(duì)封面bitmap 進(jìn)行可能殿遂,這種情況下诈铛,發(fā)生crash是必然的,可能正因如此墨礁,google在后面的support 庫修復(fù)了這個(gè)問題幢竹。support-v4-23.4.0版本發(fā)生np ,原因前面已經(jīng)說了恩静,是因?yàn)閎itmap.getConfig()為null導(dǎo)致 (這種情況極其少焕毫,因而極難發(fā)現(xiàn))。
感慨:本文主要記錄了一次系統(tǒng)填坑經(jīng)歷 驶乾;反思邑飒,在使用系統(tǒng)API時(shí)一定要全面弄清,盡量避免進(jìn)坑级乐。