【android】音樂播放器之設計思路

           學習Android有一個多月,看完了《第一行代碼》以及mars老師的第一期視頻通過音樂播放器小項目加深對知識點的理解。從本文開始,將詳細的介紹簡單仿多米音樂播放器的實現,以及網絡解析數據獲取百度音樂最新排行音樂以及下載功能。

        功能介紹如下:    

        1、獲取本地歌曲列表,實現歌曲播放功能。 
        2、利用jsoup解析網頁數據,從網絡獲取歌曲列表,同時實現歌曲和歌詞下載到手機本地的功能。 
        3、通知欄提醒,實現仿QQ音樂播放器的通知欄功能. 
     

       涉及的技術有: 
       1、jsoup解析網絡網頁,從而獲取需要的數據 
       2、android中訪問網絡,獲取文件到本地的網絡請求技術,以及下載文件到本地實現斷點下載 
       3、線程池 
       4、圖片緩存 
       5、service一直在後臺運行 
       6、Activity與Fragment間的切換以及通信 
       7、notification通知欄設計 
       8、自定義廣播 
       9、android系統文件管

     

      該播放器詳細設計參考下面博文:  

       UI設計部分見 【android】音樂播放器之UI設計的點點滴滴

      Service服務設i【android】音樂播放器之service服務設計

       數據存儲技術的運用見 【android】音樂播放器之數據存儲總結


       下面是最終結果展示:



               圖一主界面以及本地音樂列表



圖二: 在本地列表中長按菜單鍵可以彈出功能選擇鍵,短按listview中的歌曲列表跳轉到播放列表,長按listview中的個人去items可以歌曲的刪除。



       上面兩幅圖都關於網絡列表:按listview中的歌曲items可以實現下載功能


       播放界面了~!~我還是比較喜歡這個播放界面的,仿QQ音樂播放界面風格。通過左右滑動可以實現播放歌曲當前的的圖片到播放歌曲歌詞顯示的切換


     上面這兩幅圖分別爲下載時和播放歌曲時候通知欄的顯示,其餘的小功能就不一一展示。如果感興趣可以下載代碼編也好改也好隨你便吧,~!~hahahha!!!


            這邊文章主要講講大致的設計思路~~~~~~。

        還是先談談service服務吧,音樂播放器用到了兩個service服務:PlayService和DownLoadService。PlayService主要負責的是播放音樂的功能,當然,在啓動的時候到sdcard中讀取數據、動態跟新通知欄以及歌詞等這些也都是在PlayService中完成的;而DownLoadService主要結合DownLoad類實現歌曲下載的功能。也是必須要考慮到服務必須要在後臺長期運行的緣故,因此,這邊的處理是在應用啓動時候,通過application類調用它的startService方法啓動service。詳細情況可以參考我的博文【android】音樂播放器之service服務設計~!~哈哈哈哈哈哈,application類已經在前面提到的博文中已經貼出來這邊就不再貼出相關的代碼。

        有了application類後可以着手去設計Activity類,很自然想到實現一個父類,把子類中複用的代碼都放在BaseActivity。然後在子類中覆蓋或者重寫父類中的相關代碼 ,代碼如下:

public abstract class BaseActivity extends FragmentActivity {
	protected PlayService mPlayService;
	protected DownloadService mDownloadService;
	
	private final String TAG = BaseActivity.class.getSimpleName();
	
	private ServiceConnection mPlayServiceConnection = new ServiceConnection() {

		@Override
		public void onServiceConnected(ComponentName arg0, IBinder service) {
			// TODO Auto-generated method stub
			mPlayService = ((PlayService.PlayBinder) service).getService();
			mPlayService.setOnMusicEventListener(mMusicEventListener);
			onChange(mPlayService.getPlayingPosition());
		}

		@Override
		public void onServiceDisconnected(ComponentName arg0) {
			mPlayService = null;
			
		}
		
	};
	
	private ServiceConnection mDownloadServiceConnection = new ServiceConnection() {
		@Override
		public void onServiceDisconnected(ComponentName name) {
			L.l(TAG, "download--->onServiceDisconnected");
			mDownloadService = null;
		}
		
		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			mDownloadService = ((DownloadService.DownloadBinder) service).getService();
		}
	};
	
	
	/*
	 * 音樂播放服務回調接口的實現類
	 */
	
	private PlayService.OnMusicEventListener mMusicEventListener = 
			new PlayService.OnMusicEventListener() {
		@Override
		public void onPublish(int progress) {
			BaseActivity.this.onPublish(progress);
		}

		@Override
		public void onChange(int position) {
			BaseActivity.this.onChange(position);
		}
	};
	
	/**
	 * Fragment的view加載完成後回調
	 */
	public void allowBindService() {
		bindService(new Intent(this, PlayService.class), mPlayServiceConnection,
				Context.BIND_AUTO_CREATE);
	}
	
	/**
	 * fragment的view消失後回調
	 */
	public void allowUnbindService() {
		unbindService(mPlayServiceConnection);
	}
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		bindService(new Intent(this, DownloadService.class), mDownloadServiceConnection,Context.BIND_AUTO_CREATE);
	}
	
	@Override
	protected void onDestroy() {
		unbindService(mDownloadServiceConnection);
		super.onDestroy();
	}
	
	
	public DownloadService getDownloadService() {
		return mDownloadService;
	}
	
	/**
	 * 更新進度
	 * @param progress 進度
	 */
	public abstract void onPublish(int progress);
	/**
	 * 切換歌曲
	 * @param position 歌曲在list中的位置
	 */
	public abstract void onChange(int position);
}
        有了BaseActivity,可以設計子類部分。在一個準備類啓動加載一個佈局全屏顯示一張啓動封面延時兩秒鐘進入主界面。主界面設計仿多米音樂風格,在MainActivity中添加5個Fragment(詳細的UI設計見: 【android】音樂播放器之UI設計的點點滴滴),同時監聽按鈕實現Fragment的切換:

@Override
	public void onClick(View v)
	{
		resetImgs();
		switch (v.getId())
		{
		case R.id.id_tab_user:
			setSelect(TAB_USER);
			break;
		case R.id.id_tab_cd:
			setSelect(TAB_CD);
			break;
		case R.id.id_tab_search:
			setSelect(TAB_SEARCH);
			break;
		case R.id.id_tab_compass:
			setSelect(TAB_COMPASS);
			break;
		case R.id.id_tab_topjump:
			startActivity(new Intent(this, PlayActivity.class));
			break;
		case R.id.tv_pop_exit:
			stopService(new Intent(this, PlayService.class));
			//stopService(new Intent(this, DownloadService.class));
		case R.id.tv_pop_shutdown:
			finish();
		case R.id.tv_pop_cancel:
			if(mPopupWindow != null && mPopupWindow.isShowing()) mPopupWindow.dismiss();
			onPopupWindowDismiss();
	 		break;
		default:
			break;
		}
	}
       其中,1、需要注意的是本地音樂列表LocalFragment的監聽事件是通過在UserFragment中監聽獲得並調用主活動的的select方法跳轉到本地音樂列表。當然,此時本地音樂列表已經初始化過~~~初始化過程主要在PlayService中啓動過程完成(這個在上面提到的相關博文有詳細的說明);2、在LocalFragment啓動過程通過bingservice()綁定服務,這樣就可以實現當點擊本地音樂列表時候播放按鈕同時跳轉到PlayActivity播放界面。代碼如下:

@Override
	public void onStart() {
		super.onStart();
		mActivity.allowBindService();
	}
	
	@Override
	public void onStop() {
		super.onStop();
		mActivity.allowUnbindService();
	}
@Override
	public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
		Intent intent = new Intent(mActivity, PlayActivity.class);
		intent.putExtra("pos", position);
		startActivity(intent);
		play(position);
	}
private void play(int position) {
		int pos = mActivity.getPlayService().play(position);
		onPlay(pos);
	}
        同時,當長按本地音樂播放列表可以實現刪除本地音樂的功能,通過listView的setOnItemLongClickListener方法監聽,獲取事件時調用MusicUtils的remove方法刪除本地音樂,並啓動掃描sdcard通過廣播通知列表變化更新本地列表:

private OnItemLongClickListener mItemLongClickListener = 
			new OnItemLongClickListener() {
		@Override
		public boolean onItemLongClick(AdapterView<?> parent, View view,
				int position, long id) {
			final int pos = position;

			AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
			builder.setTitle("刪除該條目");
			builder.setMessage("確認要刪除該條目嗎?");
			builder.setPositiveButton("刪除",
				new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int which) {
						Music music = MusicUtils.sMusicList.remove(pos);
						mMusicListAdapter.notifyDataSetChanged();
						if (new File(music.getUri()).delete()) {
							scanSDCard();
						}
					}
				});
			builder.setNegativeButton("取消", null);
			builder.create().show();
			return true;
		}
	};
private void scanSDCard() {
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
			// 判斷SDK版本是不是4.4或者高於4.4
			String[] paths = new String[]{
					Environment.getExternalStorageDirectory().toString()};
			MediaScannerConnection.scanFile(mActivity, paths, null, null);
		} else {
			Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED);
			intent.setClassName("com.android.providers.media",
					"com.android.providers.media.MediaScannerReceiver");
			intent.setData(Uri.parse("file://"+ MusicUtils.getMusicDir()));
			mActivity.sendBroadcast(intent);
		}
	}
        跳轉到PlayActivity後實現和在LocalFragment實現十分相同,綁定Playservice服務,初始化監聽事件,獲取事件調用服務中的方法播放音樂,具體的Ui設計部分見 【android】音樂播放器之UI設計的點點滴滴。另外,注意一點是:在佈局文件中介個監聽聽按鈕設置了onclick屬性:

