文章集合:
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);
}
}
}
}
這段代碼告訴我兩件事情,
- 一個(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)和原理