MediaSessionCompat::setMetadata呼叫時遇到的深坑

weixin_34054866發表於2017-07-27

本文主要講解了一下幾個方面:

(1)bitmap.copy()發生np的原因及Config 為何為null;

(2)不同系統setMetadata的實現機制及其差異性對比;

(3)不同版本support 庫MediaSessioCompat::setMetadata實現差異。

一、深坑背景 

既然是深坑,必然是難以發現(偶現問題),大家可能在想MediaSessionCompat::setMetadata()一個方法呼叫還未出現什麼問題,正因我也是這麼想,所以問題發生了 。

問題現場  :在RDM問題列表中發現了一個np問題 (只有幾例,上報機型均為小米系統,api19),來自bitmap.copy() ,對應到專案呼叫處 mMediaSessionCompat.setMetadata() 如下圖所示 ,這裡是專門針對小米系統鎖屏進行的相關處理。

3315418-934225360dedf668.jpg
圖1 問題現場
3315418-1cecfa03081aea74.png
圖2 bitmap.copy np 處

一看便知 ,傳入的congfig為null導致 ,為什麼config為null,我們後面再講,先來看看為什麼setMetadata會導致bitmap.copy ,帶著疑問去看看setMetadata的呼叫過程。


二、MediaSessioCompat::setMetadata 呼叫過程

      在講解MediaSessioCompat::setMetadata前,先了解一下RemoteControlClient、MediaSession等多媒體控制相關類。

       RemoteControlClient 主要用於鎖屏狀態控制音樂播放,鎖屏介面由系統提供,在Android 4.0 (API 14)提供,目前已經被標記為deprecated(目前已推薦使用MediaSession)。 MediaSession  是一個專門解決媒體播放時介面和服務通訊問題的框架,在Android 5.0才被引進,可應用在鎖屏、耳機線控、藍芽耳機。

      而這裡的MediaSessioCompat 是support包提供的一個相容性類 ,從命名就可以看出來,setMetadata方法作用主要是更新資料(包括歌曲名、歌手、背景圖、字幕等資訊),因此對於音訊類系統鎖屏是相當重要的。

MediaSessioCompat::setMetadata()內部實踐上呼叫的是 MediaSessionImpl::setMetadata,既然是相容類 ,對應的MediaSessionImpl 肯定在不同版本有著不同實現 (api 21 作為區分節點),如下:

3315418-8aba28b5ce54d477.png
圖3 MediaSessionImpl實現

(1)MediaSessionImplBase::setMetadata  (api <21)

       在MediaSessionImplBase內部 主要進行了兩大操作,先對metadata資料中的 bitmaps資料進行拷貝 (發現bitmap相關操作了),然後又對api 19 和 api 14 分別進行了區分(後面解釋為什麼會進行區分),呼叫各自的setMetadata。

3315418-9857ecdad5657e55.png
圖 4 MediaSessionImplBase::setMetadata  (api <21)

沒錯,bitmap.copy()的np問題正是發生在這裡,通過Bitmap相關api註釋知,bitmap.getConfig()可能返回為null,奈何這竟然是一個系統bug,迭代了這麼多版本google竟未修復。

3315418-300a47aa679ec77a.jpg
圖5 cloneMetadataIfNeeded()

這裡的Config其實指的是 bitmap的型別 :可以看到確實有兩類返回的config為null

3315418-f51fbd95f0be33ef.png
圖6 Config

      具體什麼情況為null了 ,我在Bitmapcreate Bitmap(Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter) 實現中找到了蛛絲馬跡,如下圖中註釋:gif 檔案會產出 null configs,但是對於我們專案來說,專輯封面採用的都不是gif格式,因此對於bitmap.getConfig()返回為null還是為弄清(那位大神知道請告訴一下,謝謝)。

3315418-8233ee940868c98b.png
圖7 BitmapcreateBitmap 內部實現

       雖然這個np問題可以輕而易舉的解決,單本人不甘停留如此,還有有個疑問為什麼要clone bitmap相關資料(註釋已經說了,防止recycle),那在什麼時候會recycle 。

(2)在那裡進行recycle操作

在(1)中已經說過,MediaSessionImplBase裡面會進行兩部分操作,第二部分操作是setMetadata,其內部實現如下:

建立editor 、put 資料、最後apply。  這裡分別使用了RemoteControlClient.MetadataEditor和MediaMetadataEditor (api 19 才提供)。

3315418-168a1da58ab40c3e.jpg
圖8 MediaSessionCompatApi19

        apply()才是關鍵 ,內部實現如下:其中mOriginalArtWork就是專門用來快取封面的bitmap ,可能考慮到鎖屏比較頻繁,防止頻繁呼叫吧,但是bitmap.recycle() 不是在2.3.3以後就不推薦使用嗎,真不知道為何在此還要如此實現。

3315418-5f41382e2d612ba0.jpg
圖9 RemoteControlClient::apply()

      細心一點的同學可能發現,在這個裡面竟然呼叫了MediaSession::setMetadata()根據,前面對MediaSession(5.0 引進)介紹知,在5.0以下實現方式肯定不一致:

3315418-98b5666e772e960b.jpg
圖 10 4.4.4 版本的apply()方法

另外 ,在高版本中又是如何處理相關問題的了,帶著這些疑問我們直接分析MediaSession::setMetadata()方法即可。


三、MediaSession::setMetadata()

       在第二部分,主要講解了MediaSessioCompat::setMetadata api 21以下的呼叫,在api >=21時,其內部呼叫的是 MediaSession::setMetadata(),具體如下:

3315418-59fa4a1aa5a8b87d.png
圖11 MediaSession::setMetadata()

這裡也是分為兩步 :更新metadata 資料,執行更新 。

3315418-6a220a2945b49644.jpg

相比api <21 情況,這裡並未clone 封面背景bitmap ,保證了一定效能。內部使用的是scaleBitmap 最後實際呼叫Bitmapcreate Bitmap(Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter) ,整個過程簡單明瞭。

而對於執行更新,這裡採用的IPC 方式,上面的mBinder 其實是 ISession.aidl 物件,效率相比之前的提高不少。(這裡具體就不展開。)


四、不同版本support 庫MediaSessioCompat::setMetadata實現差異

       前面講的MediaSessioCompat 是基於support-v4-23.4.0 版本,可以看到MediaSessioCompat為了配合RemoteContorlClient::apply() 方法中的 bitmap.recyle() , 在setMetadta內部對進行了Clone。

3315418-28a5adf6fc52b278.png
圖12 support-v4-23.4.0 MediaSessioCompat::setMetadata()

      而在support-v4-22.2.1 中發現,並未對封面bitmap 進行可能,這種情況下,發生crash是必然的,可能正因如此,google在後面的support 庫修復了這個問題。support-v4-23.4.0版本發生np ,原因前面已經說了,是因為bitmap.getConfig()為null導致 (這種情況極其少,因而極難發現)。

3315418-bce8360219f1ce6e.png
圖13 support-v4-22.2.1 MediaSessioCompat::setMetadata()


感慨:本文主要記錄了一次系統填坑經歷 ;反思,在使用系統API時一定要全面弄清,儘量避免進坑。

相關文章