Universal Music Player 源碼分析 (三)-- 其他類分析

文章集合:
Universal Music Player 源碼解析(一)--MediaSession框架

Univeral Music Player 源碼解析 -- 讓人頭疼的playback

Universal Music Player 源碼解析(二)--MusicService 和 MediaController

Universal Music Player 源碼分析 (三)-- 其他類分析

這篇文章的內(nèi)容有:

  • UMP的一些基本的其他類 如工具類的分析 和圖片緩存類
  • UMP 的 NotificationManager類和庇叮活措施

BitmapHelper

BitmapHelper中封裝了一些對(duì)于位圖的裁剪處理處理和對(duì)圖片的獲取:

    public static Bitmap scaleBitmap(int scaleFactor, InputStream is) {
        // Get the dimensions of the bitmap
        BitmapFactory.Options bmOptions = new BitmapFactory.Options();

        // Decode the image file into a Bitmap sized to fill the View
        //沒(méi)有必要將位圖加載,只是將位圖的寬和長(zhǎng)獲取一下 避免OOM
        bmOptions.inJustDecodeBounds = false;
        bmOptions.inSampleSize = scaleFactor;

        return BitmapFactory.decodeStream(is, null, bmOptions);
    }

    @SuppressWarnings("SameParameterValue")
    public static Bitmap fetchAndRescaleBitmap(String uri, int width, int height)
            throws IOException {
        URL url = new URL(uri);
        BufferedInputStream is = null;
        try {
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            is = new BufferedInputStream(urlConnection.getInputStream());
            is.mark(MAX_READ_LIMIT_PER_IMG);
            int scaleFactor = findScaleFactor(width, height, is);
            LogHelper.d(TAG, "Scaling bitmap ", uri, " by factor ", scaleFactor, " to support ",
                    width, "x", height, "requested dimension");
            is.reset();
            return scaleBitmap(scaleFactor, is);
        } finally {
            if (is != null) {
                is.close();
            }
        }
    }

然后在通過(guò)網(wǎng)絡(luò)請(qǐng)求拿到經(jīng)過(guò)裁剪了的bitmap

AlbumArtCache

這個(gè)類使用了LruCache作為緩存處理,key是url value 是bitmap,
聲明了一個(gè)最大的緩存大小
private static final int MAX_ALBUM_ART_CACHE_SIZE = 12*1024*1024;

看一下構(gòu)造函數(shù)

 private AlbumArtCache() {
        // Holds no more than MAX_ALBUM_ART_CACHE_SIZE bytes, bounded by maxmemory/4 and
        // Integer.MAX_VALUE:
        //自定義單位
        int maxSize = Math.min(MAX_ALBUM_ART_CACHE_SIZE,
            (int) (Math.min(Integer.MAX_VALUE, Runtime.getRuntime().maxMemory()/4)));
        mCache = new LruCache<String, Bitmap[]>(maxSize) {
            @Override
            protected int sizeOf(String key, Bitmap[] value) {
                //返回的單位是用戶自定義的
                return value[BIG_BITMAP_INDEX].getByteCount()
                    + value[ICON_BITMAP_INDEX].getByteCount();
            }
        };
    }

值得一提的是,文檔中說(shuō)明的sizeof返回的大小是用戶自定義的,也就是是說(shuō)不需要嚴(yán)格限定大小是字節(jié)還是別的什么的.

MediaNotificationManager

MediaNotificationManager 繼承了 BroadcastReceiver 一來(lái)是為了讓用戶可以在通知欄操作播放,另一個(gè)是為了讓MusicService作為一個(gè)前臺(tái)服務(wù),在low memory的時(shí)候不會(huì)被干掉,我們看一下他的構(gòu)造函數(shù):

 public MediaNotificationManager(MusicService service) throws RemoteException {
        mService = service;
        updateSessionToken();

        mNotificationColor = ResourceHelper.getThemeColor(mService, R.attr.colorPrimary,
                Color.DKGRAY);

        mNotificationManager = (NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE);

        String pkg = mService.getPackageName();
        mPauseIntent = PendingIntent.getBroadcast(mService, REQUEST_CODE,
                new Intent(ACTION_PAUSE).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT);
        mPlayIntent = PendingIntent.getBroadcast(mService, REQUEST_CODE,
                new Intent(ACTION_PLAY).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT);
        mPreviousIntent = PendingIntent.getBroadcast(mService, REQUEST_CODE,
                new Intent(ACTION_PREV).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT);
        mNextIntent = PendingIntent.getBroadcast(mService, REQUEST_CODE,
                new Intent(ACTION_NEXT).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT);
        mStopIntent = PendingIntent.getBroadcast(mService, REQUEST_CODE,
                new Intent(ACTION_STOP).setPackage(pkg), PendingIntent.FLAG_CANCEL_CURRENT);
        mStopCastIntent = PendingIntent.getBroadcast(mService, REQUEST_CODE,
                new Intent(ACTION_STOP_CASTING).setPackage(pkg),
                PendingIntent.FLAG_CANCEL_CURRENT);

        // Cancel all notifications to handle the case where the Service was killed and
        // restarted by the system.
        mNotificationManager.cancelAll();
    }