<ImageButton
                android:id="@+id/ib_play_pre"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@android:color/transparent"
                android:contentDescription="@string/app_name"
                android:onClick="pre"
                android:src="@drawable/player_btn_pre_normal" />

            <ImageButton
                android:id="@+id/ib_play_start"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="20dp"
                android:layout_marginRight="20dp"
                android:background="@android:color/transparent"
                android:contentDescription="@string/app_name"
                android:onClick="play"
                android:src="@drawable/player_btn_play_normal" />

            <ImageButton
                android:id="@+id/ib_play_next"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@android:color/transparent"
                android:contentDescription="@string/app_name"
                android:onClick="next"
                android:src="@drawable/player_btn_next_normal" />
       當在MainActivity中捕獲到網絡按鈕時,跳轉到SearchFragment網絡列表,因爲在BaseActivity的Oncreate方法直接綁定DownLoadService,所以在該Fragment中就沒必要再去綁定服務。在SearchFragment創建後調用setUserVisibleHint方法實現Fragment實現懶加載,結合SongsRecommendation類解析百度音樂中頁面獲取音樂數據,顯示在SearchFragment佈局中,網頁解析獲取歌曲列表主要通過JSOUP解析技術實現(可以參考:jsoup下載地址 以及jsoup中文開發指南)。代碼如下:

@Override
	public void onResume() {
		super.onResume();
		if(getUserVisibleHint()){
			Boolean isVisibleToUser = getUserVisibleHint();
			setUserVisibleHint(isVisibleToUser);
		}
	}
	
	/**
	 * 該方法實現的功能是: 當該Fragment不可見時,isVisibleToUser=false
	 * 當該Fragment可見時,isVisibleToUser=true
	 * 該方法由系統調用,重寫該方法實現用戶可見當前Fragment時再進行數據的加載
	 */
	@Override
	public void setUserVisibleHint(boolean isVisibleToUser) {
		super.setUserVisibleHint(isVisibleToUser);
		// 當Fragment可見且是第一次加載時
		if (isVisibleToUser && isFirstShown) {
			mSearchProgressBar.setVisibility(View.VISIBLE);
			mSearchResultListView.setVisibility(View.GONE);
			SongsRecommendation
				.getInstance()
				.setListener(
					new SongsRecommendation.OnRecommendationListener() {
						@Override
						public void onRecommend(
							ArrayList<SearchResult> results) {
							if (results == null || results.isEmpty())
								return;
							mSearchProgressBar.setVisibility(View.GONE);
							mSearchResultListView
									.setVisibility(View.VISIBLE);
							mResultData.clear();
							mResultData.addAll(results);
							mSearchResultAdapter.notifyDataSetChanged();
						}
					}).get();
			isFirstShown = false;
		}
	}
/**
	 * 真正執行網頁解析的方法
	 * 線程池中開啓新的線程執行解析,解析完成之後發送消息
	 * 將結果傳遞到主線程中
	 */
	public void get() {
		mThreadPool.execute(new Runnable() {
			@Override
			public void run() {
				ArrayList<SearchResult> result = getMusicList();
				if (result == null) {
					mHandler.sendEmptyMessage(Constants.FAILED);
					return;
				}
				mHandler.obtainMessage(Constants.SUCCESS, result)
						.sendToTarget();
			}
		});
	}

	private ArrayList<SearchResult> getMusicList() {
		try {
			/**
			 * 一下方法調用請參考官網
			 * 說明:timeout設置請求時間,不宜過短。
			 * 時間過短導致異常,無法獲取。
			 */
			Document doc = Jsoup
					.connect(URL)
					.userAgent(
							"Mozilla/5.0 (Windows NT 6.1; Win64; x64)" +
							" AppleWebKit/537.36"
									+ " (KHTML, like Gecko)" +
									" Chrome/42.0.2311.22 Safari/537.36")
					.timeout(60 * 1000).get();
			//select爲選擇器,請參考官網說明
			Elements songTitles = doc.select("span.song-title");
			Elements artists = doc.select("span.author_list");
			ArrayList<SearchResult> searchResults = new ArrayList<SearchResult>();

			for (int i = 0; i < songTitles.size(); i++) {
				SearchResult searchResult = new SearchResult();
				Elements urls = songTitles.get(i).getElementsByTag("a");
				searchResult.setUrl(urls.get(0).attr("href"));
				searchResult.setMusicName(urls.get(0).text());

				Elements artistElements = artists.get(i).getElementsByTag("a");
				searchResult.setArtist(artistElements.get(0).text());
				searchResult.setAlbum("最新推薦");
				searchResults.add(searchResult);
			}
			return searchResults;
		} catch (IOException e) {
			e.printStackTrace();
		}

		return null;
	}
        具體監聽點擊事件開始下載功能的實現見其他幾篇小博文。這邊就不一一介紹了,如果感興趣還是建議看下源碼。



源碼地址:音樂播放器源碼

git 源碼地址(免積分):音樂播放器源碼



理 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章