是這樣,前幾天接觸到一個能夠隨機獲取網絡音樂及其熱評的 API(關於該API:github.com/isecret/yun… ),因而乎就想着要作一個小 demo 來練練手吧!java
目前的效果就是上面那個樣子。git
我目前有打算把這個 demo 長期維護下去,後面會加入更多功能,例如收藏、下載等。github
GitHub 地址:github.com/MzoneCL/Onl…json
需求很簡單,就是經過 API 隨機獲取一首在線音樂及其某一條熱評,實現音樂的後臺播放、暫停、隨機切換,顯示熱評及其點贊數。api
API 返回的數據示例以下:bash
{
"song_id": 400162138,
"title": "海闊天空",
"images": "https://p1.music.126.net/a9oLdcFPhqQyuouJzG2mAQ==/3273246124149810.jpg",
"author": "Beyond",
"album": "華納23週年記念精選系列",
"description": "歌手:Beyond。所屬專輯:華納23週年記念精選系列。",
"mp3_url": "https://api.comments.hk/music/400162138",
"pub_date": "2001-08-31 16:00:00",
"comment_id": 168923809,
"comment_user_id": 6942157,
"comment_nickname": "斑馬斑斑",
"comment_avatar_url": "https://p1.music.126.net/O-z-71Ffl1VimPDElVDKcQ==/6057209557649645.jpg",
"comment_liked_count": 105599,
"comment_content": "若是家駒沒走,如今是什麼樣的存在?",
"comment_pub_date": "2016-06-14 12:26:33"
}
複製代碼
參數釋義:網絡
網絡請求:OkHttp - github.com/square/okht…app
json 解析:Gson - github.com/google/gsondom
圖片加載:Glide - github.com/bumptech/gl…異步
背景虛化:Glide-transformations - github.com/wasabeef/gl…
OK,那接下來就看看是如何一點一點作出來的吧!
public class MusicPlayService extends Service {
private static String TAG = "MusicPlayService";
MediaPlayer mediaPlayer;
boolean firstTimePlay;
public MusicPlayService(){}
@Override
public void onCreate() {
super.onCreate();
if (mediaPlayer == null){
mediaPlayer = new MediaPlayer();
firstTimePlay = true;
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
return new MyMusicPlayBinder();
}
public class MyMusicPlayBinder extends Binder {
public void playMusic(){
if (!mediaPlayer.isPlaying())
mediaPlayer.start();
}
public void pauseMusic(){
if (mediaPlayer.isPlaying())
mediaPlayer.pause();
}
public void playRandomMusic(String url, final OnNetworkMusicPreparedListener onNetworkMusicPreparedListener) {
try {
mediaPlayer.stop();
mediaPlayer.reset();
mediaPlayer.setDataSource(url);
mediaPlayer.prepareAsync();
mediaPlayer.setLooping(true); // 循環播放
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mediaPlayer.start();
onNetworkMusicPreparedListener.onPrepared();
firstTimePlay = false;
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
public boolean isPlaying(){
return mediaPlayer.isPlaying();
}
public int getMusicDuration(){
return mediaPlayer.getDuration();
}
// 獲取當前播放進度
public int getCurPosition(){
return mediaPlayer.getCurrentPosition();
}
public boolean isFirstTimePlay(){
return firstTimePlay;
}
}
@Override
public void onDestroy() {
mediaPlayer.release();
super.onDestroy();
}
}
複製代碼
分析一下主要代碼。
首先聲明一個了 MediaPlayer 對象(第 4 行),用於音樂的播放。隨後在 Service 被建立的時候對其進行初始化(第 14 行)。
定義了一個內部類 MyMusicPlayBinder(29 - 76 行),用以和 Activity 通訊。在該類中,定義了一系列方法(播放、暫停、獲取進度等等)供 Activity 調用。而且在 onBind() 方法中返回了一個 MyMusicPlayBinder 實例(26 行)。
這其中注意一下 playRandomMusic(40 - 58 行) 方法,因爲咱們播放的是網絡音樂,因此要調用的是 mediaPlayer.prepareAsync(),即異步準備,而後必需要設置回調,即 mediaPlayer.setOnPreparedListener(),該回調會在 mediaPlayer 準備好以後被調用,咱們應該在該回調中開始播放(調用 mediaPlayer.start())。
接下來看看主界面的實現。
仍是先看代碼吧:
(爲了不代碼看起來過長,省略了一些不重要的代碼)
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static String TAG = "MainActivity";
private int IMAGE_SOURCE_PLAY = R.drawable.play_white;
private int IMAGE_SOURCE_PAUSE = R.drawable.pause_white;
/*
控件聲明 省略
*/
private MusicPlayService.MyMusicPlayBinder musicController;
ObjectAnimator objectAnimator; // 圖片旋轉動畫
SeekBar seekBar; // 進度條
private ServiceConnection serviceConnection = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
musicController = (MusicPlayService.MyMusicPlayBinder) service;
if (musicController.isPlaying())
btn_play_or_pause.setImageResource(IMAGE_SOURCE_PAUSE);
else
btn_play_or_pause.setImageResource(IMAGE_SOURCE_PLAY);
}
@Override
public void onServiceDisconnected(ComponentName name) {}
};
Handler mHandler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initService();
if (savedInstanceState != null)
tv_music_title.setText(savedInstanceState.getString("musicTitle"));
}
private void initView(){
/*
各個控件的初始化 省略
*/
seekBar = (SeekBar) findViewById(R.id.music_progress_seek_bar);
// 禁止拖動 點擊
seekBar.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
objectAnimator = ObjectAnimator.ofFloat(image_view_music, "rotation", 0, 360);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.setDuration(20 * 1000);
objectAnimator.setRepeatCount(ValueAnimator.INFINITE);//Animation.INFINITE 表示重複屢次
objectAnimator.setRepeatMode(ValueAnimator.RESTART);//RESTART表示從頭開始,REVERSE表示從末尾倒播
btn_play_or_pause.setOnClickListener(this);
btn_next_random.setOnClickListener(this);
}
private void initService(){
Intent intent = new Intent(this, MusicPlayService.class);
startService(intent);
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_play_or_pause:
playOrPauseMusic();
break;
case R.id.next_random:
nextRandomMusic();
default:
break;
}
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private void playMusic(){
musicController.playMusic();
objectAnimator.resume();
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private void pauseMusic(){
musicController.pauseMusic();
objectAnimator.pause();
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private void playOrPauseMusic(){
if (musicController.isFirstTimePlay()){
Toast.makeText(MainActivity.this, "沒有正在播放的音樂!", Toast.LENGTH_SHORT).show();
return;
}
if (musicController.isPlaying()){
pauseMusic();
btn_play_or_pause.setImageResource(IMAGE_SOURCE_PLAY);
}else {
playMusic();
btn_play_or_pause.setImageResource(IMAGE_SOURCE_PAUSE);
}
}
private void nextRandomMusic(){
btn_play_or_pause.setImageResource(IMAGE_SOURCE_PLAY);
MusicModel.getRandomMusic(new GetRandomMusicListener() {
@Override
public void onSuccess(final Music music) {
LogUtil.e(TAG, music.getDescription());
musicController.playRandomMusic(music.getMp3_url(), new OnNetworkMusicPreparedListener() {
@Override
public void onPrepared() {
btn_play_or_pause.setImageResource(IMAGE_SOURCE_PAUSE);
mHandler.post(new Runnable() {
@Override
public void run() {
tv_music_title.setText(music.getTitle());
tv_music_desc.setText(music.getDescription().replaceAll("。", " "));
tv_comment.setText(music.getComment_content());
tv_comment_time.setText(music.getComment_pub_date());
tv_comment_username.setText(music.getComment_nickname());
tv_liked_count.setText(music.getComment_liked_count() + "");
seekBar.setMax(musicController.getMusicDuration());
new UpdateProgressThread().start();
tv_music_total_time.setText(TimeTool.format(musicController.getMusicDuration()));
Glide.with(MainActivity.this).load(music.getImages()).into(image_view_music);
Glide.with(MainActivity.this).load(music.getImages()).
apply(RequestOptions.bitmapTransform(new BlurTransformation(50,10))).into(image_view_bg);
Glide.with(MainActivity.this).load(music.getComment_avatar_url()).placeholder(R.drawable.placeholder).into(image_view_user_avatar);
objectAnimator.start();
}
});
}
});
}
@Override
public void onFailed() {
LogUtil.e(TAG, "獲取音樂失敗!");
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(serviceConnection);
}
@Override
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
super.onSaveInstanceState(outState, outPersistentState);
outState.putString("musicTitle", tv_music_title.getText().toString());
}
class UpdateProgressThread extends Thread {
@Override
public void run() {
super.run();
while (seekBar.getProgress() < seekBar.getMax()){
final int curPosition = musicController.getCurPosition();
seekBar.setProgress(curPosition);
SystemClock.sleep(1000);
mHandler.post(new Runnable() {
@Override
public void run() {
tv_music_cur_time.setText(TimeTool.format(curPosition));
}
});
}
}
}
}
複製代碼
簡單分析下代碼。首先第 14 - 25 行,定義了一個 serviceConnection,用於 Activity 和 Service 創建鏈接。27 行定義一個 Handler 用於界面的更新(切換線程更新界面)。第 56 - 60 行初始化 objectAnimator,並將其與 image_view_music 綁定,該動畫用於歌曲圖片的旋轉。169 - 185 行,定義了一個內部類 UpdateProgressThread,這個線程類是用來 SeekBar 的更新的,也就是實時更新播放進度條,因爲不能在子線程更新 UI,因此必需要是用 mHandler.post() 切換到主線程去更新。
主要的代碼差很少就這些了。
在使用 Okhttp 的時候,連續屢次調用 response.body().string() 會致使 java.lang.IllegalStateException: closed 錯誤。
Service 的綁定(bindService())其實是一個異步的過程。
使用 bindService() 綁定服務就必定要調用 unbindService() 解綁,要記住了啊!