傳入的參數(shù)是一個(gè)MusicService ,同樣的,通過(guò)傳入的遮蓋MusicService的參數(shù)很容易得到Session.Token

請(qǐng)看updateSessionToken函數(shù):

 private void updateSessionToken() throws RemoteException {
            MediaSessionCompat.Token freshToken = mService.getSessionToken();
        if (mSessionToken == null && freshToken != null ||
                mSessionToken != null && !mSessionToken.equals(freshToken)) {
            if (mController != null) {
                mController.unregisterCallback(mCb);
            }
            mSessionToken = freshToken;
            if (mSessionToken != null) {
                mController = new MediaControllerCompat(mService, mSessionToken);
                mTransportControls = mController.getTransportControls();
                if (mStarted) {
                    mController.registerCallback(mCb);
                }
            }
        }
    }

這段代碼告訴我兩件事情,

  1. 一個(gè)MediaController最好和一個(gè)callback綁定,雖然沒(méi)有找到文檔中相關(guān)證據(jù),但是這樣肯定有一定的道理;
    2.需要時(shí)用最新的sessionToken,如果是舊的sessionToken,一定需要更新

我們來(lái)看一下剛才構(gòu)造函數(shù)中的pendingIntent有干了什么:

首先是 playIntent 和 pauseIntent

 private int addActions(final NotificationCompat.Builder notificationBuilder) {
     ....
        // Play or pause button, depending on the current state.
        final String label;
        final int icon;
        final PendingIntent intent;
        if (mPlaybackState.getState() == PlaybackStateCompat.STATE_PLAYING) {
            label = mService.getString(R.string.label_pause);
            icon = R.drawable.uamp_ic_pause_white_24dp;
            intent = mPauseIntent;
        } else {
            label = mService.getString(R.string.label_play);
            icon = R.drawable.uamp_ic_play_arrow_white_24dp;
            intent = mPlayIntent;
        }
        notificationBuilder.addAction(new NotificationCompat.Action(icon, label, intent));

        // If skip to next action is enabled
        if ((mPlaybackState.getActions() & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
            notificationBuilder.addAction(R.drawable.ic_skip_next_white_24dp,
                    mService.getString(R.string.label_next), mNextIntent);
        }

        return playPauseButtonPosition;
    }

一個(gè)Notification.Action 就是這個(gè)封裝了label intent 還有icon 作為提示的一部分,比如播放action 就是一個(gè)播放的按鈕,暫停action就是一個(gè)暫停的按鈕
addActions()最終在createNotifications()中被調(diào)用:

  private Notification createNotification() {
            LogHelper.d(TAG, "updateNotificationMetadata. mMetadata=" + mMetadata);
            if (mMetadata == null || mPlaybackState == null) {
                return null;
            }

            MediaDescriptionCompat description = mMetadata.getDescription();

            String fetchArtUrl = null;
            Bitmap art = null;
            if (description.getIconUri() != null) {
                // This sample assumes the iconUri will be a valid URL formatted String, but
                // it can actually be any valid Android Uri formatted String.
                // async fetch the album art icon
                String artUrl = description.getIconUri().toString();
                art = AlbumArtCache.getInstance().getBigImage(artUrl);
                if (art == null) {
                    fetchArtUrl = artUrl;
                    // use a placeholder art while the remote art is being downloaded
                    art = BitmapFactory.decodeResource(mService.getResources(),
                            R.drawable.ic_default_art);
                }
            }

            // Notification channels are only supported on Android O+.
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                createNotificationChannel();
            }

            final NotificationCompat.Builder notificationBuilder =
                    new NotificationCompat.Builder(mService, CHANNEL_ID);

            final int playPauseButtonPosition = addActions(notificationBuilder);
            notificationBuilder
                    //使用了 MediaStyle的特殊的notification
                    .setStyle(new MediaStyle()
                            // show only play/pause in compact view
                            .setShowActionsInCompactView(playPauseButtonPosition)
                            .setShowCancelButton(true)
                            .setCancelButtonIntent(mStopIntent)
                            .setMediaSession(mSessionToken))
                    .setDeleteIntent(mStopIntent)
                    .setColor(mNotificationColor)
                    .setSmallIcon(R.drawable.ic_notification)
                    .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
                    .setOnlyAlertOnce(true)
                    .setContentIntent(createContentIntent(description))
                    .setContentTitle(description.getTitle())
                    .setContentText(description.getSubtitle())
                    .setLargeIcon(art);

            if (mController != null && mController.getExtras() != null) {
                String castName = mController.getExtras().getString(MusicService.EXTRA_CONNECTED_CAST);
                if (castName != null) {
                    String castInfo = mService.getResources()
                            .getString(R.string.casting_to_device, castName);
                    notificationBuilder.setSubText(castInfo);
                    notificationBuilder.addAction(R.drawable.ic_close_black_24dp,
                            mService.getString(R.string.stop_casting), mStopCastIntent);
                }
            }

            setNotificationPlaybackState(notificationBuilder);
            if (fetchArtUrl != null) {
                fetchBitmapFromURLAsync(fetchArtUrl, notificationBuilder);
            }

            return notificationBuilder.build();
        }

