在上一篇源碼分析中,我們提到了AIDL,這部分的詳解網(wǎng)上已經(jīng)很詳細(xì),就不再贅述了,本篇僅僅針對其實(shí)現(xiàn)部分的代碼做一些分析。在這部分源碼中,IMediaPlaybackService.aidl中定義了一系列方法,MediaPlaybackService則是對其中定義方法的具體實(shí)現(xiàn),由于這個(gè)類的方法非常多,本篇僅對其中部分函數(shù)進(jìn)行簡要的分析。
MediaPlaybackService
首先,我們看一看這個(gè)service定義的mMediaplayerHandler,其結(jié)構(gòu)并不復(fù)雜,根據(jù)msg的類型分成四種情況進(jìn)行處理。其代碼如下:
private Handler mMediaplayerHandler = new Handler() {
float mCurrentVolume = 1.0f;
@Override
public void handleMessage(Message msg) {
MusicUtils.debugLog("mMediaplayerHandler.handleMessage " + msg.what);
switch (msg.what) {
//反復(fù)發(fā)送延遲msg,完成聲音漸強(qiáng)的效果
case FADEIN:
if (!isPlaying()) {
mCurrentVolume = 0f;
mPlayer.setVolume(mCurrentVolume);
play();
mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
} else {
mCurrentVolume += 0.01f;
if (mCurrentVolume < 1.0f) {
mMediaplayerHandler.sendEmptyMessageDelayed(FADEIN, 10);
} else {
mCurrentVolume = 1.0f;
}
mPlayer.setVolume(mCurrentVolume);
}
break;
//分為兩種情況。第一種是正在播放時(shí),因?yàn)橐纛l損壞
//等類似的原因?qū)е虏シ欧?wù)無法繼續(xù),則強(qiáng)制播放下
//一首;另一種則是當(dāng)服務(wù)暫停時(shí)又重啟,
//則重新播放當(dāng)前曲目
case SERVER_DIED:
if (mIsSupposedToBePlaying) {
next(true);
} else {
// the server died when we were idle, so just
// reopen the same song (it will start again
// from the beginning though when the user
// restarts)
openCurrent();
}
break;
//音軌沒有歌曲后的處理方式
case TRACK_ENDED:
if (mRepeatMode == REPEAT_CURRENT) {
seek(0);
play();
} else {
next(false);
}
break;
//解鎖后的操作
case RELEASE_WAKELOCK:
mWakeLock.release();
break;
default:
break;
}
}
};
接下來我們看看service中的生命周期函數(shù)。在MediaPlaybackService中重寫了onCreate和onDestory兩個(gè)方法。在onCreate中,MediaPlaybackService新建了一個(gè)AudioManager并綁定了一個(gè)按鈕事件的監(jiān)聽,同時(shí)綁定了SD卡相關(guān)事件的監(jiān)聽,onDestory所做的主要工作和前者相反,主要是進(jìn)行一些解綁。在這部分中,我們只有一個(gè)特殊的地方需要額外關(guān)注,即saveQueue(boolean full)方法,下面針對其中個(gè)人覺得有意思的代碼做一下簡要的分析,首先該方法具體代碼如下:
private void saveQueue(boolean full) {
if (!mQueueIsSaveable) {
return;
}
Editor ed = mPreferences.edit();
if (full) {
//用于記錄轉(zhuǎn)換歌曲id號所得的序列化字符串。歌曲id
//可作為歌曲在數(shù)據(jù)庫中的唯一索引
StringBuilder q = new StringBuilder();
// 為加快生成速度,這里使用反轉(zhuǎn)十六進(jìn)制存儲(性能較優(yōu))
int len = mPlayListLen;
for (int i = 0; i < len; i++) {
long n = mPlayList[i];
if (n < 0) {
continue;
} else if (n == 0) {
q.append("0;");
} else {
//這里是進(jìn)行的十進(jìn)制向十六進(jìn)制的轉(zhuǎn)換
//hexdigits[]事先存儲好了1-9,a-f十六進(jìn)制的char字符
while (n != 0) {
int digit = (int)(n & 0xf);
n >>>= 4;
q.append(hexdigits[digit]);
}
q.append(";");
}
}
ed.putString("queue", q.toString());
ed.putInt("cardid", mCardId);
if (mShuffleMode != SHUFFLE_NONE) {
// 隨機(jī)模式下也需要記錄歷史數(shù)據(jù)
len = mHistory.size();
q.setLength(0);
for (int i = 0; i < len; i++) {
int n = mHistory.get(i);
if (n == 0) {
q.append("0;");
} else {
while (n != 0) {
int digit = (n & 0xf);
n >>>= 4;
q.append(hexdigits[digit]);
}
q.append(";");
}
}
ed.putString("history", q.toString());
}
}
ed.putInt("curpos", mPlayPos);
if (mPlayer.isInitialized()) {
ed.putLong("seekpos", mPlayer.position());
}
ed.putInt("repeatmode", mRepeatMode);
ed.putInt("shufflemode", mShuffleMode);
SharedPreferencesCompat.apply(ed);
}
這一段代碼整體直接看邏輯并不難理解,可能產(chǎn)生的疑問主要有關(guān)用反轉(zhuǎn)十六進(jìn)制存儲是什么意思。要明白這部分的問題,我們先來看看reloadQueue相關(guān)代碼。
int plen = 0;
int n = 0;
int shift = 0;
for (int i = 0; i < qlen; i++) {
char c = q.charAt(i);
if (c == ';') {
ensurePlayListCapacity(plen + 1);
mPlayList[plen] = n;
plen++;
n = 0;
shift = 0;
} else {
if (c >= '0' && c <= '9') {
n += ((c - '0') << shift);
} else if (c >= 'a' && c <= 'f') {
n += ((10 + c - 'a') << shift);
} else {
// bogus playlist data
plen = 0;
break;
}
shift += 4;
}
}
以上部分的代碼是reloadQueue方法中對應(yīng)的解析部分,對照saveQueue部分代碼,我們可以看出,它實(shí)質(zhì)上是將id號進(jìn)行序列化和反序列化的過程。而從十進(jìn)制轉(zhuǎn)化為十六進(jìn)制可以減少序列化后字符串的長度,也可以減少序列化和反序列化過程中的循環(huán)次數(shù)。反轉(zhuǎn)存儲的方式則給讀取帶來了很多方便(若不反轉(zhuǎn),則解析的時(shí)候需要首先讀出一段字符,計(jì)算長度才能進(jìn)行反序列化)。reloadQueue剩下部分的代碼注釋比較清晰,主要是進(jìn)行一些邊界和特殊情況處理,這里就不贅述了。
MediaPlaybackService剩下的部分主要是基本的邏輯,包括play,stop,pause等方法,主要都是考慮了邊界條件,增刪相關(guān)數(shù)據(jù)或者調(diào)用MediaPlayer的相關(guān)方法,內(nèi)容多但邏輯并不復(fù)雜,暫時(shí)就不在本篇文章上進(jìn)行分析了。在下一篇源碼解析中,我們會回到MusicBrowserActivity,跟著其內(nèi)部調(diào)用過程往下看,謝謝大家。