Universal Music Player 源码分析 (三)-- 其他类分析

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

Univeral Music Player 源码解析 -- 让人头疼的playback

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

Universal Music Player 源码分析 (三)-- 其他类分析

这篇文章的内容有:

  • UMP的一些基本的其他类 如工具类的分析 和图片缓存类
  • UMP 的 NotificationManager类和保活措施

BitmapHelper

BitmapHelper中封装了一些对于位图的裁剪处理处理和对图片的获取:

    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
        //没有必要将位图加载,只是将位图的宽和长获取一下 避免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();
            }
        }
    }

然后在通过网络请求拿到经过裁剪了的bitmap

AlbumArtCache

这个类使用了LruCache作为缓存处理,key是url value 是bitmap,
声明了一个最大的缓存大小
private static final int MAX_ALBUM_ART_CACHE_SIZE = 12*1024*1024;

看一下构造函数

 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(maxSize) {
            @Override
            protected int sizeOf(String key, Bitmap[] value) {
                //返回的单位是用户自定义的
                return value[BIG_BITMAP_INDEX].getByteCount()
                    + value[ICON_BITMAP_INDEX].getByteCount();
            }
        };
    }

值得一提的是,文档中说明的sizeof返回的大小是用户自定义的,也就是是说不需要严格限定大小是字节还是别的什么的.

MediaNotificationManager

MediaNotificationManager 继承了 BroadcastReceiver 一来是为了让用户可以在通知栏操作播放,另一个是为了让MusicService作为一个前台服务,在low memory的时候不会被干掉,我们看一下他的构造函数:

 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();
    }

传入的参数是一个MusicService ,同样的,通过传入的遮盖MusicService的参数很容易得到Session.Token

请看updateSessionToken函数:

 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. 一个MediaController最好和一个callback绑定,虽然没有找到文档中相关证据,但是这样肯定有一定的道理;
    2.需要时用最新的sessionToken,如果是旧的sessionToken,一定需要更新

我们来看一下刚才构造函数中的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;
    }

一个Notification.Action 就是这个封装了label intent 还有icon 作为提示的一部分,比如播放action 就是一个播放的按钮,暂停action就是一个暂停的按钮
addActions()最终在createNotifications()中被调用:

  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();
        }

这段代码就是对有 mediaController获取的mediaMetaData进行解析,然后对这个notification设置了特殊的style,在addActions的时候,因为最多会有三个按钮,依次是previous play/pause next 所以需要获取到他们的index,然后在

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

指定哪个加进去的action可以显示 但是源代码中明确注明了只是加入了play/pause 但是只要稍微改一下:

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

就可以看到三种按钮

再说一下playbackState是如何"告知"notification的:
还记得之前的这个函数嘛:

 mController.registerCallback(mCb);

通过在回调函数中

  @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);
                }
            }
        }

同样的对metaData改变的回调也在里面写好了:

  @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()进行替换

links
Notifications: Styling
Java 和 Android中的LRU的实现和原理

你可能感兴趣的:(Universal Music Player 源码分析 (三)-- 其他类分析)