這段代碼就是對(duì)有 mediaController獲取的mediaMetaData進(jìn)行解析,然后對(duì)這個(gè)notification設(shè)置了特殊的style,在addActions的時(shí)候,因?yàn)樽疃鄷?huì)有三個(gè)按鈕,依次是previous play/pause next 所以需要獲取到他們的index,然后在

new MediaStyle()
                            // show only play/pause in compact view
                            .setShowActionsInCompactView(index)

指定哪個(gè)加進(jìn)去的action可以顯示 但是源代碼中明確注明了只是加入了play/pause 但是只要稍微改一下:

new MediaStyle()
                      
                            .setShowActionsInCompactView(0,1,2)

就可以看到三種按鈕

再說(shuō)一下playbackState是如何"告知"notification的:
還記得之前的這個(gè)函數(shù)嘛:

 mController.registerCallback(mCb);

通過(guò)在回調(diào)函數(shù)中

  @Override
        public void onPlaybackStateChanged(@NonNull PlaybackStateCompat state) {
            mPlaybackState = state;
            LogHelper.d(TAG, "Received new playback state", state);
            if (state.getState() == PlaybackStateCompat.STATE_STOPPED ||
                    state.getState() == PlaybackStateCompat.STATE_NONE) {
                stopNotification();
            } else {
                Notification notification = createNotification();
                if (notification != null) {
                    mNotificationManager.notify(NOTIFICATION_ID, notification);
                }
            }
        }

同樣的對(duì)metaData改變的回調(diào)也在里面寫(xiě)好了:

  @Override
        public void onMetadataChanged(MediaMetadataCompat metadata) {
            mMetadata = metadata;
            LogHelper.d(TAG, "Received new metadata ", metadata);
            Notification notification = createNotification();
            if (notification != null) {
                mNotificationManager.notify(NOTIFICATION_ID, notification);
            }
        }

都是換湯不換藥的notify()進(jìn)行替換

links
Notifications: Styling
Java 和 Android中的LRU的實(shí)現(xiàn)和原理

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末较鼓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子违柏,更是在濱河造成了極大的恐慌博烂,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漱竖,死亡現(xiàn)場(chǎng)離奇詭異禽篱,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)馍惹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)躺率,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人万矾,你說(shuō)我怎么就攤上這事悼吱。” “怎么了良狈?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵后添,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我们颜,道長(zhǎng)吕朵,這世上最難降的妖魔是什么猎醇? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮努溃,結(jié)果婚禮上硫嘶,老公的妹妹穿的比我還像新娘。我一直安慰自己梧税,他們只是感情好沦疾,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著第队,像睡著了一般哮塞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上凳谦,一...
    開(kāi)封第一講書(shū)人閱讀 51,365評(píng)論 1 302
  • 那天忆畅,我揣著相機(jī)與錄音,去河邊找鬼尸执。 笑死家凯,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的如失。 我是一名探鬼主播绊诲,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼褪贵!你這毒婦竟也來(lái)了掂之?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤脆丁,失蹤者是張志新(化名)和其女友劉穎世舰,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體偎快,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡冯乘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了晒夹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡姊氓,死狀恐怖丐怯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情翔横,我是刑警寧澤读跷,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站禾唁,受9級(jí)特大地震影響效览,放射性物質(zhì)發(fā)生泄漏无切。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一丐枉、第九天 我趴在偏房一處隱蔽的房頂上張望哆键。 院中可真熱鬧,春花似錦瘦锹、人聲如沸籍嘹。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)辱士。三九已至,卻和暖如春听绳,著一層夾襖步出監(jiān)牢的瞬間颂碘,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工椅挣, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留凭涂,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓贴妻,卻偏偏與公主長(zhǎng)得像切油,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子名惩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

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