1、 1 / 78从本文开始,详细讲述一个音乐播放器的实现,以及从网络解析数据获取最新推荐歌曲以及歌曲下载的功能。 功能介绍如下: 1、获取本地歌曲列表,实现歌曲播放功能。 2、利用硬件加速感应器,摇动手机实现切换歌曲的功能 3、利用 jsoup 解析网页数据,从网络获取歌曲列表,同时实现歌曲和歌词下载到手机本地的功能。 4、通知栏提醒,实现仿 QQ 音乐播放器的通知栏功能. 涉及的技术有: 1、jsoup 解析网络网页,从而获取需要的数据 2、Android 中访问网络,获取文件到本地的网络请求技术,以及下载文件到本地实现断点下载 3、线程池 4、图片缓存 5、service 一直在后台运行 6
2、、手机硬件加速器 7、notification 通知栏设计 8、自定义广播 9、android 系统文件管理 主要技术是这些,其中,利用 jsoup 解析网络网页,从而获取需要的数据,请参考我的博文: android 中使用 JSOUP 如何解析网页数据详述下面是最终结果展示: 2 / 78图一 本地歌曲列表图二 网络歌曲列表图三 播放歌曲界面图四 播放界面歌词显示部分图五 通知栏展示 播放和下载时的展示3 / 78在本地歌曲列表界面长按菜单键可以显示菜单:在网络歌曲列表中,点击歌曲,可以显示下载和取消菜单项: 基本的界面就是上面的图所展示的。大家觉得还可以的话,就给留个言_【握手】下面就开始
3、详细讲述实现细节。首先提一点,播放器中获取的网络歌曲部分,请参看我的博文: android 中使用 JSOUP 如何解析网页数据详述 网络歌曲列表的获取就是利用 jsoup 从网页抓取数据进行展示的。关于这部分的东西,在本系列博文中不再详细说明了。关于音乐播放器部分,需要使用到 service 服务,总共需要两个,一个是音乐播放的service,一个是歌曲下载的 service,关于 service 一直在后台运行的问题,请参考我的博文: 实现音乐播放器后台 Service 服务一直存在的解决思路根据这篇博文的说明,很容易设计 application 类的实现,application 类实现代
4、码如下:/* 2015 年 8 月 15 日 16:34:37* 博文地址:http:/ / 78public class App extends Application public static Context sContext;public static int sScreenWidth;public static int sScreenHeight;Overridepublic void onCreate() super.onCreate();sContext = getApplicationContext();startService(new Intent(this, PlaySer
5、vice.class);startService(new Intent(this, DownloadService.class);WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);DisplayMetrics dm = new DisplayMetrics();wm.getDefaultDisplay().getMetrics(dm);sScreenWidth = dm.widthPixels;sScreenHeight = dm.heightPixels;application 类中启动了上
6、面的两个 service 服务,同时获取了手机屏幕的宽和高。手机宽和高是为了后 i 面设计界面使用的。 5 / 78有了 application 之后就可以设计 Activity 类,首先设计 BaseActivity 类,把复用的代码放在基类中,子类覆盖或重写 BaseActivity 中的方法,代码如下:/* 2015 年 8 月 15 日 16:34:37* 博文地址:http:/ abstract class BaseActivity extends FragmentActivity protected PlayService mPlayService;protected Downlo
7、adService mDownloadService;private final String TAG = BaseActivity.class.getSimpleName();private ServiceConnection mPlayServiceConnection = new ServiceConnection() Overridepublic void onServiceDisconnected(ComponentName name) L.l(TAG, “play-onServiceDisconnected“);mPlayService = null;Overridepublic
8、void onServiceConnected(ComponentName name, IBinder service) mPlayService = (PlayService.PlayBinder) service).getService();mPlayService.setOnMusicEventListener(mMusicEventListener);onChange(mPlayService.getPlayingPosition();6 / 78;private ServiceConnection mDownloadServiceConnection = new ServiceCon
9、nection() Overridepublic void onServiceDisconnected(ComponentName name) L.l(TAG, “download-onServiceDisconnected“);mDownloadService = null;Overridepublic void onServiceConnected(ComponentName name, IBinder service) mDownloadService = (DownloadService.DownloadBinder) service).getService();/* 音乐播放服务回调
10、接口的实现类*/private PlayService.OnMusicEventListener mMusicEventListener = new PlayService.OnMusicEventListener() Overridepublic void onPublish(int progress) BaseActivity.this.onPublish(progress);7 / 78Overridepublic void onChange(int position) BaseActivity.this.onChange(position);/* Fragment 的 view 加载完
11、成后回调* * 注意:* allowBindService()使用绑定的方式启动歌曲播放的服务* allowUnbindService()方法解除绑定* * 在 SplashActivity.java 中使用 startService()方法启动过该音乐播放服务了* 那么大家需要注意的事,该服务不会因为调用 allowUnbindService()方法解除绑定* 而停止。*/public void allowBindService() getApplicationContext().bindService(new Intent(this, PlayService.class),mPlaySer
12、viceConnection,Context.BIND_AUTO_CREATE);8 / 78/* fragment 的 view 消失后回调*/public void allowUnbindService() getApplicationContext().unbindService(mPlayServiceConnection);Overrideprotected void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState);/绑定下载服务bindService(new Intent(this, Do
13、wnloadService.class),mDownloadServiceConnection,Context.BIND_AUTO_CREATE);Overrideprotected void onDestroy() unbindService(mDownloadServiceConnection);super.onDestroy();public DownloadService getDownloadService() 9 / 78return mDownloadService;/* 更新进度* 抽象方法由子类实现* 实现 service 与主界面通信* param progress 进度*
14、/public abstract void onPublish(int progress);/* 切换歌曲* 抽象方法由子类实现* 实现 service 与主界面通信* param position 歌曲在 list 中的位置*/public abstract void onChange(int position);下面给出启动界面,启动界面非常简单,延时 2 秒进入主界面,代码如下:/* 2015 年 8 月 15 日 16:34:37* 博文地址:http:/ class SplashActivity extends Activity 10 / 78Overrideprotected vo
15、id onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState);/ no titlerequestWindowFeature(Window.FEATURE_NO_TITLE);/ 全屏getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);setContentView(R.layout.splash_layout);/ 2s 跳转到主界面new Hand
16、ler().postDelayed(new Runnable() Overridepublic void run() startActivity(new Intent(SplashActivity.this, MainActivity.class);finish();, 2000);本系列博文,详细讲述一个音乐播放器的实现,以及从网络解析数据获取最新推荐歌曲以及歌曲下载的功能。 功能介绍如下: 1、获取本地歌曲列表,实现歌曲播放功能。 2、利用硬件加速感应器,摇动手机实现切换歌曲的功能 3、利用 jsoup 解析网页数据,从网络获取歌曲列表,同时实现歌曲和歌词下载到手机本地11 / 78的功能
17、。 4、通知栏提醒,实现仿 QQ 音乐播放器的通知栏功能. 涉及的技术有: 1、jsoup 解析网络网页,从而获取需要的数据 2、Android 中访问网络,获取文件到本地的网络请求技术,以及下载文件到本地实现断点下载 3、线程池 4、图片缓存 5、service 一直在后台运行 6、手机硬件加速器 7、notification 通知栏设计 8、自定义广播 9、android 系统文件管理 主要技术是这些,其中,利用 jsoup 解析网络网页,从而获取需要的数据,请参考我的博文: android 中使用 JSOUP 如何解析网页数据详述上一篇博文: android-音乐播放器实现及源码下载(一
18、)有了上一篇博文的准备,现在可以设计主界面,代码如下:/* 2015 年 8 月 15 日 16:34:37* 博文地址:http:/ class MainActivity extends BaseActivity implements OnClickListener private static final String TAG = MainActivity.class.getSimpleName();private ScrollRelativeLayout mMainContainer;private Indicator mIndicator;private TextView mLocal
19、TextView;private TextView mSearchTextView;private ViewPager mViewPager;private View mPopshownView;private PopupWindow mPopupWindow;private ArrayList mFragments = new ArrayList();Overrideprotected void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState);setContentView(R.layout.acti
20、vity_main);registerReceiver();initFragments();setupViews();12 / 78/* 注册广播接收器* 在下载歌曲完成或删除歌曲时,更新歌曲列表*/private void registerReceiver() IntentFilter filter = new IntentFilter( Intent.ACTION_MEDIA_SCANNER_STARTED);filter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);filter.addDataScheme(“file“);registe
21、rReceiver(mScanSDCardReceiver, filter);private void setupViews() mMainContainer = (ScrollRelativeLayout) findViewById(R.id.rl_main_container);mIndicator = (Indicator) findViewById(R.id.main_indicator);mLocalTextView = (TextView) findViewById(R.id.tv_main_local);mSearchTextView = (TextView) findViewB
22、yId(R.id.tv_main_remote);mViewPager = (ViewPager) findViewById(R.id.vp_main_container);mPopshownView = findViewById(R.id.view_pop_show);mViewPager.setAdapter(mPagerAdapter);mViewPager.setOnPageChangeListener(mPageChangeListener);mLocalTextView.setOnClickListener(this);mSearchTextView.setOnClickListe
23、ner(this);selectTab(0);private OnPageChangeListener mPageChangeListener = new OnPageChangeListener() Overridepublic void onPageSelected(int position) selectTab(position);mMainContainer.showIndicator();Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) mI
24、ndicator.scroll(position, positionOffset);13 / 78Overridepublic void onPageScrollStateChanged(int position) ;private FragmentPagerAdapter mPagerAdapter = new FragmentPagerAdapter(getSupportFragmentManager() Overridepublic int getCount() return mFragments.size();Overridepublic Fragment getItem(int po
25、sition) return mFragments.get(position);/* 切换导航 indicator* param index*/private void selectTab(int index) switch (index) case 0:mLocalTextView.setTextColor(getResources().getColor(R.color.main);mSearchTextView.setTextColor(getResources().getColor(R.color.main_dark);break;case 1:mLocalTextView.setTex
26、tColor(getResources().getColor(R.color.main_dark);mSearchTextView.setTextColor(getResources().getColor(R.color.main);break;private void initFragments() LocalFragment localFragment = new LocalFragment();NetSearchFragment netSearchFragment = new NetSearchFragment();mFragments.add(localFragment);mFragm
27、ents.add(netSearchFragment);14 / 78/* 获取音乐播放服务* return*/public PlayService getPlayService() return mPlayService;public void hideIndicator() mMainContainer.hideIndicator();public void showIndicator() mMainContainer.showIndicator();public void onPopupWindowShown() mPopshownView.startAnimation(Animatio
28、nUtils.loadAnimation(this, R.anim.layer_show_anim);mPopshownView.setVisibility(View.VISIBLE);public void onPopupWindowDismiss() mPopshownView.startAnimation(AnimationUtils.loadAnimation(this, R.anim.layer_gone_anim);mPopshownView.setVisibility(View.GONE);Overridepublic void onPublish(int progress) /
29、 如果当前显示的 fragment 是音乐列表 fragment/ 则调用 fragment 的 setProgress 设置进度if(mViewPager.getCurrentItem() = 0) (LocalFragment)mFragments.get(0).setProgress(progress);Overridepublic void onChange(int position) / 如果当前显示的 fragment 是音乐列表 fragment/ 则调用 fragment 的 setProgress 切换歌曲if(mViewPager.getCurrentItem() = 0)
30、 15 / 78(LocalFragment)mFragments.get(0).onPlay(position);private void onShowMenu() onPopupWindowShown();if(mPopupWindow = null) View view = View.inflate(this, R.layout.exit_pop_layout, null);View shutdown = view.findViewById(R.id.tv_pop_shutdown);View exit = view.findViewById(R.id.tv_pop_exit);View
31、 cancel = view.findViewById(R.id.tv_pop_cancel);/ 不需要共享变量, 所以放这没事shutdown.setOnClickListener(this);exit.setOnClickListener(this);cancel.setOnClickListener(this);mPopupWindow = new PopupWindow(view,LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT, true);mPopupWindow.setBa
32、ckgroundDrawable(new ColorDrawable(Color.TRANSPARENT);mPopupWindow.setAnimationStyle(R.style.popwin_anim);mPopupWindow.setFocusable(true);mPopupWindow.setOnDismissListener(new OnDismissListener() Overridepublic void onDismiss() onPopupWindowDismiss(););mPopupWindow.showAtLocation(getWindow().getDeco
33、rView(), Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL, 0, 0);Overridepublic void onClick(View v) switch (v.getId() case R.id.tv_main_local:mViewPager.setCurrentItem(0);break;case R.id.tv_main_remote:mViewPager.setCurrentItem(1);16 / 78break;case R.id.tv_pop_exit:stopService(new Intent(this, PlayService.
34、class);stopService(new Intent(this, DownloadService.class);case R.id.tv_pop_shutdown:finish();case R.id.tv_pop_cancel:if(mPopupWindow != null onPopupWindowDismiss();break;Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) if(keyCode = KeyEvent.KEYCODE_MENU) onShowMenu();return true;return
35、 super.onKeyDown(keyCode, event);Overrideprotected void onDestroy() unregisterReceiver(mScanSDCardReceiver);super.onDestroy();private BroadcastReceiver mScanSDCardReceiver = new BroadcastReceiver() public void onReceive(Context context, Intent intent) L.l(TAG, “mScanSDCardReceiver-onReceive()“);if(i
36、ntent.getAction().equals(Intent.ACTION_MEDIA_SCANNER_FINISHED) MusicUtils.initMusicList();(LocalFragment)mFragments.get(0).onMusicListChanged();主界面主要是两个 Fragment,一个是获取本地歌曲列表,亮一个是获取网络歌曲列表。 获取本地歌曲列表 LocalFragment 代码如下:/* 2015 年 8 月 15 日 16:34:37* 博文地址:http:/ / 78*/public class LocalFragment extends Fr
37、agment implements OnClickListener private ListView mMusicListView;private ImageView mMusicIcon;private TextView mMusicTitle;private TextView mMusicArtist;private ImageView mPreImageView;private ImageView mPlayImageView;private ImageView mNextImageView;private SeekBar mMusicProgress;private MusicList
38、Adapter mMusicListAdapter = new MusicListAdapter();private MainActivity mActivity;private boolean isPause;Overridepublic void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState);setRetainInstance(true);Overridepublic void onAttach(Activity activity) super.onAttach(activity);mActiv
39、ity = (MainActivity) activity;SuppressLint(“InflateParams“)Overridepublic View onCreateView(LayoutInflater inflater,ViewGroup container, Bundle savedInstanceState) View layout = inflater.inflate(R.layout.local_music_layout, null);setupViews(layout);return layout;/* view 创建完毕 回调通知 activity 绑定歌曲播放服务18
40、 / 78*/Overridepublic void onStart() super.onStart();L.l(“fragment“, “onViewCreated“);mActivity.allowBindService();Overridepublic void onResume() super.onResume();isPause = false;Overridepublic void onPause() isPause = true;super.onPause();/* stop 时, 回调通知 activity 解除绑定歌曲播放服务*/Overridepublic void onS
41、top() super.onStop();L.l(“fragment“, “onDestroyView“);mActivity.allowUnbindService();private void setupViews(View layout) mMusicListView = (ListView) layout.findViewById(R.id.lv_music_list);mMusicIcon = (ImageView) layout.findViewById(R.id.iv_play_icon);mMusicTitle = (TextView) layout.findViewById(R
42、.id.tv_play_title);mMusicArtist = (TextView) layout.findViewById(R.id.tv_play_artist);mPreImageView = (ImageView) layout.findViewById(R.id.iv_pre);mPlayImageView = (ImageView) layout.findViewById(R.id.iv_play);mNextImageView = (ImageView) layout.findViewById(R.id.iv_next);mMusicProgress = (SeekBar)
43、layout.findViewById(R.id.play_progress);mMusicListView.setAdapter(mMusicListAdapter);mMusicListView.setOnItemClickListener(mMusicItemClickListener);19 / 78mMusicListView.setOnItemLongClickListener(mItemLongClickListener);mMusicIcon.setOnClickListener(this);mPreImageView.setOnClickListener(this);mPla
44、yImageView.setOnClickListener(this);mNextImageView.setOnClickListener(this);private OnItemLongClickListener mItemLongClickListener = new OnItemLongClickListener() Overridepublic boolean onItemLongClick(AdapterView parent, View view,int position, long id) final int pos = position;AlertDialog.Builder
45、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.notify
46、DataSetChanged();if (new File(music.getUri().delete() scanSDCard(););builder.setNegativeButton(“取消“, null);builder.create().show();return true;private OnItemClickListener mMusicItemClickListener = new OnItemClickListener() Overridepublic void onItemClick(AdapterView parent, View view, int position,l
47、ong id) play(position);/*20 / 78* 发送广播,通知系统扫描指定的文件* 请参考我的博文:* http:/ */private void scanSDCard() if (Build.VERSION.SDK_INT = Build.VERSION_CODES.KITKAT) / 判断 SDK 版本是不是 4.4 或者高于 4.4String paths = new StringEnvironment.getExternalStorageDirectory().toString();MediaScannerConnection.scanFile(mActivity,
48、 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);/* 播放时高亮当前播放条目* 实现播放的歌曲条目可见,且实现指示标记可见* param position*/private void onItemPlay(int position) / 将 ListView 列表滑动到播放的歌曲的位置,是播放的歌曲可见mMusicLis